mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-27 00:00:02 -04:00
Merge branch 'master' of https://github.com/haraldk/TwelveMonkeys
This commit is contained in:
+13
-13
@@ -74,7 +74,7 @@ import java.util.Map;
|
||||
* @see <A href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</A>
|
||||
*/
|
||||
public class SVGImageReader extends ImageReaderBase {
|
||||
private Rasterizer mRasterizer = new Rasterizer();
|
||||
private Rasterizer rasterizer = new Rasterizer();
|
||||
|
||||
/**
|
||||
* Creates an {@code SVGImageReader}.
|
||||
@@ -91,16 +91,16 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
mRasterizer = null;
|
||||
rasterizer = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInput(Object pInput, boolean pSeekForwardOnly, boolean pIgnoreMetadata) {
|
||||
super.setInput(pInput, pSeekForwardOnly, pIgnoreMetadata);
|
||||
public void setInput(Object pInput, boolean seekForwardOnly, boolean ignoreMetadata) {
|
||||
super.setInput(pInput, seekForwardOnly, ignoreMetadata);
|
||||
|
||||
if (mImageInput != null) {
|
||||
TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(mImageInput));
|
||||
mRasterizer.setInput(input);
|
||||
if (imageInput != null) {
|
||||
TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
||||
rasterizer.setInput(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
// Set IIOParams as hints
|
||||
// Note: The cast to Map invokes a different method that preserves
|
||||
// unset defaults, DO NOT REMOVE!
|
||||
mRasterizer.setTranscodingHints((Map) paramsToHints(svgParam));
|
||||
rasterizer.setTranscodingHints((Map) paramsToHints(svgParam));
|
||||
|
||||
// Get the base URI (not a hint)
|
||||
baseURI = svgParam.getBaseURI();
|
||||
@@ -134,8 +134,8 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
try {
|
||||
processImageStarted(pIndex);
|
||||
|
||||
mRasterizer.mTranscoderInput.setURI(baseURI);
|
||||
BufferedImage image = mRasterizer.getImage();
|
||||
rasterizer.mTranscoderInput.setURI(baseURI);
|
||||
BufferedImage image = rasterizer.getImage();
|
||||
|
||||
Graphics2D g = destination.createGraphics();
|
||||
try {
|
||||
@@ -223,7 +223,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
checkBounds(pIndex);
|
||||
try {
|
||||
return mRasterizer.getDefaultWidth();
|
||||
return rasterizer.getDefaultWidth();
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
@@ -233,7 +233,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
checkBounds(pIndex);
|
||||
try {
|
||||
return mRasterizer.getDefaultHeight();
|
||||
return rasterizer.getDefaultHeight();
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
@@ -241,7 +241,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(mRasterizer.createImage(1, 1))).iterator();
|
||||
return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-6
@@ -37,23 +37,23 @@ import java.awt.*;
|
||||
*
|
||||
*/
|
||||
public class SVGReadParam extends ImageReadParam {
|
||||
private Paint mBackground;
|
||||
private String mBaseURI;
|
||||
private Paint background;
|
||||
private String baseURI;
|
||||
|
||||
public Paint getBackgroundColor() {
|
||||
return mBackground;
|
||||
return background;
|
||||
}
|
||||
|
||||
public void setBackgroundColor(Paint pColor) {
|
||||
mBackground = pColor;
|
||||
background = pColor;
|
||||
}
|
||||
|
||||
public String getBaseURI() {
|
||||
return mBaseURI;
|
||||
return baseURI;
|
||||
}
|
||||
|
||||
public void setBaseURI(String pBaseURI) {
|
||||
mBaseURI = pBaseURI;
|
||||
baseURI = pBaseURI;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-189
@@ -1,189 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import org.apache.batik.ext.awt.image.codec.SeekableStream;
|
||||
import org.apache.batik.ext.awt.image.codec.tiff.TIFFDecodeParam;
|
||||
import org.apache.batik.ext.awt.image.codec.tiff.TIFFImageDecoder;
|
||||
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TIFFImageReader class description.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: TIFFImageReader.java,v 1.0 29.jul.2004 12:52:33 haku Exp $
|
||||
*/
|
||||
// TODO: Massive clean-up
|
||||
// TODO: Support raster decoding...
|
||||
public class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
private TIFFImageDecoder mDecoder = null;
|
||||
private List<RenderedImage> mImages = new ArrayList<RenderedImage>();
|
||||
|
||||
protected TIFFImageReader(final ImageReaderSpi pOriginatingProvider) {
|
||||
super(pOriginatingProvider);
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
mDecoder = null;
|
||||
}
|
||||
|
||||
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
||||
// Decode image, convert and return as BufferedImage
|
||||
RenderedImage image = readAsRenderedImage(pIndex, pParam);
|
||||
return ImageUtil.toBuffered(image);
|
||||
}
|
||||
|
||||
public RenderedImage readAsRenderedImage(int pIndex, ImageReadParam pParam) throws IOException {
|
||||
init(pIndex);
|
||||
|
||||
processImageStarted(pIndex);
|
||||
|
||||
if (pParam == null) {
|
||||
// Cache image for use by getWidth and getHeight methods
|
||||
RenderedImage image;
|
||||
if (mImages.size() > pIndex && mImages.get(pIndex) != null) {
|
||||
image = mImages.get(pIndex);
|
||||
}
|
||||
else {
|
||||
// Decode
|
||||
image = mDecoder.decodeAsRenderedImage(pIndex);
|
||||
|
||||
// Make room
|
||||
for (int i = mImages.size(); i < pIndex; i++) {
|
||||
mImages.add(pIndex, null);
|
||||
}
|
||||
mImages.add(pIndex, image);
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
return image;
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
return image;
|
||||
}
|
||||
else {
|
||||
// TODO: Parameter conversion
|
||||
mDecoder.setParam(new TIFFDecodeParam());
|
||||
|
||||
RenderedImage image = mDecoder.decodeAsRenderedImage(pIndex);
|
||||
|
||||
// Subsample and apply AOI
|
||||
if (pParam.getSourceRegion() != null) {
|
||||
image = fakeAOI(ImageUtil.toBuffered(image), pParam);
|
||||
}
|
||||
if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
|
||||
image = ImageUtil.toBuffered(fakeSubsampling(ImageUtil.toBuffered(image), pParam));
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
private void init(int pIndex) throws IOException {
|
||||
init();
|
||||
checkBounds(pIndex);
|
||||
}
|
||||
|
||||
protected void checkBounds(int pIndex) throws IOException {
|
||||
if (pIndex < getMinIndex()){
|
||||
throw new IndexOutOfBoundsException("index < minIndex");
|
||||
}
|
||||
else if (pIndex >= getNumImages(true)) {
|
||||
throw new IndexOutOfBoundsException("index > numImages");
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void init() {
|
||||
if (mDecoder == null) {
|
||||
if (mImageInput == null) {
|
||||
throw new IllegalStateException("input == null");
|
||||
}
|
||||
|
||||
mDecoder = new TIFFImageDecoder(new SeekableStream() {
|
||||
public int read() throws IOException {
|
||||
return mImageInput.read();
|
||||
}
|
||||
|
||||
public int read(final byte[] pBytes, final int pStart, final int pLength) throws IOException {
|
||||
return mImageInput.read(pBytes, pStart, pLength);
|
||||
}
|
||||
|
||||
public long getFilePointer() throws IOException {
|
||||
return mImageInput.getStreamPosition();
|
||||
}
|
||||
|
||||
public void seek(final long pPos) throws IOException {
|
||||
mImageInput.seek(pPos);
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
init(pIndex);
|
||||
|
||||
// TODO: Use cache...
|
||||
return mDecoder.decodeAsRenderedImage(pIndex).getWidth();
|
||||
}
|
||||
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
init(pIndex);
|
||||
|
||||
// TODO: Use cache...
|
||||
return mDecoder.decodeAsRenderedImage(pIndex).getHeight();
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||
throw new UnsupportedOperationException("Method getImageTypes not implemented");// TODO: Implement
|
||||
}
|
||||
|
||||
public int getNumImages(boolean pAllowSearch) throws IOException {
|
||||
init();
|
||||
if (pAllowSearch) {
|
||||
return mDecoder.getNumPages();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* TIFFImageReaderSpi
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: TIFFImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 wmhakur Exp $
|
||||
*/
|
||||
public class TIFFImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
final static boolean TIFF_CLASSES_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader");
|
||||
|
||||
/**
|
||||
* Creates a {@code TIFFImageReaderSpi}.
|
||||
*/
|
||||
public TIFFImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(TIFFImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private TIFFImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
pProviderInfo.getVendorName(), // Vendor name
|
||||
pProviderInfo.getVersion(), // Version
|
||||
TIFF_CLASSES_AVAILABLE ? new String[]{"tiff", "TIFF"} : new String[] {""}, // Names
|
||||
TIFF_CLASSES_AVAILABLE ? new String[]{"tiff", "tif"} : null, // Suffixes
|
||||
TIFF_CLASSES_AVAILABLE ? new String[]{"image/tiff", "image/x-tiff"} : null, // Mime-types
|
||||
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader", // Writer class name..?
|
||||
ImageReaderSpi.STANDARD_INPUT_TYPE, // Output types
|
||||
new String[]{"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi"}, // Writer SPI names
|
||||
true, // Supports standard stream metadata format
|
||||
null, // Native stream metadata format name
|
||||
null, // Native stream metadata format class name
|
||||
null, // Extra stream metadata format names
|
||||
null, // Extra stream metadata format class names
|
||||
true, // Supports standard image metadata format
|
||||
null, // Native image metadata format name
|
||||
null, // Native image metadata format class name
|
||||
null, // Extra image metadata format names
|
||||
null // Extra image metadata format class names
|
||||
);
|
||||
}
|
||||
|
||||
public boolean canDecodeInput(Object source) throws IOException {
|
||||
return source instanceof ImageInputStream && TIFF_CLASSES_AVAILABLE && canDecode((ImageInputStream) source);
|
||||
}
|
||||
|
||||
|
||||
static boolean canDecode(ImageInputStream pInput) throws IOException {
|
||||
try {
|
||||
pInput.mark();
|
||||
int byte0 = pInput.read(); // Byte order 1 (M or I)
|
||||
int byte1 = pInput.read(); // Byte order 2 (always same as 1)
|
||||
int byte2 = pInput.read(); // Version number 1 (M: 0, I: 42)
|
||||
int byte3 = pInput.read(); // Version number 2 (M: 42, I: 0)
|
||||
|
||||
// Test for Motorola or Intel byte order, and version number == 42
|
||||
if ((byte0 == 'M' && byte1 == 'M' && byte2 == 0 && byte3 == 42)
|
||||
|| (byte0 == 'I' && byte1 == 'I' && byte2 == 42 && byte3 == 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ImageReader createReaderInstance(Object extension) throws IOException {
|
||||
return new TIFFImageReader(this);
|
||||
}
|
||||
|
||||
public String getDescription(Locale locale) {
|
||||
return "Tagged Image File Format (TIFF) image reader";
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation"})
|
||||
@Override
|
||||
public void onRegistration(ServiceRegistry registry, Class<?> category) {
|
||||
if (!TIFF_CLASSES_AVAILABLE) {
|
||||
IIOUtil.deregisterProvider(registry, this, category);
|
||||
}
|
||||
}
|
||||
}
|
||||
-145
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import org.apache.batik.ext.awt.image.codec.ImageEncodeParam;
|
||||
import org.apache.batik.ext.awt.image.codec.tiff.TIFFEncodeParam;
|
||||
import org.apache.batik.ext.awt.image.codec.tiff.TIFFImageEncoder;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* TIFFImageWriter class description.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: TIFFImageWriter.java,v 1.0 29.jul.2004 12:52:54 haku Exp $
|
||||
*/
|
||||
public class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
private TIFFImageEncoder mEncoder = null;
|
||||
|
||||
protected TIFFImageWriter(final ImageWriterSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutput(final Object pOutput) {
|
||||
mEncoder = null;
|
||||
super.setOutput(pOutput);
|
||||
}
|
||||
|
||||
public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
throw new UnsupportedOperationException("Method getDefaultImageMetadata not implemented");// TODO: Implement
|
||||
}
|
||||
|
||||
public IIOMetadata convertImageMetadata(final IIOMetadata inData, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
throw new UnsupportedOperationException("Method convertImageMetadata not implemented");// TODO: Implement
|
||||
}
|
||||
|
||||
public void write(final IIOMetadata pStreamMetadata, final IIOImage pImage, final ImageWriteParam pParam) throws IOException {
|
||||
RenderedImage renderedImage = pImage.getRenderedImage();
|
||||
init();
|
||||
|
||||
ImageEncodeParam param;
|
||||
if (pParam != null) {
|
||||
param = new TIFFEncodeParam();
|
||||
// TODO: Convert params
|
||||
|
||||
mEncoder.setParam(param);
|
||||
}
|
||||
|
||||
BufferedImage image;
|
||||
|
||||
// FIX: TIFFEnocder chokes on a any of the TYPE_INT_* types...
|
||||
// (The TIFFEncoder expects int types to have 1 sample of size 32
|
||||
// while there actually is 4 samples of size 8, according to the
|
||||
// SampleModel...)
|
||||
if (renderedImage instanceof BufferedImage && (
|
||||
((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_ARGB
|
||||
|| ((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_ARGB_PRE)) {
|
||||
image = ImageUtil.toBuffered(renderedImage, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
}
|
||||
else if (renderedImage instanceof BufferedImage && (
|
||||
((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_BGR
|
||||
|| ((BufferedImage) renderedImage).getType() == BufferedImage.TYPE_INT_RGB)) {
|
||||
image = ImageUtil.toBuffered(renderedImage, BufferedImage.TYPE_3BYTE_BGR);
|
||||
}
|
||||
else {
|
||||
image = ImageUtil.toBuffered(renderedImage);
|
||||
}
|
||||
|
||||
image = fakeAOI(image, pParam);
|
||||
image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
|
||||
|
||||
/*
|
||||
System.out.println("Image: " + pImage);
|
||||
SampleModel sampleModel = pImage.getSampleModel();
|
||||
System.out.println("SampleModel: " + sampleModel);
|
||||
int sampleSize[] = sampleModel.getSampleSize();
|
||||
System.out.println("Samples: " + sampleSize.length);
|
||||
for (int i = 0; i < sampleSize.length; i++) {
|
||||
System.out.println("SampleSize[" + i + "]: " + sampleSize[i]);
|
||||
}
|
||||
int dataType = sampleModel.getDataType();
|
||||
System.out.println("DataType: " + dataType);
|
||||
*/
|
||||
|
||||
processImageStarted(0);
|
||||
|
||||
mEncoder.encode(image);
|
||||
mImageOutput.flush();
|
||||
|
||||
processImageComplete();
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
mEncoder = null;
|
||||
}
|
||||
|
||||
private synchronized void init() {
|
||||
if (mEncoder == null) {
|
||||
if (mImageOutput == null) {
|
||||
throw new IllegalStateException("output == null");
|
||||
}
|
||||
mEncoder = new TIFFImageEncoder(IIOUtil.createStreamAdapter(mImageOutput), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* TIFFmageWriterSpi
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: TIFFImageWriterSpi.java,v 1.2 2004/01/14 15:21:44 wmhakur Exp $
|
||||
*/
|
||||
public class TIFFImageWriterSpi extends ImageWriterSpi {
|
||||
|
||||
/**
|
||||
* Creates a {@code TIFFImageWriterSpi}.
|
||||
*/
|
||||
public TIFFImageWriterSpi() {
|
||||
this(IIOUtil.getProviderInfo(TIFFImageWriterSpi.class));
|
||||
}
|
||||
|
||||
private TIFFImageWriterSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
pProviderInfo.getVendorName(), // Vendor name
|
||||
pProviderInfo.getVersion(), // Version
|
||||
new String[]{"tiff", "TIFF"}, // Names
|
||||
new String[]{"tif", "tiff"}, // Suffixes
|
||||
new String[]{"image/tiff", "image/x-tiff"}, // Mime-types
|
||||
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter", // Writer class name..?
|
||||
STANDARD_OUTPUT_TYPE, // Output types
|
||||
new String[]{"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"}, // Reader SPI names
|
||||
true, // Supports standard stream metadata format
|
||||
null, // Native stream metadata format name
|
||||
null, // Native stream metadata format class name
|
||||
null, // Extra stream metadata format names
|
||||
null, // Extra stream metadata format class names
|
||||
true, // Supports standard image metadata format
|
||||
null, // Native image metadata format name
|
||||
null, // Native image metadata format class name
|
||||
null, // Extra image metadata format names
|
||||
null // Extra image metadata format class names
|
||||
);
|
||||
}
|
||||
|
||||
public boolean canEncodeImage(ImageTypeSpecifier type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public ImageWriter createWriterInstance(Object extension) throws IOException {
|
||||
try {
|
||||
return new TIFFImageWriter(this);
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// Wrap in IOException if the writer can't be instantiated.
|
||||
// This makes the IIORegistry deregister this service provider
|
||||
IOException exception = new IOException(t.getMessage());
|
||||
exception.initCause(t);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
public String getDescription(Locale locale) {
|
||||
return "Tagged Image File Format (TIFF) image writer";
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation"})
|
||||
@Override
|
||||
public void onRegistration(ServiceRegistry registry, Class<?> category) {
|
||||
if (!TIFFImageReaderSpi.TIFF_CLASSES_AVAILABLE) {
|
||||
IIOUtil.deregisterProvider(registry, this, category);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-13
@@ -57,17 +57,18 @@ import java.util.Iterator;
|
||||
// TODO: Consider using temp file instead of in-memory stream
|
||||
public class WMFImageReader extends ImageReaderBase {
|
||||
|
||||
private SVGImageReader mReader = null;
|
||||
private SVGImageReader reader = null;
|
||||
|
||||
public WMFImageReader(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
if (mReader != null) {
|
||||
mReader.dispose();
|
||||
if (reader != null) {
|
||||
reader.dispose();
|
||||
}
|
||||
mReader = null;
|
||||
|
||||
reader = null;
|
||||
}
|
||||
|
||||
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
||||
@@ -75,11 +76,12 @@ public class WMFImageReader extends ImageReaderBase {
|
||||
|
||||
processImageStarted(pIndex);
|
||||
|
||||
BufferedImage image = mReader.read(pIndex, pParam);
|
||||
BufferedImage image = reader.read(pIndex, pParam);
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
return image;
|
||||
}
|
||||
processImageProgress(100f);
|
||||
|
||||
processImageComplete();
|
||||
|
||||
@@ -88,17 +90,17 @@ public class WMFImageReader extends ImageReaderBase {
|
||||
|
||||
private synchronized void init() throws IOException {
|
||||
// Need the extra test, to avoid throwing an IOException from the Transcoder
|
||||
if (mImageInput == null) {
|
||||
if (imageInput == null) {
|
||||
throw new IllegalStateException("input == null");
|
||||
}
|
||||
|
||||
if (mReader == null) {
|
||||
if (reader == null) {
|
||||
WMFTranscoder transcoder = new WMFTranscoder();
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
Writer writer = new OutputStreamWriter(output, "UTF8");
|
||||
try {
|
||||
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(mImageInput));
|
||||
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
||||
TranscoderOutput out = new TranscoderOutput(writer);
|
||||
|
||||
// TODO: Transcodinghints?
|
||||
@@ -109,8 +111,8 @@ public class WMFImageReader extends ImageReaderBase {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
mReader = new SVGImageReader(getOriginatingProvider());
|
||||
mReader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(output.toByteArray())));
|
||||
reader = new SVGImageReader(getOriginatingProvider());
|
||||
reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(output.toByteArray())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,17 +123,17 @@ public class WMFImageReader extends ImageReaderBase {
|
||||
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
init();
|
||||
return mReader.getWidth(pIndex);
|
||||
return reader.getWidth(pIndex);
|
||||
}
|
||||
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
init();
|
||||
return mReader.getHeight(pIndex);
|
||||
return reader.getHeight(pIndex);
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
||||
init();
|
||||
return mReader.getImageTypes(pImageIndex);
|
||||
return reader.getImageTypes(pImageIndex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -1,3 +1,2 @@
|
||||
com.twelvemonkeys.imageio.plugins.svg.SVGImageReaderSpi
|
||||
com.twelvemonkeys.imageio.plugins.wmf.WMFImageReaderSpi
|
||||
#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
#com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
|
||||
+2
-2
@@ -43,7 +43,7 @@ import java.util.List;
|
||||
* @version $Id: SVGImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class SVGImageReaderTestCase extends ImageReaderAbstractTestCase<SVGImageReader> {
|
||||
private SVGImageReaderSpi mSVGImageReaderSpi = new SVGImageReaderSpi();
|
||||
private SVGImageReaderSpi provider = new SVGImageReaderSpi();
|
||||
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
@@ -52,7 +52,7 @@ public class SVGImageReaderTestCase extends ImageReaderAbstractTestCase<SVGImage
|
||||
}
|
||||
|
||||
protected ImageReaderSpi createProvider() {
|
||||
return mSVGImageReaderSpi;
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ import java.util.List;
|
||||
* @version $Id: SVGImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class WMFImageReaderTestCase extends ImageReaderAbstractTestCase<WMFImageReader> {
|
||||
private WMFImageReaderSpi mSVGImageReaderSpi = new WMFImageReaderSpi();
|
||||
private WMFImageReaderSpi provider = new WMFImageReaderSpi();
|
||||
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
@@ -53,7 +53,7 @@ public class WMFImageReaderTestCase extends ImageReaderAbstractTestCase<WMFImage
|
||||
}
|
||||
|
||||
protected ImageReaderSpi createProvider() {
|
||||
return mSVGImageReaderSpi;
|
||||
return provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,4 +22,9 @@ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---
|
||||
|
||||
ClayRGB1998.icc color profile is public domain, downloaded from http://www.argyllcms.com/
|
||||
"Public Domain. No Warranty, Use at own risk."
|
||||
+158
-68
@@ -37,9 +37,7 @@ import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.io.File;
|
||||
@@ -57,11 +55,13 @@ import java.util.Iterator;
|
||||
*/
|
||||
public abstract class ImageReaderBase extends ImageReader {
|
||||
|
||||
private static final Point ORIGIN = new Point(0, 0);
|
||||
|
||||
/**
|
||||
* For convenience. Only set if the input is an {@code ImageInputStream}.
|
||||
* @see #setInput(Object, boolean, boolean)
|
||||
*/
|
||||
protected ImageInputStream mImageInput;
|
||||
protected ImageInputStream imageInput;
|
||||
|
||||
/**
|
||||
* Constructs an {@code ImageReader} and sets its
|
||||
@@ -73,21 +73,21 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
* the extension object is unsuitable, an
|
||||
* {@code IllegalArgumentException} should be thrown.
|
||||
*
|
||||
* @param pProvider the {@code ImageReaderSpi} that is invoking this constructor, or {@code null}.
|
||||
* @param provider the {@code ImageReaderSpi} that is invoking this constructor, or {@code null}.
|
||||
*/
|
||||
protected ImageReaderBase(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
protected ImageReaderBase(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides {@code setInput}, to allow easy access to the input, in case
|
||||
* it is an {@code ImageInputStream}.
|
||||
*
|
||||
* @param pInput the {@code ImageInputStream} or other
|
||||
* @param input the {@code ImageInputStream} or other
|
||||
* {@code Object} to use for future decoding.
|
||||
* @param pSeekForwardOnly if {@code true}, images and metadata
|
||||
* @param seekForwardOnly if {@code true}, images and metadata
|
||||
* may only be read in ascending order from this input source.
|
||||
* @param pIgnoreMetadata if {@code true}, metadata
|
||||
* @param ignoreMetadata if {@code true}, metadata
|
||||
* may be ignored during reads.
|
||||
*
|
||||
* @exception IllegalArgumentException if {@code input} is
|
||||
@@ -98,11 +98,15 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
* @see ImageInputStream
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final Object pInput, final boolean pSeekForwardOnly, final boolean pIgnoreMetadata) {
|
||||
public void setInput(final Object input, final boolean seekForwardOnly, final boolean ignoreMetadata) {
|
||||
resetMembers();
|
||||
super.setInput(pInput, pSeekForwardOnly, pIgnoreMetadata);
|
||||
if (pInput instanceof ImageInputStream) {
|
||||
mImageInput = (ImageInputStream) pInput;
|
||||
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
||||
|
||||
if (input instanceof ImageInputStream) {
|
||||
imageInput = (ImageInputStream) input;
|
||||
}
|
||||
else {
|
||||
imageInput = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,11 +136,11 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
/**
|
||||
* Default implementation that always returns {@code null}.
|
||||
*
|
||||
* @param pImageIndex ignored, unless overridden
|
||||
* @param imageIndex ignored, unless overridden
|
||||
* @return {@code null}, unless overridden
|
||||
* @throws IOException never, unless overridden.
|
||||
*/
|
||||
public IIOMetadata getImageMetadata(int pImageIndex) throws IOException {
|
||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -153,11 +157,11 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
/**
|
||||
* Default implementation that always returns {@code 1}.
|
||||
*
|
||||
* @param pAllowSearch ignored, unless overridden
|
||||
* @param allowSearch ignored, unless overridden
|
||||
* @return {@code 1}, unless overridden
|
||||
* @throws IOException never, unless overridden
|
||||
*/
|
||||
public int getNumImages(boolean pAllowSearch) throws IOException {
|
||||
public int getNumImages(boolean allowSearch) throws IOException {
|
||||
assertInput();
|
||||
return 1;
|
||||
}
|
||||
@@ -165,18 +169,18 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
/**
|
||||
* Convenience method to make sure image index is within bounds.
|
||||
*
|
||||
* @param pIndex the image index
|
||||
* @param index the image index
|
||||
*
|
||||
* @throws java.io.IOException if an error occurs during reading
|
||||
* @throws IndexOutOfBoundsException if not {@code minIndex <= pIndex < numImages}
|
||||
* @throws IndexOutOfBoundsException if not {@code minIndex <= index < numImages}
|
||||
*/
|
||||
protected void checkBounds(int pIndex) throws IOException {
|
||||
protected void checkBounds(int index) throws IOException {
|
||||
assertInput();
|
||||
if (pIndex < getMinIndex()) {
|
||||
if (index < getMinIndex()) {
|
||||
throw new IndexOutOfBoundsException("index < minIndex");
|
||||
}
|
||||
else if (getNumImages(false) != -1 && pIndex >= getNumImages(false)) {
|
||||
throw new IndexOutOfBoundsException("index >= numImages (" + pIndex + " >= " + getNumImages(false) + ")");
|
||||
else if (getNumImages(false) != -1 && index >= getNumImages(false)) {
|
||||
throw new IndexOutOfBoundsException("index >= numImages (" + index + " >= " + getNumImages(false) + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,50 +196,56 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code BufferedImage} to which decoded pixel
|
||||
* data should be written.
|
||||
* Returns the {@code BufferedImage} to which decoded pixel data should be written.
|
||||
* <p/>
|
||||
* As {@link javax.imageio.ImageReader#getDestination} but tests if the explicit destination
|
||||
* image (if set) is valid according to the {@code ImageTypeSpecifier}s given in {@code pTypes}
|
||||
* image (if set) is valid according to the {@code ImageTypeSpecifier}s given in {@code types}.
|
||||
*
|
||||
*
|
||||
* @param pParam an {@code ImageReadParam} to be used to get
|
||||
* @param param an {@code ImageReadParam} to be used to get
|
||||
* the destination image or image type, or {@code null}.
|
||||
* @param pTypes an {@code Iterator} of
|
||||
* @param types an {@code Iterator} of
|
||||
* {@code ImageTypeSpecifier}s indicating the legal image
|
||||
* types, with the default first.
|
||||
* @param pWidth the true width of the image or tile begin decoded.
|
||||
* @param pHeight the true width of the image or tile being decoded.
|
||||
* @param width the true width of the image or tile begin decoded.
|
||||
* @param height the true width of the image or tile being decoded.
|
||||
*
|
||||
* @return the {@code BufferedImage} to which decoded pixel
|
||||
* data should be written.
|
||||
*
|
||||
* @exception IIOException if the {@code ImageTypeSpecifier} or {@code BufferedImage}
|
||||
* specified by {@code pParam} does not match any of the legal
|
||||
* ones from {@code pTypes}.
|
||||
* @throws IllegalArgumentException if {@code pTypes}
|
||||
* @exception javax.imageio.IIOException if the {@code ImageTypeSpecifier} or {@code BufferedImage}
|
||||
* specified by {@code param} does not match any of the legal
|
||||
* ones from {@code types}.
|
||||
* @throws IllegalArgumentException if {@code types}
|
||||
* is {@code null} or empty, or if an object not of type
|
||||
* {@code ImageTypeSpecifier} is retrieved from it.
|
||||
* Or, if the resulting image would have a width or height less than 1,
|
||||
* or if the product of {@code pWidth} and {@code pHeight} is greater than
|
||||
* or if the product of {@code width} and {@code height} of the resulting image is greater than
|
||||
* {@code Integer.MAX_VALUE}.
|
||||
*/
|
||||
public static BufferedImage getDestination(final ImageReadParam pParam, final Iterator<ImageTypeSpecifier> pTypes,
|
||||
final int pWidth, final int pHeight) throws IIOException {
|
||||
BufferedImage image = ImageReader.getDestination(pParam, pTypes, pWidth, pHeight);
|
||||
public static BufferedImage getDestination(final ImageReadParam param, final Iterator<ImageTypeSpecifier> types,
|
||||
final int width, final int height) throws IIOException {
|
||||
// Adapted from http://java.net/jira/secure/attachment/29712/TIFFImageReader.java.patch,
|
||||
// to allow reading parts/tiles of huge images.
|
||||
|
||||
if (types == null || !types.hasNext()) {
|
||||
throw new IllegalArgumentException("imageTypes null or empty!");
|
||||
}
|
||||
|
||||
ImageTypeSpecifier imageType = null;
|
||||
|
||||
// If param is non-null, use it
|
||||
if (param != null) {
|
||||
// Try to get the explicit destinaton image
|
||||
BufferedImage dest = param.getDestination();
|
||||
|
||||
if (pParam != null) {
|
||||
BufferedImage dest = pParam.getDestination();
|
||||
if (dest != null) {
|
||||
boolean found = false;
|
||||
|
||||
// NOTE: This is bad, as it relies on implementation details of super method...
|
||||
// We know that the iterator has not been touched if explicit destination..
|
||||
while (pTypes.hasNext()) {
|
||||
ImageTypeSpecifier specifier = pTypes.next();
|
||||
int imageType = specifier.getBufferedImageType();
|
||||
while (types.hasNext()) {
|
||||
ImageTypeSpecifier specifier = types.next();
|
||||
int bufferedImageType = specifier.getBufferedImageType();
|
||||
|
||||
if (imageType != 0 && imageType == dest.getType()) {
|
||||
if (bufferedImageType != 0 && bufferedImageType == dest.getType()) {
|
||||
// Known types equal, perfect match
|
||||
found = true;
|
||||
break;
|
||||
@@ -254,12 +264,50 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
throw new IIOException(String.format("Illegal explicit destination image %s", dest));
|
||||
throw new IIOException(String.format("Destination image from ImageReadParam does not match legal imageTypes from reader: %s", dest));
|
||||
}
|
||||
|
||||
return dest;
|
||||
}
|
||||
|
||||
// No image, get the image type
|
||||
imageType = param.getDestinationType();
|
||||
}
|
||||
|
||||
// No info from param, use fallback image type
|
||||
if (imageType == null) {
|
||||
imageType = types.next();
|
||||
}
|
||||
else {
|
||||
boolean foundIt = false;
|
||||
|
||||
while (types.hasNext()) {
|
||||
ImageTypeSpecifier type = types.next();
|
||||
|
||||
if (type.equals(imageType)) {
|
||||
foundIt = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundIt) {
|
||||
throw new IIOException(String.format("Destination type from ImageReadParam does not match legal imageTypes from reader: %s", imageType));
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
Rectangle srcRegion = new Rectangle(0, 0, 0, 0);
|
||||
Rectangle destRegion = new Rectangle(0, 0, 0, 0);
|
||||
computeRegions(param, width, height, null, srcRegion, destRegion);
|
||||
|
||||
int destWidth = destRegion.x + destRegion.width;
|
||||
int destHeight = destRegion.y + destRegion.height;
|
||||
|
||||
if ((long) destWidth * destHeight > Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException(String.format("destination width * height > Integer.MAX_VALUE: %d", (long) destWidth * destHeight));
|
||||
}
|
||||
|
||||
// Create a new image based on the type specifier
|
||||
return imageType.createBufferedImage(destWidth, destHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,10 +352,26 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
return IIOUtil.fakeSubsampling(pImage, pParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if param has explicit destination.
|
||||
*
|
||||
* @param pParam the image read parameter, or {@code null}
|
||||
* @return true if {@code pParam} is non-{@code null} and either its {@code getDestination},
|
||||
* {@code getDestinationType} returns a non-{@code null} value,
|
||||
* or {@code getDestinationOffset} returns a {@link Point} that is not the upper left corner {@code (0, 0)}.
|
||||
*/
|
||||
protected static boolean hasExplicitDestination(final ImageReadParam pParam) {
|
||||
return pParam != null &&
|
||||
(
|
||||
pParam.getDestination() != null || pParam.getDestinationType() != null ||
|
||||
!ORIGIN.equals(pParam.getDestinationOffset())
|
||||
);
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
BufferedImage image = ImageIO.read(new File(pArgs[0]));
|
||||
if (image == null) {
|
||||
System.err.println("Supported formats: " + Arrays.toString(ImageIO.getReaderFormatNames()));
|
||||
System.err.println("Supported formats: " + Arrays.toString(IIOUtil.getNormalizedReaderFormatNames()));
|
||||
System.exit(1);
|
||||
}
|
||||
showIt(image, pArgs[0]);
|
||||
@@ -318,10 +382,21 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
SwingUtilities.invokeAndWait(new Runnable() {
|
||||
public void run() {
|
||||
JFrame frame = new JFrame(pTitle);
|
||||
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
|
||||
frame.getRootPane().getActionMap().put("window-close", new AbstractAction() {
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Window window = SwingUtilities.getWindowAncestor((Component) e.getSource());
|
||||
window.setVisible(false);
|
||||
window.dispose();
|
||||
}
|
||||
});
|
||||
frame.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "window-close");
|
||||
frame.addWindowListener(new ExitIfNoWindowPresentHandler());
|
||||
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
|
||||
frame.setLocationByPlatform(true);
|
||||
JPanel pane = new JPanel(new BorderLayout());
|
||||
JScrollPane scroll = new JScrollPane(new ImageLabel(pImage));
|
||||
JScrollPane scroll = new JScrollPane(pImage != null ? new ImageLabel(pImage) : new JLabel("(no image data)", JLabel.CENTER));
|
||||
scroll.setBorder(null);
|
||||
pane.add(scroll);
|
||||
frame.setContentPane(pane);
|
||||
@@ -334,27 +409,31 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof RuntimeException) {
|
||||
throw (RuntimeException) e.getCause();
|
||||
}
|
||||
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImageLabel extends JLabel {
|
||||
Paint mBackground;
|
||||
Paint backgroundPaint;
|
||||
|
||||
final Paint mCheckeredBG;
|
||||
final Color mDefaultBG;
|
||||
final Paint checkeredBG;
|
||||
final Color defaultBG;
|
||||
|
||||
public ImageLabel(final BufferedImage pImage) {
|
||||
super(new BufferedImageIcon(pImage));
|
||||
setOpaque(false);
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||||
|
||||
mCheckeredBG = createTexture();
|
||||
checkeredBG = createTexture();
|
||||
|
||||
// For indexed color, default to the color of the transparent pixel, if any
|
||||
mDefaultBG = getDefaultBackground(pImage);
|
||||
defaultBG = getDefaultBackground(pImage);
|
||||
|
||||
mBackground = mDefaultBG != null ? mDefaultBG : mCheckeredBG;
|
||||
backgroundPaint = defaultBG != null ? defaultBG : checkeredBG;
|
||||
|
||||
JPopupMenu popup = createBackgroundPopup();
|
||||
|
||||
@@ -373,7 +452,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Checkered", mCheckeredBG), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Checkered", checkeredBG), popup, group);
|
||||
popup.addSeparator();
|
||||
addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Light", Color.LIGHT_GRAY), popup, group);
|
||||
@@ -381,7 +460,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), popup, group);
|
||||
popup.addSeparator();
|
||||
addCheckBoxItem(new ChooseBackgroundAction("Choose...", mDefaultBG != null ? mDefaultBG : Color.BLUE), popup, group);
|
||||
addCheckBoxItem(new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE), popup, group);
|
||||
|
||||
return popup;
|
||||
}
|
||||
@@ -425,21 +504,21 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
Graphics2D gr = (Graphics2D) g;
|
||||
gr.setPaint(mBackground);
|
||||
gr.setPaint(backgroundPaint);
|
||||
gr.fillRect(0, 0, getWidth(), getHeight());
|
||||
super.paintComponent(g);
|
||||
}
|
||||
|
||||
private class ChangeBackgroundAction extends AbstractAction {
|
||||
protected Paint mPaint;
|
||||
protected Paint paint;
|
||||
|
||||
public ChangeBackgroundAction(final String pName, final Paint pPaint) {
|
||||
super(pName);
|
||||
mPaint = pPaint;
|
||||
paint = pPaint;
|
||||
}
|
||||
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
mBackground = mPaint;
|
||||
backgroundPaint = paint;
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
@@ -450,7 +529,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
putValue(Action.SMALL_ICON, new Icon() {
|
||||
public void paintIcon(Component c, Graphics pGraphics, int x, int y) {
|
||||
Graphics g = pGraphics.create();
|
||||
g.setColor((Color) mPaint);
|
||||
g.setColor((Color) paint);
|
||||
g.fillRect(x, y, 16, 16);
|
||||
g.dispose();
|
||||
}
|
||||
@@ -467,12 +546,23 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
Color selected = JColorChooser.showDialog(ImageLabel.this, "Choose background", (Color) mPaint);
|
||||
Color selected = JColorChooser.showDialog(ImageLabel.this, "Choose background", (Color) paint);
|
||||
if (selected != null) {
|
||||
mPaint = selected;
|
||||
paint = selected;
|
||||
super.actionPerformed(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExitIfNoWindowPresentHandler extends WindowAdapter {
|
||||
@Override
|
||||
public void windowClosed(final WindowEvent e) {
|
||||
Window[] windows = Window.getWindows();
|
||||
|
||||
if (windows == null || windows.length == 0) {
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ public abstract class ImageWriterBase extends ImageWriter {
|
||||
* For convenience. Only set if the output is an {@code ImageInputStream}.
|
||||
* @see #setOutput(Object)
|
||||
*/
|
||||
protected ImageOutputStream mImageOutput;
|
||||
protected ImageOutputStream imageOutput;
|
||||
|
||||
/**
|
||||
* Constructs an {@code ImageWriter} and sets its
|
||||
@@ -65,10 +65,10 @@ public abstract class ImageWriterBase extends ImageWriter {
|
||||
* the extension object is unsuitable, an
|
||||
* {@code IllegalArgumentException} should be thrown.
|
||||
*
|
||||
* @param pProvider the {@code ImageWriterSpi} that is constructing this object, or {@code null}.
|
||||
* @param provider the {@code ImageWriterSpi} that is constructing this object, or {@code null}.
|
||||
*/
|
||||
protected ImageWriterBase(final ImageWriterSpi pProvider) {
|
||||
super(pProvider);
|
||||
protected ImageWriterBase(final ImageWriterSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
public String getFormatName() throws IOException {
|
||||
@@ -76,11 +76,15 @@ public abstract class ImageWriterBase extends ImageWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutput(final Object pOutput) {
|
||||
super.setOutput(pOutput);
|
||||
public void setOutput(final Object output) {
|
||||
resetMembers();
|
||||
super.setOutput(output);
|
||||
|
||||
if (pOutput instanceof ImageOutputStream) {
|
||||
mImageOutput = (ImageOutputStream) pOutput;
|
||||
if (output instanceof ImageOutputStream) {
|
||||
imageOutput = (ImageOutputStream) output;
|
||||
}
|
||||
else {
|
||||
imageOutput = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,24 +99,33 @@ public abstract class ImageWriterBase extends ImageWriter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
super.reset();
|
||||
resetMembers();
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code null}
|
||||
*
|
||||
* @param pParam ignored.
|
||||
* @param param ignored.
|
||||
* @return {@code null}.
|
||||
*/
|
||||
public IIOMetadata getDefaultStreamMetadata(final ImageWriteParam pParam) {
|
||||
public IIOMetadata getDefaultStreamMetadata(final ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code null}
|
||||
*
|
||||
* @param pInData ignored.
|
||||
* @param pParam ignored.
|
||||
* @param inData ignored.
|
||||
* @param param ignored.
|
||||
* @return {@code null}.
|
||||
*/
|
||||
public IIOMetadata convertStreamMetadata(final IIOMetadata pInData, final ImageWriteParam pParam) {
|
||||
public IIOMetadata convertStreamMetadata(final IIOMetadata inData, final ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+36
-34
@@ -1,17 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
* Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
@@ -26,25 +26,24 @@
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
|
||||
/**
|
||||
* CMYKColorSpace
|
||||
* A fallback CMYK ColorSpace, in case none can be read from disk.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: CMYKColorSpace.java,v 1.0 Apr 30, 2008 1:38:13 PM haraldk Exp$
|
||||
*/
|
||||
// TODO: Move to com.twelvemonkeys.image?
|
||||
// TODO: Read a ICC CMYK profile from classpath resource (from ECI)? ISO coated?
|
||||
final class CMYKColorSpace extends ColorSpace {
|
||||
|
||||
static final ColorSpace INSTANCE = new CMYKColorSpace();
|
||||
|
||||
final ColorSpace sRGB = getInstance(CS_sRGB);
|
||||
|
||||
CMYKColorSpace() {
|
||||
private CMYKColorSpace() {
|
||||
super(ColorSpace.TYPE_CMYK, 4);
|
||||
}
|
||||
|
||||
@@ -58,6 +57,7 @@ final class CMYKColorSpace extends ColorSpace {
|
||||
(1 - colorvalue[1]) * (1 - colorvalue[3]),
|
||||
(1 - colorvalue[2]) * (1 - colorvalue[3])
|
||||
};
|
||||
|
||||
// TODO: Convert via CIEXYZ space using sRGB space, as suggested in docs
|
||||
// return sRGB.fromCIEXYZ(toCIEXYZ(colorvalue));
|
||||
}
|
||||
@@ -73,33 +73,35 @@ final class CMYKColorSpace extends ColorSpace {
|
||||
|
||||
// Convert to CMYK values
|
||||
return new float[] {(c - k), (m - k), (y - k), k};
|
||||
/*
|
||||
http://www.velocityreviews.com/forums/t127265-rgb-to-cmyk.html
|
||||
|
||||
(Step 0: Normalize R,G, and B values to fit into range [0.0 ... 1.0], or
|
||||
adapt the following matrix.)
|
||||
/*
|
||||
http://www.velocityreviews.com/forums/t127265-rgb-to-cmyk.html
|
||||
|
||||
Step 1: RGB to CMY
|
||||
(Step 0: Normalize R,G, and B values to fit into range [0.0 ... 1.0], or
|
||||
adapt the following matrix.)
|
||||
|
||||
| C | | 1 | | R |
|
||||
| M | = | 1 | - | G |
|
||||
| Y | | 1 | | B |
|
||||
Step 1: RGB to CMY
|
||||
|
||||
Step 2: CMY to CMYK
|
||||
| C | | 1 | | R |
|
||||
| M | = | 1 | - | G |
|
||||
| Y | | 1 | | B |
|
||||
|
||||
| C' | | C | | min(C,M,Y) |
|
||||
| M' | | M | | min(C,M,Y) |
|
||||
| Y' | = | Y | - | min(C,M,Y) |
|
||||
| K' | | min(C,M,Y) | | 0 |
|
||||
Step 2: CMY to CMYK
|
||||
|
||||
Easier to calculate if K' is calculated first, because K' = min(C,M,Y):
|
||||
| C' | | C | | min(C,M,Y) |
|
||||
| M' | | M | | min(C,M,Y) |
|
||||
| Y' | = | Y | - | min(C,M,Y) |
|
||||
| K' | | min(C,M,Y) | | 0 |
|
||||
|
||||
| C' | | C | | K' |
|
||||
| M' | | M | | K' |
|
||||
| Y' | = | Y | - | K' |
|
||||
| K' | | K'| | 0 |
|
||||
*/
|
||||
// return fromCIEXYZ(sRGB.toCIEXYZ(rgbvalue));
|
||||
Easier to calculate if K' is calculated first, because K' = min(C,M,Y):
|
||||
|
||||
| C' | | C | | K' |
|
||||
| M' | | M | | K' |
|
||||
| Y' | = | Y | - | K' |
|
||||
| K' | | K'| | 0 |
|
||||
*/
|
||||
|
||||
// return fromCIEXYZ(sRGB.toCIEXYZ(rgbvalue));
|
||||
}
|
||||
|
||||
public float[] toCIEXYZ(float[] colorvalue) {
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.lang.Platform;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import com.twelvemonkeys.util.LRUHashMap;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* A helper class for working with ICC color profiles and color spaces.
|
||||
* <p />
|
||||
* Standard ICC color profiles are read from system-specific locations
|
||||
* for known operating systems.
|
||||
* <p />
|
||||
* Color profiles may be configured by placing a property-file
|
||||
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||
* on the classpath, specifying the full path to the profile.
|
||||
* ICC color profiles are probably already present on your system, or
|
||||
* can be downloaded from
|
||||
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||
* <a href="http://www.adobe.com/downloads/">Adobe</a> or other places.
|
||||
* <p />
|
||||
* Example property file:
|
||||
* <pre>
|
||||
* # icc_profiles.properties
|
||||
* ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
|
||||
* GENERIC_CMYK=/path/to/Generic CMYK.icc
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||
*/
|
||||
public final class ColorSpaces {
|
||||
|
||||
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
||||
|
||||
// JDK 7 seems to handle non-perceptual rendering intents gracefully, so we don't need to fiddle with the profiles
|
||||
private final static boolean JDK_HANDLES_RENDERING_INTENTS = SystemUtil.isClassAvailable("java.lang.invoke.CallSite");
|
||||
|
||||
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
||||
|
||||
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
|
||||
public static final int CS_ADOBE_RGB_1998 = 5000;
|
||||
|
||||
/** A best-effort "generic" CMYK color space. Either read from disk or built-in. */
|
||||
public static final int CS_GENERIC_CMYK = 5001;
|
||||
|
||||
// Weak references to hold the color spaces while cached
|
||||
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<ICC_Profile>(null);
|
||||
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<ICC_Profile>(null);
|
||||
|
||||
// Cache for the latest used color spaces
|
||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<Key, ICC_ColorSpace>(10);
|
||||
|
||||
private ColorSpaces() {}
|
||||
|
||||
/**
|
||||
* Creates an ICC color space from the given ICC color profile.
|
||||
* <p />
|
||||
* For standard Java color spaces, the built-in instance is returned.
|
||||
* Otherwise, color spaces are looked up from cache and created on demand.
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return an ICC color space
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
public static ICC_ColorSpace createColorSpace(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
byte[] profileHeader = profile.getData(ICC_Profile.icSigHead);
|
||||
|
||||
ICC_ColorSpace cs = getInternalCS(profile.getColorSpaceType(), profileHeader);
|
||||
if (cs != null) {
|
||||
return cs;
|
||||
}
|
||||
|
||||
// Special case for color profiles with rendering intent != 0, see isOffendingColorProfile method
|
||||
// NOTE: Rendering intent is really a 4 byte value, but legal values are 0-3 (ICC1v42_2006_05_1.pdf, 7.2.15, p. 19)
|
||||
if (profileHeader[ICC_Profile.icHdrRenderingIntent] != 0) {
|
||||
profileHeader[ICC_Profile.icHdrRenderingIntent] = 0;
|
||||
|
||||
// Test again if this is an internal CS
|
||||
cs = getInternalCS(profile.getColorSpaceType(), profileHeader);
|
||||
if (cs != null) {
|
||||
return cs;
|
||||
}
|
||||
|
||||
// NOTE: The intent change breaks JDK7: Seems to be a bug in ICC_Profile.getData/setData,
|
||||
// as calling it with unchanged header data, still breaks when creating new ICC_ColorSpace...
|
||||
// However, we simply skip that, as JDK7 handles the rendering intents already.
|
||||
if (!JDK_HANDLES_RENDERING_INTENTS) {
|
||||
// Fix profile before lookup/create
|
||||
profile.setData(ICC_Profile.icSigHead, profileHeader);
|
||||
}
|
||||
}
|
||||
|
||||
return getCachedOrCreateCS(profile, profileHeader);
|
||||
}
|
||||
|
||||
private static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ICC_ColorSpace getCachedOrCreateCS(final ICC_Profile profile, final byte[] profileHeader) {
|
||||
Key key = new Key(profileHeader);
|
||||
|
||||
synchronized (cache) {
|
||||
ICC_ColorSpace cs = cache.get(key);
|
||||
|
||||
if (cs == null) {
|
||||
cs = new ICC_ColorSpace(profile);
|
||||
cache.put(key, cs);
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p />
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
public static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// NOTE:
|
||||
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
||||
// The problem with these embedded ICC profiles seems to be the rendering intent
|
||||
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
||||
// and 0 (00000000) - "Perceptual" in the good profiles
|
||||
// (that is 1 single bit of difference right there.. ;-)
|
||||
|
||||
// This is particularly annoying, as the byte copying isn't really necessary,
|
||||
// except the getRenderingIntent method is package protected in java.awt.color
|
||||
byte[] data = profile.getData(ICC_Profile.icSigHead);
|
||||
|
||||
return data[ICC_Profile.icHdrRenderingIntent] != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color space specified by the given color space constant.
|
||||
* <p />
|
||||
* For standard Java color spaces, the built-in instance is returned.
|
||||
* Otherwise, color spaces are looked up from cache and created on demand.
|
||||
*
|
||||
* @param colorSpace the color space constant.
|
||||
* @return the {@link ColorSpace} specified by the color space constant.
|
||||
* @throws IllegalArgumentException if {@code colorSpace} is not one of the defined color spaces ({@code CS_*}).
|
||||
* @see ColorSpace
|
||||
* @see ColorSpaces#CS_ADOBE_RGB_1998
|
||||
* @see ColorSpaces#CS_GENERIC_CMYK
|
||||
*/
|
||||
public static ColorSpace getColorSpace(int colorSpace) {
|
||||
ICC_Profile profile;
|
||||
|
||||
switch (colorSpace) {
|
||||
case CS_ADOBE_RGB_1998:
|
||||
synchronized (ColorSpaces.class) {
|
||||
profile = adobeRGB1998.get();
|
||||
|
||||
if (profile == null) {
|
||||
// Try to get system default or user-defined profile
|
||||
profile = readProfileFromPath(Profiles.getPath("ADOBE_RGB_1998"));
|
||||
|
||||
if (profile == null) {
|
||||
// Fall back to the bundled ClayRGB1998 public domain Adobe RGB 1998 compatible profile,
|
||||
// identical for all practical purposes
|
||||
profile = readProfileFromClasspathResource("/profiles/ClayRGB1998.icc");
|
||||
|
||||
if (profile == null) {
|
||||
// Should never happen given we now bundle fallback profile...
|
||||
throw new IllegalStateException("Could not read AdobeRGB1998 profile");
|
||||
}
|
||||
}
|
||||
|
||||
adobeRGB1998 = new WeakReference<ICC_Profile>(profile);
|
||||
}
|
||||
}
|
||||
|
||||
return createColorSpace(profile);
|
||||
|
||||
case CS_GENERIC_CMYK:
|
||||
synchronized (ColorSpaces.class) {
|
||||
profile = genericCMYK.get();
|
||||
|
||||
if (profile == null) {
|
||||
// Try to get system default or user-defined profile
|
||||
profile = readProfileFromPath(Profiles.getPath("GENERIC_CMYK"));
|
||||
|
||||
if (profile == null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Using fallback profile");
|
||||
}
|
||||
|
||||
// Fall back to generic CMYK ColorSpace, which is *insanely slow* using ColorConvertOp... :-P
|
||||
return CMYKColorSpace.getInstance();
|
||||
}
|
||||
|
||||
genericCMYK = new WeakReference<ICC_Profile>(profile);
|
||||
}
|
||||
}
|
||||
|
||||
return createColorSpace(profile);
|
||||
|
||||
default:
|
||||
// Default cases for convenience
|
||||
return ColorSpace.getInstance(colorSpace);
|
||||
}
|
||||
}
|
||||
|
||||
private static ICC_Profile readProfileFromClasspathResource(final String profilePath) {
|
||||
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
||||
|
||||
if (stream != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from classpath resource: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
return ICC_Profile.getInstance(stream);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ICC_Profile readProfileFromPath(final String profilePath) {
|
||||
if (profilePath != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(profilePath);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class Key {
|
||||
private final byte[] data;
|
||||
|
||||
public Key(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other instanceof Key && Arrays.equals(data, ((Key) other).data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(data);
|
||||
}
|
||||
}
|
||||
|
||||
// Cache header profile data to avoid excessive array creation/copying in static inner class for on-demand lazy init
|
||||
private static class sRGB {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
private static class CIEXYZ {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
private static class PYCC {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_PYCC).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
private static class GRAY {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_GRAY).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
private static class LINEAR_RGB {
|
||||
private static final byte[] header = ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB).getData(ICC_Profile.icSigHead);
|
||||
}
|
||||
|
||||
private static class Profiles {
|
||||
private static final Properties PROFILES = loadProfiles(Platform.os());
|
||||
|
||||
private static Properties loadProfiles(final Platform.OperatingSystem os) {
|
||||
Properties systemDefaults;
|
||||
try {
|
||||
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + os.id());
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
ignore.printStackTrace();
|
||||
systemDefaults = null;
|
||||
}
|
||||
|
||||
// Create map with defaults and add user overrides if any
|
||||
Properties profiles = new Properties(systemDefaults);
|
||||
|
||||
try {
|
||||
Properties userOverrides = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles");
|
||||
profiles.putAll(userOverrides);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("User ICC profiles: " + profiles);
|
||||
System.out.println("System ICC profiles : " + systemDefaults);
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
public static String getPath(final String profileName) {
|
||||
return PROFILES.getProperty(profileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -16,9 +16,9 @@ public class ProviderInfo {
|
||||
// TODO: Consider reading the META-INF/MANIFEST.MF from the class path using java.util.jar.Manifest.
|
||||
// Use the manifest that is located in the same class path folder as the package.
|
||||
|
||||
private final String mTitle;
|
||||
private final String mVendorName;
|
||||
private final String mVersion;
|
||||
private final String title;
|
||||
private final String vendorName;
|
||||
private final String version;
|
||||
|
||||
/**
|
||||
* Creates a provider information instance based on the given package.
|
||||
@@ -32,13 +32,13 @@ public class ProviderInfo {
|
||||
Validate.notNull(pPackage, "package");
|
||||
|
||||
String title = pPackage.getImplementationTitle();
|
||||
mTitle = title != null ? title : pPackage.getName();
|
||||
this.title = title != null ? title : pPackage.getName();
|
||||
|
||||
String vendor = pPackage.getImplementationVendor();
|
||||
mVendorName = vendor != null ? vendor : fakeVendor(pPackage);
|
||||
vendorName = vendor != null ? vendor : fakeVendor(pPackage);
|
||||
|
||||
String version = pPackage.getImplementationVersion();
|
||||
mVersion = version != null ? version : fakeVersion(pPackage);
|
||||
this.version = version != null ? version : fakeVersion(pPackage);
|
||||
}
|
||||
|
||||
private static String fakeVendor(final Package pPackage) {
|
||||
@@ -60,7 +60,7 @@ public class ProviderInfo {
|
||||
* @return the implementation title
|
||||
*/
|
||||
final String getImplementationTitle() {
|
||||
return mTitle;
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +72,7 @@ public class ProviderInfo {
|
||||
* @return the vendor name.
|
||||
*/
|
||||
public final String getVendorName() {
|
||||
return mVendorName;
|
||||
return vendorName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,11 +83,11 @@ public class ProviderInfo {
|
||||
* @return the vendor name.
|
||||
*/
|
||||
public final String getVersion() {
|
||||
return mVersion;
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mTitle + ", " + mVersion + " by " + mVendorName;
|
||||
return title + ", " + version + " by " + vendorName;
|
||||
}
|
||||
}
|
||||
|
||||
+34
-34
@@ -23,12 +23,12 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
|
||||
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
private ImageInputStream mStream;
|
||||
private ImageInputStream stream;
|
||||
|
||||
private byte[] mBuffer;
|
||||
private long mBufferStart = 0;
|
||||
private int mBufferPos = 0;
|
||||
private int mBufferLength = 0;
|
||||
private byte[] buffer;
|
||||
private long bufferStart = 0;
|
||||
private int bufferPos = 0;
|
||||
private int bufferLength = 0;
|
||||
|
||||
public BufferedImageInputStream(final ImageInputStream pStream) throws IOException {
|
||||
this(pStream, DEFAULT_BUFFER_SIZE);
|
||||
@@ -37,19 +37,19 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
private BufferedImageInputStream(final ImageInputStream pStream, final int pBufferSize) throws IOException {
|
||||
Validate.notNull(pStream, "stream");
|
||||
|
||||
mStream = pStream;
|
||||
stream = pStream;
|
||||
streamPos = pStream.getStreamPosition();
|
||||
mBuffer = new byte[pBufferSize];
|
||||
buffer = new byte[pBufferSize];
|
||||
}
|
||||
|
||||
private void fillBuffer() throws IOException {
|
||||
mBufferStart = streamPos;
|
||||
mBufferLength = mStream.read(mBuffer, 0, mBuffer.length);
|
||||
mBufferPos = 0;
|
||||
bufferStart = streamPos;
|
||||
bufferLength = stream.read(buffer, 0, buffer.length);
|
||||
bufferPos = 0;
|
||||
}
|
||||
|
||||
private boolean isBufferValid() throws IOException {
|
||||
return mBufferPos < mBufferLength && mBufferStart == mStream.getStreamPosition() - mBufferLength;
|
||||
return bufferPos < bufferLength && bufferStart == stream.getStreamPosition() - bufferLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,14 +58,14 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
fillBuffer();
|
||||
}
|
||||
|
||||
if (mBufferLength <= 0) {
|
||||
if (bufferLength <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
streamPos++;
|
||||
|
||||
return mBuffer[mBufferPos++] & 0xff;
|
||||
return buffer[bufferPos++] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,7 +74,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
|
||||
if (!isBufferValid()) {
|
||||
// Bypass cache if cache is empty for reads longer than buffer
|
||||
if (pLength >= mBuffer.length) {
|
||||
if (pLength >= buffer.length) {
|
||||
return readDirect(pBuffer, pOffset, pLength);
|
||||
}
|
||||
else {
|
||||
@@ -87,30 +87,30 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
|
||||
private int readDirect(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
// TODO: Figure out why reading more than the buffer length causes alignment issues...
|
||||
int read = mStream.read(pBuffer, pOffset, Math.min(mBuffer.length, pLength));
|
||||
int read = stream.read(pBuffer, pOffset, Math.min(buffer.length, pLength));
|
||||
|
||||
if (read > 0) {
|
||||
streamPos += read;
|
||||
}
|
||||
|
||||
mBufferStart = mStream.getStreamPosition();
|
||||
mBufferLength = 0;
|
||||
bufferStart = stream.getStreamPosition();
|
||||
bufferLength = 0;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
|
||||
private int readBuffered(final byte[] pBuffer, final int pOffset, final int pLength) {
|
||||
if (mBufferLength <= 0) {
|
||||
if (bufferLength <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read as much as possible from buffer
|
||||
int length = Math.min(mBufferLength - mBufferPos, pLength);
|
||||
int length = Math.min(bufferLength - bufferPos, pLength);
|
||||
|
||||
if (length > 0) {
|
||||
System.arraycopy(mBuffer, mBufferPos, pBuffer, pOffset, length);
|
||||
mBufferPos += length;
|
||||
System.arraycopy(buffer, bufferPos, pBuffer, pOffset, length);
|
||||
bufferPos += length;
|
||||
}
|
||||
|
||||
streamPos += length;
|
||||
@@ -121,42 +121,42 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
@Override
|
||||
public void seek(long pPosition) throws IOException {
|
||||
// TODO: Could probably be optimized to not invalidate buffer if new position is within current buffer
|
||||
mStream.seek(pPosition);
|
||||
mBufferLength = 0; // Will invalidate buffer
|
||||
streamPos = mStream.getStreamPosition();
|
||||
stream.seek(pPosition);
|
||||
bufferLength = 0; // Will invalidate buffer
|
||||
streamPos = stream.getStreamPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBefore(long pos) throws IOException {
|
||||
mStream.flushBefore(pos);
|
||||
stream.flushBefore(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFlushedPosition() {
|
||||
return mStream.getFlushedPosition();
|
||||
return stream.getFlushedPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCached() {
|
||||
return mStream.isCached();
|
||||
return stream.isCached();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCachedMemory() {
|
||||
return mStream.isCachedMemory();
|
||||
return stream.isCachedMemory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCachedFile() {
|
||||
return mStream.isCachedFile();
|
||||
return stream.isCachedFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (mStream != null) {
|
||||
//mStream.close();
|
||||
mStream = null;
|
||||
mBuffer = null;
|
||||
if (stream != null) {
|
||||
//stream.close();
|
||||
stream = null;
|
||||
buffer = null;
|
||||
}
|
||||
super.close();
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
public long length() {
|
||||
// WTF?! This method is allowed to throw IOException in the interface...
|
||||
try {
|
||||
return mStream.length();
|
||||
return stream.length();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
|
||||
+23
-8
@@ -13,35 +13,50 @@ import java.io.IOException;
|
||||
* @version $Id: ByteArrayImageInputStream.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$
|
||||
*/
|
||||
public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
|
||||
private final byte[] mData;
|
||||
private final byte[] data;
|
||||
private final int dataOffset;
|
||||
private final int dataLength;
|
||||
|
||||
public ByteArrayImageInputStream(final byte[] pData) {
|
||||
this(pData, 0, pData != null ? pData.length : -1);
|
||||
}
|
||||
|
||||
public ByteArrayImageInputStream(final byte[] pData, int offset, int length) {
|
||||
Validate.notNull(pData, "data");
|
||||
mData = pData;
|
||||
Validate.isTrue(offset >= 0 && offset <= pData.length, offset, "offset out of range: %d");
|
||||
Validate.isTrue(length >= 0 && length <= pData.length - offset, length, "length out of range: %d");
|
||||
|
||||
data = pData;
|
||||
dataOffset = offset;
|
||||
dataLength = length;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (streamPos >= mData.length) {
|
||||
if (streamPos >= dataLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
return mData[((int) streamPos++)] & 0xff;
|
||||
|
||||
return data[((int) streamPos++) + dataOffset] & 0xff;
|
||||
}
|
||||
|
||||
public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||
if (streamPos >= mData.length) {
|
||||
if (streamPos >= dataLength) {
|
||||
return -1;
|
||||
}
|
||||
int length = (int) Math.min(mData.length - streamPos, pLength);
|
||||
|
||||
int length = (int) Math.min(this.dataLength - streamPos, pLength);
|
||||
bitOffset = 0;
|
||||
System.arraycopy(mData, (int) streamPos, pBuffer, pOffset, length);
|
||||
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length);
|
||||
streamPos += length;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return mData.length;
|
||||
return dataLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
-17
@@ -16,9 +16,9 @@ import java.io.IOException;
|
||||
public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||
// NOTE: This class is based on com.sun.imageio.plugins.common.SubImageInputStream, but fixes some of its bugs.
|
||||
|
||||
private final ImageInputStream mStream;
|
||||
private final long mStartPos;
|
||||
private final long mLength;
|
||||
private final ImageInputStream stream;
|
||||
private final long startPos;
|
||||
private final long length;
|
||||
|
||||
/**
|
||||
* Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream.
|
||||
@@ -32,21 +32,19 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||
*/
|
||||
public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException {
|
||||
Validate.notNull(pStream, "stream");
|
||||
if (pLength < 0) {
|
||||
throw new IllegalArgumentException("length < 0");
|
||||
}
|
||||
Validate.isTrue(pLength >= 0, pLength, "length < 0: %d");
|
||||
|
||||
mStream = pStream;
|
||||
mStartPos = pStream.getStreamPosition();
|
||||
mLength = pLength;
|
||||
stream = pStream;
|
||||
startPos = pStream.getStreamPosition();
|
||||
length = pLength;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (streamPos >= mLength) { // Local EOF
|
||||
if (streamPos >= length) { // Local EOF
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
int read = mStream.read();
|
||||
int read = stream.read();
|
||||
|
||||
if (read >= 0) {
|
||||
streamPos++;
|
||||
@@ -57,13 +55,13 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||
}
|
||||
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (streamPos >= mLength) { // Local EOF
|
||||
if (streamPos >= length) { // Local EOF
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Safe cast, as pLength can never cause int overflow
|
||||
int length = (int) Math.min(pLength, mLength - streamPos);
|
||||
int count = mStream.read(pBytes, pOffset, length);
|
||||
int length = (int) Math.min(pLength, this.length - streamPos);
|
||||
int count = stream.read(pBytes, pOffset, length);
|
||||
|
||||
if (count >= 0) {
|
||||
streamPos += count;
|
||||
@@ -75,8 +73,8 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||
@Override
|
||||
public long length() {
|
||||
try {
|
||||
long length = mStream.length();
|
||||
return length < 0 ? -1 : Math.min(length - mStartPos, mLength);
|
||||
long length = stream.length();
|
||||
return length < 0 ? -1 : Math.min(length - startPos, this.length);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
@@ -90,7 +88,7 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||
throw new IndexOutOfBoundsException("pos < flushedPosition");
|
||||
}
|
||||
|
||||
mStream.seek(mStartPos + pPosition);
|
||||
stream.seek(startPos + pPosition);
|
||||
streamPos = pPosition;
|
||||
}
|
||||
|
||||
|
||||
+33
-34
@@ -28,6 +28,8 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -43,10 +45,10 @@ import java.io.InputStream;
|
||||
* @version $Id: IIOInputStreamAdapter.java,v 1.0 Sep 26, 2007 11:35:59 AM haraldk Exp$
|
||||
*/
|
||||
class IIOInputStreamAdapter extends InputStream {
|
||||
private ImageInputStream mInput;
|
||||
private final boolean mHasLength;
|
||||
private long mLeft;
|
||||
private long mMarkPosition;
|
||||
private ImageInputStream input;
|
||||
private final boolean hasLength;
|
||||
private long left;
|
||||
private long markPosition;
|
||||
|
||||
// TODO: Enforce stream boundaries!
|
||||
// TODO: Stream start position....
|
||||
@@ -75,16 +77,12 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
}
|
||||
|
||||
private IIOInputStreamAdapter(ImageInputStream pInput, long pLength, boolean pHasLength) {
|
||||
if (pInput == null) {
|
||||
throw new IllegalArgumentException("stream == null");
|
||||
}
|
||||
if (pHasLength && pLength < 0) {
|
||||
throw new IllegalArgumentException("length < 0");
|
||||
}
|
||||
Validate.notNull(pInput, "stream");
|
||||
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %f");
|
||||
|
||||
mInput = pInput;
|
||||
mHasLength = pHasLength;
|
||||
mLeft = pLength;
|
||||
input = pInput;
|
||||
left = pLength;
|
||||
hasLength = pHasLength;
|
||||
}
|
||||
|
||||
|
||||
@@ -93,18 +91,19 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
* This implementation does <em>not</em> close the underlying stream.
|
||||
*/
|
||||
public void close() throws IOException {
|
||||
if (mHasLength) {
|
||||
mInput.seek(mInput.getStreamPosition() + mLeft);
|
||||
if (hasLength) {
|
||||
input.seek(input.getStreamPosition() + left);
|
||||
}
|
||||
|
||||
mLeft = 0;
|
||||
mInput = null;
|
||||
left = 0;
|
||||
input = null;
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
if (mHasLength) {
|
||||
return mLeft > 0 ? (int) Math.min(Integer.MAX_VALUE, mLeft) : 0;
|
||||
if (hasLength) {
|
||||
return left > 0 ? (int) Math.min(Integer.MAX_VALUE, left) : 0;
|
||||
}
|
||||
|
||||
return 0; // We don't really know, so we say 0 to be safe.
|
||||
}
|
||||
|
||||
@@ -115,7 +114,7 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
|
||||
public void mark(int pReadLimit) {
|
||||
try {
|
||||
mMarkPosition = mInput.getStreamPosition();
|
||||
markPosition = input.getStreamPosition();
|
||||
}
|
||||
catch (IOException e) {
|
||||
// Let's hope this never happens, because it's not possible to reset then...
|
||||
@@ -124,17 +123,17 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
}
|
||||
|
||||
public void reset() throws IOException {
|
||||
long diff = mInput.getStreamPosition() - mMarkPosition;
|
||||
mInput.seek(mMarkPosition);
|
||||
mLeft += diff;
|
||||
long diff = input.getStreamPosition() - markPosition;
|
||||
input.seek(markPosition);
|
||||
left += diff;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (mHasLength && mLeft-- <= 0) {
|
||||
mLeft = 0;
|
||||
if (hasLength && left-- <= 0) {
|
||||
left = 0;
|
||||
return -1;
|
||||
}
|
||||
return mInput.read();
|
||||
return input.read();
|
||||
}
|
||||
|
||||
public final int read(byte[] pBytes) throws IOException {
|
||||
@@ -142,13 +141,13 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
}
|
||||
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (mHasLength && mLeft <= 0) {
|
||||
if (hasLength && left <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read = mInput.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
||||
if (mHasLength) {
|
||||
mLeft = read < 0 ? 0 : mLeft - read;
|
||||
int read = input.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
||||
if (hasLength) {
|
||||
left = read < 0 ? 0 : left - read;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
@@ -161,8 +160,8 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
* @return the maximum number of bytes to read
|
||||
*/
|
||||
private long findMaxLen(long pLength) {
|
||||
if (mHasLength && mLeft < pLength) {
|
||||
return Math.max(mLeft, 0);
|
||||
if (hasLength && left < pLength) {
|
||||
return Math.max(left, 0);
|
||||
}
|
||||
else {
|
||||
return Math.max(pLength, 0);
|
||||
@@ -170,8 +169,8 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
}
|
||||
|
||||
public long skip(long pLength) throws IOException {
|
||||
long skipped = mInput.skipBytes(findMaxLen(pLength)); // Skips 0 or more, never -1
|
||||
mLeft -= skipped;
|
||||
long skipped = input.skipBytes(findMaxLen(pLength)); // Skips 0 or more, never -1
|
||||
left -= skipped;
|
||||
return skipped;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-7
@@ -28,6 +28,8 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
@@ -40,34 +42,47 @@ import java.io.OutputStream;
|
||||
* @version $Id: IIOOutputStreamAdapter.java,v 1.0 Sep 26, 2007 11:50:38 AM haraldk Exp$
|
||||
*/
|
||||
class IIOOutputStreamAdapter extends OutputStream {
|
||||
private ImageOutputStream mOutput;
|
||||
private ImageOutputStream output;
|
||||
|
||||
public IIOOutputStreamAdapter(final ImageOutputStream pOutput) {
|
||||
mOutput = pOutput;
|
||||
Validate.notNull(pOutput, "stream == null");
|
||||
|
||||
output = pOutput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final byte[] pBytes) throws IOException {
|
||||
mOutput.write(pBytes);
|
||||
assertOpen();
|
||||
output.write(pBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
mOutput.write(pBytes, pOffset, pLength);
|
||||
assertOpen();
|
||||
output.write(pBytes, pOffset, pLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final int pByte) throws IOException {
|
||||
mOutput.write(pByte);
|
||||
assertOpen();
|
||||
output.write(pByte);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
mOutput.flush();
|
||||
// NOTE: The contract of OutputStream.flush is very different from ImageOutputStream.flush. We can't delegate.
|
||||
// TODO: Fulfill the contract of OutputStream.flush? This seems to be good enough for now.
|
||||
assertOpen();
|
||||
}
|
||||
|
||||
private void assertOpen() throws IOException {
|
||||
if (output == null) {
|
||||
throw new IOException("stream already closed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
mOutput = null;
|
||||
output = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
|
||||
import javax.imageio.IIOParam;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.spi.IIOServiceProvider;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -14,6 +15,8 @@ import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* IIOUtil
|
||||
@@ -54,6 +57,9 @@ public final class IIOUtil {
|
||||
|
||||
/**
|
||||
* Creates an {@code OutputStream} adapter that writes to an underlying {@code ImageOutputStream}.
|
||||
* <p/>
|
||||
* Note: The adapter is buffered, and <em>MUST</em> be properly flushed/closed after use,
|
||||
* otherwise data may be lost.
|
||||
*
|
||||
* @param pStream the stream to write to.
|
||||
* @return an {@code OutputSteam} writing to {@code pStream}.
|
||||
@@ -146,4 +152,35 @@ public final class IIOUtil {
|
||||
pRegistry.deregisterServiceProvider(pCategory.cast(pProvider), pCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted array of format names, that can be read by ImageIO.
|
||||
* The names are all upper-case, and contains no duplicates.
|
||||
*
|
||||
* @return a normalized array of {@code String}s.
|
||||
* @see javax.imageio.ImageIO#getReaderFormatNames()
|
||||
*/
|
||||
public static String[] getNormalizedReaderFormatNames() {
|
||||
return normalizeNames(ImageIO.getReaderFormatNames());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a sorted array of format names, that can be written by ImageIO.
|
||||
* The names are all upper-case, and contains no duplicates.
|
||||
*
|
||||
* @return a normalized array of {@code String}s.
|
||||
* @see javax.imageio.ImageIO#getWriterFormatNames()
|
||||
*/
|
||||
public static String[] getNormalizedWriterFormatNames() {
|
||||
return normalizeNames(ImageIO.getWriterFormatNames());
|
||||
}
|
||||
|
||||
private static String[] normalizeNames(final String[] names) {
|
||||
SortedSet<String> normalizedNames = new TreeSet<String>();
|
||||
|
||||
for (String name : names) {
|
||||
normalizedNames.add(name.toUpperCase());
|
||||
}
|
||||
|
||||
return normalizedNames.toArray(new String[normalizedNames.size()]);
|
||||
}
|
||||
}
|
||||
-1
@@ -37,5 +37,4 @@ public class IndexedImageTypeSpecifier extends ImageTypeSpecifier {
|
||||
throw new IllegalArgumentException("Array size > Integer.MAX_VALUE!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
-16
@@ -44,51 +44,51 @@ public abstract class ProgressListenerBase implements IIOReadProgressListener, I
|
||||
protected ProgressListenerBase() {
|
||||
}
|
||||
|
||||
public void imageComplete(ImageReader pSource) {
|
||||
public void imageComplete(ImageReader source) {
|
||||
}
|
||||
|
||||
public void imageProgress(ImageReader pSource, float pPercentageDone) {
|
||||
public void imageProgress(ImageReader source, float percentageDone) {
|
||||
}
|
||||
|
||||
public void imageStarted(ImageReader pSource, int pImageIndex) {
|
||||
public void imageStarted(ImageReader source, int imageIndex) {
|
||||
}
|
||||
|
||||
public void readAborted(ImageReader pSource) {
|
||||
public void readAborted(ImageReader source) {
|
||||
}
|
||||
|
||||
public void sequenceComplete(ImageReader pSource) {
|
||||
public void sequenceComplete(ImageReader source) {
|
||||
}
|
||||
|
||||
public void sequenceStarted(ImageReader pSource, int pMinIndex) {
|
||||
public void sequenceStarted(ImageReader source, int minIndex) {
|
||||
}
|
||||
|
||||
public void thumbnailComplete(ImageReader pSource) {
|
||||
public void thumbnailComplete(ImageReader source) {
|
||||
}
|
||||
|
||||
public void thumbnailProgress(ImageReader pSource, float pPercentageDone) {
|
||||
public void thumbnailProgress(ImageReader source, float percentageDone) {
|
||||
}
|
||||
|
||||
public void thumbnailStarted(ImageReader pSource, int pImageIndex, int pThumbnailIndex) {
|
||||
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
|
||||
}
|
||||
|
||||
public void imageComplete(ImageWriter pSource) {
|
||||
public void imageComplete(ImageWriter source) {
|
||||
}
|
||||
|
||||
public void imageProgress(ImageWriter pSource, float pPercentageDone) {
|
||||
public void imageProgress(ImageWriter source, float percentageDone) {
|
||||
}
|
||||
|
||||
public void imageStarted(ImageWriter pSource, int pImageIndex) {
|
||||
public void imageStarted(ImageWriter source, int imageIndex) {
|
||||
}
|
||||
|
||||
public void thumbnailComplete(ImageWriter pSource) {
|
||||
public void thumbnailComplete(ImageWriter source) {
|
||||
}
|
||||
|
||||
public void thumbnailProgress(ImageWriter pSource, float pPercentageDone) {
|
||||
public void thumbnailProgress(ImageWriter source, float percentageDone) {
|
||||
}
|
||||
|
||||
public void thumbnailStarted(ImageWriter pSource, int pImageIndex, int pThumbnailIndex) {
|
||||
public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) {
|
||||
}
|
||||
|
||||
public void writeAborted(ImageWriter pSource) {
|
||||
public void writeAborted(ImageWriter source) {
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -47,15 +47,15 @@ import java.util.Map;
|
||||
* @version $Id: ReaderFileSuffixFilter.java,v 1.0 11.okt.2006 20:05:36 haku Exp$
|
||||
*/
|
||||
public final class ReaderFileSuffixFilter extends FileFilter implements java.io.FileFilter {
|
||||
private final String mDescription;
|
||||
private final Map<String, Boolean> mKnownSuffixes = new HashMap<String, Boolean>(32);
|
||||
private final String description;
|
||||
private final Map<String, Boolean> knownSuffixes = new HashMap<String, Boolean>(32);
|
||||
|
||||
public ReaderFileSuffixFilter() {
|
||||
this("Images (all supported input formats)");
|
||||
}
|
||||
|
||||
public ReaderFileSuffixFilter(String pDescription) {
|
||||
mDescription = pDescription;
|
||||
description = pDescription;
|
||||
}
|
||||
|
||||
public boolean accept(File pFile) {
|
||||
@@ -71,19 +71,20 @@ public final class ReaderFileSuffixFilter extends FileFilter implements java.io.
|
||||
}
|
||||
|
||||
private boolean hasReaderForSuffix(String pSuffix) {
|
||||
if (mKnownSuffixes.get(pSuffix) == Boolean.TRUE) {
|
||||
if (knownSuffixes.get(pSuffix) == Boolean.TRUE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Cahce lookup
|
||||
Iterator iterator = ImageIO.getImageReadersBySuffix(pSuffix);
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
mKnownSuffixes.put(pSuffix, Boolean.TRUE);
|
||||
knownSuffixes.put(pSuffix, Boolean.TRUE);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
mKnownSuffixes.put(pSuffix, Boolean.FALSE);
|
||||
knownSuffixes.put(pSuffix, Boolean.FALSE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -93,6 +94,6 @@ public final class ReaderFileSuffixFilter extends FileFilter implements java.io.
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return mDescription;
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -47,15 +47,15 @@ import java.util.Map;
|
||||
* @version $Id: WriterFileSuffixFilter.java,v 1.0 11.okt.2006 20:05:36 haku Exp$
|
||||
*/
|
||||
public final class WriterFileSuffixFilter extends FileFilter implements java.io.FileFilter {
|
||||
private final String mDescription;
|
||||
private Map<String, Boolean>mKnownSuffixes = new HashMap<String, Boolean>(32);
|
||||
private final String description;
|
||||
private Map<String, Boolean> knownSuffixes = new HashMap<String, Boolean>(32);
|
||||
|
||||
public WriterFileSuffixFilter() {
|
||||
this("Images (all supported output formats)");
|
||||
}
|
||||
|
||||
public WriterFileSuffixFilter(String pDescription) {
|
||||
mDescription = pDescription;
|
||||
description = pDescription;
|
||||
}
|
||||
|
||||
public boolean accept(File pFile) {
|
||||
@@ -66,24 +66,25 @@ public final class WriterFileSuffixFilter extends FileFilter implements java.io.
|
||||
|
||||
// Test if we have an ImageWriter for this suffix
|
||||
String suffix = FileUtil.getExtension(pFile);
|
||||
return !StringUtil.isEmpty(suffix) && hasWriterForSuffix(suffix);
|
||||
|
||||
return !StringUtil.isEmpty(suffix) && hasWriterForSuffix(suffix);
|
||||
}
|
||||
|
||||
private boolean hasWriterForSuffix(String pSuffix) {
|
||||
if (mKnownSuffixes.get(pSuffix) == Boolean.TRUE) {
|
||||
if (knownSuffixes.get(pSuffix) == Boolean.TRUE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
// Cahce lookup
|
||||
Iterator iterator = ImageIO.getImageWritersBySuffix(pSuffix);
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
mKnownSuffixes.put(pSuffix, Boolean.TRUE);
|
||||
knownSuffixes.put(pSuffix, Boolean.TRUE);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
mKnownSuffixes.put(pSuffix, Boolean.FALSE);
|
||||
knownSuffixes.put(pSuffix, Boolean.FALSE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -93,6 +94,6 @@ public final class WriterFileSuffixFilter extends FileFilter implements java.io.
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return mDescription;
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#
|
||||
# Copyright (c) 2011, Harald Kuhr
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name "TwelveMonkeys" nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
GENERIC_CMYK=/System/Library/ColorSync/Profiles/Generic CMYK Profile.icc
|
||||
ADOBE_RGB_1998=/System/Library/ColorSync/Profiles/AdobeRGB1998.icc
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#
|
||||
# Copyright (c) 2011, Harald Kuhr
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name "TwelveMonkeys" nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
GENERIC_CMYK=/C:/Windows/System32/spool/drivers/color/RSWOP.icm
|
||||
#ADOBE_RGB_1998=use built in for now
|
||||
Binary file not shown.
+268
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* ImageReaderBaseTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ImageReaderBaseTest.java,v 1.0 23.05.12 09:50 haraldk Exp$
|
||||
*/
|
||||
public class ImageReaderBaseTest {
|
||||
|
||||
private static final List<ImageTypeSpecifier> TYPES = Arrays.asList(
|
||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB),
|
||||
ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)
|
||||
);
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetDestinationZeroWidth() throws IIOException {
|
||||
ImageReaderBase.getDestination(null, TYPES.iterator(), 0, 42);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetDestinationNegativeWidth() throws IIOException {
|
||||
ImageReaderBase.getDestination(null, TYPES.iterator(), -1, 42);
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetDestinationZeroHeight() throws IIOException {
|
||||
ImageReaderBase.getDestination(null, TYPES.iterator(), 42, 0);
|
||||
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetDestinationNegativeHeight() throws IIOException {
|
||||
ImageReaderBase.getDestination(null, TYPES.iterator(), 42, -1);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetDestinationNullTypes() throws IIOException {
|
||||
ImageReaderBase.getDestination(null, null, 42, 42);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetDestinationNoTypes() throws IIOException {
|
||||
ImageReaderBase.getDestination(null, Collections.<ImageTypeSpecifier>emptyList().iterator(), 42, 42);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationParamSourceRegionWider() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setSourceRegion(new Rectangle(42, 1));
|
||||
BufferedImage destination = ImageReaderBase.getDestination(param, TYPES.iterator(), 3, 3);
|
||||
assertEquals(3, destination.getWidth());
|
||||
assertEquals(1, destination.getHeight());
|
||||
assertEquals(TYPES.get(0).getBufferedImageType(), destination.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationParamSourceRegionTaller() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setSourceRegion(new Rectangle(1, 42));
|
||||
BufferedImage destination = ImageReaderBase.getDestination(param, TYPES.iterator(), 3, 3);
|
||||
assertEquals(1, destination.getWidth());
|
||||
assertEquals(3, destination.getHeight());
|
||||
assertEquals(TYPES.get(0).getBufferedImageType(), destination.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationParamDestinationWider() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestination(new BufferedImage(42, 1, BufferedImage.TYPE_INT_RGB));
|
||||
BufferedImage destination = ImageReaderBase.getDestination(param, TYPES.iterator(), 3, 3);
|
||||
assertEquals(42, destination.getWidth());
|
||||
assertEquals(1, destination.getHeight());
|
||||
assertEquals(BufferedImage.TYPE_INT_RGB, destination.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationParamDestinationTaller() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestination(new BufferedImage(1, 42, BufferedImage.TYPE_INT_ARGB));
|
||||
BufferedImage destination = ImageReaderBase.getDestination(param, TYPES.iterator(), 3, 3);
|
||||
assertEquals(1, destination.getWidth());
|
||||
assertEquals(42, destination.getHeight());
|
||||
assertEquals(BufferedImage.TYPE_INT_ARGB, destination.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationNoParam() throws IIOException {
|
||||
BufferedImage destination = ImageReaderBase.getDestination(null, TYPES.iterator(), 42, 1);
|
||||
assertEquals(BufferedImage.TYPE_INT_RGB, destination.getType());
|
||||
assertEquals(42, destination.getWidth());
|
||||
assertEquals(1, destination.getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationParamNoDestination() throws IIOException {
|
||||
BufferedImage destination = ImageReaderBase.getDestination(new ImageReadParam(), TYPES.iterator(), 42, 1);
|
||||
assertEquals(BufferedImage.TYPE_INT_RGB, destination.getType());
|
||||
assertEquals(42, destination.getWidth());
|
||||
assertEquals(1, destination.getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationParamGoodDestination() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestination(new BufferedImage(21, 1, BufferedImage.TYPE_INT_ARGB));
|
||||
BufferedImage destination = ImageReaderBase.getDestination(param, TYPES.iterator(), 42, 1);
|
||||
assertEquals(BufferedImage.TYPE_INT_ARGB, destination.getType());
|
||||
assertEquals(21, destination.getWidth());
|
||||
assertEquals(1, destination.getHeight());
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testGetDestinationParamIllegalDestination() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestination(new BufferedImage(21, 1, BufferedImage.TYPE_USHORT_565_RGB));
|
||||
ImageReaderBase.getDestination(param, TYPES.iterator(), 42, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationParamGoodDestinationType() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB));
|
||||
BufferedImage destination = ImageReaderBase.getDestination(param, TYPES.iterator(), 6, 7);
|
||||
assertEquals(BufferedImage.TYPE_INT_ARGB, destination.getType());
|
||||
assertEquals(6, destination.getWidth());
|
||||
assertEquals(7, destination.getHeight());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationParamGoodDestinationTypeAlt() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
// In essence, this is the same as TYPE_INT_ARGB
|
||||
ImageTypeSpecifier type = ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), 0xff0000, 0xff00, 0xff, 0xff000000, DataBuffer.TYPE_INT, false);
|
||||
param.setDestinationType(type);
|
||||
BufferedImage destination = ImageReaderBase.getDestination(param, TYPES.iterator(), 6, 7);
|
||||
assertEquals(BufferedImage.TYPE_INT_ARGB, destination.getType());
|
||||
assertEquals(6, destination.getWidth());
|
||||
assertEquals(7, destination.getHeight());
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testGetDestinationParamIllegalDestinationType() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY));
|
||||
ImageReaderBase.getDestination(param, TYPES.iterator(), 6, 7);
|
||||
}
|
||||
|
||||
@Test(expected = IIOException.class)
|
||||
public void testGetDestinationParamIllegalDestinationTypeAlt() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
||||
ImageReaderBase.getDestination(param, TYPES.iterator(), 6, 7);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDestinationSourceExceedsIntegerMax() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setSourceRegion(new Rectangle(42, 7));
|
||||
BufferedImage destination = ImageReaderBase.getDestination(param, TYPES.iterator(), Integer.MAX_VALUE, 42);// 90 194 313 174 pixels
|
||||
assertEquals(42, destination.getWidth());
|
||||
assertEquals(7, destination.getHeight());
|
||||
assertEquals(TYPES.get(0).getBufferedImageType(), destination.getType());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetDestinationParamDestinationExceedsIntegerMax() throws IIOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setSourceRegion(new Rectangle(3 * Short.MAX_VALUE, 2 * Short.MAX_VALUE)); // 6 442 057 734 pixels
|
||||
ImageReaderBase.getDestination(param, TYPES.iterator(), 6 * Short.MAX_VALUE, 4 * Short.MAX_VALUE); // 25 768 230 936 pixels
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testGetDestinationExceedsIntegerMax() throws IIOException {
|
||||
ImageReaderBase.getDestination(null, TYPES.iterator(), 3 * Short.MAX_VALUE, 2 * Short.MAX_VALUE); // 6 442 057 734 pixels
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasExplicitDestinationNull() {
|
||||
assertFalse(ImageReaderBase.hasExplicitDestination(null));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasExplicitDestinationDefaultParam() {
|
||||
assertFalse(ImageReaderBase.hasExplicitDestination(new ImageReadParam()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasExplicitDestinationParamWithDestination() {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestination(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY));
|
||||
assertTrue(ImageReaderBase.hasExplicitDestination(param));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasExplicitDestinationParamWithDestinationType() {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB));
|
||||
assertTrue(ImageReaderBase.hasExplicitDestination(param));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasExplicitDestinationParamWithDestinationOffset() {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestinationOffset(new Point(42, 42));
|
||||
assertTrue(ImageReaderBase.hasExplicitDestination(param));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasExplicitDestinationParamWithDestinationOffsetUnspecified() {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
// getDestinationOffset should now return new Point(0, 0)
|
||||
assertFalse(ImageReaderBase.hasExplicitDestination(param));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasExplicitDestinationParamWithDestinationOffsetOrigin() {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestinationOffset(new Point(0, 0));
|
||||
assertFalse(ImageReaderBase.hasExplicitDestination(param));
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* ColorSpacesTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ColorSpacesTest.java,v 1.0 07.02.11 14.32 haraldk Exp$
|
||||
*/
|
||||
public class ColorSpacesTest {
|
||||
@Test
|
||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_sRGB() {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(created, ColorSpace.getInstance(ColorSpace.CS_sRGB));
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromKnownProfileDataReturnsInternalCS_sRGB() {
|
||||
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
byte[] data = internal.getData();
|
||||
assertNotSame(internal.getData(), data); // Sanity check
|
||||
|
||||
ICC_Profile profile = ICC_Profile.getInstance(data);
|
||||
assertNotSame(internal, profile); // Sanity check
|
||||
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
|
||||
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile = createBrokenProfile(internal);
|
||||
assertNotSame(internal, profile); // Sanity check
|
||||
|
||||
assertTrue(ColorSpaces.isOffendingColorProfile(profile));
|
||||
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
|
||||
byte[] data = internal.getData();
|
||||
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric
|
||||
return ICC_Profile.getInstance(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsOffendingColorProfile() {
|
||||
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
assertTrue(ColorSpaces.isOffendingColorProfile(broken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_GRAY() {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_GRAY), created);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_PYCC() {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_PYCC);
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_PYCC), created);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_CIEXYZ() {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), created);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_LINEAR_RGB() {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB), created);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdobeRGB98NotNull() {
|
||||
assertNotNull(ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_1998));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdobeRGB98IsTypeRGB() {
|
||||
assertEquals(ColorSpace.TYPE_RGB, ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_1998).getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdobeRGB98AlwaysSame() {
|
||||
ColorSpace cs = ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_1998);
|
||||
assertSame(cs, ColorSpaces.getColorSpace(ColorSpaces.CS_ADOBE_RGB_1998));
|
||||
|
||||
if (cs instanceof ICC_ColorSpace) {
|
||||
ICC_ColorSpace iccCs = (ICC_ColorSpace) cs;
|
||||
assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile()));
|
||||
}
|
||||
else {
|
||||
System.err.println("Not an ICC_ColorSpace: " + cs);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMYKNotNull() {
|
||||
assertNotNull(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMYKIsTypeCMYK() {
|
||||
assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCMYKAlwaysSame() {
|
||||
ColorSpace cs = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||
assertSame(cs, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK));
|
||||
|
||||
if (cs instanceof ICC_ColorSpace) {
|
||||
ICC_ColorSpace iccCs = (ICC_ColorSpace) cs;
|
||||
assertSame(cs, ColorSpaces.createColorSpace(iccCs.getProfile()));
|
||||
}
|
||||
else {
|
||||
System.err.println("Not an ICC_ColorSpace: " + cs);
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -18,7 +18,7 @@ import java.util.Random;
|
||||
* @version $Id: BufferedImageInputStreamTestCase.java,v 1.0 Jun 30, 2008 3:07:42 PM haraldk Exp$
|
||||
*/
|
||||
public class BufferedImageInputStreamTestCase extends TestCase {
|
||||
protected final Random mRandom = new Random();
|
||||
protected final Random random = new Random();
|
||||
|
||||
public void testCreate() throws IOException {
|
||||
new BufferedImageInputStream(new ByteArrayImageInputStream(new byte[0]));
|
||||
@@ -58,7 +58,7 @@ public class BufferedImageInputStreamTestCase extends TestCase {
|
||||
|
||||
// Fill bytes
|
||||
byte[] bytes = new byte[size * 2];
|
||||
mRandom.nextBytes(bytes);
|
||||
random.nextBytes(bytes);
|
||||
|
||||
// Create wrapper stream
|
||||
BufferedImageInputStream stream = new BufferedImageInputStream(new ByteArrayImageInputStream(bytes));
|
||||
@@ -79,7 +79,7 @@ public class BufferedImageInputStreamTestCase extends TestCase {
|
||||
public void testBufferPositionCorrect() throws IOException {
|
||||
// Fill bytes
|
||||
byte[] bytes = new byte[1024];
|
||||
mRandom.nextBytes(bytes);
|
||||
random.nextBytes(bytes);
|
||||
|
||||
ByteArrayImageInputStream input = new ByteArrayImageInputStream(bytes);
|
||||
|
||||
|
||||
+105
-6
@@ -1,11 +1,12 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTestCase.rangeEquals;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTestCase.rangeEquals;
|
||||
|
||||
/**
|
||||
* ByteArrayImageInputStreamTestCase
|
||||
*
|
||||
@@ -14,7 +15,7 @@ import java.util.Random;
|
||||
* @version $Id: ByteArrayImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||
*/
|
||||
public class ByteArrayImageInputStreamTestCase extends TestCase {
|
||||
protected final Random mRandom = new Random();
|
||||
protected final Random random = new Random();
|
||||
|
||||
public void testCreate() {
|
||||
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(new byte[0]);
|
||||
@@ -34,9 +35,74 @@ public class ByteArrayImageInputStreamTestCase extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateNullOffLen() {
|
||||
try {
|
||||
new ByteArrayImageInputStream(null, 0, -1);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("data"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateNegativeOff() {
|
||||
try {
|
||||
new ByteArrayImageInputStream(new byte[0], -1, 1);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("offset"));
|
||||
assertTrue("Exception message does not contain -1", message.contains("-1"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateBadOff() {
|
||||
try {
|
||||
new ByteArrayImageInputStream(new byte[1], 2, 0);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("offset"));
|
||||
assertTrue("Exception message does not contain 2", message.contains("2"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateNegativeLen() {
|
||||
try {
|
||||
new ByteArrayImageInputStream(new byte[0], 0, -1);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("length"));
|
||||
assertTrue("Exception message does not contain -1", message.contains("-1"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateBadLen() {
|
||||
try {
|
||||
new ByteArrayImageInputStream(new byte[1], 0, 2);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("length"));
|
||||
assertTrue("Exception message does not contain ™", message.contains("2"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testRead() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
mRandom.nextBytes(data);
|
||||
random.nextBytes(data);
|
||||
|
||||
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data);
|
||||
|
||||
@@ -47,9 +113,24 @@ public class ByteArrayImageInputStreamTestCase extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadOffsetLen() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
random.nextBytes(data);
|
||||
|
||||
int offset = random.nextInt(data.length / 10);
|
||||
int length = random.nextInt(data.length - offset);
|
||||
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data, offset, length);
|
||||
|
||||
assertEquals("Data length should be same as stream length", length, stream.length());
|
||||
|
||||
for (int i = offset; i < offset + length; i++) {
|
||||
assertEquals("Wrong data read", data[i] & 0xff, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadArray() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
mRandom.nextBytes(data);
|
||||
random.nextBytes(data);
|
||||
|
||||
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data);
|
||||
|
||||
@@ -63,9 +144,27 @@ public class ByteArrayImageInputStreamTestCase extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadArrayOffLen() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
random.nextBytes(data);
|
||||
|
||||
int offset = random.nextInt(data.length - 10240);
|
||||
int length = 10240;
|
||||
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data, offset, length);
|
||||
|
||||
assertEquals("Data length should be same as stream length", length, stream.length());
|
||||
|
||||
byte[] result = new byte[1024];
|
||||
|
||||
for (int i = 0; i < length / result.length; i++) {
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, offset + i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
|
||||
public void testReadSkip() throws IOException {
|
||||
byte[] data = new byte[1024 * 14];
|
||||
mRandom.nextBytes(data);
|
||||
random.nextBytes(data);
|
||||
|
||||
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data);
|
||||
|
||||
@@ -82,7 +181,7 @@ public class ByteArrayImageInputStreamTestCase extends TestCase {
|
||||
|
||||
public void testReadSeek() throws IOException {
|
||||
byte[] data = new byte[1024 * 18];
|
||||
mRandom.nextBytes(data);
|
||||
random.nextBytes(data);
|
||||
|
||||
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data);
|
||||
|
||||
|
||||
+2
-2
@@ -19,12 +19,12 @@ import java.util.Random;
|
||||
*/
|
||||
public class SubImageInputStreamTestCase extends TestCase {
|
||||
// TODO: Extract super test case for all stream tests
|
||||
private final Random mRandom = new Random(837468l);
|
||||
private final Random random = new Random(837468l);
|
||||
|
||||
private ImageInputStream createStream(final int pSize) {
|
||||
byte[] bytes = new byte[pSize];
|
||||
|
||||
mRandom.nextBytes(bytes);
|
||||
random.nextBytes(bytes);
|
||||
|
||||
return new MemoryCacheImageInputStream(new ByteArrayInputStream(bytes)) {
|
||||
@Override
|
||||
|
||||
+14
-4
@@ -29,11 +29,14 @@
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.io.InputStreamAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* IIOInputStreamAdapter
|
||||
@@ -43,14 +46,17 @@ import java.io.IOException;
|
||||
* @version $Id: IIOInputStreamAdapter.java,v 1.0 Apr 11, 2008 1:04:42 PM haraldk Exp$
|
||||
*/
|
||||
public class IIOInputStreamAdapterTestCase extends InputStreamAbstractTestCase {
|
||||
public IIOInputStreamAdapterTestCase(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
protected InputStream makeInputStream(byte[] pBytes) {
|
||||
return new IIOInputStreamAdapter(new MemoryCacheImageInputStream(new ByteArrayInputStream(pBytes)), pBytes.length);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateNull() {
|
||||
new IIOInputStreamAdapter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSubstreamOpenEnd() throws IOException {
|
||||
byte[] bytes = new byte[20];
|
||||
|
||||
@@ -74,6 +80,7 @@ public class IIOInputStreamAdapterTestCase extends InputStreamAbstractTestCase {
|
||||
input.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSubstream() throws IOException {
|
||||
byte[] bytes = new byte[20];
|
||||
|
||||
@@ -92,6 +99,7 @@ public class IIOInputStreamAdapterTestCase extends InputStreamAbstractTestCase {
|
||||
input.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSubstreamRepositionOnClose() throws IOException {
|
||||
byte[] bytes = new byte[20];
|
||||
|
||||
@@ -111,6 +119,7 @@ public class IIOInputStreamAdapterTestCase extends InputStreamAbstractTestCase {
|
||||
input.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekBeforeStreamNoEnd() throws IOException {
|
||||
byte[] bytes = new byte[20];
|
||||
|
||||
@@ -124,6 +133,7 @@ public class IIOInputStreamAdapterTestCase extends InputStreamAbstractTestCase {
|
||||
assertEquals(10, input.getStreamPosition());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekBeforeStream() throws IOException {
|
||||
byte[] bytes = new byte[20];
|
||||
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.io.OutputStreamAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.stream.MemoryCacheImageOutputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* IIOOutputStreamAdapterTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IIOOutputStreamAdapterTestCase.java,v 1.0 30.11.11 12:21 haraldk Exp$
|
||||
*/
|
||||
public class IIOOutputStreamAdapterTestCase extends OutputStreamAbstractTestCase {
|
||||
@Override
|
||||
protected OutputStream makeObject() {
|
||||
return new IIOOutputStreamAdapter(new MemoryCacheImageOutputStream(new ByteArrayOutputStream()));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateNull() {
|
||||
new IIOOutputStreamAdapter(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFlushOnAdapterDoesNotMoveFlushedPositionInBacking() throws IOException {
|
||||
MemoryCacheImageOutputStream backing = new MemoryCacheImageOutputStream(new ByteArrayOutputStream());
|
||||
IIOOutputStreamAdapter adapter = new IIOOutputStreamAdapter(backing);
|
||||
|
||||
// Sanity check
|
||||
assertEquals(0, backing.getFlushedPosition());
|
||||
|
||||
// Write & flush
|
||||
adapter.write(0xCA);
|
||||
adapter.write(new byte[8]);
|
||||
adapter.write(0xFE);
|
||||
adapter.flush();
|
||||
|
||||
// Assertions
|
||||
assertEquals(10, backing.length());
|
||||
assertEquals(10, backing.getStreamPosition());
|
||||
assertEquals(0, backing.getFlushedPosition());
|
||||
|
||||
// Just make sure we can safely seek back to start and read data back
|
||||
backing.seek(0);
|
||||
assertEquals(0, backing.getStreamPosition());
|
||||
|
||||
// If this can be read, I think the contract of flush is also fulfilled (kind of)
|
||||
assertEquals(0xCA, backing.read());
|
||||
assertEquals(8, backing.skipBytes(8));
|
||||
assertEquals(0xFE, backing.read());
|
||||
}
|
||||
}
|
||||
+366
-192
File diff suppressed because it is too large
Load Diff
+131
-100
@@ -28,16 +28,24 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import org.jmock.Mock;
|
||||
import org.jmock.cglib.MockObjectTestCase;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.event.IIOWriteProgressListener;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* ImageReaderAbstractTestCase class description.
|
||||
@@ -46,12 +54,38 @@ import java.io.IOException;
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: ImageReaderAbstractTestCase.java,v 1.0 18.nov.2004 17:38:33 haku Exp $
|
||||
*/
|
||||
public abstract class ImageWriterAbstractTestCase extends MockObjectTestCase {
|
||||
public abstract class ImageWriterAbstractTestCase {
|
||||
|
||||
protected abstract ImageWriter createImageWriter();
|
||||
|
||||
protected abstract RenderedImage getTestData();
|
||||
protected abstract List<? extends RenderedImage> getTestData();
|
||||
|
||||
protected static BufferedImage drawSomething(final BufferedImage image) {
|
||||
Graphics2D g = image.createGraphics();
|
||||
try {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
g.clearRect(0, 0, width, height);
|
||||
g.setPaint(new LinearGradientPaint(0, 0, width, 0, new float[] {0.2f, 1}, new Color[] {new Color(0x0, true), Color.BLUE}));
|
||||
g.fillRect(0, 0, width, height);
|
||||
g.setPaint(new LinearGradientPaint(0, 0, 0, height, new float[] {0.2f, 1}, new Color[] {new Color(0x0, true), Color.RED}));
|
||||
g.fillRect(0, 0, width, height);
|
||||
g.setPaint(new LinearGradientPaint(0, 0, 0, height, new float[] {0, 1}, new Color[] {new Color(0x00ffffff, true), Color.WHITE}));
|
||||
g.fill(new Polygon(new int[] {0, width, width}, new int[] {0, height, 0}, 3));
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
protected final RenderedImage getTestData(final int index) {
|
||||
return getTestData().get(index);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetOutput() throws IOException {
|
||||
// Should just pass with no exceptions
|
||||
ImageWriter writer = createImageWriter();
|
||||
@@ -59,6 +93,7 @@ public abstract class ImageWriterAbstractTestCase extends MockObjectTestCase {
|
||||
writer.setOutput(ImageIO.createImageOutputStream(new ByteArrayOutputStream()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetOutputNull() {
|
||||
// Should just pass with no exceptions
|
||||
ImageWriter writer = createImageWriter();
|
||||
@@ -66,40 +101,35 @@ public abstract class ImageWriterAbstractTestCase extends MockObjectTestCase {
|
||||
writer.setOutput(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrite() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
try {
|
||||
writer.write(getTestData());
|
||||
|
||||
for (RenderedImage testData : getTestData()) {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
|
||||
writer.setOutput(stream);
|
||||
|
||||
try {
|
||||
writer.write(drawSomething((BufferedImage) testData));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
stream.close(); // Force data to be written
|
||||
}
|
||||
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
}
|
||||
|
||||
public void testWrite2() {
|
||||
// Note: There's a difference between new ImageOutputStreamImpl and
|
||||
// ImageIO.createImageOutputStream... Make sure writers handle both
|
||||
// cases
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
try {
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
writer.write(getTestData());
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteNull() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
|
||||
try {
|
||||
writer.write((RenderedImage) null);
|
||||
}
|
||||
@@ -108,21 +138,23 @@ public abstract class ImageWriterAbstractTestCase extends MockObjectTestCase {
|
||||
catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
|
||||
assertTrue("Image data written", buffer.size() == 0);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testWriteNoOutput() {
|
||||
ImageWriter writer = createImageWriter();
|
||||
|
||||
try {
|
||||
writer.write(getTestData());
|
||||
}
|
||||
catch (IllegalStateException ignore) {
|
||||
writer.write(getTestData(0));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDefaultWriteParam() {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
@@ -132,191 +164,190 @@ public abstract class ImageWriterAbstractTestCase extends MockObjectTestCase {
|
||||
// TODO: Test writing with params
|
||||
// TODO: Source region and subsampling at least
|
||||
|
||||
@Test
|
||||
public void testAddIIOWriteProgressListener() {
|
||||
ImageWriter writer = createImageWriter();
|
||||
Mock mockListener = new Mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListener.proxy());
|
||||
writer.addIIOWriteProgressListener(mock(IIOWriteProgressListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddIIOWriteProgressListenerNull() {
|
||||
ImageWriter writer = createImageWriter();
|
||||
writer.addIIOWriteProgressListener(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddIIOWriteProgressListenerCallbacks() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
|
||||
Mock mockListener = new Mock(IIOWriteProgressListener.class);
|
||||
String started = "Started";
|
||||
mockListener.expects(once()).method("imageStarted").withAnyArguments().id(started);
|
||||
mockListener.stubs().method("imageProgress").withAnyArguments().after(started);
|
||||
mockListener.expects(once()).method("imageComplete").withAnyArguments().after(started);
|
||||
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListener.proxy());
|
||||
IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener(listener);
|
||||
|
||||
try {
|
||||
writer.write(getTestData());
|
||||
writer.write(getTestData(0));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("Could not write image");
|
||||
}
|
||||
|
||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||
mockListener.verify();
|
||||
InOrder ordered = inOrder(listener);
|
||||
ordered.verify(listener).imageStarted(writer, 0);
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
||||
ordered.verify(listener).imageComplete(writer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleAddIIOWriteProgressListenerCallbacks() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
|
||||
Mock mockListener = new Mock(IIOWriteProgressListener.class);
|
||||
String started = "Started";
|
||||
mockListener.expects(once()).method("imageStarted").withAnyArguments().id(started);
|
||||
mockListener.stubs().method("imageProgress").withAnyArguments().after(started);
|
||||
mockListener.expects(once()).method("imageComplete").withAnyArguments().after(started);
|
||||
IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class);
|
||||
IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class);
|
||||
IIOWriteProgressListener listenerThree = mock(IIOWriteProgressListener.class);
|
||||
|
||||
Mock mockListenerToo = new Mock(IIOWriteProgressListener.class);
|
||||
String startedToo = "Started Two";
|
||||
mockListenerToo.expects(once()).method("imageStarted").withAnyArguments().id(startedToo);
|
||||
mockListenerToo.stubs().method("imageProgress").withAnyArguments().after(startedToo);
|
||||
mockListenerToo.expects(once()).method("imageComplete").withAnyArguments().after(startedToo);
|
||||
|
||||
Mock mockListenerThree = new Mock(IIOWriteProgressListener.class);
|
||||
String startedThree = "Started Three";
|
||||
mockListenerThree.expects(once()).method("imageStarted").withAnyArguments().id(startedThree);
|
||||
mockListenerThree.stubs().method("imageProgress").withAnyArguments().after(startedThree);
|
||||
mockListenerThree.expects(once()).method("imageComplete").withAnyArguments().after(startedThree);
|
||||
|
||||
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListener.proxy());
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListenerToo.proxy());
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListenerThree.proxy());
|
||||
writer.addIIOWriteProgressListener(listener);
|
||||
writer.addIIOWriteProgressListener(listenerToo);
|
||||
writer.addIIOWriteProgressListener(listenerThree);
|
||||
|
||||
try {
|
||||
writer.write(getTestData());
|
||||
writer.write(getTestData(0));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("Could not write image");
|
||||
}
|
||||
|
||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||
mockListener.verify();
|
||||
mockListenerToo.verify();
|
||||
mockListenerThree.verify();
|
||||
InOrder ordered = inOrder(listener, listenerToo, listenerThree);
|
||||
|
||||
ordered.verify(listener).imageStarted(writer, 0);
|
||||
ordered.verify(listenerToo).imageStarted(writer, 0);
|
||||
ordered.verify(listenerThree).imageStarted(writer, 0);
|
||||
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
||||
ordered.verify(listenerThree, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
||||
|
||||
ordered.verify(listener).imageComplete(writer);
|
||||
ordered.verify(listenerToo).imageComplete(writer);
|
||||
ordered.verify(listenerThree).imageComplete(writer);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemoveIIOWriteProgressListenerNull() {
|
||||
ImageWriter writer = createImageWriter();
|
||||
writer.removeIIOWriteProgressListener(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveIIOWriteProgressListenerNone() {
|
||||
ImageWriter writer = createImageWriter();
|
||||
Mock mockListener = new Mock(IIOWriteProgressListener.class);
|
||||
writer.removeIIOWriteProgressListener((IIOWriteProgressListener) mockListener.proxy());
|
||||
writer.removeIIOWriteProgressListener(mock(IIOWriteProgressListener.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveIIOWriteProgressListener() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
|
||||
Mock mockListener = new Mock(IIOWriteProgressListener.class);
|
||||
IIOWriteProgressListener listener = (IIOWriteProgressListener) mockListener.proxy();
|
||||
IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener(listener);
|
||||
writer.removeIIOWriteProgressListener(listener);
|
||||
|
||||
try {
|
||||
writer.write(getTestData());
|
||||
writer.write(getTestData(0));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("Could not write image");
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
mockListener.verify();
|
||||
verifyZeroInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveIIOWriteProgressListenerMultiple() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
|
||||
IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener(listener);
|
||||
|
||||
Mock mockListener = new Mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListener.proxy());
|
||||
IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener(listenerToo);
|
||||
|
||||
Mock mockListenerToo = new Mock(IIOWriteProgressListener.class);
|
||||
mockListenerToo.stubs().method("imageStarted").withAnyArguments();
|
||||
mockListenerToo.stubs().method("imageProgress").withAnyArguments();
|
||||
mockListenerToo.stubs().method("imageComplete").withAnyArguments();
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListenerToo.proxy());
|
||||
|
||||
writer.removeIIOWriteProgressListener((IIOWriteProgressListener) mockListener.proxy());
|
||||
writer.removeIIOWriteProgressListener(listener);
|
||||
|
||||
try {
|
||||
writer.write(getTestData());
|
||||
writer.write(getTestData(0));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("Could not write image");
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
mockListener.verify();
|
||||
mockListenerToo.verify();
|
||||
verifyZeroInteractions(listener);
|
||||
|
||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||
InOrder ordered = inOrder(listenerToo);
|
||||
ordered.verify(listenerToo).imageStarted(writer, 0);
|
||||
ordered.verify(listenerToo, atLeastOnce()).imageProgress(eq(writer), anyInt());
|
||||
ordered.verify(listenerToo).imageComplete(writer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemoveAllIIOWriteProgressListeners() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
|
||||
|
||||
Mock mockListener = new Mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListener.proxy());
|
||||
IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener(listener);
|
||||
|
||||
writer.removeAllIIOWriteProgressListeners();
|
||||
|
||||
try {
|
||||
writer.write(getTestData());
|
||||
writer.write(getTestData(0));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("Could not write image");
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
mockListener.verify();
|
||||
verifyZeroInteractions(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveAllIIOWriteProgressListenersMultiple() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
writer.setOutput(ImageIO.createImageOutputStream(buffer));
|
||||
|
||||
|
||||
Mock mockListener = new Mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListener.proxy());
|
||||
IIOWriteProgressListener listener = mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener(listener);
|
||||
|
||||
Mock mockListenerToo = new Mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener((IIOWriteProgressListener) mockListenerToo.proxy());
|
||||
IIOWriteProgressListener listenerToo = mock(IIOWriteProgressListener.class);
|
||||
writer.addIIOWriteProgressListener(listenerToo);
|
||||
|
||||
writer.removeAllIIOWriteProgressListeners();
|
||||
|
||||
try {
|
||||
writer.write(getTestData());
|
||||
writer.write(getTestData(0));
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail("Could not write image");
|
||||
}
|
||||
|
||||
// Should not have called any methods...
|
||||
mockListener.verify();
|
||||
mockListenerToo.verify();
|
||||
verifyZeroInteractions(listener);
|
||||
verifyZeroInteractions(listenerToo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
- Rename to imageio-common?
|
||||
- Separate modules for more for more plugins
|
||||
- The BMP reader spports some special formats not supported by Sun reader
|
||||
- PNM package is pretty complete, but useless, as it's provided by Sun? Licencse?
|
||||
- The BMP reader supports some special formats not supported by Sun reader
|
||||
- PNM package is pretty complete, but useless, as it's provided by Sun? License?
|
||||
- WBMP?
|
||||
- XBM?
|
||||
DONE:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-icns</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
||||
<description>ImageIO plugin for Apple Icon Image (icns) format.</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
/**
|
||||
* ICNS
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNS.java,v 1.0 25.10.11 19:10 haraldk Exp$
|
||||
*/
|
||||
interface ICNS {
|
||||
/** Resource header size (8). */
|
||||
int RESOURCE_HEADER_SIZE = 8;
|
||||
|
||||
/** ICNS magic identifier ("icns"). */
|
||||
int MAGIC = ('i' << 24) + ('c' << 16) + ('n' << 8) + 's';
|
||||
|
||||
/** 32×32 1-bit mono icon. */
|
||||
int ICON = ('I' << 24) + ('C' << 16) + ('O' << 8) + 'N';
|
||||
/** 32×32 1-bit mono icon with 1-bit mask. */
|
||||
int ICN_ = ('I' << 24) + ('C' << 16) + ('N' << 8) + '#';
|
||||
|
||||
/** 16×12 1 bit mask. */
|
||||
int icm_ = ('i' << 24) + ('c' << 16) + ('m' << 8) + '#';
|
||||
/** 16×12 4 bit icon. */
|
||||
int icm4 = ('i' << 24) + ('c' << 16) + ('m' << 8) + '4';
|
||||
/** 16×12 8 bit icon. */
|
||||
int icm8 = ('i' << 24) + ('c' << 16) + ('m' << 8) + '8';
|
||||
|
||||
/** 16×16 1-bit icon with 1-bit mask. */
|
||||
int ics_ = ('i' << 24) + ('c' << 16) + ('s' << 8) + '#';
|
||||
/** 16×16 4-bit icon. */
|
||||
int ics4 = ('i' << 24) + ('c' << 16) + ('s' << 8) + '4';
|
||||
/** 16×16 8-bit icon. */
|
||||
int ics8 = ('i' << 24) + ('c' << 16) + ('s' << 8) + '8';
|
||||
/** 16×16 24-bit icon, possibly run-length compressed. */
|
||||
int is32 = ('i' << 24) + ('s' << 16) + ('3' << 8) + '2';
|
||||
/** 16x16 8-bit mask. */
|
||||
int s8mk = ('s' << 24) + ('8' << 16) + ('m' << 8) + 'k';
|
||||
|
||||
/** 32×32 4-bit icon. */
|
||||
int icl4 = ('i' << 24) + ('c' << 16) + ('l' << 8) + '4';
|
||||
/** 32×32 8-bit icon. */
|
||||
int icl8 = ('i' << 24) + ('c' << 16) + ('l' << 8) + '8';
|
||||
/** 32×32 24-bit icon, possibly run-length compressed. */
|
||||
int il32 = ('i' << 24) + ('l' << 16) + ('3' << 8) + '2';
|
||||
/** 32×32 8-bit mask. */
|
||||
int l8mk = ('l' << 24) + ('8' << 16) + ('m' << 8) + 'k';
|
||||
|
||||
/** 48×48 1-bit icon with 1 bit mask. */
|
||||
int ich_ = ('i' << 24) + ('c' << 16) + ('h' << 8) + '#';
|
||||
/** 48×48 4-bit icon. */
|
||||
int ich4 = ('i' << 24) + ('c' << 16) + ('h' << 8) + '4';
|
||||
/** 48×48 8-bit icon. */
|
||||
int ich8 = ('i' << 24) + ('c' << 16) + ('h' << 8) + '8';
|
||||
/** 48×48 24-bit icon, possibly run-length compressed. */
|
||||
int ih32 = ('i' << 24) + ('h' << 16) + ('3' << 8) + '2';
|
||||
/** 48×48 8-bit mask. */
|
||||
int h8mk = ('h' << 24) + ('8' << 16) + ('m' << 8) + 'k';
|
||||
|
||||
/** 128×128 24-bit icon, possibly run-length compressed. */
|
||||
int it32 = ('i' << 24) + ('t' << 16) + ('3' << 8) + '2';
|
||||
/** 128×128 8-bit mask. */
|
||||
int t8mk = ('t' << 24) + ('8' << 16) + ('m' << 8) + 'k';
|
||||
|
||||
/** 256×256 JPEG 2000 or PNG icon (10.x+). */
|
||||
int ic08 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '8';
|
||||
|
||||
/** 512×512 JPEG 2000 or PNG icon (10.x+). */
|
||||
int ic09 = ('i' << 24) + ('c' << 16) + ('0' << 8) + '9';
|
||||
|
||||
/** 1024×1024 PNG icon (10.7+). */
|
||||
int ic10 = ('i' << 24) + ('c' << 16) + ('1' << 8) + '0';
|
||||
|
||||
/** Unknown (Version). */
|
||||
int icnV = ('i' << 24) + ('c' << 16) + ('n' << 8) + 'V';
|
||||
|
||||
/** Unknown (Table of Contents). */
|
||||
int TOC_ = ('T' << 24) + ('O' << 16) + ('C' << 8) + ' ';
|
||||
|
||||
/** JPEG 2000 magic header. */
|
||||
byte[] JPEG_2000_MAGIC = new byte[] {0x00, 0x00, 0x00, 0x0C, 'j', 'P', 0x20, 0x20, 0x0D, 0x0A, (byte) 0x87, 0x0A};
|
||||
|
||||
/** PNG magic header. */
|
||||
byte[] PNG_MAGIC = new byte[] {(byte) 0x89, (byte) 'P', (byte) 'N', (byte) 'G', 0x0d, 0x0a, 0x1a, 0x0a};
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
/**
|
||||
* ICNS1BitColorModel
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNS1BitColorModel.java,v 1.0 07.11.11 10:49 haraldk Exp$
|
||||
*/
|
||||
final class ICNS1BitColorModel extends IndexColorModel {
|
||||
private static final int[] CMAP = {
|
||||
// Inverted from default Java 1 bit...
|
||||
0xffffffff, 0xff000000
|
||||
};
|
||||
|
||||
static final IndexColorModel INSTANCE = new ICNS1BitColorModel();
|
||||
|
||||
private ICNS1BitColorModel() {
|
||||
super(1, 2, CMAP, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
/**
|
||||
* ICNS4BitColorModel
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNS4BitColorModel.java,v 1.0 07.11.11 10:49 haraldk Exp$
|
||||
*/
|
||||
final class ICNS4BitColorModel extends IndexColorModel {
|
||||
private static final int[] CMAP = {
|
||||
0xffffffff, 0xfffcf305, 0xffff6402, 0xffdd0806, 0xfff20884, 0xff4600a5, 0xff0000d4, 0xff02abea,
|
||||
0xff1fb714, 0xff006411, 0xff562c05, 0xff90713a, 0xffc0c0c0, 0xff808080, 0xff404040, 0xff000000
|
||||
};
|
||||
|
||||
static final IndexColorModel INSTANCE = new ICNS4BitColorModel();
|
||||
|
||||
private ICNS4BitColorModel() {
|
||||
super(4, 16, CMAP, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
/**
|
||||
* ICNS8BitColorModel
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNS8BitColorModel.java,v 1.0 07.11.11 10:49 haraldk Exp$
|
||||
*/
|
||||
final class ICNS8BitColorModel extends IndexColorModel {
|
||||
private static final int[] CMAP = {
|
||||
0xffffffff, 0xffffffcc, 0xffffff99, 0xffffff66, 0xffffff33, 0xffffff00, 0xffffccff, 0xffffcccc,
|
||||
0xffffcc99, 0xffffcc66, 0xffffcc33, 0xffffcc00, 0xffff99ff, 0xffff99cc, 0xffff9999, 0xffff9966,
|
||||
0xffff9933, 0xffff9900, 0xffff66ff, 0xffff66cc, 0xffff6699, 0xffff6666, 0xffff6633, 0xffff6600,
|
||||
0xffff33ff, 0xffff33cc, 0xffff3399, 0xffff3366, 0xffff3333, 0xffff3300, 0xffff00ff, 0xffff00cc,
|
||||
0xffff0099, 0xffff0066, 0xffff0033, 0xffff0000, 0xffccffff, 0xffccffcc, 0xffccff99, 0xffccff66,
|
||||
0xffccff33, 0xffccff00, 0xffccccff, 0xffcccccc, 0xffcccc99, 0xffcccc66, 0xffcccc33, 0xffcccc00,
|
||||
0xffcc99ff, 0xffcc99cc, 0xffcc9999, 0xffcc9966, 0xffcc9933, 0xffcc9900, 0xffcc66ff, 0xffcc66cc,
|
||||
0xffcc6699, 0xffcc6666, 0xffcc6633, 0xffcc6600, 0xffcc33ff, 0xffcc33cc, 0xffcc3399, 0xffcc3366,
|
||||
0xffcc3333, 0xffcc3300, 0xffcc00ff, 0xffcc00cc, 0xffcc0099, 0xffcc0066, 0xffcc0033, 0xffcc0000,
|
||||
0xff99ffff, 0xff99ffcc, 0xff99ff99, 0xff99ff66, 0xff99ff33, 0xff99ff00, 0xff99ccff, 0xff99cccc,
|
||||
0xff99cc99, 0xff99cc66, 0xff99cc33, 0xff99cc00, 0xff9999ff, 0xff9999cc, 0xff999999, 0xff999966,
|
||||
0xff999933, 0xff999900, 0xff9966ff, 0xff9966cc, 0xff996699, 0xff996666, 0xff996633, 0xff996600,
|
||||
0xff9933ff, 0xff9933cc, 0xff993399, 0xff993366, 0xff993333, 0xff993300, 0xff9900ff, 0xff9900cc,
|
||||
0xff990099, 0xff990066, 0xff990033, 0xff990000, 0xff66ffff, 0xff66ffcc, 0xff66ff99, 0xff66ff66,
|
||||
0xff66ff33, 0xff66ff00, 0xff66ccff, 0xff66cccc, 0xff66cc99, 0xff66cc66, 0xff66cc33, 0xff66cc00,
|
||||
0xff6699ff, 0xff6699cc, 0xff669999, 0xff669966, 0xff669933, 0xff669900, 0xff6666ff, 0xff6666cc,
|
||||
0xff666699, 0xff666666, 0xff666633, 0xff666600, 0xff6633ff, 0xff6633cc, 0xff663399, 0xff663366,
|
||||
0xff663333, 0xff663300, 0xff6600ff, 0xff6600cc, 0xff660099, 0xff660066, 0xff660033, 0xff660000,
|
||||
0xff33ffff, 0xff33ffcc, 0xff33ff99, 0xff33ff66, 0xff33ff33, 0xff33ff00, 0xff33ccff, 0xff33cccc,
|
||||
0xff33cc99, 0xff33cc66, 0xff33cc33, 0xff33cc00, 0xff3399ff, 0xff3399cc, 0xff339999, 0xff339966,
|
||||
0xff339933, 0xff339900, 0xff3366ff, 0xff3366cc, 0xff336699, 0xff336666, 0xff336633, 0xff336600,
|
||||
0xff3333ff, 0xff3333cc, 0xff333399, 0xff333366, 0xff333333, 0xff333300, 0xff3300ff, 0xff3300cc,
|
||||
0xff330099, 0xff330066, 0xff330033, 0xff330000, 0xff00ffff, 0xff00ffcc, 0xff00ff99, 0xff00ff66,
|
||||
0xff00ff33, 0xff00ff00, 0xff00ccff, 0xff00cccc, 0xff00cc99, 0xff00cc66, 0xff00cc33, 0xff00cc00,
|
||||
0xff0099ff, 0xff0099cc, 0xff009999, 0xff009966, 0xff009933, 0xff009900, 0xff0066ff, 0xff0066cc,
|
||||
0xff006699, 0xff006666, 0xff006633, 0xff006600, 0xff0033ff, 0xff0033cc, 0xff003399, 0xff003366,
|
||||
0xff003333, 0xff003300, 0xff0000ff, 0xff0000cc, 0xff000099, 0xff000066, 0xff000033, 0xffee0000,
|
||||
0xffdd0000, 0xffbb0000, 0xffaa0000, 0xff880000, 0xff770000, 0xff550000, 0xff440000, 0xff220000,
|
||||
0xff110000, 0xff00ee00, 0xff00dd00, 0xff00bb00, 0xff00aa00, 0xff008800, 0xff007700, 0xff005500,
|
||||
0xff004400, 0xff002200, 0xff001100, 0xff0000ee, 0xff0000dd, 0xff0000bb, 0xff0000aa, 0xff000088,
|
||||
0xff000077, 0xff000055, 0xff000044, 0xff000022, 0xff000011, 0xffeeeeee, 0xffdddddd, 0xffbbbbbb,
|
||||
0xffaaaaaa, 0xff888888, 0xff777777, 0xff555555, 0xff444444, 0xff222222, 0xff111111, 0xff000000
|
||||
};
|
||||
|
||||
static final IndexColorModel INSTANCE = new ICNS8BitColorModel();
|
||||
|
||||
private ICNS8BitColorModel() {
|
||||
super(8, 256, CMAP, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
}
|
||||
+619
@@ -0,0 +1,619 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ImageReader for Apple Icon Image (ICNS) format.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNSImageReader.java,v 1.0 25.10.11 18:42 haraldk Exp$
|
||||
*
|
||||
* @see <a href="http://www.macdisk.com/maciconen.php">Macintosh Icons</a>
|
||||
* @see <a href="http://en.wikipedia.org/wiki/Apple_Icon_Image_format">Apple Icon Image format (Wikipedia)</a>
|
||||
*/
|
||||
public final class ICNSImageReader extends ImageReaderBase {
|
||||
// TODO: Support ToC resource for faster parsing/faster determine number of icons?
|
||||
// TODO: Subsampled reading for completeness, even if never used?
|
||||
private List<IconResource> icons = new ArrayList<IconResource>();
|
||||
private List<IconResource> masks = new ArrayList<IconResource>();
|
||||
private IconResource lastResourceRead;
|
||||
|
||||
private int length;
|
||||
|
||||
public ICNSImageReader() {
|
||||
this(new ICNSImageReaderSpi());
|
||||
}
|
||||
|
||||
ICNSImageReader(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetMembers() {
|
||||
length = 0;
|
||||
|
||||
lastResourceRead = null;
|
||||
icons.clear();
|
||||
masks.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth(int imageIndex) throws IOException {
|
||||
return readIconResource(imageIndex).size().width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(int imageIndex) throws IOException {
|
||||
return readIconResource(imageIndex).size().height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||
IconResource resource = readIconResource(imageIndex);
|
||||
|
||||
switch (resource.depth()) {
|
||||
case 1:
|
||||
return IndexedImageTypeSpecifier.createFromIndexColorModel(ICNS1BitColorModel.INSTANCE);
|
||||
case 4:
|
||||
return IndexedImageTypeSpecifier.createFromIndexColorModel(ICNS4BitColorModel.INSTANCE);
|
||||
case 8:
|
||||
return IndexedImageTypeSpecifier.createFromIndexColorModel(ICNS8BitColorModel.INSTANCE);
|
||||
case 32:
|
||||
if (resource.isCompressed()) {
|
||||
return ImageTypeSpecifier.createBanded(
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
new int[]{0, 1, 2, 3}, createBandOffsets(resource.size().width * resource.size().height),
|
||||
DataBuffer.TYPE_BYTE, true, false
|
||||
);
|
||||
}
|
||||
else {
|
||||
return ImageTypeSpecifier.createInterleaved(
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
new int[]{1, 2, 3, 0},
|
||||
DataBuffer.TYPE_BYTE, true, false
|
||||
);
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException(String.format("Unknown bit depth: %d", resource.depth()));
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] createBandOffsets(int bandLen) {
|
||||
return new int[]{0, bandLen, 2 * bandLen, 3 * bandLen};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
IconResource resource = readIconResource(imageIndex);
|
||||
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
|
||||
|
||||
switch (resource.depth()) {
|
||||
case 1:
|
||||
case 4:
|
||||
case 8:
|
||||
// Fall through & convert during read
|
||||
case 32:
|
||||
specifiers.add(ImageTypeSpecifier.createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB), 0xff0000, 0x00ff00, 0x0000ff, 0xff000000, DataBuffer.TYPE_INT, false));
|
||||
specifiers.add(ImageTypeSpecifier.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(String.format("Unknown bit depth: %d", resource.depth()));
|
||||
}
|
||||
|
||||
specifiers.add(rawType);
|
||||
|
||||
return specifiers.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumImages(boolean allowSearch) throws IOException {
|
||||
assertInput();
|
||||
|
||||
if (!allowSearch) {
|
||||
// Return icons.size if we know we have read all?
|
||||
// TODO: If the first resource is a TOC_ resource, we don't need to perform a search.
|
||||
return -1;
|
||||
}
|
||||
|
||||
int num = icons.size();
|
||||
while (true) {
|
||||
try {
|
||||
readIconResource(num++);
|
||||
}
|
||||
catch (IndexOutOfBoundsException expected) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return icons.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||
IconResource resource = readIconResource(imageIndex);
|
||||
|
||||
imageInput.seek(resource.start + ICNS.RESOURCE_HEADER_SIZE);
|
||||
|
||||
// Special handling of PNG/JPEG 2000 icons
|
||||
if (resource.isForeignFormat()) {
|
||||
return readForeignFormat(imageIndex, param, resource);
|
||||
}
|
||||
|
||||
return readICNSFormat(imageIndex, param, resource);
|
||||
}
|
||||
|
||||
private BufferedImage readICNSFormat(final int imageIndex, final ImageReadParam param, final IconResource resource) throws IOException {
|
||||
Dimension size = resource.size();
|
||||
|
||||
int width = size.width;
|
||||
int height = size.height;
|
||||
|
||||
BufferedImage image = getDestination(param, getImageTypes(imageIndex), width, height);
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
if (rawType instanceof IndexedImageTypeSpecifier && rawType.getBufferedImageType() != image.getType()) {
|
||||
checkReadParamBandSettings(param, 4, image.getSampleModel().getNumBands());
|
||||
}
|
||||
else {
|
||||
checkReadParamBandSettings(param, rawType.getNumBands(), image.getSampleModel().getNumBands());
|
||||
}
|
||||
|
||||
final Rectangle source = new Rectangle();
|
||||
final Rectangle dest = new Rectangle();
|
||||
computeRegions(param, width, height, image, source, dest);
|
||||
|
||||
processImageStarted(imageIndex);
|
||||
|
||||
// Read image data
|
||||
byte[] data;
|
||||
if (resource.isCompressed()) {
|
||||
// Only 32 bit icons may be compressed
|
||||
data = new byte[width * height * resource.depth() / 8];
|
||||
|
||||
int packedSize = resource.length - ICNS.RESOURCE_HEADER_SIZE;
|
||||
|
||||
if (width >= 128 && height >= 128) {
|
||||
// http://www.macdisk.com/maciconen.php:
|
||||
// "In some icon sizes, there is a 32bit integer at the beginning of the run, whose role remains unknown."
|
||||
imageInput.skipBytes(4); // Seems to be 4 byte 0-pad
|
||||
packedSize -= 4;
|
||||
}
|
||||
|
||||
InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize);
|
||||
|
||||
try {
|
||||
ICNSUtil.decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
data = new byte[resource.length - ICNS.RESOURCE_HEADER_SIZE];
|
||||
imageInput.readFully(data);
|
||||
}
|
||||
|
||||
if (resource.depth() == 1) {
|
||||
// Binary
|
||||
DataBufferByte buffer = new DataBufferByte(data, data.length / 2, 0);
|
||||
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, resource.depth(), null);
|
||||
|
||||
if (image.getType() == rawType.getBufferedImageType() && ((IndexColorModel) image.getColorModel()).getMapSize() == 2) {
|
||||
// Preserve raw data as read (binary), discard mask
|
||||
image.setData(raster);
|
||||
}
|
||||
else {
|
||||
// Convert to 32 bit ARGB
|
||||
DataBufferByte maskBuffer = new DataBufferByte(data, data.length / 2, data.length / 2);
|
||||
WritableRaster mask = Raster.createPackedRaster(maskBuffer, width, height, resource.depth(), null);
|
||||
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
|
||||
try {
|
||||
// Apply image data
|
||||
BufferedImage temp = new BufferedImage(rawType.getColorModel(), raster, false, null);
|
||||
graphics.drawImage(temp, 0, 0, null);
|
||||
|
||||
// Apply mask
|
||||
temp = new BufferedImage(ICNSBitMaskColorModel.INSTANCE, mask, false, null);
|
||||
temp.setData(mask);
|
||||
graphics.setComposite(AlphaComposite.DstIn);
|
||||
graphics.drawImage(temp, 0, 0, null);
|
||||
}
|
||||
finally {
|
||||
graphics.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (resource.depth() <= 8) {
|
||||
// Indexed
|
||||
DataBufferByte buffer = new DataBufferByte(data, data.length);
|
||||
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, resource.depth(), null);
|
||||
|
||||
if (image.getType() == rawType.getBufferedImageType()) {
|
||||
// Preserve raw data as read (indexed), discard mask
|
||||
image.setData(raster);
|
||||
}
|
||||
else {
|
||||
// Convert to 32 bit ARGB
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
|
||||
try {
|
||||
BufferedImage temp = new BufferedImage(rawType.getColorModel(), raster, false, null);
|
||||
graphics.drawImage(temp, 0, 0, null);
|
||||
}
|
||||
finally {
|
||||
graphics.dispose();
|
||||
}
|
||||
|
||||
processImageProgress(50f);
|
||||
|
||||
// Read mask and apply
|
||||
IconResource maskResource = findMaskResource(resource);
|
||||
|
||||
if (maskResource != null) {
|
||||
Raster mask = readMask(maskResource);
|
||||
image.getAlphaRaster().setRect(mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 32 bit ARGB (true color)
|
||||
int bandLen = data.length / 4;
|
||||
|
||||
DataBufferByte buffer = new DataBufferByte(data, data.length);
|
||||
|
||||
WritableRaster raster;
|
||||
|
||||
if (resource.isCompressed()) {
|
||||
raster = Raster.createBandedRaster(buffer, width, height, width, new int[]{0, 0, 0, 0}, createBandOffsets(bandLen), null);
|
||||
}
|
||||
else {
|
||||
// NOTE: Uncompressed 32bit is interleaved RGBA, not banded...
|
||||
raster = Raster.createInterleavedRaster(buffer, width, height, width * 4, 4, new int[]{1, 2, 3, 0}, null);
|
||||
}
|
||||
|
||||
image.setData(raster);
|
||||
|
||||
processImageProgress(75f);
|
||||
|
||||
// Read mask and apply
|
||||
IconResource maskResource = findMaskResource(resource);
|
||||
|
||||
if (maskResource != null) {
|
||||
Raster mask = readMask(maskResource);
|
||||
image.getAlphaRaster().setRect(mask);
|
||||
}
|
||||
else {
|
||||
// TODO: This is simply stupid. Rewrite to use no alpha instead?
|
||||
byte[] solid = new byte[width * height];
|
||||
Arrays.fill(solid, (byte) -1);
|
||||
WritableRaster mask = Raster.createBandedRaster(new DataBufferByte(solid, solid.length), width, height, width, new int[]{0}, new int[]{0}, null);
|
||||
image.getAlphaRaster().setRect(mask);
|
||||
}
|
||||
}
|
||||
|
||||
// For now: Make listener tests happy
|
||||
// TODO: Implement more sophisticated reading
|
||||
processImageProgress(100f);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
}
|
||||
else {
|
||||
processImageComplete();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private Raster readMask(final IconResource resource) throws IOException {
|
||||
Dimension size = resource.size();
|
||||
|
||||
int width = size.width;
|
||||
int height = size.height;
|
||||
|
||||
byte[] mask = new byte[width * height];
|
||||
imageInput.seek(resource.start + ICNS.RESOURCE_HEADER_SIZE);
|
||||
|
||||
if (resource.isMaskType()) {
|
||||
// 8 bit mask
|
||||
imageInput.readFully(mask, 0, resource.length - ICNS.RESOURCE_HEADER_SIZE);
|
||||
}
|
||||
else if (resource.hasMask()) {
|
||||
// Embedded 1bit mask
|
||||
byte[] maskData = new byte[(resource.length - ICNS.RESOURCE_HEADER_SIZE) / 2];
|
||||
imageInput.skipBytes(maskData.length); // Skip the 1 bit image data
|
||||
imageInput.readFully(maskData);
|
||||
|
||||
// Unpack 1bit mask to 8 bit
|
||||
int bitPos = 0x80;
|
||||
|
||||
for (int i = 0, maskLength = mask.length; i < maskLength; i++) {
|
||||
mask[i] = (byte) ((maskData[i / 8] & bitPos) != 0 ? 0xff : 0x00);
|
||||
|
||||
if ((bitPos >>= 1) == 0) {
|
||||
bitPos = 0x80;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException(String.format("Not a mask resource: %s", resource));
|
||||
}
|
||||
|
||||
return Raster.createBandedRaster(new DataBufferByte(mask, mask.length), width, height, width, new int[]{0}, new int[]{0}, null);
|
||||
}
|
||||
|
||||
private IconResource findMaskResource(final IconResource iconResource) throws IOException {
|
||||
// Find 8 bit mask
|
||||
try {
|
||||
int i = 0;
|
||||
|
||||
while (true) {
|
||||
IconResource mask = i < masks.size() ? masks.get(i++) : readNextIconResource();
|
||||
|
||||
if (mask.isMaskType() && mask.size().equals(iconResource.size())) {
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfBoundsException ignore) {
|
||||
}
|
||||
|
||||
// Fall back to mask from 1 bit resource if no 8 bit mask
|
||||
for (IconResource resource : icons) {
|
||||
if (resource.hasMask() && resource.size().equals(iconResource.size())) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private BufferedImage readForeignFormat(int imageIndex, final ImageReadParam param, final IconResource resource) throws IOException {
|
||||
// TODO: Optimize by caching readers that work?
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, resource.length));
|
||||
|
||||
try {
|
||||
// Try first using ImageIO
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
|
||||
|
||||
while (readers.hasNext()) {
|
||||
ImageReader reader = readers.next();
|
||||
reader.setInput(stream);
|
||||
|
||||
try {
|
||||
return reader.read(0, param);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
if (stream.getFlushedPosition() <= 0) {
|
||||
stream.seek(0);
|
||||
}
|
||||
else {
|
||||
stream.close();
|
||||
stream = ImageIO.createImageInputStream(IIOUtil.createStreamAdapter(imageInput, resource.length));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
String format = getForeignFormat(stream);
|
||||
|
||||
// OS X quick fix
|
||||
if ("JPEG 2000".equals(format) && SipsJP2Reader.isAvailable()) {
|
||||
SipsJP2Reader reader = new SipsJP2Reader();
|
||||
reader.setInput(stream);
|
||||
BufferedImage image = reader.read(0, param);
|
||||
|
||||
if (image != null) {
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
// There's no JPEG 2000 reader installed in ImageIO by default (requires JAI ImageIO installed).
|
||||
// Return blank icon + issue warning. We know the image dimensions, we just can't read the data.
|
||||
processWarningOccurred(String.format(
|
||||
"Cannot read %s format in type '%s' icon (no reader; installed: %s)",
|
||||
format, ICNSUtil.intToStr(resource.type), Arrays.toString(IIOUtil.getNormalizedReaderFormatNames())
|
||||
));
|
||||
|
||||
Dimension size = resource.size();
|
||||
|
||||
return getDestination(param, getImageTypes(imageIndex), size.width, size.height);
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private String getForeignFormat(final ImageInputStream stream) throws IOException {
|
||||
byte[] magic = new byte[12]; // Length of JPEG 2000 magic
|
||||
|
||||
try {
|
||||
stream.readFully(magic);
|
||||
}
|
||||
finally {
|
||||
stream.seek(0);
|
||||
}
|
||||
|
||||
String format;
|
||||
|
||||
if (Arrays.equals(ICNS.PNG_MAGIC, magic)) {
|
||||
format = "PNG";
|
||||
}
|
||||
else if (Arrays.equals(ICNS.JPEG_2000_MAGIC, magic)) {
|
||||
format = "JPEG 2000";
|
||||
}
|
||||
else {
|
||||
format = "unknown";
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
private IconResource readIconResource(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readeFileHeader();
|
||||
|
||||
while (icons.size() <= imageIndex) {
|
||||
readNextIconResource();
|
||||
}
|
||||
|
||||
return icons.get(imageIndex);
|
||||
}
|
||||
|
||||
private IconResource readNextIconResource() throws IOException {
|
||||
long lastReadPos = lastResourceRead == null ? ICNS.RESOURCE_HEADER_SIZE : lastResourceRead.start + lastResourceRead.length;
|
||||
|
||||
imageInput.seek(lastReadPos);
|
||||
|
||||
if (imageInput.getStreamPosition() >= length) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
IconResource resource = IconResource.read(imageInput);
|
||||
// System.err.println("resource: " + resource);
|
||||
|
||||
lastResourceRead = resource;
|
||||
|
||||
// Filter out special cases like 'icnV' or 'TOC ' resources
|
||||
if (resource.isMaskType()) {
|
||||
masks.add(resource);
|
||||
}
|
||||
else if (!resource.isUnknownType()) {
|
||||
icons.add(resource);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
private void readeFileHeader() throws IOException {
|
||||
assertInput();
|
||||
|
||||
if (length <= 0) {
|
||||
imageInput.seek(0);
|
||||
|
||||
if (imageInput.readInt() != ICNS.MAGIC) {
|
||||
throw new IIOException("Not an Apple Icon Image");
|
||||
}
|
||||
|
||||
length = imageInput.readInt();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ICNSBitMaskColorModel extends IndexColorModel {
|
||||
static final IndexColorModel INSTANCE = new ICNSBitMaskColorModel();
|
||||
|
||||
private ICNSBitMaskColorModel() {
|
||||
super(1, 2, new int[]{0, 0xffffffff}, 0, true, 0, DataBuffer.TYPE_BYTE);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedAssignment"})
|
||||
public static void main(String[] args) throws IOException {
|
||||
int argIndex = 0;
|
||||
|
||||
int requested = -1;
|
||||
if (args[argIndex].charAt(0) == '-') {
|
||||
argIndex++;
|
||||
requested = Integer.parseInt(args[argIndex++]);
|
||||
}
|
||||
|
||||
int imagesRead = 0;
|
||||
int imagesSkipped = 0;
|
||||
ImageReader reader = new ICNSImageReader();
|
||||
|
||||
while(argIndex < args.length) {
|
||||
File input = new File(args[argIndex++]);
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(input);
|
||||
|
||||
if (stream == null) {
|
||||
System.err.printf("Cannot read: %s\n", input.getAbsolutePath());
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
reader.setInput(stream);
|
||||
|
||||
int start = requested != -1 ? requested : 0;
|
||||
int numImages = requested != -1 ? requested + 1 : reader.getNumImages(true);
|
||||
for (int i = start; i < numImages; i++) {
|
||||
try {
|
||||
long begin = System.currentTimeMillis();
|
||||
BufferedImage image = reader.read(i);
|
||||
imagesRead++;
|
||||
// System.err.println("image: " + image);
|
||||
System.err.println(System.currentTimeMillis() - begin + "ms");
|
||||
showIt(image, String.format("%s - %d", input.getName(), i));
|
||||
}
|
||||
catch (IOException e) {
|
||||
imagesSkipped++;
|
||||
if (e.getMessage().contains("JPEG 2000")) {
|
||||
System.err.printf("%s: %s\n", input, e.getMessage());
|
||||
}
|
||||
else {
|
||||
System.err.printf("%s: ", input);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.err.printf("%s: ", input);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
System.err.printf("Read %s images (%d skipped) in %d files\n", imagesRead, imagesSkipped, args.length);
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* ICNSImageReaderSpi
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNSImageReaderSpi.java,v 1.0 25.10.11 18:41 haraldk Exp$
|
||||
*/
|
||||
public final class ICNSImageReaderSpi extends ImageReaderSpi{
|
||||
public ICNSImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(ICNSImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private ICNSImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"icns", "ICNS"},
|
||||
new String[]{"icns"},
|
||||
new String[]{
|
||||
"image/x-apple-icons", // Common extension MIME
|
||||
},
|
||||
"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReader",
|
||||
STANDARD_INPUT_TYPE,
|
||||
null,
|
||||
true, null, null, null, null,
|
||||
true,
|
||||
null, null,
|
||||
null, null
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecodeInput(Object source) throws IOException {
|
||||
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
|
||||
}
|
||||
|
||||
private static boolean canDecode(ImageInputStream input) throws IOException {
|
||||
try {
|
||||
input.mark();
|
||||
return input.readInt() == ICNS.MAGIC;
|
||||
}
|
||||
finally {
|
||||
input.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReader createReaderInstance(Object extension) throws IOException {
|
||||
return new ICNSImageReader(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(Locale locale) {
|
||||
return "Apple Icon Image (icns) format Reader";
|
||||
}
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* ICNSUtil
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNSUtil.java,v 1.0 26.10.11 11:49 haraldk Exp$
|
||||
*/
|
||||
final class ICNSUtil {
|
||||
|
||||
private ICNSUtil() {}
|
||||
|
||||
// TODO: Duplicated code from IFF plugin, move to some common util?
|
||||
static String intToStr(int pChunkId) {
|
||||
return new String(
|
||||
new byte[]{
|
||||
(byte) ((pChunkId & 0xff000000) >> 24),
|
||||
(byte) ((pChunkId & 0x00ff0000) >> 16),
|
||||
(byte) ((pChunkId & 0x0000ff00) >> 8),
|
||||
(byte) ((pChunkId & 0x000000ff))
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* http://www.macdisk.com/maciconen.php:
|
||||
* "For [...] (width * height of the icon), read a byte.
|
||||
* if bit 8 of the byte is set:
|
||||
* This is a compressed run, for some value (next byte).
|
||||
* The length is byte - 125. (*
|
||||
* Put so many copies of the byte in the current color channel.
|
||||
* Else:
|
||||
* This is an uncompressed run, whose values follow.
|
||||
* The length is byte + 1.
|
||||
* Read the bytes and put them in the current color channel."
|
||||
*
|
||||
* *): With signed bytes, byte is always negative in this case, so it's actually -byte - 125,
|
||||
* which is the same as byte + 131.
|
||||
*/
|
||||
// NOTE: This is very close to PackBits (as described by the Wikipedia article), but it is not PackBits!
|
||||
static void decompress(final DataInputStream input, final byte[] result, int offset, int length) throws IOException {
|
||||
int resultPos = offset;
|
||||
int remaining = length;
|
||||
|
||||
while (remaining > 0) {
|
||||
byte run = input.readByte();
|
||||
int runLength;
|
||||
|
||||
if ((run & 0x80) != 0) {
|
||||
// Compressed run
|
||||
runLength = run + 131; // PackBits: -run + 1 and run == 0x80 is no-op... This allows 1 byte longer runs...
|
||||
|
||||
byte runData = input.readByte();
|
||||
|
||||
for (int i = 0; i < runLength; i++) {
|
||||
result[resultPos++] = runData;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Uncompressed run
|
||||
runLength = run + 1;
|
||||
|
||||
input.readFully(result, resultPos, runLength);
|
||||
resultPos += runLength;
|
||||
}
|
||||
|
||||
remaining -= runLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* IconResource
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IconResource.java,v 1.0 23.11.11 13:35 haraldk Exp$
|
||||
*/
|
||||
final class IconResource {
|
||||
// TODO: Rewrite using subclasses/instances!
|
||||
|
||||
protected final long start;
|
||||
protected final int type;
|
||||
protected final int length;
|
||||
|
||||
private IconResource(long start, int type, int length) {
|
||||
validate(type, length);
|
||||
|
||||
this.start = start;
|
||||
this.type = type;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public static IconResource read(ImageInputStream input) throws IOException {
|
||||
return new IconResource(input.getStreamPosition(), input.readInt(), input.readInt());
|
||||
}
|
||||
|
||||
private void validate(int type, int length) {
|
||||
switch (type) {
|
||||
case ICNS.ICON:
|
||||
validateLengthForType(type, length, 128);
|
||||
break;
|
||||
case ICNS.ICN_:
|
||||
validateLengthForType(type, length, 256);
|
||||
break;
|
||||
case ICNS.icm_:
|
||||
validateLengthForType(type, length, 48);
|
||||
break;
|
||||
case ICNS.icm4:
|
||||
validateLengthForType(type, length, 96);
|
||||
break;
|
||||
case ICNS.icm8:
|
||||
validateLengthForType(type, length, 192);
|
||||
break;
|
||||
case ICNS.ics_:
|
||||
validateLengthForType(type, length, 64);
|
||||
break;
|
||||
case ICNS.ics4:
|
||||
validateLengthForType(type, length, 128);
|
||||
break;
|
||||
case ICNS.ics8:
|
||||
case ICNS.s8mk:
|
||||
validateLengthForType(type, length, 256);
|
||||
break;
|
||||
case ICNS.icl4:
|
||||
validateLengthForType(type, length, 512);
|
||||
break;
|
||||
case ICNS.icl8:
|
||||
case ICNS.l8mk:
|
||||
validateLengthForType(type, length, 1024);
|
||||
break;
|
||||
case ICNS.ich_:
|
||||
validateLengthForType(type, length, 576);
|
||||
break;
|
||||
case ICNS.ich4:
|
||||
validateLengthForType(type, length, 1152);
|
||||
break;
|
||||
case ICNS.ich8:
|
||||
case ICNS.h8mk:
|
||||
validateLengthForType(type, length, 2304);
|
||||
break;
|
||||
case ICNS.t8mk:
|
||||
validateLengthForType(type, length, 16384);
|
||||
break;
|
||||
case ICNS.ih32:
|
||||
case ICNS.is32:
|
||||
case ICNS.il32:
|
||||
case ICNS.it32:
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
if (length > ICNS.RESOURCE_HEADER_SIZE) {
|
||||
break;
|
||||
}
|
||||
throw new IllegalArgumentException(String.format("Wrong combination of icon type '%s' and length: %d", ICNSUtil.intToStr(type), length));
|
||||
case ICNS.icnV:
|
||||
validateLengthForType(type, length, 4);
|
||||
break;
|
||||
case ICNS.TOC_:
|
||||
default:
|
||||
if (length > ICNS.RESOURCE_HEADER_SIZE) {
|
||||
break;
|
||||
}
|
||||
throw new IllegalStateException(String.format("Unknown icon type: '%s' length: %d", ICNSUtil.intToStr(type), length));
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLengthForType(int type, int length, final int expectedLength) {
|
||||
Validate.isTrue(
|
||||
length == expectedLength + ICNS.RESOURCE_HEADER_SIZE, // Compute to make lengths more logical
|
||||
String.format(
|
||||
"Wrong combination of icon type '%s' and length: %d (expected: %d)",
|
||||
ICNSUtil.intToStr(type), length - ICNS.RESOURCE_HEADER_SIZE, expectedLength
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Dimension size() {
|
||||
switch (type) {
|
||||
case ICNS.ICON:
|
||||
case ICNS.ICN_:
|
||||
return new Dimension(32, 32);
|
||||
case ICNS.icm_:
|
||||
case ICNS.icm4:
|
||||
case ICNS.icm8:
|
||||
return new Dimension(16, 12);
|
||||
case ICNS.ics_:
|
||||
case ICNS.ics4:
|
||||
case ICNS.ics8:
|
||||
case ICNS.is32:
|
||||
case ICNS.s8mk:
|
||||
return new Dimension(16, 16);
|
||||
case ICNS.icl4:
|
||||
case ICNS.icl8:
|
||||
case ICNS.il32:
|
||||
case ICNS.l8mk:
|
||||
return new Dimension(32, 32);
|
||||
case ICNS.ich_:
|
||||
case ICNS.ich4:
|
||||
case ICNS.ich8:
|
||||
case ICNS.ih32:
|
||||
case ICNS.h8mk:
|
||||
return new Dimension(48, 48);
|
||||
case ICNS.it32:
|
||||
case ICNS.t8mk:
|
||||
return new Dimension(128, 128);
|
||||
case ICNS.ic08:
|
||||
return new Dimension(256, 256);
|
||||
case ICNS.ic09:
|
||||
return new Dimension(512, 512);
|
||||
case ICNS.ic10:
|
||||
return new Dimension(1024, 1024);
|
||||
default:
|
||||
throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type)));
|
||||
}
|
||||
}
|
||||
|
||||
public int depth() {
|
||||
switch (type) {
|
||||
case ICNS.ICON:
|
||||
case ICNS.ICN_:
|
||||
case ICNS.icm_:
|
||||
case ICNS.ics_:
|
||||
case ICNS.ich_:
|
||||
return 1;
|
||||
case ICNS.icm4:
|
||||
case ICNS.ics4:
|
||||
case ICNS.icl4:
|
||||
case ICNS.ich4:
|
||||
return 4;
|
||||
case ICNS.icm8:
|
||||
case ICNS.ics8:
|
||||
case ICNS.icl8:
|
||||
case ICNS.ich8:
|
||||
case ICNS.s8mk:
|
||||
case ICNS.l8mk:
|
||||
case ICNS.h8mk:
|
||||
case ICNS.t8mk:
|
||||
return 8;
|
||||
case ICNS.is32:
|
||||
case ICNS.il32:
|
||||
case ICNS.ih32:
|
||||
case ICNS.it32:
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
return 32;
|
||||
default:
|
||||
throw new IllegalStateException(String.format("Unknown icon type: '%s'", ICNSUtil.intToStr(type)));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUnknownType() {
|
||||
// Unknown types should simply be skipped when reading
|
||||
switch (type) {
|
||||
case ICNS.ICON:
|
||||
case ICNS.ICN_:
|
||||
case ICNS.icm_:
|
||||
case ICNS.ics_:
|
||||
case ICNS.ich_:
|
||||
case ICNS.icm4:
|
||||
case ICNS.ics4:
|
||||
case ICNS.icl4:
|
||||
case ICNS.ich4:
|
||||
case ICNS.icm8:
|
||||
case ICNS.ics8:
|
||||
case ICNS.icl8:
|
||||
case ICNS.ich8:
|
||||
case ICNS.s8mk:
|
||||
case ICNS.l8mk:
|
||||
case ICNS.h8mk:
|
||||
case ICNS.t8mk:
|
||||
case ICNS.is32:
|
||||
case ICNS.il32:
|
||||
case ICNS.ih32:
|
||||
case ICNS.it32:
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean hasMask() {
|
||||
switch (type) {
|
||||
case ICNS.ICN_:
|
||||
case ICNS.icm_:
|
||||
case ICNS.ics_:
|
||||
case ICNS.ich_:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isMaskType() {
|
||||
switch (type) {
|
||||
case ICNS.s8mk:
|
||||
case ICNS.l8mk:
|
||||
case ICNS.h8mk:
|
||||
case ICNS.t8mk:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCompressed() {
|
||||
switch (type) {
|
||||
case ICNS.is32:
|
||||
case ICNS.il32:
|
||||
case ICNS.ih32:
|
||||
case ICNS.it32:
|
||||
// http://www.macdisk.com/maciconen.php
|
||||
// "One should check whether the data length corresponds to the theoretical length (width * height)."
|
||||
Dimension size = size();
|
||||
if (length != (size.width * size.height * depth() / 8 + ICNS.RESOURCE_HEADER_SIZE)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isForeignFormat() {
|
||||
// Recent entries contains full JPEG 2000 or PNG streams
|
||||
switch (type) {
|
||||
case ICNS.ic08:
|
||||
case ICNS.ic09:
|
||||
case ICNS.ic10:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (int) start ^ type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
return other == this || other != null && other.getClass() == getClass() && isEqual((IconResource) other);
|
||||
}
|
||||
|
||||
private boolean isEqual(IconResource other) {
|
||||
// This isn't strictly true, as resource must reside in same stream as well, but good enough for now
|
||||
return start == other.start && type == other.type && length == other.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s['%s' start: %d, length: %d%s]", getClass().getSimpleName(), ICNSUtil.intToStr(type), start, length, isCompressed() ? " (compressed)" : "");
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* QuickFix for OS X (where ICNS are most useful) and JPEG 2000.
|
||||
* Dumps the stream to disk and converts using sips command line tool:
|
||||
* {@code sips -s format png <temp>}.
|
||||
* Reads image back using ImageIO and known format (png).
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: JPEG2000Reader.java,v 1.0 25.11.11 14:17 haraldk Exp$
|
||||
*/
|
||||
final class SipsJP2Reader {
|
||||
|
||||
private static final File SIPS_COMMAND = new File("/usr/bin/sips");
|
||||
private static final boolean SIPS_EXISTS_AND_EXECUTES = existsAndExecutes(SIPS_COMMAND);
|
||||
|
||||
private static boolean existsAndExecutes(final File cmd) {
|
||||
try {
|
||||
return cmd.exists() && cmd.canExecute();
|
||||
}
|
||||
catch (SecurityException ignore) {
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ImageInputStream input;
|
||||
|
||||
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||
// Test if we have sips before dumping to be fail-fast
|
||||
if (SIPS_EXISTS_AND_EXECUTES) {
|
||||
File tempFile = dumpToFile(input);
|
||||
|
||||
if (convertToPNG(tempFile)) {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(tempFile);
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
|
||||
|
||||
while (readers.hasNext()) {
|
||||
ImageReader reader = readers.next();
|
||||
reader.setInput(stream);
|
||||
|
||||
try {
|
||||
return reader.read(imageIndex, param);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
if (stream.getFlushedPosition() <= 0) {
|
||||
stream.seek(0);
|
||||
}
|
||||
else {
|
||||
stream.close();
|
||||
stream = ImageIO.createImageInputStream(tempFile);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setInput(final ImageInputStream input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
private static boolean convertToPNG(final File tempFile) throws IIOException {
|
||||
try {
|
||||
Process process = Runtime.getRuntime().exec(buildCommand(SIPS_COMMAND, tempFile));
|
||||
|
||||
// NOTE: sips return status is 0, even if error, need to check error message
|
||||
int status = process.waitFor();
|
||||
String message = checkErrorMessage(process);
|
||||
|
||||
if (status == 0 && message == null) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
throw new IOException(message);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
throw new IIOException("Interrupted converting JPEG 2000 format", e);
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
// Exec might need permissions in sandboxed environment
|
||||
throw new IIOException("Cannot convert JPEG 2000 format without file permissions", e);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IIOException("Error converting JPEG 2000 format: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String checkErrorMessage(final Process process) throws IOException {
|
||||
InputStream stream = process.getErrorStream();
|
||||
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
||||
String message = reader.readLine();
|
||||
|
||||
return message != null && message.startsWith("Error: ") ? message.substring(7) : null;
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] buildCommand(final File sipsCommand, final File tempFile) {
|
||||
return new String[]{
|
||||
sipsCommand.getAbsolutePath(), "-s", "format", "png", tempFile.getAbsolutePath()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static File dumpToFile(final ImageInputStream stream) throws IOException {
|
||||
File tempFile = File.createTempFile("imageio-icns-", ".png");
|
||||
tempFile.deleteOnExit();
|
||||
|
||||
FileOutputStream out = new FileOutputStream(tempFile);
|
||||
|
||||
try {
|
||||
FileUtil.copy(IIOUtil.createStreamAdapter(stream), out);
|
||||
}
|
||||
finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
static boolean isAvailable() {
|
||||
return SIPS_EXISTS_AND_EXECUTES;
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# Copyright (c) 2011, Harald Kuhr
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
# * Neither the name "TwelveMonkeys" nor the
|
||||
# names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.icns;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ICNSImageReaderTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ICNSImageReaderTest.java,v 1.0 25.10.11 18:44 haraldk Exp$
|
||||
*/
|
||||
public class ICNSImageReaderTest extends ImageReaderAbstractTestCase {
|
||||
@Override
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/GenericJavaApp.icns"),
|
||||
new Dimension(16, 16), // 1 bit + 1 bit mask
|
||||
new Dimension(16, 16), new Dimension(16, 16), // 8 bit CMAP, 24 bit + 8 bit mask
|
||||
new Dimension(32, 32), // 1 bit + 1 bit mask
|
||||
new Dimension(32, 32), new Dimension(32, 32), // 8 bit CMAP, 24 bit + 8 bit mask
|
||||
new Dimension(128, 128) // 24 bit + 8 bit mask
|
||||
),
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/Apple Retro.icns"),
|
||||
new Dimension(16, 16), // 24 bit + 8 bit mask
|
||||
new Dimension(32, 32), // 24 bit + 8 bit mask
|
||||
new Dimension(48, 48), // 24 bit + 8 bit mask
|
||||
new Dimension(128, 128), // 24 bit + 8 bit mask
|
||||
new Dimension(256, 256), // JPEG 2000 ic08
|
||||
new Dimension(512, 512) // JPEG 2000 ic09
|
||||
),
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/7zIcon.icns"), // Contains the icnV resource, that isn't an icon
|
||||
new Dimension(16, 16), // 24 bit + 8 bit mask
|
||||
new Dimension(32, 32), // 24 bit + 8 bit mask
|
||||
new Dimension(128, 128), // 24 bit + 8 bit mask
|
||||
new Dimension(256, 256), // JPEG 2000 ic08
|
||||
new Dimension(512, 512) // JPEG 2000 ic09
|
||||
),
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/appStore.icns"), // Contains the 'TOC ' and icnV resources + PNGs in ic08-10
|
||||
new Dimension(16, 16), // 24 bit + 8 bit mask
|
||||
new Dimension(32, 32), // 24 bit + 8 bit mask
|
||||
new Dimension(128, 128), // 24 bit + 8 bit mask
|
||||
new Dimension(256, 256), // PNG ic08
|
||||
new Dimension(512, 512), // PNG ic09
|
||||
new Dimension(1024, 1024) // PNG ic10
|
||||
),
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/XLW.icns"), // No 8 bit mask for 16x16 & 32x32, fall back to 1 bit mask
|
||||
new Dimension(16, 16), // 1 bit + 1 bit mask
|
||||
new Dimension(16, 16), new Dimension(16, 16), // 4 bit CMAP, 8 bit CMAP (no 8 bit mask)
|
||||
new Dimension(32, 32), // 1 bit + 1 bit mask
|
||||
new Dimension(32, 32), new Dimension(32, 32), // 4 bit CMAP, 8 bit CMAP (no 8 bit mask)
|
||||
new Dimension(128, 128) // 24 bit + 8 bit mask
|
||||
),
|
||||
new TestData(
|
||||
getClassLoaderResource("/icns/XMLExport.icns"), // No masks at all, uncompressed 32 bit data
|
||||
new Dimension(128, 128), // 32 bit interleaved
|
||||
new Dimension(48, 48), // 32 bit interleaved
|
||||
new Dimension(32, 32), // 32 bit interleaved
|
||||
new Dimension(16, 16) // 32 bit interleaved
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageReaderSpi createProvider() {
|
||||
return new ICNSImageReaderSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageReader createReader() {
|
||||
return new ICNSImageReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class getReaderClass() {
|
||||
return ICNSImageReader.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("icns");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("icns");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/x-apple-icons");
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
The icon files in this folder may contain copyrighted artwork. However, I believe that using them for test purposes
|
||||
(without actually displaying the artwork) must be considered fair use.
|
||||
If you disagree for any reason, please send me a note, and I will remove your icon from the distribution.
|
||||
|
||||
-- harald.kuhr@gmail.com
|
||||
+9
-9
@@ -39,34 +39,34 @@ import java.awt.image.BufferedImage;
|
||||
* @version $Id: Bitmap.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
abstract class BitmapDescriptor {
|
||||
protected final DirectoryEntry mEntry;
|
||||
protected final DIBHeader mHeader;
|
||||
protected final DirectoryEntry entry;
|
||||
protected final DIBHeader header;
|
||||
|
||||
protected BufferedImage mImage;
|
||||
protected BufferedImage image;
|
||||
|
||||
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||
Validate.notNull(pEntry, "entry");
|
||||
Validate.notNull(pHeader, "header");
|
||||
|
||||
mEntry = pEntry;
|
||||
mHeader = pHeader;
|
||||
entry = pEntry;
|
||||
header = pHeader;
|
||||
}
|
||||
|
||||
abstract public BufferedImage getImage();
|
||||
|
||||
public final int getWidth() {
|
||||
return mEntry.getWidth();
|
||||
return entry.getWidth();
|
||||
}
|
||||
|
||||
public final int getHeight() {
|
||||
return mEntry.getHeight();
|
||||
return entry.getHeight();
|
||||
}
|
||||
|
||||
protected final int getColorCount() {
|
||||
return mEntry.getColorCount() != 0 ? mEntry.getColorCount() : 1 << getBitCount();
|
||||
return entry.getColorCount() != 0 ? entry.getColorCount() : 1 << getBitCount();
|
||||
}
|
||||
|
||||
protected final int getBitCount() {
|
||||
return mEntry.getBitCount() != 0 ? mEntry.getBitCount() : mHeader.getBitCount();
|
||||
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
|
||||
}
|
||||
}
|
||||
|
||||
+19
-20
@@ -43,17 +43,17 @@ import java.util.Hashtable;
|
||||
* @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
class BitmapIndexed extends BitmapDescriptor {
|
||||
protected final int[] mBits;
|
||||
protected final int[] mColors;
|
||||
protected final int[] bits;
|
||||
protected final int[] colors;
|
||||
|
||||
private BitmapMask mMask;
|
||||
private BitmapMask mask;
|
||||
|
||||
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
||||
super(pEntry, pHeader);
|
||||
mBits = new int[getWidth() * getHeight()];
|
||||
bits = new int[getWidth() * getHeight()];
|
||||
|
||||
// NOTE: We're adding space for one extra color, for transparency
|
||||
mColors = new int[getColorCount() + 1];
|
||||
colors = new int[getColorCount() + 1];
|
||||
}
|
||||
|
||||
public BufferedImage createImageIndexed() {
|
||||
@@ -64,10 +64,9 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
|
||||
// This is slightly obscure, and should probably be moved..
|
||||
Hashtable<String, Object> properties = null;
|
||||
if (mEntry instanceof DirectoryEntry.CUREntry) {
|
||||
DirectoryEntry.CUREntry entry = (DirectoryEntry.CUREntry) mEntry;
|
||||
if (entry instanceof DirectoryEntry.CUREntry) {
|
||||
properties = new Hashtable<String, Object>(1);
|
||||
properties.put("cursor_hotspot", entry.getHotspot());
|
||||
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
||||
}
|
||||
|
||||
BufferedImage image = new BufferedImage(
|
||||
@@ -78,17 +77,17 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
|
||||
WritableRaster raster = image.getRaster();
|
||||
|
||||
// Make pixels transparant according to mask
|
||||
// Make pixels transparent according to mask
|
||||
final int trans = icm.getTransparentPixel();
|
||||
for (int y = 0; y < getHeight(); y++) {
|
||||
for (int x = 0; x < getWidth(); x++) {
|
||||
if (mMask.isTransparent(x, y)) {
|
||||
mBits[x + getWidth() * y] = trans;
|
||||
if (mask.isTransparent(x, y)) {
|
||||
bits[x + getWidth() * y] = trans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
raster.setSamples(0, 0, getWidth(), getHeight(), 0, mBits);
|
||||
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
|
||||
|
||||
//System.out.println("Image: " + image);
|
||||
|
||||
@@ -102,18 +101,18 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
// NOTE: This is a hack to make room for transparent pixel for mask
|
||||
int bits = getBitCount();
|
||||
|
||||
int colors = mColors.length;
|
||||
int colors = this.colors.length;
|
||||
int trans = -1;
|
||||
|
||||
// Try to avoid USHORT transfertype, as it results in BufferedImage TYPE_CUSTOM
|
||||
// NOTE: This code assumes icons are small, and is NOT optimized for performance...
|
||||
if (colors > (1 << getBitCount())) {
|
||||
int index = BitmapIndexed.findTransIndexMaybeRemap(mColors, mBits);
|
||||
int index = BitmapIndexed.findTransIndexMaybeRemap(this.colors, this.bits);
|
||||
|
||||
if (index == -1) {
|
||||
// No duplicate found, increase bitcount
|
||||
bits++;
|
||||
trans = mColors.length - 1;
|
||||
trans = this.colors.length - 1;
|
||||
}
|
||||
else {
|
||||
// Found a duplicate, use it as trans
|
||||
@@ -124,7 +123,7 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
|
||||
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
||||
return new InverseColorMapIndexColorModel(
|
||||
bits, colors, mColors, 0, true, trans,
|
||||
bits, colors, this.colors, 0, true, trans,
|
||||
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
||||
);
|
||||
}
|
||||
@@ -170,13 +169,13 @@ class BitmapIndexed extends BitmapDescriptor {
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
if (mImage == null) {
|
||||
mImage = createImageIndexed();
|
||||
if (image == null) {
|
||||
image = createImageIndexed();
|
||||
}
|
||||
return mImage;
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setMask(final BitmapMask pMask) {
|
||||
mMask = pMask;
|
||||
mask = pMask;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -38,19 +38,19 @@ import java.awt.image.BufferedImage;
|
||||
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
class BitmapMask extends BitmapDescriptor {
|
||||
protected final BitmapIndexed mMask;
|
||||
protected final BitmapIndexed mask;
|
||||
|
||||
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
||||
super(pParent, pHeader);
|
||||
mMask = new BitmapIndexed(pParent, pHeader);
|
||||
mask = new BitmapIndexed(pParent, pHeader);
|
||||
}
|
||||
|
||||
boolean isTransparent(final int pX, final int pY) {
|
||||
// NOTE: 1: Fully transparent, 0: Opaque...
|
||||
return mMask.mBits[pX + pY * getWidth()] != 0;
|
||||
return mask.bits[pX + pY * getWidth()] != 0;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return mMask.getImage();
|
||||
return mask.getImage();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -43,6 +43,6 @@ class BitmapRGB extends BitmapDescriptor {
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return mImage;
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -38,15 +38,15 @@ import java.awt.image.BufferedImage;
|
||||
* @version $Id: BitmapUnsupported.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
class BitmapUnsupported extends BitmapDescriptor {
|
||||
private String mMessage;
|
||||
private String message;
|
||||
|
||||
public BitmapUnsupported(final DirectoryEntry pEntry, final String pMessage) {
|
||||
super(pEntry, null);
|
||||
|
||||
mMessage = pMessage;
|
||||
message = pMessage;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
throw new IllegalStateException(mMessage);
|
||||
throw new IllegalStateException(message);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -39,13 +39,11 @@ import java.io.IOException;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: CURImageReader.java,v 1.0 Apr 20, 2009 11:54:28 AM haraldk Exp$
|
||||
*
|
||||
* @see com.twelvemonkeys.imageio.plugins.ico.ICOImageReader
|
||||
* @see ICOImageReader
|
||||
*/
|
||||
public class CURImageReader extends ICOImageReader {
|
||||
// NOTE: All implementation is part of the ICOImageReader
|
||||
|
||||
public final class CURImageReader extends DIBImageReader {
|
||||
public CURImageReader() {
|
||||
super(DIB.TYPE_CUR);
|
||||
super(new CURImageReaderSpi());
|
||||
}
|
||||
|
||||
protected CURImageReader(final ImageReaderSpi pProvider) {
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ import java.util.Locale;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: CURImageReaderSpi.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
public class CURImageReaderSpi extends ImageReaderSpi {
|
||||
public final class CURImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
public CURImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(CURImageReaderSpi.class));
|
||||
|
||||
+33
-33
@@ -41,16 +41,16 @@ import java.io.IOException;
|
||||
* @see <a href="http://en.wikipedia.org/wiki/BMP_file_format">BMP file format (Wikipedia)</a>
|
||||
*/
|
||||
abstract class DIBHeader {
|
||||
protected int mSize;
|
||||
protected int size;
|
||||
|
||||
protected int mWidth;
|
||||
protected int width;
|
||||
|
||||
// NOTE: If a bitmask is present, this value includes the height of the mask
|
||||
// (so often header.height = entry.height * 2)
|
||||
protected int mHeight;
|
||||
protected int height;
|
||||
|
||||
protected int mPlanes;
|
||||
protected int mBitCount;
|
||||
protected int planes;
|
||||
protected int bitCount;
|
||||
|
||||
/**
|
||||
* 0 = BI_RGB: No compression
|
||||
@@ -58,18 +58,18 @@ abstract class DIBHeader {
|
||||
* 2 = BI_RLE4: 4 bit RLE Compression (4 bit only)
|
||||
* 3 = BI_BITFIELDS: No compression (16 & 32 bit only)
|
||||
*/
|
||||
protected int mCompression;
|
||||
protected int compression;
|
||||
|
||||
// May be 0 if not known
|
||||
protected int mImageSize;
|
||||
protected int imageSize;
|
||||
|
||||
protected int mXPixelsPerMeter;
|
||||
protected int mYPixelsPerMeter;
|
||||
protected int xPixelsPerMeter;
|
||||
protected int yPixelsPerMeter;
|
||||
|
||||
protected int mColorsUsed;
|
||||
protected int colorsUsed;
|
||||
|
||||
// 0 means all colors are important
|
||||
protected int mColorsImportant;
|
||||
protected int colorsImportant;
|
||||
|
||||
protected DIBHeader() {
|
||||
}
|
||||
@@ -102,47 +102,47 @@ abstract class DIBHeader {
|
||||
protected abstract void read(int pSize, DataInput pStream) throws IOException;
|
||||
|
||||
public final int getSize() {
|
||||
return mSize;
|
||||
return size;
|
||||
}
|
||||
|
||||
public final int getWidth() {
|
||||
return mWidth;
|
||||
return width;
|
||||
}
|
||||
|
||||
public final int getHeight() {
|
||||
return mHeight;
|
||||
return height;
|
||||
}
|
||||
|
||||
public final int getPlanes() {
|
||||
return mPlanes;
|
||||
return planes;
|
||||
}
|
||||
|
||||
public final int getBitCount() {
|
||||
return mBitCount;
|
||||
return bitCount;
|
||||
}
|
||||
|
||||
public int getCompression() {
|
||||
return mCompression;
|
||||
return compression;
|
||||
}
|
||||
|
||||
public int getImageSize() {
|
||||
return mImageSize;
|
||||
return imageSize;
|
||||
}
|
||||
|
||||
public int getXPixelsPerMeter() {
|
||||
return mXPixelsPerMeter;
|
||||
return xPixelsPerMeter;
|
||||
}
|
||||
|
||||
public int getYPixelsPerMeter() {
|
||||
return mYPixelsPerMeter;
|
||||
return yPixelsPerMeter;
|
||||
}
|
||||
|
||||
public int getColorsUsed() {
|
||||
return mColorsUsed;
|
||||
return colorsUsed;
|
||||
}
|
||||
|
||||
public int getColorsImportant() {
|
||||
return mColorsImportant;
|
||||
return colorsImportant;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -176,22 +176,22 @@ abstract class DIBHeader {
|
||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.WINDOWS_V3_HEADER_SIZE));
|
||||
}
|
||||
|
||||
mSize = pSize;
|
||||
size = pSize;
|
||||
|
||||
mWidth = pStream.readInt();
|
||||
mHeight = pStream.readInt();
|
||||
width = pStream.readInt();
|
||||
height = pStream.readInt();
|
||||
|
||||
mPlanes = pStream.readUnsignedShort();
|
||||
mBitCount = pStream.readUnsignedShort();
|
||||
mCompression = pStream.readInt();
|
||||
planes = pStream.readUnsignedShort();
|
||||
bitCount = pStream.readUnsignedShort();
|
||||
compression = pStream.readInt();
|
||||
|
||||
mImageSize = pStream.readInt();
|
||||
imageSize = pStream.readInt();
|
||||
|
||||
mXPixelsPerMeter = pStream.readInt();
|
||||
mYPixelsPerMeter = pStream.readInt();
|
||||
xPixelsPerMeter = pStream.readInt();
|
||||
yPixelsPerMeter = pStream.readInt();
|
||||
|
||||
mColorsUsed = pStream.readInt();
|
||||
mColorsImportant = pStream.readInt();
|
||||
colorsUsed = pStream.readInt();
|
||||
colorsImportant = pStream.readInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
+689
@@ -0,0 +1,689 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.ico;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
||||
import com.twelvemonkeys.util.WeakWeakMap;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ImageReader for Microsoft Windows ICO (icon) format.
|
||||
* 1, 4, 8 bit palette support with bitmask transparency, and 16, 24 and 32 bit
|
||||
* true color support with alpha. Also supports Windows Vista PNG encoded icons.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: ICOImageReader.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*
|
||||
* @see <a href="http://en.wikipedia.org/wiki/BMP_file_format">BMP file format (Wikipedia)</a>
|
||||
* @see <a href="http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)">ICO file format (Wikipedia)</a>
|
||||
*/
|
||||
// SEE http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)
|
||||
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
|
||||
// TODO: Support loading icons from DLLs, see
|
||||
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
|
||||
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
|
||||
abstract class DIBImageReader extends ImageReaderBase {
|
||||
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
|
||||
private Directory directory;
|
||||
|
||||
// TODO: Review these, make sure we don't have a memory leak
|
||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<DirectoryEntry, DIBHeader>();
|
||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<DirectoryEntry, BitmapDescriptor>();
|
||||
|
||||
private ImageReader pngImageReader;
|
||||
|
||||
protected DIBImageReader(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
directory = null;
|
||||
|
||||
headers.clear();
|
||||
descriptors.clear();
|
||||
|
||||
if (pngImageReader != null) {
|
||||
pngImageReader.dispose();
|
||||
pngImageReader = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
||||
DirectoryEntry entry = getEntry(pImageIndex);
|
||||
|
||||
// NOTE: Delegate to PNG reader
|
||||
if (isPNG(entry)) {
|
||||
return getImageTypesPNG(entry);
|
||||
}
|
||||
|
||||
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
|
||||
DIBHeader header = getHeader(entry);
|
||||
|
||||
// Use data from header to create specifier
|
||||
ImageTypeSpecifier specifier;
|
||||
switch (header.getBitCount()) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
// TODO: This is slightly QnD...
|
||||
int offset = entry.getOffset() + header.getSize();
|
||||
if (offset != imageInput.getStreamPosition()) {
|
||||
imageInput.seek(offset);
|
||||
}
|
||||
BitmapIndexed indexed = new BitmapIndexed(entry, header);
|
||||
readColorMap(indexed);
|
||||
specifier = IndexedImageTypeSpecifier.createFromIndexColorModel(indexed.createColorModel());
|
||||
break;
|
||||
case 16:
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||
break;
|
||||
case 24:
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
break;
|
||||
case 32:
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
||||
break;
|
||||
default:
|
||||
throw new IIOException(String.format("Unknown bit depth: %d", header.getBitCount()));
|
||||
}
|
||||
|
||||
types.add(specifier);
|
||||
|
||||
return types.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumImages(final boolean allowSearch) throws IOException {
|
||||
return getDirectory().count();
|
||||
}
|
||||
|
||||
public int getWidth(final int pImageIndex) throws IOException {
|
||||
return getEntry(pImageIndex).getWidth();
|
||||
}
|
||||
|
||||
public int getHeight(final int pImageIndex) throws IOException {
|
||||
return getEntry(pImageIndex).getHeight();
|
||||
}
|
||||
|
||||
public BufferedImage read(final int pImageIndex, final ImageReadParam pParam) throws IOException {
|
||||
checkBounds(pImageIndex);
|
||||
|
||||
processImageStarted(pImageIndex);
|
||||
|
||||
DirectoryEntry entry = getEntry(pImageIndex);
|
||||
|
||||
BufferedImage destination;
|
||||
|
||||
if (isPNG(entry)) {
|
||||
// NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header...
|
||||
destination = readPNG(entry, pParam);
|
||||
}
|
||||
else {
|
||||
// NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later,
|
||||
// to allow for storing the cursor hotspot for CUR images
|
||||
destination = hasExplicitDestination(pParam) ?
|
||||
getDestination(pParam, getImageTypes(pImageIndex), getWidth(pImageIndex), getHeight(pImageIndex)) : null;
|
||||
|
||||
BufferedImage image = readBitmap(entry);
|
||||
|
||||
// TODO: Handle AOI and subsampling inline, probably not of big importance...
|
||||
if (pParam != null) {
|
||||
image = fakeAOI(image, pParam);
|
||||
image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
|
||||
}
|
||||
|
||||
if (destination == null) {
|
||||
// This is okay, as long as the client did not request explicit destination image/type
|
||||
destination = image;
|
||||
}
|
||||
else {
|
||||
Graphics2D g = destination.createGraphics();
|
||||
try {
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.drawImage(image, 0, 0, null);
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(100);
|
||||
processImageComplete();
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private boolean isPNG(final DirectoryEntry pEntry) throws IOException {
|
||||
long magic;
|
||||
|
||||
imageInput.seek(pEntry.getOffset());
|
||||
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try {
|
||||
magic = imageInput.readLong();
|
||||
}
|
||||
finally {
|
||||
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
return magic == DIB.PNG_MAGIC;
|
||||
}
|
||||
|
||||
private BufferedImage readPNG(final DirectoryEntry pEntry, final ImageReadParam pParam) throws IOException {
|
||||
// TODO: Consider delegating listener calls
|
||||
return initPNGReader(pEntry).read(0, pParam);
|
||||
}
|
||||
|
||||
private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry pEntry) throws IOException {
|
||||
return initPNGReader(pEntry).getImageTypes(0);
|
||||
}
|
||||
|
||||
private ImageReader initPNGReader(final DirectoryEntry pEntry) throws IOException {
|
||||
ImageReader pngReader = getPNGReader();
|
||||
|
||||
imageInput.seek(pEntry.getOffset());
|
||||
InputStream inputStream = IIOUtil.createStreamAdapter(imageInput, pEntry.getSize());
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(inputStream);
|
||||
|
||||
// NOTE: Will throw IOException on later reads if input is not PNG
|
||||
pngReader.setInput(stream);
|
||||
|
||||
return pngReader;
|
||||
}
|
||||
|
||||
private ImageReader getPNGReader() throws IIOException {
|
||||
// TODO: Prefer Sun's std JDK PNGImagerReader, because it has known behaviour?
|
||||
if (pngImageReader == null) {
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("PNG");
|
||||
|
||||
if (readers.hasNext()) {
|
||||
pngImageReader = readers.next();
|
||||
}
|
||||
else {
|
||||
throw new IIOException("No PNGImageReader found using ImageIO, can't read PNG encoded ICO format.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
pngImageReader.reset();
|
||||
}
|
||||
|
||||
return pngImageReader;
|
||||
}
|
||||
|
||||
private DIBHeader getHeader(final DirectoryEntry pEntry) throws IOException {
|
||||
if (!headers.containsKey(pEntry)) {
|
||||
imageInput.seek(pEntry.getOffset());
|
||||
DIBHeader header = DIBHeader.read(imageInput);
|
||||
headers.put(pEntry, header);
|
||||
}
|
||||
|
||||
return headers.get(pEntry);
|
||||
}
|
||||
|
||||
private BufferedImage readBitmap(final DirectoryEntry pEntry) throws IOException {
|
||||
// TODO: Get rid of the caching, as the images are mutable
|
||||
BitmapDescriptor descriptor = descriptors.get(pEntry);
|
||||
|
||||
if (descriptor == null || !descriptors.containsKey(pEntry)) {
|
||||
DIBHeader header = getHeader(pEntry);
|
||||
|
||||
int offset = pEntry.getOffset() + header.getSize();
|
||||
if (offset != imageInput.getStreamPosition()) {
|
||||
imageInput.seek(offset);
|
||||
}
|
||||
|
||||
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
|
||||
if (header.getCompression() != 0) {
|
||||
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported compression: %d", header.getCompression()));
|
||||
}
|
||||
else {
|
||||
int bitCount = header.getBitCount();
|
||||
|
||||
switch (bitCount) {
|
||||
// Palette style
|
||||
case 1:
|
||||
case 4:
|
||||
case 8:
|
||||
descriptor = new BitmapIndexed(pEntry, header);
|
||||
readBitmapIndexed((BitmapIndexed) descriptor);
|
||||
break;
|
||||
// RGB style
|
||||
case 16:
|
||||
descriptor = new BitmapRGB(pEntry, header);
|
||||
readBitmap16(descriptor);
|
||||
break;
|
||||
case 24:
|
||||
descriptor = new BitmapRGB(pEntry, header);
|
||||
readBitmap24(descriptor);
|
||||
break;
|
||||
case 32:
|
||||
descriptor = new BitmapRGB(pEntry, header);
|
||||
readBitmap32(descriptor);
|
||||
break;
|
||||
|
||||
default:
|
||||
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported bit count %d", bitCount));
|
||||
}
|
||||
}
|
||||
|
||||
descriptors.put(pEntry, descriptor);
|
||||
}
|
||||
|
||||
return descriptor.getImage();
|
||||
}
|
||||
|
||||
private void readBitmapIndexed(final BitmapIndexed pBitmap) throws IOException {
|
||||
readColorMap(pBitmap);
|
||||
|
||||
switch (pBitmap.getBitCount()) {
|
||||
case 1:
|
||||
readBitmapIndexed1(pBitmap, false);
|
||||
break;
|
||||
case 4:
|
||||
readBitmapIndexed4(pBitmap);
|
||||
break;
|
||||
case 8:
|
||||
readBitmapIndexed8(pBitmap);
|
||||
break;
|
||||
}
|
||||
|
||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
||||
readBitmapIndexed1(mask.mask, true);
|
||||
pBitmap.setMask(mask);
|
||||
}
|
||||
|
||||
private void readColorMap(final BitmapIndexed pBitmap) throws IOException {
|
||||
int colorCount = pBitmap.getColorCount();
|
||||
|
||||
for (int i = 0; i < colorCount; i++) {
|
||||
// aRGB (a is "Reserved")
|
||||
pBitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000;
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException {
|
||||
int width = adjustToPadding(pBitmap.getWidth() >> 3);
|
||||
byte[] row = new byte[width];
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
imageInput.readFully(row, 0, width);
|
||||
int rowPos = 0;
|
||||
int xOrVal = 0x80;
|
||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
|
||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
||||
pBitmap.bits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF;
|
||||
|
||||
if (xOrVal == 1) {
|
||||
xOrVal = 0x80;
|
||||
rowPos++;
|
||||
}
|
||||
else {
|
||||
xOrVal >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: If we are reading the mask, we don't abort or progress
|
||||
if (!pAsMask) {
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException {
|
||||
int width = adjustToPadding(pBitmap.getWidth() >> 1);
|
||||
byte[] row = new byte[width];
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
imageInput.readFully(row, 0, width);
|
||||
int rowPos = 0;
|
||||
boolean high4 = true;
|
||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
|
||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
||||
int value;
|
||||
|
||||
if (high4) {
|
||||
value = (row[rowPos] & 0xF0) >> 4;
|
||||
}
|
||||
else {
|
||||
value = row[rowPos] & 0x0F;
|
||||
rowPos++;
|
||||
}
|
||||
|
||||
pBitmap.bits[pos++] = value & 0xFF;
|
||||
high4 = !high4;
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmapIndexed8(final BitmapIndexed pBitmap) throws IOException {
|
||||
int width = adjustToPadding(pBitmap.getWidth());
|
||||
|
||||
byte[] row = new byte[width];
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
imageInput.readFully(row, 0, width);
|
||||
int rowPos = 0;
|
||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
|
||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
||||
pBitmap.bits[pos++] = row[rowPos++] & 0xFF;
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pWidth Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 1)
|
||||
* @return padded width
|
||||
*/
|
||||
private static int adjustToPadding(final int pWidth) {
|
||||
if ((pWidth & 0x03) != 0) {
|
||||
return (pWidth & ~0x03) + 4;
|
||||
}
|
||||
return pWidth;
|
||||
}
|
||||
|
||||
private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
|
||||
// TODO: No idea if this actually works..
|
||||
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
||||
|
||||
// Will create TYPE_USHORT_555;
|
||||
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
||||
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
||||
WritableRaster raster = Raster.createPackedRaster(
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
|
||||
);
|
||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
imageInput.readFully(pixels, offset, pBitmap.getWidth());
|
||||
|
||||
|
||||
// Skip to 32 bit boundary
|
||||
if (pBitmap.getWidth() % 2 != 0) {
|
||||
imageInput.readShort();
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
||||
byte[] pixels = new byte[pBitmap.getWidth() * pBitmap.getHeight() * 3];
|
||||
|
||||
// Create TYPE_3BYTE_BGR
|
||||
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
||||
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
int[] nBits = {8, 8, 8};
|
||||
int[] bOffs = {2, 1, 0};
|
||||
ComponentColorModel cm = new ComponentColorModel(
|
||||
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||
);
|
||||
|
||||
WritableRaster raster = Raster.createInterleavedRaster(
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
|
||||
);
|
||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
imageInput.readFully(pixels, offset, pBitmap.getWidth() * 3);
|
||||
|
||||
// TODO: Possibly read padding byte here!
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
||||
int[] pixels = new int[pBitmap.getWidth() * pBitmap.getHeight()];
|
||||
|
||||
// Will create TYPE_INT_ARGB
|
||||
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
|
||||
DataBuffer buffer = new DataBufferInt(pixels, pixels.length);
|
||||
WritableRaster raster = Raster.createPackedRaster(
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
|
||||
);
|
||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
imageInput.readFully(pixels, offset, pBitmap.getWidth());
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private Directory getDirectory() throws IOException {
|
||||
assertInput();
|
||||
|
||||
if (directory == null) {
|
||||
readFileHeader();
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private void readFileHeader() throws IOException {
|
||||
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
// Read file header
|
||||
imageInput.readUnsignedShort(); // Reserved
|
||||
|
||||
// Should be same as type as the provider
|
||||
int type = imageInput.readUnsignedShort();
|
||||
int imageCount = imageInput.readUnsignedShort();
|
||||
|
||||
// Read directory
|
||||
directory = Directory.read(type, imageCount, imageInput);
|
||||
}
|
||||
|
||||
final DirectoryEntry getEntry(final int pImageIndex) throws IOException {
|
||||
Directory directory = getDirectory();
|
||||
if (pImageIndex < 0 || pImageIndex >= directory.count()) {
|
||||
throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", pImageIndex, directory.count()));
|
||||
}
|
||||
|
||||
return directory.getEntry(pImageIndex);
|
||||
}
|
||||
|
||||
/// Test code below, ignore.. :-)
|
||||
public static void main(final String[] pArgs) throws IOException {
|
||||
if (pArgs.length == 0) {
|
||||
System.err.println("Please specify the icon file name");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
String title = new File(pArgs[0]).getName();
|
||||
JFrame frame = createWindow(title);
|
||||
JPanel root = new JPanel(new FlowLayout());
|
||||
JScrollPane scroll =
|
||||
new JScrollPane(root, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
scroll.setBorder(BorderFactory.createEmptyBorder());
|
||||
frame.setContentPane(scroll);
|
||||
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("ico");
|
||||
if (!readers.hasNext()) {
|
||||
System.err.println("No reader for format 'ico' found");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
ImageReader reader = readers.next();
|
||||
|
||||
for (String arg : pArgs) {
|
||||
JPanel panel = new JPanel(null);
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
readImagesInFile(arg, reader, panel);
|
||||
root.add(panel);
|
||||
}
|
||||
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
private static void readImagesInFile(String pFileName, ImageReader pReader, final Container pContainer) throws IOException {
|
||||
File file = new File(pFileName);
|
||||
if (!file.isFile()) {
|
||||
System.err.println(pFileName + " not found, or is no file");
|
||||
}
|
||||
|
||||
pReader.setInput(ImageIO.createImageInputStream(file));
|
||||
int imageCount = pReader.getNumImages(true);
|
||||
for (int i = 0; i < imageCount; i++) {
|
||||
try {
|
||||
addImage(pContainer, pReader, i);
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.err.println("FileName: " + pFileName);
|
||||
System.err.println("Icon: " + i);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static JFrame createWindow(final String pTitle) {
|
||||
JFrame frame = new JFrame(pTitle);
|
||||
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
return frame;
|
||||
}
|
||||
|
||||
private static void addImage(final Container pParent, final ImageReader pReader, final int pImageNo) throws IOException {
|
||||
final JButton button = new JButton();
|
||||
|
||||
BufferedImage image = pReader.read(pImageNo);
|
||||
button.setIcon(new ImageIcon(image) {
|
||||
TexturePaint texture;
|
||||
|
||||
private void createTexture(final GraphicsConfiguration pGraphicsConfiguration) {
|
||||
BufferedImage pattern = pGraphicsConfiguration.createCompatibleImage(20, 20);
|
||||
Graphics2D g = pattern.createGraphics();
|
||||
try {
|
||||
g.setColor(Color.LIGHT_GRAY);
|
||||
g.fillRect(0, 0, pattern.getWidth(), pattern.getHeight());
|
||||
g.setColor(Color.GRAY);
|
||||
g.fillRect(0, 0, pattern.getWidth() / 2, pattern.getHeight() / 2);
|
||||
g.fillRect(pattern.getWidth() / 2, pattern.getHeight() / 2, pattern.getWidth() / 2, pattern.getHeight() / 2);
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
texture = new TexturePaint(pattern, new Rectangle(pattern.getWidth(), pattern.getHeight()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
if (texture == null) {
|
||||
createTexture(c.getGraphicsConfiguration());
|
||||
}
|
||||
|
||||
Graphics2D gr = (Graphics2D) g;
|
||||
gr.setPaint(texture);
|
||||
gr.fillRect(x, y, getIconWidth(), getIconHeight());
|
||||
super.paintIcon(c, g, x, y);
|
||||
}
|
||||
});
|
||||
|
||||
button.setText("" + image.getWidth() + "x" +
|
||||
image.getHeight() + ": "
|
||||
+ ((image.getColorModel() instanceof IndexColorModel) ?
|
||||
"" + ((IndexColorModel) image.getColorModel()).getMapSize() :
|
||||
"TrueColor"));
|
||||
|
||||
pParent.add(button);
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -41,10 +41,10 @@ import java.util.List;
|
||||
* @version $Id: Directory.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
class Directory {
|
||||
private final List<DirectoryEntry> mEntries;
|
||||
private final List<DirectoryEntry> entries;
|
||||
|
||||
private Directory(int pImageCount) {
|
||||
mEntries = Arrays.asList(new DirectoryEntry[pImageCount]);
|
||||
entries = Arrays.asList(new DirectoryEntry[pImageCount]);
|
||||
}
|
||||
|
||||
public static Directory read(final int pType, final int pImageCount, final DataInput pStream) throws IOException {
|
||||
@@ -54,21 +54,21 @@ class Directory {
|
||||
}
|
||||
|
||||
private void readEntries(final int pType, final DataInput pStream) throws IOException {
|
||||
for (int i = 0; i < mEntries.size(); i++) {
|
||||
mEntries.set(i, DirectoryEntry.read(pType, pStream));
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
entries.set(i, DirectoryEntry.read(pType, pStream));
|
||||
}
|
||||
}
|
||||
|
||||
public DirectoryEntry getEntry(final int pEntryIndex) {
|
||||
return mEntries.get(pEntryIndex);
|
||||
return entries.get(pEntryIndex);
|
||||
}
|
||||
|
||||
public int count() {
|
||||
return mEntries.size();
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s%s", getClass().getSimpleName(), mEntries);
|
||||
return String.format("%s%s", getClass().getSimpleName(), entries);
|
||||
}
|
||||
}
|
||||
|
||||
+29
-30
@@ -31,7 +31,6 @@ package com.twelvemonkeys.imageio.plugins.ico;
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
@@ -43,13 +42,13 @@ import java.awt.*;
|
||||
* @see <a href="http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)#Directory">Wikipedia</a>
|
||||
*/
|
||||
abstract class DirectoryEntry {
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mColorCount;
|
||||
int mPlanes;
|
||||
int mBitCount;
|
||||
private int mSize;
|
||||
private int mOffset;
|
||||
private int width;
|
||||
private int height;
|
||||
private int colorCount;
|
||||
int planes;
|
||||
int bitCount;
|
||||
private int size;
|
||||
private int offset;
|
||||
|
||||
private DirectoryEntry() {
|
||||
}
|
||||
@@ -79,81 +78,81 @@ abstract class DirectoryEntry {
|
||||
protected void read(final DataInput pStream) throws IOException {
|
||||
// Width/height = 0, means 256
|
||||
int w = pStream.readUnsignedByte();
|
||||
mWidth = w == 0 ? 256 : w;
|
||||
width = w == 0 ? 256 : w;
|
||||
int h = pStream.readUnsignedByte();
|
||||
mHeight = h == 0 ? 256 : h;
|
||||
height = h == 0 ? 256 : h;
|
||||
|
||||
// Color count = 0, means 256 or more colors
|
||||
mColorCount = pStream.readUnsignedByte();
|
||||
colorCount = pStream.readUnsignedByte();
|
||||
|
||||
// Ignore. Should be 0, but .NET (System.Drawing.Icon.Save) sets this value to 255, according to Wikipedia
|
||||
pStream.readUnsignedByte();
|
||||
|
||||
mPlanes = pStream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR
|
||||
mBitCount = pStream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR
|
||||
planes = pStream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR
|
||||
bitCount = pStream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR
|
||||
|
||||
// Size of bitmap in bytes
|
||||
mSize = pStream.readInt();
|
||||
mOffset = pStream.readInt();
|
||||
size = pStream.readInt();
|
||||
offset = pStream.readInt();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"%s: width: %d, height: %d, colors: %d, planes: %d, bit count: %d, size: %d, offset: %d",
|
||||
getClass().getSimpleName(),
|
||||
mWidth, mHeight, mColorCount, mPlanes, mBitCount, mSize, mOffset
|
||||
width, height, colorCount, planes, bitCount, size, offset
|
||||
);
|
||||
}
|
||||
|
||||
public int getBitCount() {
|
||||
return mBitCount;
|
||||
return bitCount;
|
||||
}
|
||||
|
||||
public int getColorCount() {
|
||||
return mColorCount;
|
||||
return colorCount;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return mHeight;
|
||||
return height;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return mOffset;
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getPlanes() {
|
||||
return mPlanes;
|
||||
return planes;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return mSize;
|
||||
return size;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return mWidth;
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cursor directory entry.
|
||||
*/
|
||||
static class CUREntry extends DirectoryEntry {
|
||||
private int mXHotspot;
|
||||
private int mYHotspot;
|
||||
private int xHotspot;
|
||||
private int yHotspot;
|
||||
|
||||
@Override
|
||||
protected void read(final DataInput pStream) throws IOException {
|
||||
super.read(pStream);
|
||||
|
||||
// NOTE: This is a hack...
|
||||
mXHotspot = mPlanes;
|
||||
mYHotspot = mBitCount;
|
||||
xHotspot = planes;
|
||||
yHotspot = bitCount;
|
||||
|
||||
mPlanes = 1; // Always 1 for all BMP types
|
||||
mBitCount = 0;
|
||||
planes = 1; // Always 1 for all BMP types
|
||||
bitCount = 0;
|
||||
}
|
||||
|
||||
public Point getHotspot() {
|
||||
return new Point(mXHotspot, mYHotspot);
|
||||
return new Point(xHotspot, yHotspot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-665
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -28,682 +28,24 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.ico;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
||||
import com.twelvemonkeys.util.WeakWeakMap;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ImageReader for Microsoft Windows ICO (icon) format.
|
||||
* 1, 4, 8 bit palette support with bitmask transparency, and 16, 24 and 32 bit
|
||||
* true color support with alpha. Also supports Windows Vista PNG encoded icons.
|
||||
* <p/>
|
||||
* ImageReader for Microsoft Windows CUR (cursor) format.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: ICOImageReader.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: CURImageReader.java,v 1.0 Apr 20, 2009 11:54:28 AM haraldk Exp$
|
||||
*
|
||||
* @see <a href="http://en.wikipedia.org/wiki/BMP_file_format">BMP file format (Wikipedia)</a>
|
||||
* @see <a href="http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)">ICO file format (Wikipedia)</a>
|
||||
* @see CURImageReader
|
||||
*/
|
||||
// SEE http://en.wikipedia.org/wiki/ICO_(icon_image_file_format)
|
||||
// TODO: Decide wether DirectoryEntry or DIBHeader should be primary source for color count/bit count
|
||||
// TODO: Support loading icons from DLLs, see
|
||||
// <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwui/html/msdn_icons.asp">MSDN</a>
|
||||
// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
|
||||
public class ICOImageReader extends ImageReaderBase {
|
||||
// TODO: Consider moving the reading to inner classes (subclasses of BitmapDescriptor)
|
||||
private Directory mDirectory;
|
||||
|
||||
// TODO: Review these, make sure we don't have a memory leak
|
||||
private Map<DirectoryEntry, DIBHeader> mHeaders = new WeakHashMap<DirectoryEntry, DIBHeader>();
|
||||
private Map<DirectoryEntry, BitmapDescriptor> mDescriptors = new WeakWeakMap<DirectoryEntry, BitmapDescriptor>();
|
||||
|
||||
private ImageReader mPNGImageReader;
|
||||
|
||||
public final class ICOImageReader extends DIBImageReader {
|
||||
public ICOImageReader() {
|
||||
this(DIB.TYPE_ICO);
|
||||
}
|
||||
|
||||
ICOImageReader(final int pType) {
|
||||
this(createProviderForConstructor(pType));
|
||||
super(new ICOImageReaderSpi());
|
||||
}
|
||||
|
||||
protected ICOImageReader(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
private static ImageReaderSpi createProviderForConstructor(final int pType) {
|
||||
switch (pType) {
|
||||
case DIB.TYPE_ICO:
|
||||
return new ICOImageReaderSpi();
|
||||
case DIB.TYPE_CUR:
|
||||
return new CURImageReaderSpi();
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unsupported ICO/CUR type: %d", pType));
|
||||
}
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
mDirectory = null;
|
||||
|
||||
mHeaders.clear();
|
||||
mDescriptors.clear();
|
||||
|
||||
if (mPNGImageReader != null) {
|
||||
mPNGImageReader.dispose();
|
||||
mPNGImageReader = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
||||
DirectoryEntry entry = getEntry(pImageIndex);
|
||||
|
||||
// NOTE: Delegate to PNG reader
|
||||
if (isPNG(entry)) {
|
||||
return getImageTypesPNG(entry);
|
||||
}
|
||||
|
||||
List<ImageTypeSpecifier> types = new ArrayList<ImageTypeSpecifier>();
|
||||
DIBHeader header = getHeader(entry);
|
||||
|
||||
// Use data from header to create specifier
|
||||
ImageTypeSpecifier specifier;
|
||||
switch (header.getBitCount()) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
// TODO: This is slightly QnD...
|
||||
int offset = entry.getOffset() + header.getSize();
|
||||
if (offset != mImageInput.getStreamPosition()) {
|
||||
mImageInput.seek(offset);
|
||||
}
|
||||
BitmapIndexed indexed = new BitmapIndexed(entry, header);
|
||||
readColorMap(indexed);
|
||||
specifier = IndexedImageTypeSpecifier.createFromIndexColorModel(indexed.createColorModel());
|
||||
break;
|
||||
case 16:
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
|
||||
break;
|
||||
case 24:
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
break;
|
||||
case 32:
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
|
||||
break;
|
||||
default:
|
||||
throw new IIOException(String.format("Unknown bit depth: %d", header.getBitCount()));
|
||||
}
|
||||
|
||||
types.add(specifier);
|
||||
|
||||
return types.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumImages(final boolean pAllowSearch) throws IOException {
|
||||
return getDirectory().count();
|
||||
}
|
||||
|
||||
public int getWidth(final int pImageIndex) throws IOException {
|
||||
return getEntry(pImageIndex).getWidth();
|
||||
}
|
||||
|
||||
public int getHeight(final int pImageIndex) throws IOException {
|
||||
return getEntry(pImageIndex).getHeight();
|
||||
}
|
||||
|
||||
public BufferedImage read(final int pImageIndex, final ImageReadParam pParam) throws IOException {
|
||||
checkBounds(pImageIndex);
|
||||
|
||||
processImageStarted(pImageIndex);
|
||||
|
||||
DirectoryEntry entry = getEntry(pImageIndex);
|
||||
|
||||
BufferedImage destination;
|
||||
|
||||
if (isPNG(entry)) {
|
||||
// NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header...
|
||||
destination = readPNG(entry, pParam);
|
||||
}
|
||||
else {
|
||||
// NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later,
|
||||
// to allow for storing the cursor hotspot for CUR images
|
||||
destination = hasExplicitDestination(pParam) ?
|
||||
getDestination(pParam, getImageTypes(pImageIndex), getWidth(pImageIndex), getHeight(pImageIndex)) :
|
||||
null;
|
||||
|
||||
BufferedImage image = readBitmap(entry);
|
||||
|
||||
// TODO: Handle AOI and subsampling inline, probably not of big importance...
|
||||
if (pParam != null) {
|
||||
image = fakeAOI(image, pParam);
|
||||
image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
|
||||
}
|
||||
|
||||
if (destination == null) {
|
||||
// This is okay, as long as the client did not request explicit destination image/type
|
||||
destination = image;
|
||||
}
|
||||
else {
|
||||
Graphics2D g = destination.createGraphics();
|
||||
try {
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.drawImage(image, 0, 0, null);
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(100);
|
||||
processImageComplete();
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private boolean hasExplicitDestination(final ImageReadParam pParam) {
|
||||
return (pParam != null && (pParam.getDestination() != null || pParam.getDestinationType() != null || pParam.getDestinationOffset() != null));
|
||||
}
|
||||
|
||||
private boolean isPNG(final DirectoryEntry pEntry) throws IOException {
|
||||
long magic;
|
||||
|
||||
mImageInput.seek(pEntry.getOffset());
|
||||
mImageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try {
|
||||
magic = mImageInput.readLong();
|
||||
}
|
||||
finally {
|
||||
mImageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
return magic == DIB.PNG_MAGIC;
|
||||
}
|
||||
|
||||
private BufferedImage readPNG(final DirectoryEntry pEntry, final ImageReadParam pParam) throws IOException {
|
||||
// TODO: Consider delegating listener calls
|
||||
return initPNGReader(pEntry).read(0, pParam);
|
||||
}
|
||||
|
||||
private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry pEntry) throws IOException {
|
||||
return initPNGReader(pEntry).getImageTypes(0);
|
||||
}
|
||||
|
||||
private ImageReader initPNGReader(final DirectoryEntry pEntry) throws IOException {
|
||||
ImageReader pngReader = getPNGReader();
|
||||
|
||||
mImageInput.seek(pEntry.getOffset());
|
||||
InputStream inputStream = IIOUtil.createStreamAdapter(mImageInput, pEntry.getSize());
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(inputStream);
|
||||
|
||||
// NOTE: Will throw IOException on later reads if input is not PNG
|
||||
pngReader.setInput(stream);
|
||||
|
||||
return pngReader;
|
||||
}
|
||||
|
||||
private ImageReader getPNGReader() throws IIOException {
|
||||
// TODO: Prefer Sun's std JDK PNGImagerReader, because it has known behaviour?
|
||||
if (mPNGImageReader == null) {
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("PNG");
|
||||
|
||||
if (readers.hasNext()) {
|
||||
mPNGImageReader = readers.next();
|
||||
}
|
||||
else {
|
||||
throw new IIOException("No PNGImageReader found using ImageIO, can't read PNG encoded ICO format.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
mPNGImageReader.reset();
|
||||
}
|
||||
|
||||
return mPNGImageReader;
|
||||
}
|
||||
|
||||
private DIBHeader getHeader(final DirectoryEntry pEntry) throws IOException {
|
||||
if (!mHeaders.containsKey(pEntry)) {
|
||||
mImageInput.seek(pEntry.getOffset());
|
||||
DIBHeader header = DIBHeader.read(mImageInput);
|
||||
mHeaders.put(pEntry, header);
|
||||
}
|
||||
|
||||
return mHeaders.get(pEntry);
|
||||
}
|
||||
|
||||
private BufferedImage readBitmap(final DirectoryEntry pEntry) throws IOException {
|
||||
// TODO: Get rid of the caching, as the images are mutable
|
||||
BitmapDescriptor descriptor = mDescriptors.get(pEntry);
|
||||
|
||||
if (descriptor == null || !mDescriptors.containsKey(pEntry)) {
|
||||
DIBHeader header = getHeader(pEntry);
|
||||
|
||||
int offset = pEntry.getOffset() + header.getSize();
|
||||
if (offset != mImageInput.getStreamPosition()) {
|
||||
mImageInput.seek(offset);
|
||||
}
|
||||
|
||||
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
|
||||
if (header.getCompression() != 0) {
|
||||
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported compression: %d", header.getCompression()));
|
||||
}
|
||||
else {
|
||||
int bitCount = header.getBitCount();
|
||||
|
||||
switch (bitCount) {
|
||||
// Palette style
|
||||
case 1:
|
||||
case 4:
|
||||
case 8:
|
||||
descriptor = new BitmapIndexed(pEntry, header);
|
||||
readBitmapIndexed((BitmapIndexed) descriptor);
|
||||
break;
|
||||
// RGB style
|
||||
case 16:
|
||||
descriptor = new BitmapRGB(pEntry, header);
|
||||
readBitmap16(descriptor);
|
||||
break;
|
||||
case 24:
|
||||
descriptor = new BitmapRGB(pEntry, header);
|
||||
readBitmap24(descriptor);
|
||||
break;
|
||||
case 32:
|
||||
descriptor = new BitmapRGB(pEntry, header);
|
||||
readBitmap32(descriptor);
|
||||
break;
|
||||
|
||||
default:
|
||||
descriptor = new BitmapUnsupported(pEntry, String.format("Unsupported bit count %d", bitCount));
|
||||
}
|
||||
}
|
||||
|
||||
mDescriptors.put(pEntry, descriptor);
|
||||
}
|
||||
|
||||
return descriptor.getImage();
|
||||
}
|
||||
|
||||
private void readBitmapIndexed(final BitmapIndexed pBitmap) throws IOException {
|
||||
readColorMap(pBitmap);
|
||||
|
||||
switch (pBitmap.getBitCount()) {
|
||||
case 1:
|
||||
readBitmapIndexed1(pBitmap, false);
|
||||
break;
|
||||
case 4:
|
||||
readBitmapIndexed4(pBitmap);
|
||||
break;
|
||||
case 8:
|
||||
readBitmapIndexed8(pBitmap);
|
||||
break;
|
||||
}
|
||||
|
||||
BitmapMask mask = new BitmapMask(pBitmap.mEntry, pBitmap.mHeader);
|
||||
readBitmapIndexed1(mask.mMask, true);
|
||||
pBitmap.setMask(mask);
|
||||
}
|
||||
|
||||
private void readColorMap(final BitmapIndexed pBitmap) throws IOException {
|
||||
int colorCount = pBitmap.getColorCount();
|
||||
|
||||
for (int i = 0; i < colorCount; i++) {
|
||||
// aRGB (a is "Reserved")
|
||||
pBitmap.mColors[i] = (mImageInput.readInt() & 0xffffff) | 0xff000000;
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException {
|
||||
int width = adjustToPadding(pBitmap.getWidth() >> 3);
|
||||
byte[] row = new byte[width];
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
mImageInput.readFully(row, 0, width);
|
||||
int rowPos = 0;
|
||||
int xOrVal = 0x80;
|
||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
|
||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
||||
pBitmap.mBits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF;
|
||||
|
||||
if (xOrVal == 1) {
|
||||
xOrVal = 0x80;
|
||||
rowPos++;
|
||||
}
|
||||
else {
|
||||
xOrVal >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: If we are reading the mask, we don't abort or progress
|
||||
if (!pAsMask) {
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException {
|
||||
int width = adjustToPadding(pBitmap.getWidth() >> 1);
|
||||
byte[] row = new byte[width];
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
mImageInput.readFully(row, 0, width);
|
||||
int rowPos = 0;
|
||||
boolean high4 = true;
|
||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
|
||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
||||
int value;
|
||||
|
||||
if (high4) {
|
||||
value = (row[rowPos] & 0xF0) >> 4;
|
||||
}
|
||||
else {
|
||||
value = row[rowPos] & 0x0F;
|
||||
rowPos++;
|
||||
}
|
||||
|
||||
pBitmap.mBits[pos++] = value & 0xFF;
|
||||
high4 = !high4;
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmapIndexed8(final BitmapIndexed pBitmap) throws IOException {
|
||||
int width = adjustToPadding(pBitmap.getWidth());
|
||||
|
||||
byte[] row = new byte[width];
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
mImageInput.readFully(row, 0, width);
|
||||
int rowPos = 0;
|
||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
|
||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
||||
pBitmap.mBits[pos++] = row[rowPos++] & 0xFF;
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pWidth Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 1)
|
||||
* @return padded width
|
||||
*/
|
||||
private static int adjustToPadding(final int pWidth) {
|
||||
if ((pWidth & 0x03) != 0) {
|
||||
return (pWidth & ~0x03) + 4;
|
||||
}
|
||||
return pWidth;
|
||||
}
|
||||
|
||||
private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
|
||||
// TODO: No idea if this actually works..
|
||||
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
||||
|
||||
// Will create TYPE_USHORT_555;
|
||||
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
||||
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
|
||||
WritableRaster raster = Raster.createPackedRaster(
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
|
||||
);
|
||||
pBitmap.mImage = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
mImageInput.readFully(pixels, offset, pBitmap.getWidth());
|
||||
|
||||
|
||||
// Skip to 32 bit boundary
|
||||
if (pBitmap.getWidth() % 2 != 0) {
|
||||
mImageInput.readShort();
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
||||
byte[] pixels = new byte[pBitmap.getWidth() * pBitmap.getHeight() * 3];
|
||||
|
||||
// Create TYPE_3BYTE_BGR
|
||||
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
||||
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
int[] nBits = {8, 8, 8};
|
||||
int[] bOffs = {2, 1, 0};
|
||||
ComponentColorModel cm = new ComponentColorModel(
|
||||
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||
);
|
||||
|
||||
WritableRaster raster = Raster.createInterleavedRaster(
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
|
||||
);
|
||||
pBitmap.mImage = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
mImageInput.readFully(pixels, offset, pBitmap.getWidth() * 3);
|
||||
|
||||
// TODO: Possibly read padding byte here!
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
||||
int[] pixels = new int[pBitmap.getWidth() * pBitmap.getHeight()];
|
||||
|
||||
// Will create TYPE_INT_ARGB
|
||||
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
|
||||
DataBuffer buffer = new DataBufferInt(pixels, pixels.length);
|
||||
WritableRaster raster = Raster.createPackedRaster(
|
||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
|
||||
);
|
||||
pBitmap.mImage = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
|
||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
||||
mImageInput.readFully(pixels, offset, pBitmap.getWidth());
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private Directory getDirectory() throws IOException {
|
||||
assertInput();
|
||||
|
||||
if (mDirectory == null) {
|
||||
readFileHeader();
|
||||
}
|
||||
|
||||
return mDirectory;
|
||||
}
|
||||
|
||||
private void readFileHeader() throws IOException {
|
||||
mImageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
// Read file header
|
||||
mImageInput.readUnsignedShort(); // Reserved
|
||||
|
||||
// Should be same as type as the provider
|
||||
int type = mImageInput.readUnsignedShort();
|
||||
int imageCount = mImageInput.readUnsignedShort();
|
||||
|
||||
// Read directory
|
||||
mDirectory = Directory.read(type, imageCount, mImageInput);
|
||||
}
|
||||
|
||||
final DirectoryEntry getEntry(final int pImageIndex) throws IOException {
|
||||
Directory directory = getDirectory();
|
||||
if (pImageIndex < 0 || pImageIndex >= directory.count()) {
|
||||
throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", pImageIndex, directory.count()));
|
||||
}
|
||||
|
||||
return directory.getEntry(pImageIndex);
|
||||
}
|
||||
|
||||
/// Test code below, ignore.. :-)
|
||||
public static void main(final String[] pArgs) throws IOException {
|
||||
if (pArgs.length == 0) {
|
||||
System.err.println("Please specify the icon file name");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
String title = new File(pArgs[0]).getName();
|
||||
JFrame frame = createWindow(title);
|
||||
JPanel root = new JPanel(new FlowLayout());
|
||||
JScrollPane scroll =
|
||||
new JScrollPane(root, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
scroll.setBorder(BorderFactory.createEmptyBorder());
|
||||
frame.setContentPane(scroll);
|
||||
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("ico");
|
||||
if (!readers.hasNext()) {
|
||||
System.err.println("No reader for format 'ico' found");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
ImageReader reader = readers.next();
|
||||
|
||||
for (String arg : pArgs) {
|
||||
JPanel panel = new JPanel(null);
|
||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||
readImagesInFile(arg, reader, panel);
|
||||
root.add(panel);
|
||||
}
|
||||
|
||||
frame.pack();
|
||||
frame.setVisible(true);
|
||||
}
|
||||
|
||||
private static void readImagesInFile(String pFileName, ImageReader pReader, final Container pContainer) throws IOException {
|
||||
File file = new File(pFileName);
|
||||
if (!file.isFile()) {
|
||||
System.err.println(pFileName + " not found, or is no file");
|
||||
}
|
||||
|
||||
pReader.setInput(ImageIO.createImageInputStream(file));
|
||||
int imageCount = pReader.getNumImages(true);
|
||||
for (int i = 0; i < imageCount; i++) {
|
||||
try {
|
||||
addImage(pContainer, pReader, i);
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.err.println("FileName: " + pFileName);
|
||||
System.err.println("Icon: " + i);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static JFrame createWindow(final String pTitle) {
|
||||
JFrame frame = new JFrame(pTitle);
|
||||
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
System.exit(0);
|
||||
}
|
||||
});
|
||||
return frame;
|
||||
}
|
||||
|
||||
private static void addImage(final Container pParent, final ImageReader pReader, final int pImageNo) throws IOException {
|
||||
final JButton button = new JButton();
|
||||
BufferedImage image = pReader.read(pImageNo);
|
||||
button.setIcon(new ImageIcon(image) {
|
||||
TexturePaint mTexture;
|
||||
|
||||
private void createTexture(final GraphicsConfiguration pGraphicsConfiguration) {
|
||||
BufferedImage pattern = pGraphicsConfiguration.createCompatibleImage(20, 20);
|
||||
Graphics2D g = pattern.createGraphics();
|
||||
try {
|
||||
g.setColor(Color.LIGHT_GRAY);
|
||||
g.fillRect(0, 0, pattern.getWidth(), pattern.getHeight());
|
||||
g.setColor(Color.GRAY);
|
||||
g.fillRect(0, 0, pattern.getWidth() / 2, pattern.getHeight() / 2);
|
||||
g.fillRect(pattern.getWidth() / 2, pattern.getHeight() / 2, pattern.getWidth() / 2, pattern.getHeight() / 2);
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
mTexture = new TexturePaint(pattern, new Rectangle(pattern.getWidth(), pattern.getHeight()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||
if (mTexture == null) {
|
||||
createTexture(c.getGraphicsConfiguration());
|
||||
}
|
||||
Graphics2D gr = (Graphics2D) g;
|
||||
gr.setPaint(mTexture);
|
||||
gr.fillRect(x, y, getIconWidth(), getIconHeight());
|
||||
super.paintIcon(c, g, x, y);
|
||||
}
|
||||
});
|
||||
button.setText("" + image.getWidth() + "x" +
|
||||
image.getHeight() + ": "
|
||||
+ ((image.getColorModel() instanceof IndexColorModel) ?
|
||||
"" + ((IndexColorModel) image.getColorModel()).getMapSize() :
|
||||
"TrueColor"));
|
||||
pParent.add(button);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ import java.util.Locale;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: ICOImageReaderSpi.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||
*/
|
||||
public class ICOImageReaderSpi extends ImageReaderSpi {
|
||||
public final class ICOImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
public ICOImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(ICOImageReaderSpi.class));
|
||||
|
||||
+29
-11
@@ -1,6 +1,8 @@
|
||||
package com.twelvemonkeys.imageio.plugins.ico;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
@@ -10,6 +12,8 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* CURImageReaderTestCase
|
||||
*
|
||||
@@ -55,35 +59,42 @@ public class CURImageReaderTestCase extends ImageReaderAbstractTestCase<CURImage
|
||||
reader.setInput(pTestData.getInputStream());
|
||||
|
||||
BufferedImage image = reader.read(0, pParam);
|
||||
Object hotspot = image.getProperty("cursor_hotspot");
|
||||
|
||||
if (hotspot == Image.UndefinedProperty) {
|
||||
hotspot = reader.getHotSpot(0);
|
||||
// We can only be sure the hotspot is defined, if no param, but if defined, it must be correct
|
||||
Object hotspot = image.getProperty("cursor_hotspot");
|
||||
if (hotspot != Image.UndefinedProperty || pParam == null) {
|
||||
|
||||
// Typically never happens, because of weirdness with UndefinedProperty
|
||||
assertNotNull("Hotspot for cursor not present", hotspot);
|
||||
|
||||
// Image weirdness
|
||||
assertTrue("Hotspot for cursor undefined (java.awt.Image.UndefinedProperty)", Image.UndefinedProperty != hotspot);
|
||||
|
||||
assertTrue(String.format("Hotspot not a java.awt.Point: %s", hotspot.getClass()), hotspot instanceof Point);
|
||||
assertEquals(pExpected, hotspot);
|
||||
}
|
||||
|
||||
// Typically never happens, because of weirdness
|
||||
assertNotNull("Hotspot for cursor not present", hotspot);
|
||||
|
||||
// Image weirdness
|
||||
assertTrue("Hotspot for cursor undefined (java.awt.Image.UndefinedProperty)", Image.UndefinedProperty != hotspot);
|
||||
|
||||
assertTrue(String.format("Hotspot not a java.awt.Point: %s", hotspot.getClass()), hotspot instanceof Point);
|
||||
assertEquals(pExpected, hotspot);
|
||||
assertNotNull("Hotspot for cursor not present", reader.getHotSpot(0));
|
||||
assertEquals(pExpected, reader.getHotSpot(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandHotspot() throws IOException {
|
||||
assertHotSpot(getTestData().get(0), null, new Point(15, 15));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZoomHotspot() throws IOException {
|
||||
assertHotSpot(getTestData().get(1), null, new Point(13, 11));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandHotspotWithParam() throws IOException {
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
assertHotSpot(getTestData().get(0), param, new Point(15, 15));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandHotspotExplicitDestination() throws IOException {
|
||||
CURImageReader reader = createReader();
|
||||
reader.setInput(getTestData().get(0).getInputStream());
|
||||
@@ -100,4 +111,11 @@ public class CURImageReaderTestCase extends ImageReaderAbstractTestCase<CURImage
|
||||
}
|
||||
|
||||
// TODO: Test cursor is transparent
|
||||
|
||||
@Test
|
||||
@Ignore("Known issue")
|
||||
@Override
|
||||
public void testNotBadCaching() throws IOException {
|
||||
super.testNotBadCaching();
|
||||
}
|
||||
}
|
||||
+10
@@ -1,9 +1,12 @@
|
||||
package com.twelvemonkeys.imageio.plugins.ico;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -63,4 +66,11 @@ public class ICOImageReaderTestCase extends ImageReaderAbstractTestCase<ICOImage
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/vnd.microsoft.icon", "image/ico", "image/x-icon");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Known issue")
|
||||
@Override
|
||||
public void testNotBadCaching() throws IOException {
|
||||
super.testNotBadCaching();
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Parts of this code is based on ilbmtoppm.c
|
||||
*
|
||||
* Copyright (C) 1989 by Jef Poskanzer.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software and its
|
||||
* documentation for any purpose and without fee is hereby granted, provided
|
||||
* that the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. This software is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*
|
||||
* Multipalette-support by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* AbstractMultiPaletteChunk
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AbstractMultiPaletteChunk.java,v 1.0 30.03.12 15:57 haraldk Exp$
|
||||
*/
|
||||
abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalette {
|
||||
/* scale factor maxval 15 -> maxval 255 */
|
||||
static final int FACTOR_4BIT = 17;
|
||||
|
||||
protected MutableIndexColorModel.PaletteChange[] initialChanges;
|
||||
protected MutableIndexColorModel.PaletteChange[][] changes;
|
||||
|
||||
protected int lastRow;
|
||||
protected WeakReference<IndexColorModel> originalPalette;
|
||||
protected MutableIndexColorModel mutablePalette;
|
||||
|
||||
public AbstractMultiPaletteChunk(int pChunkId, int pChunkLength) {
|
||||
super(pChunkId, pChunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
if (chunkId == IFF.CHUNK_SHAM) {
|
||||
pInput.readUnsignedShort(); // Version, typically 0, skipped
|
||||
}
|
||||
|
||||
int rows = chunkLength / 32; /* sizeof(word) * 16 */
|
||||
|
||||
changes = new MutableIndexColorModel.PaletteChange[rows][];
|
||||
|
||||
for (int row = 0; row < rows; row++) {
|
||||
changes[row] = new MutableIndexColorModel.PaletteChange[16];
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
changes[row][i] = new MutableIndexColorModel.PaletteChange();
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++ ) {
|
||||
int data = pInput.readUnsignedShort();
|
||||
|
||||
changes[row][i].index = i;
|
||||
changes[row][i].r = (byte) (((data & 0x0f00) >> 8) * FACTOR_4BIT);
|
||||
changes[row][i].g = (byte) (((data & 0x00f0) >> 4) * FACTOR_4BIT);
|
||||
changes[row][i].b = (byte) (((data & 0x000f) ) * FACTOR_4BIT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void writeChunk(DataOutput pOutput) throws IOException {
|
||||
throw new UnsupportedOperationException("Method writeChunk not implemented");
|
||||
}
|
||||
|
||||
|
||||
public ColorModel getColorModel(final IndexColorModel colorModel, final int rowIndex, final boolean laced) {
|
||||
if (rowIndex < lastRow || mutablePalette == null || originalPalette != null && originalPalette.get() != colorModel) {
|
||||
originalPalette = new WeakReference<IndexColorModel>(colorModel);
|
||||
mutablePalette = new MutableIndexColorModel(colorModel);
|
||||
|
||||
if (initialChanges != null) {
|
||||
mutablePalette.adjustColorMap(initialChanges);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = lastRow + 1; i <= rowIndex; i++) {
|
||||
int row;
|
||||
|
||||
if (laced && skipLaced()) {
|
||||
if (i % 2 != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
row = i / 2;
|
||||
}
|
||||
else {
|
||||
row = i;
|
||||
}
|
||||
|
||||
if (row < changes.length && changes[row] != null) {
|
||||
mutablePalette.adjustColorMap(changes[row]);
|
||||
}
|
||||
}
|
||||
|
||||
return mutablePalette;
|
||||
}
|
||||
|
||||
protected boolean skipLaced() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+60
-62
@@ -40,7 +40,7 @@ import java.io.IOException;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: BMHDChunk.java,v 1.0 28.feb.2006 00:04:32 haku Exp$
|
||||
*/
|
||||
class BMHDChunk extends IFFChunk {
|
||||
final class BMHDChunk extends IFFChunk {
|
||||
//
|
||||
// typedef UBYTE Masking; /* Choice of masking technique. */
|
||||
//
|
||||
@@ -81,100 +81,98 @@ class BMHDChunk extends IFFChunk {
|
||||
// words. The number of words per row is words=((w+15)/16)
|
||||
|
||||
// Dimensions of raster
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
// Source offsets
|
||||
// Hmm.. Consider making these Image.properties?
|
||||
int mXPos;
|
||||
int mYPos;
|
||||
int xPos;
|
||||
int yPos;
|
||||
|
||||
// The number of source bitplanes in the BODY chunk (see below) is stored in
|
||||
// nPlanes. An ILBM with a CMAP but no BODY and nPlanes = 0 is the
|
||||
// recommended way to store a color map.
|
||||
int mBitplanes;
|
||||
int bitplanes;
|
||||
|
||||
int mMaskType;
|
||||
int mCompressionType;
|
||||
int maskType;
|
||||
int compressionType;
|
||||
|
||||
int mTransparentIndex;
|
||||
int transparentIndex;
|
||||
|
||||
// NOTE: Typical values are 10:11 (320 x 200)
|
||||
int mXAspect;
|
||||
int mYAspect;
|
||||
int xAspect;
|
||||
int yAspect;
|
||||
|
||||
// Source page dimension
|
||||
// NOTE: The image may be larger than the page, probably ignore these
|
||||
int mPageWidth;
|
||||
int mPageHeight;
|
||||
int pageWidth;
|
||||
int pageHeight;
|
||||
|
||||
protected BMHDChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_BMHD, pChunkLength);
|
||||
}
|
||||
|
||||
protected BMHDChunk(int pWidth, int pHeight, int pBitplanes,
|
||||
int pMaskType, int pCompressionType,
|
||||
int pTransparentIndex) {
|
||||
protected BMHDChunk(int pWidth, int pHeight, int pBitplanes, int pMaskType, int pCompressionType, int pTransparentIndex) {
|
||||
super(IFF.CHUNK_BMHD, 20);
|
||||
mWidth = pWidth;
|
||||
mHeight = pHeight;
|
||||
mXPos = 0;
|
||||
mYPos = 0;
|
||||
mBitplanes = pBitplanes;
|
||||
mMaskType = pMaskType;
|
||||
mCompressionType = pCompressionType;
|
||||
mTransparentIndex = pTransparentIndex;
|
||||
mXAspect = 1;
|
||||
mYAspect = 1;
|
||||
mPageWidth = Math.min(pWidth, Short.MAX_VALUE); // For some reason, these are signed?
|
||||
mPageHeight = Math.min(pHeight, Short.MAX_VALUE);
|
||||
width = pWidth;
|
||||
height = pHeight;
|
||||
xPos = 0;
|
||||
yPos = 0;
|
||||
bitplanes = pBitplanes;
|
||||
maskType = pMaskType;
|
||||
compressionType = pCompressionType;
|
||||
transparentIndex = pTransparentIndex;
|
||||
xAspect = 1;
|
||||
yAspect = 1;
|
||||
pageWidth = Math.min(pWidth, Short.MAX_VALUE); // For some reason, these are signed?
|
||||
pageHeight = Math.min(pHeight, Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
void readChunk(DataInput pInput) throws IOException {
|
||||
if (mChunkLength != 20) {
|
||||
throw new IIOException("Unknown BMHD chunk length: " + mChunkLength);
|
||||
if (chunkLength != 20) {
|
||||
throw new IIOException("Unknown BMHD chunk length: " + chunkLength);
|
||||
}
|
||||
mWidth = pInput.readUnsignedShort();
|
||||
mHeight = pInput.readUnsignedShort();
|
||||
mXPos = pInput.readShort();
|
||||
mYPos = pInput.readShort();
|
||||
mBitplanes = pInput.readUnsignedByte();
|
||||
mMaskType = pInput.readUnsignedByte();
|
||||
mCompressionType = pInput.readUnsignedByte();
|
||||
width = pInput.readUnsignedShort();
|
||||
height = pInput.readUnsignedShort();
|
||||
xPos = pInput.readShort();
|
||||
yPos = pInput.readShort();
|
||||
bitplanes = pInput.readUnsignedByte();
|
||||
maskType = pInput.readUnsignedByte();
|
||||
compressionType = pInput.readUnsignedByte();
|
||||
pInput.readByte(); // PAD
|
||||
mTransparentIndex = pInput.readUnsignedShort();
|
||||
mXAspect = pInput.readUnsignedByte();
|
||||
mYAspect = pInput.readUnsignedByte();
|
||||
mPageWidth = pInput.readShort();
|
||||
mPageHeight = pInput.readShort();
|
||||
transparentIndex = pInput.readUnsignedShort();
|
||||
xAspect = pInput.readUnsignedByte();
|
||||
yAspect = pInput.readUnsignedByte();
|
||||
pageWidth = pInput.readShort();
|
||||
pageHeight = pInput.readShort();
|
||||
}
|
||||
|
||||
void writeChunk(DataOutput pOutput) throws IOException {
|
||||
pOutput.writeInt(mChunkId);
|
||||
pOutput.writeInt(mChunkLength);
|
||||
pOutput.writeInt(chunkId);
|
||||
pOutput.writeInt(chunkLength);
|
||||
|
||||
pOutput.writeShort(mWidth);
|
||||
pOutput.writeShort(mHeight);
|
||||
pOutput.writeShort(mXPos);
|
||||
pOutput.writeShort(mYPos);
|
||||
pOutput.writeByte(mBitplanes);
|
||||
pOutput.writeByte(mMaskType);
|
||||
pOutput.writeByte(mCompressionType);
|
||||
pOutput.writeShort(width);
|
||||
pOutput.writeShort(height);
|
||||
pOutput.writeShort(xPos);
|
||||
pOutput.writeShort(yPos);
|
||||
pOutput.writeByte(bitplanes);
|
||||
pOutput.writeByte(maskType);
|
||||
pOutput.writeByte(compressionType);
|
||||
pOutput.writeByte(0); // PAD
|
||||
pOutput.writeShort(mTransparentIndex);
|
||||
pOutput.writeByte(mXAspect);
|
||||
pOutput.writeByte(mYAspect);
|
||||
pOutput.writeShort(mPageWidth);
|
||||
pOutput.writeShort(mPageHeight);
|
||||
pOutput.writeShort(transparentIndex);
|
||||
pOutput.writeByte(xAspect);
|
||||
pOutput.writeByte(yAspect);
|
||||
pOutput.writeShort(pageWidth);
|
||||
pOutput.writeShort(pageHeight);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString()
|
||||
+ " {w=" + mWidth + ", h=" + mHeight
|
||||
+ ", x=" + mXPos + ", y=" + mYPos
|
||||
+ ", planes=" + mBitplanes + ", mask=" + mMaskType
|
||||
+ ", compression=" + mCompressionType + ", trans=" + mTransparentIndex
|
||||
+ ", xAspect=" + mXAspect + ", yAspect=" + mYAspect
|
||||
+ ", pageWidth=" + mPageWidth + ", pageHeight=" + mPageHeight + "}";
|
||||
+ " {w=" + width + ", h=" + height
|
||||
+ ", x=" + xPos + ", y=" + yPos
|
||||
+ ", planes=" + bitplanes + ", mask=" + maskType
|
||||
+ ", compression=" + compressionType + ", trans=" + transparentIndex
|
||||
+ ", xAspect=" + xAspect + ", yAspect=" + yAspect
|
||||
+ ", pageWidth=" + pageWidth + ", pageHeight=" + pageHeight + "}";
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -39,8 +39,7 @@ import java.io.DataOutput;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: BODYChunk.java,v 1.0 28.feb.2006 01:25:49 haku Exp$
|
||||
*/
|
||||
class BODYChunk extends IFFChunk {
|
||||
|
||||
final class BODYChunk extends IFFChunk {
|
||||
protected BODYChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_BODY, pChunkLength);
|
||||
}
|
||||
|
||||
+16
-8
@@ -40,35 +40,43 @@ import java.io.IOException;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: CAMGChunk.java,v 1.0 28.feb.2006 02:10:07 haku Exp$
|
||||
*/
|
||||
class CAMGChunk extends IFFChunk {
|
||||
|
||||
final class CAMGChunk extends IFFChunk {
|
||||
// HIRES=0x8000, LACE=0x4
|
||||
// #define CAMG_HAM 0x800 /* hold and modify */
|
||||
// #define CAMG_EHB 0x80 /* extra halfbrite */
|
||||
|
||||
private int mCAMG;
|
||||
private int camg;
|
||||
|
||||
public CAMGChunk(int pLength) {
|
||||
super(IFF.CHUNK_CAMG, pLength);
|
||||
}
|
||||
|
||||
void readChunk(DataInput pInput) throws IOException {
|
||||
if (mChunkLength != 4) {
|
||||
throw new IIOException("Unknown CAMG chunk length: " + mChunkLength);
|
||||
if (chunkLength != 4) {
|
||||
throw new IIOException("Unknown CAMG chunk length: " + chunkLength);
|
||||
}
|
||||
mCAMG = pInput.readInt();
|
||||
|
||||
camg = pInput.readInt();
|
||||
}
|
||||
|
||||
void writeChunk(DataOutput pOutput) throws IOException {
|
||||
throw new InternalError("Not implemented: writeChunk()");
|
||||
}
|
||||
|
||||
boolean isHires() {
|
||||
return (camg & 0x8000) != 0;
|
||||
}
|
||||
|
||||
boolean isLaced() {
|
||||
return (camg & 0x4) != 0;
|
||||
}
|
||||
|
||||
boolean isHAM() {
|
||||
return (mCAMG & 0x800) != 0;
|
||||
return (camg & 0x800) != 0;
|
||||
}
|
||||
|
||||
boolean isEHB() {
|
||||
return (mCAMG & 0x80) != 0;
|
||||
return (camg & 0x80) != 0;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
+64
-69
@@ -37,6 +37,7 @@ import java.awt.image.WritableRaster;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* CMAPChunk
|
||||
@@ -45,7 +46,7 @@ import java.io.IOException;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: CMAPChunk.java,v 1.0 28.feb.2006 00:38:05 haku Exp$
|
||||
*/
|
||||
class CMAPChunk extends IFFChunk {
|
||||
final class CMAPChunk extends IFFChunk {
|
||||
|
||||
// typedef struct {
|
||||
// UBYTE red, green, blue; /* color intensities 0..255 */
|
||||
@@ -53,61 +54,32 @@ class CMAPChunk extends IFFChunk {
|
||||
//
|
||||
// typedef ColorRegister ColorMap[n]; /* size = 3n bytes */
|
||||
|
||||
byte[] reds;
|
||||
byte[] greens;
|
||||
byte[] blues;
|
||||
|
||||
byte[] mReds;
|
||||
byte[] mGreens;
|
||||
byte[] mBlues;
|
||||
private IndexColorModel model;
|
||||
|
||||
boolean mEHB;
|
||||
|
||||
final private BMHDChunk mHeader;
|
||||
final private CAMGChunk mCamg;
|
||||
private IndexColorModel mModel;
|
||||
|
||||
protected CMAPChunk(int pChunkLength, BMHDChunk pHeader, CAMGChunk pCamg) {
|
||||
protected CMAPChunk(final int pChunkLength) {
|
||||
super(IFF.CHUNK_CMAP, pChunkLength);
|
||||
mHeader = pHeader;
|
||||
mCamg = pCamg;
|
||||
}
|
||||
|
||||
public CMAPChunk(IndexColorModel pModel) {
|
||||
public CMAPChunk(final IndexColorModel pModel) {
|
||||
super(IFF.CHUNK_CMAP, pModel.getMapSize() * 3);
|
||||
mModel = pModel;
|
||||
mHeader = null;
|
||||
mCamg = null;
|
||||
model = pModel;
|
||||
}
|
||||
|
||||
void readChunk(DataInput pInput) throws IOException {
|
||||
int numColors = mChunkLength / 3;
|
||||
int paletteSize = numColors;
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
int numColors = chunkLength / 3;
|
||||
|
||||
boolean isEHB = mCamg != null && mCamg.isEHB();
|
||||
if (isEHB) {
|
||||
if (numColors == 32) {
|
||||
paletteSize = 64;
|
||||
}
|
||||
else if (numColors != 64) {
|
||||
throw new IIOException("Unknown number of colors for EHB: " + numColors);
|
||||
}
|
||||
}
|
||||
|
||||
mReds = new byte[paletteSize];
|
||||
mGreens = mReds.clone();
|
||||
mBlues = mReds.clone();
|
||||
reds = new byte[numColors];
|
||||
greens = reds.clone();
|
||||
blues = reds.clone();
|
||||
|
||||
for (int i = 0; i < numColors; i++) {
|
||||
mReds[i] = pInput.readByte();
|
||||
mGreens[i] = pInput.readByte();
|
||||
mBlues[i] = pInput.readByte();
|
||||
}
|
||||
|
||||
if (isEHB && numColors == 32) {
|
||||
// Create the half-brite colors
|
||||
for (int i = 0; i < numColors; i++) {
|
||||
mReds[i + numColors] = (byte) ((mReds[i] & 0xff) / 2);
|
||||
mGreens[i + numColors] = (byte) ((mGreens[i] & 0xff) / 2);
|
||||
mBlues[i + numColors] = (byte) ((mBlues[i] & 0xff) / 2);
|
||||
}
|
||||
reds[i] = pInput.readByte();
|
||||
greens[i] = pInput.readByte();
|
||||
blues[i] = pInput.readByte();
|
||||
}
|
||||
|
||||
// TODO: When reading in a CMAP for 8-bit-per-gun display or
|
||||
@@ -116,52 +88,41 @@ class CMAPChunk extends IFFChunk {
|
||||
// rather than scaled, and provide your own scaling.
|
||||
// Use defaults if the color map is absent or has fewer color registers
|
||||
// than you need. Ignore any extra color registers.
|
||||
|
||||
// R8 := (Rn x 255 ) / maxColor
|
||||
|
||||
// All chunks are WORD aligned (even sized), may need to read pad...
|
||||
if (mChunkLength % 2 != 0) {
|
||||
if (chunkLength % 2 != 0) {
|
||||
pInput.readByte();
|
||||
}
|
||||
|
||||
// TODO: Bitmask transparency
|
||||
// Would it work to double to numbers of colors, and create an indexcolormodel,
|
||||
// with alpha, where all colors above the original color is all transparent?
|
||||
// This is a waste of time and space, of course...
|
||||
int trans = mHeader.mMaskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? mHeader.mTransparentIndex : -1;
|
||||
mModel = new InverseColorMapIndexColorModel(mHeader.mBitplanes, mReds.length, mReds, mGreens, mBlues, trans);
|
||||
}
|
||||
|
||||
void writeChunk(DataOutput pOutput) throws IOException {
|
||||
pOutput.writeInt(mChunkId);
|
||||
pOutput.writeInt(mChunkLength);
|
||||
void writeChunk(final DataOutput pOutput) throws IOException {
|
||||
pOutput.writeInt(chunkId);
|
||||
pOutput.writeInt(chunkLength);
|
||||
|
||||
final int length = mModel.getMapSize();
|
||||
final int length = model.getMapSize();
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
pOutput.writeByte(mModel.getRed(i));
|
||||
pOutput.writeByte(mModel.getGreen(i));
|
||||
pOutput.writeByte(mModel.getBlue(i));
|
||||
pOutput.writeByte(model.getRed(i));
|
||||
pOutput.writeByte(model.getGreen(i));
|
||||
pOutput.writeByte(model.getBlue(i));
|
||||
}
|
||||
|
||||
if (mChunkLength % 2 != 0) {
|
||||
if (chunkLength % 2 != 0) {
|
||||
pOutput.writeByte(0); // PAD
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString() + " {colorMap=" + mModel + "}";
|
||||
return super.toString() + " {colorMap=" + model + "}";
|
||||
}
|
||||
|
||||
IndexColorModel getIndexColorModel() {
|
||||
return mModel;
|
||||
}
|
||||
|
||||
public BufferedImage createPaletteImage() {
|
||||
BufferedImage createPaletteImage(final BMHDChunk header, boolean isEHB) throws IIOException {
|
||||
// Create a 1 x colors.length image
|
||||
IndexColorModel cm = getIndexColorModel();
|
||||
IndexColorModel cm = getIndexColorModel(header, isEHB);
|
||||
WritableRaster raster = cm.createCompatibleWritableRaster(cm.getMapSize(), 1);
|
||||
byte[] pixel = null;
|
||||
|
||||
for (int x = 0; x < cm.getMapSize(); x++) {
|
||||
pixel = (byte[]) cm.getDataElements(cm.getRGB(x), pixel);
|
||||
raster.setDataElements(x, 0, pixel);
|
||||
@@ -169,4 +130,38 @@ class CMAPChunk extends IFFChunk {
|
||||
|
||||
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
public IndexColorModel getIndexColorModel(final BMHDChunk header, boolean isEHB) throws IIOException {
|
||||
if (model == null) {
|
||||
int numColors = reds.length; // All arrays are same size
|
||||
|
||||
if (isEHB) {
|
||||
if (numColors == 32) {
|
||||
reds = Arrays.copyOf(reds, numColors * 2);
|
||||
blues = Arrays.copyOf(blues, numColors * 2);
|
||||
greens = Arrays.copyOf(greens, numColors * 2);
|
||||
}
|
||||
else if (numColors != 64) {
|
||||
throw new IIOException("Unknown number of colors for EHB: " + numColors);
|
||||
}
|
||||
|
||||
// Create the half-brite colors
|
||||
// We do this regardless of the colors read, as the color map may contain trash values
|
||||
for (int i = 0; i < 32; i++) {
|
||||
reds[i + 32] = (byte) ((reds[i] & 0xff) / 2);
|
||||
greens[i + 32] = (byte) ((greens[i] & 0xff) / 2);
|
||||
blues[i + 32] = (byte) ((blues[i] & 0xff) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Bitmask transparency
|
||||
// Would it work to double to numbers of colors, and create an indexcolormodel,
|
||||
// with alpha, where all colors above the original color is all transparent?
|
||||
// This is a waste of time and space, of course...
|
||||
int transparent = header.maskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? header.transparentIndex : -1;
|
||||
model = new InverseColorMapIndexColorModel(header.bitplanes, reds.length, reds, greens, blues, transparent);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
/**
|
||||
* CTBLChunk
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: CTBLChunk.java,v 1.0 30.03.12 14:53 haraldk Exp$
|
||||
*/
|
||||
final class CTBLChunk extends AbstractMultiPaletteChunk {
|
||||
protected CTBLChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_CTBL, pChunkLength);
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -42,12 +42,12 @@ import java.io.IOException;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: GRABChunk.java,v 1.0 28.feb.2006 01:55:05 haku Exp$
|
||||
*/
|
||||
class GRABChunk extends IFFChunk {
|
||||
final class GRABChunk extends IFFChunk {
|
||||
// typedef struct {
|
||||
// WORD x, y; /* relative coordinates (pixels) */
|
||||
// } Point2D;
|
||||
|
||||
Point2D mPoint;
|
||||
Point2D point;
|
||||
|
||||
protected GRABChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_GRAB, pChunkLength);
|
||||
@@ -55,22 +55,22 @@ class GRABChunk extends IFFChunk {
|
||||
|
||||
protected GRABChunk(Point2D pPoint) {
|
||||
super(IFF.CHUNK_GRAB, 4);
|
||||
mPoint = pPoint;
|
||||
point = pPoint;
|
||||
}
|
||||
|
||||
void readChunk(DataInput pInput) throws IOException {
|
||||
if (mChunkLength != 4) {
|
||||
throw new IIOException("Unknown GRAB chunk size: " + mChunkLength);
|
||||
if (chunkLength != 4) {
|
||||
throw new IIOException("Unknown GRAB chunk size: " + chunkLength);
|
||||
}
|
||||
mPoint = new Point(pInput.readShort(), pInput.readShort());
|
||||
point = new Point(pInput.readShort(), pInput.readShort());
|
||||
}
|
||||
|
||||
void writeChunk(DataOutput pOutput) throws IOException {
|
||||
pOutput.writeShort((int) mPoint.getX());
|
||||
pOutput.writeShort((int) mPoint.getY());
|
||||
pOutput.writeShort((int) point.getX());
|
||||
pOutput.writeShort((int) point.getY());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString() + " {point=" + mPoint + "}";
|
||||
return super.toString() + " {point=" + point + "}";
|
||||
}
|
||||
}
|
||||
|
||||
+12
-20
@@ -39,47 +39,39 @@ import java.io.DataOutput;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: UnknownChunk.java,v 1.0 28.feb.2006 00:53:47 haku Exp$
|
||||
*/
|
||||
class GenericChunk extends IFFChunk {
|
||||
final class GenericChunk extends IFFChunk {
|
||||
|
||||
byte[] mData;
|
||||
byte[] data;
|
||||
|
||||
protected GenericChunk(int pChunkId, int pChunkLength) {
|
||||
super(pChunkId, pChunkLength);
|
||||
mData = new byte[pChunkLength <= 50 ? pChunkLength : 47];
|
||||
data = new byte[pChunkLength <= 50 ? pChunkLength : 47];
|
||||
}
|
||||
|
||||
protected GenericChunk(int pChunkId, byte[] pChunkData) {
|
||||
super(pChunkId, pChunkData.length);
|
||||
mData = pChunkData;
|
||||
data = pChunkData;
|
||||
}
|
||||
|
||||
void readChunk(DataInput pInput) throws IOException {
|
||||
pInput.readFully(mData, 0, mData.length);
|
||||
pInput.readFully(data, 0, data.length);
|
||||
|
||||
int toSkip = mChunkLength - mData.length;
|
||||
while (toSkip > 0) {
|
||||
toSkip -= pInput.skipBytes(toSkip);
|
||||
}
|
||||
|
||||
// Read pad
|
||||
if (mChunkLength % 2 != 0) {
|
||||
pInput.readByte();
|
||||
}
|
||||
skipData(pInput, chunkLength, data.length);
|
||||
}
|
||||
|
||||
void writeChunk(DataOutput pOutput) throws IOException {
|
||||
pOutput.writeInt(mChunkId);
|
||||
pOutput.writeInt(mChunkLength);
|
||||
pOutput.write(mData, 0, mData.length);
|
||||
pOutput.writeInt(chunkId);
|
||||
pOutput.writeInt(chunkLength);
|
||||
pOutput.write(data, 0, data.length);
|
||||
|
||||
if (mData.length % 2 != 0) {
|
||||
if (data.length % 2 != 0) {
|
||||
pOutput.writeByte(0); // PAD
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return super.toString() + " {value=\""
|
||||
+ new String(mData, 0, mData.length <= 50 ? mData.length : 47)
|
||||
+ (mChunkLength <= 50 ? "" : "...") + "\"}";
|
||||
+ new String(data, 0, data.length <= 50 ? data.length : 47)
|
||||
+ (chunkLength <= 50 ? "" : "...") + "\"}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,22 @@ interface IFF {
|
||||
/** IFF FORM group chunk */
|
||||
int CHUNK_FORM = ('F' << 24) + ('O' << 16) + ('R' << 8) + 'M';
|
||||
|
||||
/** IFF ILBM form type */
|
||||
/** IFF ILBM form type (Interleaved bitmap) */
|
||||
int TYPE_ILBM = ('I' << 24) + ('L' << 16) + ('B' << 8) + 'M';
|
||||
|
||||
/** IFF PBM form type */
|
||||
/** IFF PBM form type (Packed bitmap) */
|
||||
int TYPE_PBM = ('P' << 24) + ('B' << 16) + ('M' << 8) + ' ';
|
||||
|
||||
// TODO:
|
||||
/** IFF DEEP form type (TVPaint) */
|
||||
int TYPE_DEEP = ('D' << 24) + ('E' << 16) + ('E' << 8) + 'P';
|
||||
/** IFF RGB8 form type (TurboSilver) */
|
||||
int TYPE_RGB8 = ('R' << 24) + ('G' << 16) + ('B' << 8) + '8';
|
||||
/** IFF RGBN form type (TurboSilver) */
|
||||
int TYPE_RGBN = ('R' << 24) + ('G' << 16) + ('B' << 8) + 'N';
|
||||
/** IFF ACBM form type (Amiga Basic) */
|
||||
int TYPE_ACBM = ('A' << 24) + ('C' << 16) + ('B' << 8) + 'M';
|
||||
|
||||
/** Bitmap Header chunk */
|
||||
int CHUNK_BMHD = ('B' << 24) + ('M' << 16) + ('H' << 8) + 'D';
|
||||
|
||||
@@ -65,4 +75,53 @@ interface IFF {
|
||||
|
||||
/** Main data (body) chunk */
|
||||
int CHUNK_BODY = ('B' << 24) + ('O' << 16) + ('D' << 8) + 'Y';
|
||||
|
||||
/** Junk (to allow garbage data in files, without re-writing the entire file) */
|
||||
int CHUNK_JUNK = ('J' << 24) + ('U' << 16) + ('N' << 8) + 'K';
|
||||
|
||||
/** EA IFF 85 Generic Author chunk */
|
||||
int CHUNK_AUTH = ('A' << 24) + ('U' << 16) + ('T' << 8) + 'H';
|
||||
/** EA IFF 85 Generic character string chunk */
|
||||
int CHUNK_CHRS = ('C' << 24) + ('H' << 16) + ('R' << 8) + 'S';
|
||||
/** EA IFF 85 Generic Name of art, music, etc. chunk */
|
||||
int CHUNK_NAME = ('N' << 24) + ('A' << 16) + ('M' << 8) + 'E';
|
||||
/** EA IFF 85 Generic unformatted ASCII text chunk */
|
||||
int CHUNK_TEXT = ('T' << 24) + ('E' << 16) + ('X' << 8) + 'T';
|
||||
/** EA IFF 85 Generic Copyright text chunk */
|
||||
int CHUNK_COPY = ('(' << 24) + ('c' << 16) + (')' << 8) + ' ';
|
||||
|
||||
/** color cycling */
|
||||
int CHUNK_CRNG = ('C' << 24) + ('R' << 16) + ('N' << 8) + 'G';
|
||||
/** color cycling */
|
||||
int CHUNK_CCRT = ('C' << 24) + ('C' << 16) + ('R' << 8) + 'T';
|
||||
/** Color Lookup Table chunk */
|
||||
int CHUNK_CLUT = ('C' << 24) + ('L' << 16) + ('U' << 8) + 'T';
|
||||
/** Dots per inch chunk */
|
||||
int CHUNK_DPI = ('D' << 24) + ('P' << 16) + ('I' << 8) + ' ';
|
||||
/** DPaint perspective chunk (EA) */
|
||||
int CHUNK_DPPV = ('D' << 24) + ('P' << 16) + ('P' << 8) + 'V';
|
||||
/** DPaint IV enhanced color cycle chunk (EA) */
|
||||
int CHUNK_DRNG = ('D' << 24) + ('R' << 16) + ('N' << 8) + 'G';
|
||||
/** Encapsulated Postscript chunk */
|
||||
int CHUNK_EPSF = ('E' << 24) + ('P' << 16) + ('S' << 8) + 'F';
|
||||
/** Cyan, Magenta, Yellow, & Black color map (Soft-Logik) */
|
||||
int CHUNK_CMYK = ('C' << 24) + ('M' << 16) + ('Y' << 8) + 'K';
|
||||
/** Color naming chunk (Soft-Logik) */
|
||||
int CHUNK_CNAM = ('C' << 24) + ('N' << 16) + ('A' << 8) + 'M';
|
||||
/** Line by line palette control information (Sebastiano Vigna) */
|
||||
int CHUNK_PCHG = ('P' << 24) + ('C' << 16) + ('H' << 8) + 'G';
|
||||
/** A mini duplicate ILBM used for preview (Gary Bonham) */
|
||||
int CHUNK_PRVW = ('P' << 24) + ('R' << 16) + ('V' << 8) + 'W';
|
||||
/** eXtended BitMap Information (Soft-Logik) */
|
||||
int CHUNK_XBMI = ('X' << 24) + ('B' << 16) + ('M' << 8) + 'I';
|
||||
/** Newtek Dynamic Ham color chunk */
|
||||
int CHUNK_CTBL = ('C' << 24) + ('T' << 16) + ('B' << 8) + 'L';
|
||||
/** Newtek Dynamic Ham chunk */
|
||||
int CHUNK_DYCP = ('D' << 24) + ('Y' << 16) + ('C' << 8) + 'P';
|
||||
/** Sliced HAM color chunk */
|
||||
int CHUNK_SHAM = ('S' << 24) + ('H' << 16) + ('A' << 8) + 'M';
|
||||
/** ACBM body chunk */
|
||||
int CHUNK_ABIT = ('A' << 24) + ('B' << 16) + ('I' << 8) + 'T';
|
||||
/** unofficial direct color */
|
||||
int CHUNK_DCOL = ('D' << 24) + ('C' << 16) + ('O' << 8) + 'L';
|
||||
}
|
||||
|
||||
+18
-5
@@ -40,19 +40,32 @@ import java.io.DataOutput;
|
||||
* @version $Id: IFFChunk.java,v 1.0 28.feb.2006 00:00:45 haku Exp$
|
||||
*/
|
||||
abstract class IFFChunk {
|
||||
int mChunkId;
|
||||
int mChunkLength;
|
||||
int chunkId;
|
||||
int chunkLength;
|
||||
|
||||
protected IFFChunk(int pChunkId, int pChunkLength) {
|
||||
mChunkId = pChunkId;
|
||||
mChunkLength = pChunkLength;
|
||||
chunkId = pChunkId;
|
||||
chunkLength = pChunkLength;
|
||||
}
|
||||
|
||||
abstract void readChunk(DataInput pInput) throws IOException;
|
||||
|
||||
abstract void writeChunk(DataOutput pOutput) throws IOException;
|
||||
|
||||
protected static void skipData(final DataInput pInput, final int chunkLength, final int dataReadSoFar) throws IOException {
|
||||
int toSkip = chunkLength - dataReadSoFar;
|
||||
|
||||
while (toSkip > 0) {
|
||||
toSkip -= pInput.skipBytes(toSkip);
|
||||
}
|
||||
|
||||
// Read pad
|
||||
if (chunkLength % 2 != 0) {
|
||||
pInput.readByte();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return IFFUtil.toChunkStr(mChunkId) + " chunk (" + mChunkLength + " bytes)";
|
||||
return IFFUtil.toChunkStr(chunkId) + " chunk (" + chunkLength + " bytes)";
|
||||
}
|
||||
}
|
||||
|
||||
+265
-148
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.image.ResampleOp;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
@@ -94,18 +95,22 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
// http://home.comcast.net/~erniew/lwsdk/docs/filefmts/ilbm.html
|
||||
// http://www.fileformat.info/format/iff/spec/7866a9f0e53c42309af667c5da3bd426/view.htm
|
||||
// - Contains definitions of some "new" chunks, as well as alternative FORM types
|
||||
// http://amigan.1emu.net/reg/iff.html
|
||||
// http://amigan.1emu.net/index/iff.html
|
||||
|
||||
private BMHDChunk mHeader;
|
||||
private CMAPChunk mColorMap;
|
||||
private BODYChunk mBody;
|
||||
private GRABChunk mGrab;
|
||||
private CAMGChunk mViewPort;
|
||||
private int mFormType;
|
||||
private long mBodyStart;
|
||||
// TODO: Allow reading rasters for HAM6/HAM8 and multipalette images that are expanded to RGB (24 bit) during read.
|
||||
|
||||
private BufferedImage mImage;
|
||||
private DataInputStream mByteRunStream;
|
||||
private BMHDChunk header;
|
||||
private CMAPChunk colorMap;
|
||||
private BODYChunk body;
|
||||
@SuppressWarnings({"FieldCanBeLocal"})
|
||||
private GRABChunk grab;
|
||||
private CAMGChunk viewPort;
|
||||
private MultiPalette paletteChange;
|
||||
private int formType;
|
||||
private long bodyStart;
|
||||
|
||||
private BufferedImage image;
|
||||
private DataInputStream byteRunStream;
|
||||
|
||||
public IFFImageReader() {
|
||||
super(IFFImageReaderSpi.sharedProvider());
|
||||
@@ -118,42 +123,44 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
private void init(int pIndex) throws IOException {
|
||||
checkBounds(pIndex);
|
||||
|
||||
if (mHeader == null) {
|
||||
if (header == null) {
|
||||
readMeta();
|
||||
}
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
mHeader = null;
|
||||
mColorMap = null;
|
||||
mBody = null;
|
||||
mViewPort = null;
|
||||
mFormType = 0;
|
||||
header = null;
|
||||
colorMap = null;
|
||||
paletteChange = null;
|
||||
body = null;
|
||||
viewPort = null;
|
||||
formType = 0;
|
||||
|
||||
mImage = null;
|
||||
mByteRunStream = null;
|
||||
image = null;
|
||||
byteRunStream = null;
|
||||
}
|
||||
|
||||
private void readMeta() throws IOException {
|
||||
if (mImageInput.readInt() != IFF.CHUNK_FORM) {
|
||||
throw new IIOException("Unknown file format for IFFImageReader");
|
||||
int chunkType = imageInput.readInt();
|
||||
if (chunkType != IFF.CHUNK_FORM) {
|
||||
throw new IIOException(String.format("Unknown file format for IFFImageReader, expected 'FORM': %s", IFFUtil.toChunkStr(chunkType)));
|
||||
}
|
||||
|
||||
int remaining = mImageInput.readInt() - 4; // We'll read 4 more in a sec
|
||||
int remaining = imageInput.readInt() - 4; // We'll read 4 more in a sec
|
||||
|
||||
mFormType = mImageInput.readInt();
|
||||
if (mFormType != IFF.TYPE_ILBM && mFormType != IFF.TYPE_PBM) {
|
||||
throw new IIOException("Only IFF (FORM) type ILBM and PBM supported: " + IFFUtil.toChunkStr(mFormType));
|
||||
formType = imageInput.readInt();
|
||||
if (formType != IFF.TYPE_ILBM && formType != IFF.TYPE_PBM) {
|
||||
throw new IIOException(String.format("Only IFF FORM types 'ILBM' and 'PBM ' supported: %s", IFFUtil.toChunkStr(formType)));
|
||||
}
|
||||
|
||||
//System.out.println("IFF type FORM " + toChunkStr(type));
|
||||
|
||||
mGrab = null;
|
||||
mViewPort = null;
|
||||
grab = null;
|
||||
viewPort = null;
|
||||
|
||||
while (remaining > 0) {
|
||||
int chunkId = mImageInput.readInt();
|
||||
int length = mImageInput.readInt();
|
||||
int chunkId = imageInput.readInt();
|
||||
int length = imageInput.readInt();
|
||||
|
||||
remaining -= 8;
|
||||
remaining -= length % 2 == 0 ? length : length + 1;
|
||||
@@ -163,61 +170,112 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
switch (chunkId) {
|
||||
case IFF.CHUNK_BMHD:
|
||||
if (mHeader != null) {
|
||||
if (header != null) {
|
||||
throw new IIOException("Multiple BMHD chunks not allowed");
|
||||
}
|
||||
|
||||
mHeader = new BMHDChunk(length);
|
||||
mHeader.readChunk(mImageInput);
|
||||
header = new BMHDChunk(length);
|
||||
header.readChunk(imageInput);
|
||||
|
||||
//System.out.println(mHeader);
|
||||
//System.out.println(header);
|
||||
break;
|
||||
case IFF.CHUNK_CMAP:
|
||||
if (mColorMap != null) {
|
||||
if (colorMap != null) {
|
||||
throw new IIOException("Multiple CMAP chunks not allowed");
|
||||
}
|
||||
mColorMap = new CMAPChunk(length, mHeader, mViewPort);
|
||||
mColorMap.readChunk(mImageInput);
|
||||
|
||||
//System.out.println(mColorMap);
|
||||
colorMap = new CMAPChunk(length);
|
||||
colorMap.readChunk(imageInput);
|
||||
|
||||
//System.out.println(colorMap);
|
||||
break;
|
||||
case IFF.CHUNK_GRAB:
|
||||
if (mGrab != null) {
|
||||
if (grab != null) {
|
||||
throw new IIOException("Multiple GRAB chunks not allowed");
|
||||
}
|
||||
mGrab = new GRABChunk(length);
|
||||
mGrab.readChunk(mImageInput);
|
||||
grab = new GRABChunk(length);
|
||||
grab.readChunk(imageInput);
|
||||
|
||||
//System.out.println(mGrab);
|
||||
//System.out.println(grab);
|
||||
break;
|
||||
case IFF.CHUNK_CAMG:
|
||||
if (mViewPort != null) {
|
||||
if (viewPort != null) {
|
||||
throw new IIOException("Multiple CAMG chunks not allowed");
|
||||
}
|
||||
mViewPort = new CAMGChunk(length);
|
||||
mViewPort.readChunk(mImageInput);
|
||||
viewPort = new CAMGChunk(length);
|
||||
viewPort.readChunk(imageInput);
|
||||
|
||||
//System.out.println(mViewPort);
|
||||
// System.out.println(viewPort);
|
||||
break;
|
||||
case IFF.CHUNK_PCHG:
|
||||
if (paletteChange instanceof PCHGChunk) {
|
||||
throw new IIOException("Multiple PCHG chunks not allowed");
|
||||
}
|
||||
|
||||
PCHGChunk pchg = new PCHGChunk(length);
|
||||
pchg.readChunk(imageInput);
|
||||
|
||||
// Always prefer PCHG style palette changes
|
||||
paletteChange = pchg;
|
||||
|
||||
// System.out.println(pchg);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_SHAM:
|
||||
if (paletteChange instanceof SHAMChunk) {
|
||||
throw new IIOException("Multiple SHAM chunks not allowed");
|
||||
}
|
||||
|
||||
SHAMChunk sham = new SHAMChunk(length);
|
||||
sham.readChunk(imageInput);
|
||||
|
||||
// NOTE: We prefer PHCG to SHAM style palette changes, if both are present
|
||||
if (paletteChange == null) {
|
||||
paletteChange = sham;
|
||||
}
|
||||
|
||||
// System.out.println(sham);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_CTBL:
|
||||
if (paletteChange instanceof CTBLChunk) {
|
||||
throw new IIOException("Multiple CTBL chunks not allowed");
|
||||
}
|
||||
|
||||
CTBLChunk ctbl = new CTBLChunk(length);
|
||||
ctbl.readChunk(imageInput);
|
||||
|
||||
// NOTE: We prefer PHCG to CTBL style palette changes, if both are present
|
||||
if (paletteChange == null) {
|
||||
paletteChange = ctbl;
|
||||
}
|
||||
|
||||
// System.out.println(ctbl);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_JUNK:
|
||||
// Always skip junk chunks
|
||||
IFFChunk.skipData(imageInput, length, 0);
|
||||
break;
|
||||
|
||||
case IFF.CHUNK_BODY:
|
||||
if (mBody != null) {
|
||||
if (body != null) {
|
||||
throw new IIOException("Multiple BODY chunks not allowed");
|
||||
}
|
||||
|
||||
mBody = new BODYChunk(length);
|
||||
mBodyStart = mImageInput.getStreamPosition();
|
||||
body = new BODYChunk(length);
|
||||
bodyStart = imageInput.getStreamPosition();
|
||||
|
||||
// NOTE: We don't read the body here, it's done later in the read(int, ImageReadParam) method
|
||||
|
||||
// Done reading meta
|
||||
return;
|
||||
default:
|
||||
// TODO: We probably want to store anno chunks as Metadata
|
||||
// ANNO, DEST, SPRT and more
|
||||
// TODO: We probably want to store ANNO, TEXT, AUTH, COPY etc chunks as Metadata
|
||||
// SHAM, ANNO, DEST, SPRT and more
|
||||
IFFChunk generic = new GenericChunk(chunkId, length);
|
||||
generic.readChunk(mImageInput);
|
||||
generic.readChunk(imageInput);
|
||||
|
||||
//System.out.println(generic);
|
||||
// System.out.println(generic);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -228,23 +286,23 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
processImageStarted(pIndex);
|
||||
|
||||
mImage = getDestination(pParam, getImageTypes(pIndex), mHeader.mWidth, mHeader.mHeight);
|
||||
//System.out.println(mBody);
|
||||
if (mBody != null) {
|
||||
image = getDestination(pParam, getImageTypes(pIndex), header.width, header.height);
|
||||
//System.out.println(body);
|
||||
if (body != null) {
|
||||
//System.out.println("Read body");
|
||||
readBody(pParam);
|
||||
}
|
||||
else {
|
||||
// TODO: Remove this hack when we have metadata
|
||||
// In the rare case of an ILBM containing nothing but a CMAP
|
||||
//System.out.println(mColorMap);
|
||||
if (mColorMap != null) {
|
||||
//System.out.println(colorMap);
|
||||
if (colorMap != null) {
|
||||
//System.out.println("Creating palette!");
|
||||
mImage = mColorMap.createPaletteImage();
|
||||
image = colorMap.createPaletteImage(header, isEHB());
|
||||
}
|
||||
}
|
||||
|
||||
BufferedImage result = mImage;
|
||||
BufferedImage result = image;
|
||||
|
||||
processImageComplete();
|
||||
|
||||
@@ -253,12 +311,12 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
init(pIndex);
|
||||
return mHeader.mWidth;
|
||||
return header.width;
|
||||
}
|
||||
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
init(pIndex);
|
||||
return mHeader.mHeight;
|
||||
return header.height;
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int pIndex) throws IOException {
|
||||
@@ -266,8 +324,8 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
List<ImageTypeSpecifier> types = Arrays.asList(
|
||||
getRawImageType(pIndex),
|
||||
ImageTypeSpecifier.createFromBufferedImageType(mHeader.mBitplanes == 32 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR)
|
||||
// TODO: ImageTypeSpecifier.createFromBufferedImageType(mHeader.mBitplanes == 32 ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB),
|
||||
ImageTypeSpecifier.createFromBufferedImageType(header.bitplanes == 32 ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR)
|
||||
// TODO: ImageTypeSpecifier.createFromBufferedImageType(header.bitplanes == 32 ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB),
|
||||
// TODO: Allow 32 bit always. Allow RGB and discard alpha, if present?
|
||||
);
|
||||
return types.iterator();
|
||||
@@ -278,9 +336,9 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
init(pIndex);
|
||||
// TODO: Stay DRY...
|
||||
// TODO: Use this for creating the Image/Buffer in the read code below...
|
||||
// NOTE: mColorMap may be null for 8 bit (gray), 24 bit or 32 bit only
|
||||
// NOTE: colorMap may be null for 8 bit (gray), 24 bit or 32 bit only
|
||||
ImageTypeSpecifier specifier;
|
||||
switch (mHeader.mBitplanes) {
|
||||
switch (header.bitplanes) {
|
||||
case 1:
|
||||
// 1 bit
|
||||
case 2:
|
||||
@@ -296,9 +354,9 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
case 8:
|
||||
// 8 bit
|
||||
// May be HAM8
|
||||
if (!isHAM()) {
|
||||
if (mColorMap != null) {
|
||||
IndexColorModel cm = mColorMap.getIndexColorModel();
|
||||
if (!isConvertToRGB()) {
|
||||
if (colorMap != null) {
|
||||
IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB());
|
||||
specifier = IndexedImageTypeSpecifier.createFromIndexColorModel(cm);
|
||||
break;
|
||||
}
|
||||
@@ -317,29 +375,33 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||
break;
|
||||
default:
|
||||
throw new IIOException(String.format("Bit depth not implemented: %d", mHeader.mBitplanes));
|
||||
throw new IIOException(String.format("Bit depth not implemented: %d", header.bitplanes));
|
||||
}
|
||||
return specifier;
|
||||
}
|
||||
|
||||
private void readBody(final ImageReadParam pParam) throws IOException {
|
||||
mImageInput.seek(mBodyStart);
|
||||
mByteRunStream = null;
|
||||
private boolean isConvertToRGB() {
|
||||
return isHAM() || isPCHG() || isSHAM();
|
||||
}
|
||||
|
||||
// NOTE: mColorMap may be null for 8 bit (gray), 24 bit or 32 bit only
|
||||
if (mColorMap != null) {
|
||||
IndexColorModel cm = mColorMap.getIndexColorModel();
|
||||
readIndexed(pParam, mImageInput, cm);
|
||||
private void readBody(final ImageReadParam pParam) throws IOException {
|
||||
imageInput.seek(bodyStart);
|
||||
byteRunStream = null;
|
||||
|
||||
// NOTE: colorMap may be null for 8 bit (gray), 24 bit or 32 bit only
|
||||
if (colorMap != null) {
|
||||
IndexColorModel cm = colorMap.getIndexColorModel(header, isEHB());
|
||||
readIndexed(pParam, imageInput, cm);
|
||||
}
|
||||
else {
|
||||
readTrueColor(pParam, mImageInput);
|
||||
readTrueColor(pParam, imageInput);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void readIndexed(final ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException {
|
||||
final int width = mHeader.mWidth;
|
||||
final int height = mHeader.mHeight;
|
||||
final int width = header.width;
|
||||
final int height = header.height;
|
||||
|
||||
final Rectangle aoi = getSourceRegion(pParam, width, height);
|
||||
final Point offset = pParam == null ? new Point(0, 0) : pParam.getDestinationOffset();
|
||||
@@ -360,9 +422,9 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// Ensure band settings from param are compatible with images
|
||||
checkReadParamBandSettings(pParam, isHAM() ? 3 : 1, mImage.getSampleModel().getNumBands());
|
||||
checkReadParamBandSettings(pParam, isConvertToRGB() ? 3 : 1, image.getSampleModel().getNumBands());
|
||||
|
||||
WritableRaster destination = mImage.getRaster();
|
||||
WritableRaster destination = image.getRaster();
|
||||
if (destinationBands != null || offset.x != 0 || offset.y != 0) {
|
||||
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
|
||||
}
|
||||
@@ -375,7 +437,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
ColorModel cm;
|
||||
WritableRaster raster;
|
||||
|
||||
if (isHAM()) {
|
||||
if (isConvertToRGB()) {
|
||||
// TODO: If HAM6, use type USHORT_444_RGB or 2BYTE_444_RGB?
|
||||
// Or create a HAMColorModel, if at all possible?
|
||||
// TYPE_3BYTE_BGR
|
||||
@@ -397,13 +459,12 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
final byte[] row = new byte[width * 8];
|
||||
|
||||
//System.out.println("Data length: " + data.length);
|
||||
//System.out.println("PlaneData length: " + planeData.length * planeData[0].length);
|
||||
//System.out.println("Row length: " + row.length);
|
||||
// System.out.println("PlaneData length: " + planeData.length);
|
||||
// System.out.println("Row length: " + row.length);
|
||||
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
|
||||
final int planes = mHeader.mBitplanes;
|
||||
final int planes = header.bitplanes;
|
||||
|
||||
Object dataElements = null;
|
||||
Object outDataElements = null;
|
||||
@@ -422,7 +483,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFormType == IFF.TYPE_ILBM) {
|
||||
if (formType == IFF.TYPE_ILBM) {
|
||||
int pixelPos = 0;
|
||||
for (int planePos = 0; planePos < planeWidth; planePos++) {
|
||||
IFFUtil.bitRotateCW(planeData, planePos, planeWidth, row, pixelPos, 1);
|
||||
@@ -432,23 +493,24 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
if (isHAM()) {
|
||||
hamToRGB(row, pModel, data, 0);
|
||||
}
|
||||
else if (isConvertToRGB()) {
|
||||
multiPaletteToRGB(srcY, row, pModel, data, 0);
|
||||
}
|
||||
else {
|
||||
raster.setDataElements(0, 0, width, 1, row);
|
||||
}
|
||||
}
|
||||
else if (mFormType == IFF.TYPE_PBM) {
|
||||
// TODO: Arraycopy might not be necessary, if it's okay with row larger than width
|
||||
System.arraycopy(planeData, 0, row, 0, mHeader.mBitplanes * planeWidth);
|
||||
raster.setDataElements(0, 0, width, 1, row);
|
||||
else if (formType == IFF.TYPE_PBM) {
|
||||
raster.setDataElements(0, 0, width, 1, planeData);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", mFormType));
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", formType));
|
||||
}
|
||||
|
||||
int dstY = (srcY - aoi.y) / sourceYSubsampling;
|
||||
// Handle non-converting raster as special case for performance
|
||||
if (cm.isCompatibleRaster(destination)) {
|
||||
// Rasters are compatible, just write to destinaiton
|
||||
// Rasters are compatible, just write to destination
|
||||
if (sourceXSubsampling == 1) {
|
||||
destination.setRect(offset.x, dstY, sourceRow);
|
||||
// dataElements = raster.getDataElements(aoi.x, 0, aoi.width, 1, dataElements);
|
||||
@@ -470,7 +532,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
for (int srcX = 0; srcX < sourceRow.getWidth(); srcX += sourceXSubsampling) {
|
||||
dataElements = sourceRow.getDataElements(srcX, 0, dataElements);
|
||||
int rgb = icm.getRGB(dataElements);
|
||||
outDataElements = mImage.getColorModel().getDataElements(rgb, outDataElements);
|
||||
outDataElements = image.getColorModel().getDataElements(rgb, outDataElements);
|
||||
int dstX = srcX / sourceXSubsampling;
|
||||
destination.setDataElements(dstX, dstY, outDataElements);
|
||||
}
|
||||
@@ -479,7 +541,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
// TODO: This branch is never tested, and is probably "dead"
|
||||
// ColorConvertOp
|
||||
if (converter == null) {
|
||||
converter = new ColorConvertOp(cm.getColorSpace(), mImage.getColorModel().getColorSpace(), null);
|
||||
converter = new ColorConvertOp(cm.getColorSpace(), image.getColorModel().getColorSpace(), null);
|
||||
}
|
||||
converter.filter(
|
||||
raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, null),
|
||||
@@ -488,7 +550,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(srcY * 100f / mHeader.mWidth);
|
||||
processImageProgress(srcY * 100f / header.width);
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
@@ -502,8 +564,8 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
// bit of the red value for each pixel, and the last holds the most
|
||||
// significant bit of the blue value.
|
||||
private void readTrueColor(ImageReadParam pParam, final ImageInputStream pInput) throws IOException {
|
||||
final int width = mHeader.mWidth;
|
||||
final int height = mHeader.mHeight;
|
||||
final int width = header.width;
|
||||
final int height = header.height;
|
||||
|
||||
final Rectangle aoi = getSourceRegion(pParam, width, height);
|
||||
final Point offset = pParam == null ? new Point(0, 0) : pParam.getDestinationOffset();
|
||||
@@ -524,29 +586,28 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// Ensure band settings from param are compatible with images
|
||||
checkReadParamBandSettings(pParam, mHeader.mBitplanes / 8, mImage.getSampleModel().getNumBands());
|
||||
checkReadParamBandSettings(pParam, header.bitplanes / 8, image.getSampleModel().getNumBands());
|
||||
|
||||
// NOTE: Each row of the image is stored in an integral number of 16 bit words.
|
||||
// The number of words per row is words=((w+15)/16)
|
||||
int planeWidth = 2 * ((width + 15) / 16);
|
||||
final byte[] planeData = new byte[8 * planeWidth];
|
||||
|
||||
WritableRaster destination = mImage.getRaster();
|
||||
WritableRaster destination = image.getRaster();
|
||||
if (destinationBands != null || offset.x != 0 || offset.y != 0) {
|
||||
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
|
||||
}
|
||||
// WritableRaster raster = mImage.getRaster().createCompatibleWritableRaster(width, 1);
|
||||
WritableRaster raster = mImage.getRaster().createCompatibleWritableRaster(8 * planeWidth, 1);
|
||||
// WritableRaster raster = image.getRaster().createCompatibleWritableRaster(width, 1);
|
||||
WritableRaster raster = image.getRaster().createCompatibleWritableRaster(8 * planeWidth, 1);
|
||||
Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
|
||||
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
final int channels = (mHeader.mBitplanes + 7) / 8;
|
||||
final int channels = (header.bitplanes + 7) / 8;
|
||||
final int planesPerChannel = 8;
|
||||
Object dataElements = null;
|
||||
|
||||
for (int srcY = 0; srcY < height; srcY++) {
|
||||
for (int c = 0; c < channels; c++) {
|
||||
|
||||
for (int p = 0; p < planesPerChannel; p++) {
|
||||
readPlaneData(pInput, planeData, p * planeWidth, planeWidth);
|
||||
}
|
||||
@@ -559,7 +620,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFormType == IFF.TYPE_ILBM) {
|
||||
if (formType == IFF.TYPE_ILBM) {
|
||||
// NOTE: Using (channels - c - 1) instead of just c,
|
||||
// effectively reverses the channel order from RGBA to ABGR
|
||||
int off = (channels - c - 1);
|
||||
@@ -570,11 +631,11 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
pixelPos += 8;
|
||||
}
|
||||
}
|
||||
else if (mFormType == IFF.TYPE_PBM) {
|
||||
else if (formType == IFF.TYPE_PBM) {
|
||||
System.arraycopy(planeData, 0, data, srcY * 8 * planeWidth, planeWidth);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", mFormType));
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", formType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +655,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(srcY * 100f / mHeader.mWidth);
|
||||
processImageProgress(srcY * 100f / header.width);
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
@@ -605,13 +666,15 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
private void readPlaneData(final ImageInputStream pInput, final byte[] pData, final int pOffset, final int pPlaneWidth)
|
||||
throws IOException {
|
||||
|
||||
switch (mHeader.mCompressionType) {
|
||||
switch (header.compressionType) {
|
||||
case BMHDChunk.COMPRESSION_NONE:
|
||||
pInput.readFully(pData, pOffset, pPlaneWidth);
|
||||
|
||||
// Uncompressed rows must have even number of bytes
|
||||
if ((mHeader.mBitplanes * pPlaneWidth) % 2 != 0) {
|
||||
if ((header.bitplanes * pPlaneWidth) % 2 != 0) {
|
||||
pInput.readByte();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case BMHDChunk.COMPRESSION_BYTE_RUN:
|
||||
@@ -620,46 +683,61 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
// However, we don't know how long each compressed row is, without decoding it...
|
||||
// The workaround below, is to use a decode buffer size of pPlaneWidth,
|
||||
// to make sure we don't decode anything we don't have to (shouldn't).
|
||||
if (mByteRunStream == null) {
|
||||
mByteRunStream = new DataInputStream(
|
||||
if (byteRunStream == null) {
|
||||
byteRunStream = new DataInputStream(
|
||||
new DecoderStream(
|
||||
IIOUtil.createStreamAdapter(pInput, mBody.mChunkLength),
|
||||
IIOUtil.createStreamAdapter(pInput, body.chunkLength),
|
||||
new PackBitsDecoder(true),
|
||||
pPlaneWidth * mHeader.mBitplanes
|
||||
pPlaneWidth * header.bitplanes
|
||||
)
|
||||
);
|
||||
}
|
||||
mByteRunStream.readFully(pData, pOffset, pPlaneWidth);
|
||||
|
||||
byteRunStream.readFully(pData, pOffset, pPlaneWidth);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IIOException(String.format("Unknown compression type: %d", mHeader.mCompressionType));
|
||||
throw new IIOException(String.format("Unknown compression type: %d", header.compressionType));
|
||||
}
|
||||
}
|
||||
|
||||
private void hamToRGB(final byte[] pIndexed, final IndexColorModel pModel,
|
||||
final byte[] pDest, final int pDestOffset) {
|
||||
final int bits = mHeader.mBitplanes;
|
||||
final int width = mHeader.mWidth;
|
||||
private void multiPaletteToRGB(final int row, final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, final int destOffset) {
|
||||
final int width = header.width;
|
||||
|
||||
ColorModel palette = paletteChange.getColorModel(colorModel, row, isLaced());
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
int pixel = indexed[x] & 0xff;
|
||||
|
||||
int rgb = palette.getRGB(pixel);
|
||||
|
||||
int offset = (x * 3) + destOffset;
|
||||
dest[2 + offset] = (byte) ((rgb >> 16) & 0xff);
|
||||
dest[1 + offset] = (byte) ((rgb >> 8) & 0xff);
|
||||
dest[ offset] = (byte) ( rgb & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
private void hamToRGB(final byte[] indexed, final IndexColorModel colorModel, final byte[] dest, final int destOffset) {
|
||||
final int bits = header.bitplanes;
|
||||
final int width = header.width;
|
||||
int lastRed = 0;
|
||||
int lastGreen = 0;
|
||||
int lastBlue = 0;
|
||||
|
||||
for (int x = 0; x < width; x++) {
|
||||
int pixel = pIndexed[x] & 0xff;
|
||||
int pixel = indexed[x] & 0xff;
|
||||
|
||||
//System.out.println("--> ham" + bits);
|
||||
int paletteIndex = bits == 6 ? pixel & 0x0f : pixel & 0x3f;
|
||||
int indexShift = bits == 6 ? 4 : 2;
|
||||
int colorMask = bits == 6 ? 0x0f : 0x03;
|
||||
//System.out.println("palette index=" + paletteIndex);
|
||||
|
||||
// Get Hold and Modify bits
|
||||
switch ((pixel >> (8 - indexShift)) & 0x03) {
|
||||
case 0x00:// HOLD
|
||||
lastRed = pModel.getRed(paletteIndex);
|
||||
lastGreen = pModel.getGreen(paletteIndex);
|
||||
lastBlue = pModel.getBlue(paletteIndex);
|
||||
lastRed = colorModel.getRed(paletteIndex);
|
||||
lastGreen = colorModel.getGreen(paletteIndex);
|
||||
lastBlue = colorModel.getBlue(paletteIndex);
|
||||
break;
|
||||
case 0x01:// MODIFY BLUE
|
||||
lastBlue = (lastBlue & colorMask) | (paletteIndex << indexShift);
|
||||
@@ -671,40 +749,79 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
lastGreen = (lastGreen & colorMask) | (paletteIndex << indexShift);
|
||||
break;
|
||||
}
|
||||
int offset = (x * 3) + pDestOffset;
|
||||
pDest[2 + offset] = (byte) lastRed;
|
||||
pDest[1 + offset] = (byte) lastGreen;
|
||||
pDest[offset] = (byte) lastBlue;
|
||||
|
||||
int offset = (x * 3) + destOffset;
|
||||
dest[2 + offset] = (byte) lastRed;
|
||||
dest[1 + offset] = (byte) lastGreen;
|
||||
dest[ offset] = (byte) lastBlue;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSHAM() {
|
||||
// TODO:
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isPCHG() {
|
||||
return paletteChange != null;
|
||||
}
|
||||
|
||||
private boolean isEHB() {
|
||||
return viewPort != null && viewPort.isEHB();
|
||||
}
|
||||
|
||||
private boolean isHAM() {
|
||||
return mViewPort != null && mViewPort.isHAM();
|
||||
return viewPort != null && viewPort.isHAM();
|
||||
}
|
||||
|
||||
public boolean isLaced() {
|
||||
return viewPort != null && viewPort.isLaced();
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
ImageReader reader = new IFFImageReader();
|
||||
|
||||
// ImageInputStream input = ImageIO.createImageInputStream(new File(pArgs[0]));
|
||||
ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(new File(pArgs[0])));
|
||||
boolean canRead = reader.getOriginatingProvider().canDecodeInput(input);
|
||||
boolean scale = false;
|
||||
for (String arg : pArgs) {
|
||||
if (arg.startsWith("-")) {
|
||||
scale = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
System.out.println("Can read: " + canRead);
|
||||
File file = new File(arg);
|
||||
if (!file.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (canRead) {
|
||||
reader.setInput(input);
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
// param.setSourceRegion(new Rectangle(0, 0, 160, 200));
|
||||
// param.setSourceRegion(new Rectangle(160, 200, 160, 200));
|
||||
// param.setSourceRegion(new Rectangle(80, 100, 160, 200));
|
||||
// param.setDestinationOffset(new Point(80, 100));
|
||||
// param.setSourceSubsampling(3, 3, 0, 0);
|
||||
// param.setSourceBands(new int[]{0, 1, 2});
|
||||
// param.setDestinationBands(new int[]{1, 0, 2});
|
||||
BufferedImage image = reader.read(0, param);
|
||||
System.out.println("image = " + image);
|
||||
try {
|
||||
ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(file));
|
||||
boolean canRead = reader.getOriginatingProvider().canDecodeInput(input);
|
||||
|
||||
showIt(image, pArgs[0]);
|
||||
if (canRead) {
|
||||
reader.setInput(input);
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
// param.setSourceRegion(new Rectangle(0, 0, 160, 200));
|
||||
// param.setSourceRegion(new Rectangle(160, 200, 160, 200));
|
||||
// param.setSourceRegion(new Rectangle(80, 100, 160, 200));
|
||||
// param.setDestinationOffset(new Point(80, 100));
|
||||
// param.setSourceSubsampling(3, 3, 0, 0);
|
||||
// param.setSourceBands(new int[]{0, 1, 2});
|
||||
// param.setDestinationBands(new int[]{1, 0, 2});
|
||||
BufferedImage image = reader.read(0, param);
|
||||
System.out.println("image = " + image);
|
||||
|
||||
if (scale) {
|
||||
image = new ResampleOp(image.getWidth() / 2, image.getHeight(), ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||
// image = ImageUtil.createResampled(image, image.getWidth(), image.getHeight() * 2, Image.SCALE_FAST);
|
||||
}
|
||||
|
||||
showIt(image, arg);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("Error reading file: " + file);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-20
@@ -99,22 +99,24 @@ public class IFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
|
||||
private void writeBody(ByteArrayOutputStream pImageData) throws IOException {
|
||||
mImageOutput.writeInt(IFF.CHUNK_BODY);
|
||||
mImageOutput.writeInt(pImageData.size());
|
||||
imageOutput.writeInt(IFF.CHUNK_BODY);
|
||||
imageOutput.writeInt(pImageData.size());
|
||||
|
||||
// NOTE: This is much faster than mOutput.write(pImageData.toByteArray())
|
||||
// NOTE: This is much faster than imageOutput.write(pImageData.toByteArray())
|
||||
// as the data array is not duplicated
|
||||
pImageData.writeTo(IIOUtil.createStreamAdapter(mImageOutput));
|
||||
|
||||
if (pImageData.size() % 2 == 0) {
|
||||
mImageOutput.writeByte(0); // PAD
|
||||
OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput);
|
||||
try {
|
||||
pImageData.writeTo(adapter);
|
||||
}
|
||||
finally {
|
||||
adapter.close();
|
||||
}
|
||||
|
||||
// NOTE: Most progress is done in packImageData, however, as we need to
|
||||
// buffer, to write correct size, we defer the last 10 percent until now.
|
||||
processImageProgress(100f);
|
||||
if (pImageData.size() % 2 == 0) {
|
||||
imageOutput.writeByte(0); // PAD
|
||||
}
|
||||
|
||||
mImageOutput.flush();
|
||||
imageOutput.flush();
|
||||
}
|
||||
|
||||
private void packImageData(OutputStream pOutput, RenderedImage pImage, ImageWriteParam pParam) throws IOException {
|
||||
@@ -167,7 +169,9 @@ public class IFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
}
|
||||
|
||||
processImageProgress(y * 90f / height);
|
||||
output.flush();
|
||||
|
||||
processImageProgress(y * 100f / height);
|
||||
}
|
||||
|
||||
output.flush();
|
||||
@@ -208,21 +212,22 @@ public class IFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
|
||||
// ILBM(4) + anno(8+len) + header(8+20) + cmap(8+len)? + body(8+len);
|
||||
int size = 4 + 8 + anno.mChunkLength + 28 + 8 + pBodyLength;
|
||||
int size = 4 + 8 + anno.chunkLength + 28 + 8 + pBodyLength;
|
||||
if (cmap != null) {
|
||||
size += 8 + cmap.mChunkLength;
|
||||
size += 8 + cmap.chunkLength;
|
||||
}
|
||||
|
||||
mImageOutput.writeInt(IFF.CHUNK_FORM);
|
||||
mImageOutput.writeInt(size);
|
||||
imageOutput.writeInt(IFF.CHUNK_FORM);
|
||||
imageOutput.writeInt(size);
|
||||
|
||||
mImageOutput.writeInt(IFF.TYPE_ILBM);
|
||||
imageOutput.writeInt(IFF.TYPE_ILBM);
|
||||
|
||||
anno.writeChunk(imageOutput);
|
||||
header.writeChunk(imageOutput);
|
||||
|
||||
anno.writeChunk(mImageOutput);
|
||||
header.writeChunk(mImageOutput);
|
||||
if (cmap != null) {
|
||||
//System.out.println("CMAP written");
|
||||
cmap.writeChunk(mImageOutput);
|
||||
cmap.writeChunk(imageOutput);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -51,7 +51,6 @@ public class IFFImageWriterSpi extends ImageWriterSpi {
|
||||
*/
|
||||
public IFFImageWriterSpi() {
|
||||
this(IIOUtil.getProviderInfo(IFFImageWriterSpi.class));
|
||||
|
||||
}
|
||||
|
||||
private IFFImageWriterSpi(final ProviderInfo pProviderInfo) {
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
/**
|
||||
* MultiPalette
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: MultiPalette.java,v 1.0 30.03.12 15:22 haraldk Exp$
|
||||
*/
|
||||
interface MultiPalette {
|
||||
ColorModel getColorModel(IndexColorModel colorModel, int rowIndex, boolean laced);
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Parts of this code is based on ilbmtoppm.c
|
||||
*
|
||||
* Copyright (C) 1989 by Jef Poskanzer.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software and its
|
||||
* documentation for any purpose and without fee is hereby granted, provided
|
||||
* that the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. This software is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*
|
||||
* Multipalette-support by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
|
||||
/**
|
||||
* A mutable indexed color model.
|
||||
* For use with images that exploits Amiga hardware to change the color
|
||||
* lookup table between scan lines.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: MutableIndexColorModel.java,v 1.0 29.03.12 15:00 haraldk Exp$
|
||||
*/
|
||||
final class MutableIndexColorModel extends ColorModel {
|
||||
final static int MP_REG_IGNORE = -1;
|
||||
|
||||
final int[] rgbs;
|
||||
|
||||
public MutableIndexColorModel(final IndexColorModel base) {
|
||||
super(base.getPixelSize(), base.getComponentSize(), base.getColorSpace(), base.hasAlpha(), base.isAlphaPremultiplied(), base.getTransparency(), base.getTransferType());
|
||||
|
||||
this.rgbs = getRGBs(base);
|
||||
}
|
||||
|
||||
private static int[] getRGBs(final IndexColorModel colorModel) {
|
||||
int[] rgbs = new int[colorModel.getMapSize()];
|
||||
colorModel.getRGBs(rgbs);
|
||||
return rgbs;
|
||||
}
|
||||
|
||||
public void adjustColorMap(final PaletteChange[] changes) {
|
||||
for (int i = 0; i < changes.length; i++) {
|
||||
int index = changes[i].index;
|
||||
|
||||
// TODO: Move validation to chunk (when reading)
|
||||
if (index >= rgbs.length) {
|
||||
// TODO: Issue IIO warning
|
||||
System.err.printf("warning - palette change register out of range\n");
|
||||
System.err.printf(" change structure %d index=%d (max %d)\n", i, index, getMapSize() - 1);
|
||||
System.err.printf(" ignoring it... colors might get messed up from here\n");
|
||||
}
|
||||
else if (index != MP_REG_IGNORE) {
|
||||
updateRGB(index, ((changes[i].r & 0xff) << 16) | ((changes[i].g & 0xff) << 8) | (changes[i].b & 0xff));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRGB(int pixel) {
|
||||
return rgbs[pixel];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRed(int pixel) {
|
||||
return (rgbs[pixel] >> 16) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGreen(int pixel) {
|
||||
return (rgbs[pixel] >> 8) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlue(int pixel) {
|
||||
return rgbs[pixel] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha(int pixel) {
|
||||
return (rgbs[pixel] >> 24) & 0xff;
|
||||
}
|
||||
|
||||
private void updateRGB(int index, int rgb) {
|
||||
rgbs[index] = rgb;
|
||||
}
|
||||
|
||||
public int getMapSize() {
|
||||
return rgbs.length;
|
||||
}
|
||||
|
||||
static class PaletteChange {
|
||||
/* palette index to change */
|
||||
public int index;
|
||||
/* new colors for index */
|
||||
public byte r;
|
||||
public byte g;
|
||||
public byte b;
|
||||
}
|
||||
}
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Parts of this code is based on ilbmtoppm.c
|
||||
*
|
||||
* Copyright (C) 1989 by Jef Poskanzer.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software and its
|
||||
* documentation for any purpose and without fee is hereby granted, provided
|
||||
* that the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. This software is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*
|
||||
* Multipalette-support by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
|
||||
*/
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* PCHGChunk
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PCHGChunk.java,v 1.0 27.03.12 13:02 haraldk Exp$
|
||||
*/
|
||||
final class PCHGChunk extends AbstractMultiPaletteChunk {
|
||||
// NOTE: Values from ilbm2ppm.
|
||||
final static int PCHG_COMP_NONE = 0;
|
||||
final static int PCHG_COMP_HUFFMAN = 1;
|
||||
|
||||
/** Use SmallLineChanges */
|
||||
final static int PCHGF_12BIT = 1; // NOTE: The beta spec refers to this as PHCGF_4BIT
|
||||
/** Use BigLineChanges */
|
||||
final static int PCHGF_32BIT = 2;
|
||||
/** meaningful only if PCHG_32BIT is on: use the Alpha channel info */
|
||||
final static int PCHGF_USE_ALPHA = 4;
|
||||
|
||||
private int startLine;
|
||||
private int changedLines;
|
||||
private int lineCount;
|
||||
private int totalChanges;
|
||||
private int minReg;
|
||||
|
||||
public PCHGChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_PCHG, pChunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
void readChunk(final DataInput pInput) throws IOException {
|
||||
int compression = pInput.readUnsignedShort();
|
||||
int flags = pInput.readUnsignedShort();
|
||||
startLine = pInput.readShort();
|
||||
lineCount = pInput.readUnsignedShort();
|
||||
changedLines = pInput.readUnsignedShort();
|
||||
minReg = pInput.readUnsignedShort();
|
||||
int maxReg = pInput.readUnsignedShort();
|
||||
/*int maxChangesPerLine = */pInput.readUnsignedShort(); // We don't really care, as we're not limited by the Amiga display hardware
|
||||
totalChanges = pInput.readInt();
|
||||
|
||||
byte[] data;
|
||||
|
||||
switch (compression) {
|
||||
case PCHG_COMP_NONE:
|
||||
data = new byte[chunkLength - 20];
|
||||
pInput.readFully(data);
|
||||
|
||||
break;
|
||||
case PCHG_COMP_HUFFMAN:
|
||||
// NOTE: Huffman decompression is completely untested, due to lack of source data (read: Probably broken).
|
||||
int compInfoSize = pInput.readInt();
|
||||
int originalDataSize = pInput.readInt();
|
||||
|
||||
short[] compTree = new short[compInfoSize / 2];
|
||||
for (int i = 0; i < compTree.length; i++) {
|
||||
compTree[i] = pInput.readShort();
|
||||
}
|
||||
|
||||
byte[] compData = new byte[chunkLength - 20 - 8 - compInfoSize];
|
||||
pInput.readFully(compData);
|
||||
|
||||
data = new byte[originalDataSize];
|
||||
|
||||
// decompress the change structure data
|
||||
decompressHuffman(compData, data, compTree, data.length);
|
||||
|
||||
default:
|
||||
throw new IIOException("Unknown PCHG compression: " + compression);
|
||||
}
|
||||
|
||||
changes = new MutableIndexColorModel.PaletteChange[startLine + lineCount][];
|
||||
|
||||
if (startLine < 0) {
|
||||
int numChanges = maxReg - minReg + 1;
|
||||
|
||||
initialChanges = new MutableIndexColorModel.PaletteChange[numChanges];
|
||||
}
|
||||
|
||||
// TODO: Postpone conversion to when the data is actually needed
|
||||
parseChanges(data, flags);
|
||||
}
|
||||
|
||||
static void decompressHuffman(byte[] src, byte[] dest, short[] tree, int origSize) {
|
||||
int i = 0;
|
||||
int bits = 0;
|
||||
int thisbyte = 0;
|
||||
|
||||
int treeIdx = tree.length - 1;
|
||||
int srcIdx = 0;
|
||||
int destIdx = 0;
|
||||
|
||||
while (i < origSize) {
|
||||
if (bits == 0) {
|
||||
thisbyte = src[srcIdx++];
|
||||
bits = 8;
|
||||
}
|
||||
|
||||
if ((thisbyte & (1 << 7)) != 0) {
|
||||
if (tree[treeIdx] >= 0) {
|
||||
dest[destIdx++] = (byte) tree[treeIdx];
|
||||
i++;
|
||||
treeIdx = tree.length - 1;
|
||||
}
|
||||
else {
|
||||
treeIdx += tree[treeIdx] / 2;
|
||||
}
|
||||
}
|
||||
else {
|
||||
treeIdx--;
|
||||
|
||||
if (tree[treeIdx] > 0 && (tree[treeIdx] & 0x100) != 0) {
|
||||
dest[destIdx++] = (byte) tree[treeIdx];
|
||||
i++;
|
||||
treeIdx = tree.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
thisbyte <<= 1;
|
||||
bits--;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseChanges(final byte[] data, int flags) throws IIOException {
|
||||
boolean small;
|
||||
|
||||
if ((flags & PCHGF_12BIT) != 0) {
|
||||
small = true;
|
||||
}
|
||||
else if ((flags & PCHGF_32BIT) != 0) {
|
||||
if ((flags & PCHGF_USE_ALPHA) != 0) {
|
||||
// TODO: Warning, or actually implement
|
||||
new IIOException("Alpha currently not supported.").printStackTrace();
|
||||
}
|
||||
|
||||
small = false;
|
||||
}
|
||||
else {
|
||||
throw new IIOException("Missing PCHG 12/32 bit flag.");
|
||||
}
|
||||
|
||||
int thismask = 0;
|
||||
int changeCount;
|
||||
int totalchanges = 0;
|
||||
int changedlines = changedLines;
|
||||
|
||||
int maskBytesLeft = 4 * ((lineCount + 31) / 32);
|
||||
|
||||
int maskIdx = 0;
|
||||
int dataIdx = maskBytesLeft;
|
||||
int dataBytesLeft = data.length - maskBytesLeft;
|
||||
|
||||
int bits = 0;
|
||||
for (int row = startLine; changedlines != 0 && row < 0; row++) {
|
||||
if (bits == 0) {
|
||||
if (maskBytesLeft == 0) {
|
||||
throw new IIOException("Insufficient data in line mask");
|
||||
}
|
||||
|
||||
thismask = data[maskIdx++];
|
||||
--maskBytesLeft;
|
||||
bits = 8;
|
||||
}
|
||||
|
||||
if ((thismask & (1 << 7)) != 0) {
|
||||
if (dataBytesLeft < 2) {
|
||||
throw new IIOException("Insufficient data in SmallLineChanges structures: " + dataBytesLeft);
|
||||
}
|
||||
|
||||
int changeCount16 = 0;
|
||||
if (small) {
|
||||
changeCount16 = data[dataIdx++] & 0xff;
|
||||
changeCount = changeCount16 + (data[dataIdx++] & 0xff);
|
||||
}
|
||||
else {
|
||||
changeCount = toShort(data, dataIdx);
|
||||
dataIdx += 2;
|
||||
}
|
||||
dataBytesLeft -= 2;
|
||||
|
||||
for (int i = 0; i < changeCount; i++) {
|
||||
if (totalchanges >= this.totalChanges) {
|
||||
throw new IIOException("Insufficient data in SmallLineChanges structures (changeCount): " + totalchanges);
|
||||
}
|
||||
if (dataBytesLeft < 2) {
|
||||
throw new IIOException("Insufficient data in SmallLineChanges structures: " + dataBytesLeft);
|
||||
}
|
||||
|
||||
// TODO: Make PaletteChange immutable with constructor params, assign outside test?
|
||||
if (small) {
|
||||
int smallChange = toShort(data, dataIdx);
|
||||
dataIdx += 2;
|
||||
dataBytesLeft -= 2;
|
||||
int reg = ((smallChange & 0xf000) >> 12) + (i >= changeCount16 ? 16 : 0);
|
||||
initialChanges[reg - minReg] = new MutableIndexColorModel.PaletteChange();
|
||||
initialChanges[reg - minReg].index = reg;
|
||||
initialChanges[reg - minReg].r = (byte) (((smallChange & 0x0f00) >> 8) * FACTOR_4BIT);
|
||||
initialChanges[reg - minReg].g = (byte) (((smallChange & 0x00f0) >> 4) * FACTOR_4BIT);
|
||||
initialChanges[reg - minReg].b = (byte) (((smallChange & 0x000f) ) * FACTOR_4BIT);
|
||||
}
|
||||
else {
|
||||
int reg = toShort(data, dataIdx);
|
||||
dataIdx += 2;
|
||||
initialChanges[reg - minReg] = new MutableIndexColorModel.PaletteChange();
|
||||
initialChanges[reg - minReg].index = reg;
|
||||
dataIdx++; /* skip alpha */
|
||||
initialChanges[reg - minReg].r = data[dataIdx++];
|
||||
initialChanges[reg - minReg].b = data[dataIdx++]; /* yes, RBG */
|
||||
initialChanges[reg - minReg].g = data[dataIdx++];
|
||||
dataBytesLeft -= 6;
|
||||
|
||||
}
|
||||
|
||||
++totalchanges;
|
||||
}
|
||||
|
||||
--changedlines;
|
||||
}
|
||||
|
||||
thismask <<= 1;
|
||||
bits--;
|
||||
}
|
||||
|
||||
for (int row = startLine; changedlines != 0 && row < changes.length; row++) {
|
||||
if (bits == 0) {
|
||||
if (maskBytesLeft == 0) {
|
||||
throw new IIOException("Insufficient data in line mask");
|
||||
}
|
||||
|
||||
thismask = data[maskIdx++];
|
||||
--maskBytesLeft;
|
||||
bits = 8;
|
||||
}
|
||||
|
||||
if ((thismask & (1 << 7)) != 0) {
|
||||
if (dataBytesLeft < 2) {
|
||||
throw new IIOException("Insufficient data in SmallLineChanges structures: " + dataBytesLeft);
|
||||
}
|
||||
|
||||
int changeCount16 = 0;
|
||||
if (small) {
|
||||
changeCount16 = data[dataIdx++] & 0xff;
|
||||
changeCount = changeCount16 + (data[dataIdx++] & 0xff);
|
||||
}
|
||||
else {
|
||||
changeCount = toShort(data, dataIdx);
|
||||
dataIdx += 2;
|
||||
}
|
||||
dataBytesLeft -= 2;
|
||||
|
||||
changes[row] = new MutableIndexColorModel.PaletteChange[changeCount];
|
||||
|
||||
for (int i = 0; i < changeCount; i++) {
|
||||
if (totalchanges >= this.totalChanges) {
|
||||
throw new IIOException("Insufficient data in SmallLineChanges structures (changeCount): " + totalchanges);
|
||||
}
|
||||
|
||||
if (dataBytesLeft < 2) {
|
||||
throw new IIOException("Insufficient data in SmallLineChanges structures: " + dataBytesLeft);
|
||||
}
|
||||
|
||||
if (small) {
|
||||
int smallChange = toShort(data, dataIdx);
|
||||
dataIdx += 2;
|
||||
dataBytesLeft -= 2;
|
||||
int reg = ((smallChange & 0xf000) >> 12) + (i >= changeCount16 ? 16 : 0);
|
||||
|
||||
MutableIndexColorModel.PaletteChange paletteChange = new MutableIndexColorModel.PaletteChange();
|
||||
paletteChange.index = reg;
|
||||
paletteChange.r = (byte) (((smallChange & 0x0f00) >> 8) * FACTOR_4BIT);
|
||||
paletteChange.g = (byte) (((smallChange & 0x00f0) >> 4) * FACTOR_4BIT);
|
||||
paletteChange.b = (byte) (((smallChange & 0x000f) ) * FACTOR_4BIT);
|
||||
|
||||
changes[row][i] = paletteChange;
|
||||
}
|
||||
else {
|
||||
int reg = toShort(data, dataIdx);
|
||||
dataIdx += 2;
|
||||
|
||||
MutableIndexColorModel.PaletteChange paletteChange = new MutableIndexColorModel.PaletteChange();
|
||||
paletteChange.index = reg;
|
||||
dataIdx++; /* skip alpha */
|
||||
paletteChange.r = data[dataIdx++];
|
||||
paletteChange.b = data[dataIdx++]; /* yes, RBG */
|
||||
paletteChange.g = data[dataIdx++];
|
||||
changes[row][i] = paletteChange;
|
||||
|
||||
dataBytesLeft -= 6;
|
||||
}
|
||||
|
||||
++totalchanges;
|
||||
}
|
||||
|
||||
--changedlines;
|
||||
}
|
||||
|
||||
thismask <<= 1;
|
||||
bits--;
|
||||
}
|
||||
|
||||
if (totalchanges != this.totalChanges) {
|
||||
// TODO: Issue IIO warning
|
||||
new IIOException(String.format("Got %d change structures, chunk header reports %d", totalchanges, this.totalChanges)).printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Util method
|
||||
private static short toShort(byte[] bytes, int idx) {
|
||||
return (short) ((bytes[idx] & 0xff) << 8 | (bytes[idx + 1] & 0xff));
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
/**
|
||||
* SHAMChunk
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SHAMChunk.java,v 1.0 30.03.12 14:53 haraldk Exp$
|
||||
*/
|
||||
final class SHAMChunk extends AbstractMultiPaletteChunk {
|
||||
protected SHAMChunk(int pChunkLength) {
|
||||
super(IFF.CHUNK_SHAM, pChunkLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean skipLaced() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Executable
+139
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* IFFImageReaderTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IFFImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
|
||||
*/
|
||||
public class IFFImageReaderTest extends ImageReaderAbstractTestCase<IFFImageReader> {
|
||||
protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
// 32 bit - Ok
|
||||
new TestData(getClassLoaderResource("/iff/test.iff"), new Dimension(300, 200)),
|
||||
// 24 bit - Ok
|
||||
new TestData(getClassLoaderResource("/iff/survivor.iff"), new Dimension(800, 600)),
|
||||
// HAM6 - Ok (a lot of visual "fringe", would be interesting to see on a real HAM display)
|
||||
new TestData(getClassLoaderResource("/iff/A4000T_HAM6.IFF"), new Dimension(320, 512)),
|
||||
// HAM8 - Ok
|
||||
new TestData(getClassLoaderResource("/iff/A4000T_HAM8.IFF"), new Dimension(628, 512)),
|
||||
// 2 color indexed - Ok
|
||||
new TestData(getClassLoaderResource("/iff/owl.iff"), new Dimension(160, 174)),
|
||||
// 8 color indexed - Ok
|
||||
new TestData(getClassLoaderResource("/iff/AmigaAmiga.iff"), new Dimension(200, 150)),
|
||||
// 16 color indexed - Ok
|
||||
new TestData(getClassLoaderResource("/iff/Lion.iff"), new Dimension(704, 480)),
|
||||
// 32 color indexed - Ok
|
||||
new TestData(getClassLoaderResource("/iff/GoldPorsche.iff"), new Dimension(320, 200)),
|
||||
// 64 color indexed EHB - Ok
|
||||
new TestData(getClassLoaderResource("/iff/Bryce.iff"), new Dimension(320, 200)),
|
||||
// 256 color indexed - Ok
|
||||
new TestData(getClassLoaderResource("/iff/IKKEGOD.iff"), new Dimension(640, 256)),
|
||||
// PBM, indexed - Ok
|
||||
new TestData(getClassLoaderResource("/iff/ASH.PBM"), new Dimension(320, 240)),
|
||||
// 16 color indexed, multi palette (PCHG) - Ok
|
||||
new TestData(getClassLoaderResource("/iff/Manhattan.PCHG"), new Dimension(704, 440)),
|
||||
// 16 color indexed, multi palette (PCHG + SHAM) - Ok
|
||||
new TestData(getClassLoaderResource("/iff/Somnambulist-2.SHAM"), new Dimension(704, 440))
|
||||
);
|
||||
}
|
||||
|
||||
protected ImageReaderSpi createProvider() {
|
||||
return new IFFImageReaderSpi();
|
||||
}
|
||||
|
||||
protected Class<IFFImageReader> getReaderClass() {
|
||||
return IFFImageReader.class;
|
||||
}
|
||||
|
||||
protected List<String> getFormatNames() {
|
||||
return Arrays.asList("iff");
|
||||
}
|
||||
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm");
|
||||
}
|
||||
|
||||
protected List<String> getMIMETypes() {
|
||||
return Arrays.asList("image/iff", "image/x-iff");
|
||||
}
|
||||
|
||||
// Regression tests
|
||||
|
||||
@Test
|
||||
public void testEHBColors() throws IOException {
|
||||
IFFImageReader reader = createReader();
|
||||
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/iff/Bryce.iff")));
|
||||
|
||||
BufferedImage image = reader.read(0);
|
||||
assertEquals(BufferedImage.TYPE_BYTE_INDEXED, image.getType());
|
||||
|
||||
ColorModel colorModel = image.getColorModel();
|
||||
assertNotNull(colorModel);
|
||||
assertTrue(colorModel instanceof IndexColorModel);
|
||||
|
||||
IndexColorModel indexColorModel = (IndexColorModel) colorModel;
|
||||
|
||||
assertEquals(64, indexColorModel.getMapSize());
|
||||
|
||||
byte[] reds = new byte[indexColorModel.getMapSize()];
|
||||
indexColorModel.getReds(reds);
|
||||
byte[] blues = new byte[indexColorModel.getMapSize()];
|
||||
indexColorModel.getBlues(blues);
|
||||
byte[] greens = new byte[indexColorModel.getMapSize()];
|
||||
indexColorModel.getGreens(greens);
|
||||
|
||||
for (int i = 0; i < 32; i++) {
|
||||
// Make sure the color model is really EHB
|
||||
assertEquals("red", (reds[i] & 0xff) / 2, reds[i + 32] & 0xff);
|
||||
assertEquals("blue", (blues[i] & 0xff) / 2, blues[i + 32] & 0xff);
|
||||
assertEquals("green", (greens[i] & 0xff) / 2, greens[i + 32] & 0xff);
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.image.MonochromeColorModel;
|
||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* JPEG2000ImageWriterTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: JPEG2000ImageWriterTest.java,v 1.0 20.01.12 12:19 haraldk Exp$
|
||||
*/
|
||||
public class IFFImageWriterTest extends ImageWriterAbstractTestCase {
|
||||
private final IFFImageWriterSpi provider = new IFFImageWriterSpi();
|
||||
|
||||
@Override
|
||||
protected ImageWriter createImageWriter() {
|
||||
return new IFFImageWriter(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends RenderedImage> getTestData() {
|
||||
return Arrays.asList(
|
||||
new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB),
|
||||
new BufferedImage(33, 20, BufferedImage.TYPE_BYTE_GRAY),
|
||||
new BufferedImage(31, 23, BufferedImage.TYPE_BYTE_INDEXED),
|
||||
new BufferedImage(30, 27, BufferedImage.TYPE_BYTE_BINARY),
|
||||
new BufferedImage(29, 29, BufferedImage.TYPE_BYTE_INDEXED, MonochromeColorModel.getInstance()),
|
||||
new BufferedImage(28, 31, BufferedImage.TYPE_INT_BGR),
|
||||
new BufferedImage(27, 33, BufferedImage.TYPE_INT_RGB),
|
||||
new BufferedImage(24, 37, BufferedImage.TYPE_3BYTE_BGR),
|
||||
new BufferedImage(23, 41, BufferedImage.TYPE_4BYTE_ABGR)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteReadCompare() throws IOException {
|
||||
ImageWriter writer = createImageWriter();
|
||||
|
||||
List<? extends RenderedImage> testData = getTestData();
|
||||
|
||||
for (int i = 0; i < testData.size(); i++) {
|
||||
try {
|
||||
RenderedImage image = testData.get(i);
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
|
||||
writer.setOutput(stream);
|
||||
|
||||
BufferedImage original = drawSomething((BufferedImage) image);
|
||||
|
||||
try {
|
||||
writer.write(original);
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail(e.getMessage());
|
||||
}
|
||||
finally {
|
||||
stream.close(); // Force data to be written
|
||||
}
|
||||
|
||||
assertTrue("No image data written", buffer.size() > 0);
|
||||
|
||||
ImageInputStream input = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray()));
|
||||
BufferedImage written = ImageIO.read(input);
|
||||
|
||||
assertNotNull(written);
|
||||
assertEquals(original.getWidth(), written.getWidth());
|
||||
assertEquals(original.getHeight(), written.getHeight());
|
||||
assertSameType(original, written);
|
||||
assertSameData(original, written);
|
||||
}
|
||||
catch (IOException e) {
|
||||
AssertionError fail = new AssertionError("Failure writing test data " + i + " " + e);
|
||||
fail.initCause(e);
|
||||
throw fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertSameData(BufferedImage expected, BufferedImage actual) {
|
||||
for (int y = 0; y < expected.getHeight(); y++) {
|
||||
for (int x = 0; x < expected.getWidth(); x++) {
|
||||
int expectedRGB = expected.getRGB(x, y);
|
||||
int actualRGB = actual.getRGB(x, y);
|
||||
|
||||
if (expected.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_GRAY) {
|
||||
// NOTE: For some reason, gray data seems to be one step off...
|
||||
assertEquals("R(" + x + "," + y + ")", expectedRGB & 0xff0000, actualRGB & 0xff0000, 0x10000);
|
||||
assertEquals("G(" + x + "," + y + ")", expectedRGB & 0x00ff00, actualRGB & 0x00ff00, 0x100);
|
||||
assertEquals("B(" + x + "," + y + ")", expectedRGB & 0x0000ff, actualRGB & 0x0000ff, 0x1);
|
||||
}
|
||||
else {
|
||||
assertEquals("R(" + x + "," + y + ")", expectedRGB & 0xff0000, actualRGB & 0xff0000);
|
||||
assertEquals("G(" + x + "," + y + ")", expectedRGB & 0x00ff00, actualRGB & 0x00ff00);
|
||||
assertEquals("B(" + x + "," + y + ")", expectedRGB & 0x0000ff, actualRGB & 0x0000ff);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertSameType(BufferedImage expected, BufferedImage actual) {
|
||||
if (expected.getType() != actual.getType()) {
|
||||
if (expected.getType() == BufferedImage.TYPE_INT_RGB || expected.getType() == BufferedImage.TYPE_INT_BGR) {
|
||||
assertEquals(BufferedImage.TYPE_3BYTE_BGR, actual.getType());
|
||||
}
|
||||
else if (expected.getType() == BufferedImage.TYPE_INT_ARGB || expected.getType() == BufferedImage.TYPE_INT_ARGB_PRE) {
|
||||
assertEquals(BufferedImage.TYPE_4BYTE_ABGR, actual.getType());
|
||||
}
|
||||
else if (expected.getType() == BufferedImage.TYPE_BYTE_INDEXED && expected.getColorModel().getPixelSize() <= 16) {
|
||||
assertEquals(BufferedImage.TYPE_BYTE_BINARY, actual.getType());
|
||||
}
|
||||
|
||||
// NOTE: Actually, TYPE_GRAY may be converted to TYPE_BYTE_INDEXED with linear gray color-map,
|
||||
// without being a problem (just a waste of time and space).
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user