(1);
+ ImageOutputStream outputStream = null;
+ try {
+ File outputFile = new File(outputDirectory, String.format("%04d", pageNo) + ".tif");
+ outputStream = ImageIO.createImageOutputStream(outputFile);
+ outputPages.clear();
+ outputPages.add(tiffPage);
+ writePages(outputStream, outputPages);
+ outputFiles.add(outputFile);
+ }
+ finally {
+ if (outputStream != null) {
+ outputStream.flush();
+ outputStream.close();
+ }
+ }
+ ++pageNo;
+ }
+ }
+ finally {
+ if (input != null) {
+ input.close();
+ }
+ }
+ return outputFiles;
+ }
+
+ /**
+ * Rotates all pages of a TIFF file by changing TIFF.TAG_ORIENTATION.
+ *
+ * NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do be
+ * displayed. Other metadata, such as width and height, relate to the image
+ * as how it is stored. The ImageIO TIFF plugin does not handle orientation.
+ * Use {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
+ * applying TIFF.TAG_ORIENTATION.
+ *
+ *
+ * @param imageInput
+ * @param imageOutput
+ * @param degree Rotation amount, supports 90�, 180� and 270�.
+ * @throws IOException
+ */
+ public static void rotatePages(ImageInputStream imageInput, ImageOutputStream imageOutput, int degree)
+ throws IOException {
+ rotatePage(imageInput, imageOutput, degree, -1);
+ }
+
+ /**
+ * Rotates a page of a TIFF file by changing TIFF.TAG_ORIENTATION.
+ *
+ * NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do be
+ * displayed. Other metadata, such as width and height, relate to the image
+ * as how it is stored. The ImageIO TIFF plugin does not handle orientation.
+ * Use {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
+ * applying TIFF.TAG_ORIENTATION.
+ *
+ *
+ * @param imageInput
+ * @param imageOutput
+ * @param degree Rotation amount, supports 90�, 180� and 270�.
+ * @param pageIndex page which should be rotated or -1 for all pages.
+ * @throws IOException
+ */
+ public static void rotatePage(ImageInputStream imageInput, ImageOutputStream imageOutput, int degree, int pageIndex)
+ throws IOException {
+ ImageInputStream input = null;
+ try {
+ List pages = getPages(imageInput);
+ if (pageIndex != -1) {
+ pages.get(pageIndex).rotate(degree);
+ }
+ else {
+ for (TIFFPage tiffPage : pages) {
+ tiffPage.rotate(degree);
+ }
+ }
+ writePages(imageOutput, pages);
+ }
+ finally {
+ if (input != null) {
+ input.close();
+ }
+ }
+ }
+
+ public static List getPages(ImageInputStream imageInput) throws IOException {
+ ArrayList pages = new ArrayList();
+
+ CompoundDirectory IFDs = (CompoundDirectory) new EXIFReader().read(imageInput);
+
+ int pageCount = IFDs.directoryCount();
+ for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
+ pages.add(new TIFFPage(IFDs.getDirectory(pageIndex), imageInput));
+ }
+
+ return pages;
+ }
+
+ public static void writePages(ImageOutputStream imageOutput, List pages) throws IOException {
+ EXIFWriter exif = new EXIFWriter();
+ long nextPagePos = imageOutput.getStreamPosition();
+ if (nextPagePos == 0) {
+ exif.writeTIFFHeader(imageOutput);
+ nextPagePos = imageOutput.getStreamPosition();
+ imageOutput.writeInt(0);
+ }
+ else {
+ // already has pages, so remember place of EOF to replace with
+ // IFD offset
+ nextPagePos -= 4;
+ }
+
+ for (TIFFPage tiffPage : pages) {
+ long ifdOffset = tiffPage.write(imageOutput, exif);
+
+ long tmp = imageOutput.getStreamPosition();
+ imageOutput.seek(nextPagePos);
+ imageOutput.writeInt((int) ifdOffset);
+ imageOutput.seek(tmp);
+ nextPagePos = tmp;
+ imageOutput.writeInt(0);
+ }
+ }
+
+ public static BufferedImage applyOrientation(BufferedImage input, int orientation) {
+ boolean flipExtends = false;
+ int w = input.getWidth();
+ int h = input.getHeight();
+ double cW = w / 2.0;
+ double cH = h / 2.0;
+
+ AffineTransform orientationTransform = new AffineTransform();
+ switch (orientation) {
+ case TIFFBaseline.ORIENTATION_TOPLEFT:
+ // normal
+ return input;
+ case TIFFExtension.ORIENTATION_TOPRIGHT:
+ // flipped vertically
+ orientationTransform.translate(cW, cH);
+ orientationTransform.scale(-1, 1);
+ orientationTransform.translate(-cW, -cH);
+ break;
+ case TIFFExtension.ORIENTATION_BOTRIGHT:
+ // rotated 180
+ orientationTransform.quadrantRotate(2, cW, cH);
+ break;
+ case TIFFExtension.ORIENTATION_BOTLEFT:
+ // flipped horizontally
+ orientationTransform.translate(cW, cH);
+ orientationTransform.scale(1, -1);
+ orientationTransform.translate(-cW, -cH);
+ break;
+ case TIFFExtension.ORIENTATION_LEFTTOP:
+ orientationTransform.translate(cW, cH);
+ orientationTransform.scale(-1, 1);
+ orientationTransform.quadrantRotate(1);
+ orientationTransform.translate(-cW, -cH);
+ flipExtends = true;
+ break;
+ case TIFFExtension.ORIENTATION_RIGHTTOP:
+ // rotated 90
+ orientationTransform.quadrantRotate(1, cW, cH);
+ flipExtends = true;
+ break;
+ case TIFFExtension.ORIENTATION_RIGHTBOT:
+ orientationTransform.translate(cW, cH);
+ orientationTransform.scale(1, -1);
+ orientationTransform.quadrantRotate(1);
+ orientationTransform.translate(-cW, -cH);
+ flipExtends = true;
+ break;
+ case TIFFExtension.ORIENTATION_LEFTBOT:
+ // rotated 270
+ orientationTransform.quadrantRotate(3, cW, cH);
+ flipExtends = true;
+ break;
+ }
+
+ int newW, newH;
+ if (flipExtends) {
+ newW = h;
+ newH = w;
+ }
+ else {
+ newW = w;
+ newH = h;
+ }
+
+ AffineTransform transform = AffineTransform.getTranslateInstance((newW - w) / 2.0, (newH - h) / 2.0);
+ transform.concatenate(orientationTransform);
+ AffineTransformOp transformOp = new AffineTransformOp(transform, null);
+ return transformOp.filter(input, null);
+ }
+
+ public static class TIFFPage {
+ private Directory IFD;
+ private ImageInputStream stream;
+
+ private TIFFPage(Directory IFD, ImageInputStream stream) {
+ this.IFD = IFD;
+ this.stream = stream;
+ }
+
+ private long write(ImageOutputStream outputStream, EXIFWriter exifWriter) throws IOException {
+ List newIFD = writeDirectoryData(IFD, outputStream);
+ return exifWriter.writeIFD(newIFD, outputStream);
+ }
+
+ private List writeDirectoryData(Directory IFD, ImageOutputStream outputStream) throws IOException {
+ ArrayList newIFD = new ArrayList();
+ Iterator it = IFD.iterator();
+ while (it.hasNext()) {
+ Entry e = it.next();
+ if (e.getValue() instanceof Directory) {
+ List subIFD = writeDirectoryData((Directory) e.getValue(), outputStream);
+ new TIFFEntry((Integer) e.getIdentifier(), TIFF.TYPE_IFD, new AbstractDirectory(subIFD) {
+ });
+ }
+
+ newIFD.add(e);
+ }
+
+ long[] offsets = new long[0];
+ long[] byteCounts = new long[0];
+ int[] newOffsets = new int[0];
+
+ Entry stripOffsetsEntry = IFD.getEntryById(TIFF.TAG_STRIP_OFFSETS);
+ Entry stripByteCountsEntry = IFD.getEntryById(TIFF.TAG_STRIP_BYTE_COUNTS);
+ if (stripOffsetsEntry != null && stripByteCountsEntry != null) {
+ offsets = getValueAsLongArray(stripOffsetsEntry);
+ byteCounts = getValueAsLongArray(stripByteCountsEntry);
+
+ newOffsets = writeData(offsets, byteCounts, outputStream);
+
+ newIFD.remove(stripOffsetsEntry);
+ newIFD.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, newOffsets));
+ }
+
+ Entry oldJpegData = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT);
+ Entry oldJpegDataLength = IFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
+ if (oldJpegData != null && oldJpegData.valueCount() > 0 && oldJpegDataLength != null && oldJpegDataLength.valueCount() > 0) {
+ if (!Arrays.equals(getValueAsLongArray(oldJpegData), offsets) || !Arrays.equals(getValueAsLongArray(oldJpegDataLength), byteCounts)) {
+ // data already written from TIFF.TAG_STRIP_OFFSETS
+ offsets = getValueAsLongArray(oldJpegData);
+ byteCounts = getValueAsLongArray(oldJpegDataLength);
+ newOffsets = writeData(offsets, byteCounts, outputStream);
+ }
+ newIFD.remove(oldJpegData);
+ newIFD.add(new TIFFEntry(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, newOffsets));
+ }
+
+ Entry oldJpegTable;
+ long[] tableOffsets;
+
+ oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES);
+ if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
+ tableOffsets = getValueAsLongArray(oldJpegTable);
+ byteCounts = new long[tableOffsets.length];
+ Arrays.fill(byteCounts, 64);
+ newOffsets = writeData(tableOffsets, byteCounts, outputStream);
+ newIFD.remove(oldJpegTable);
+ newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_AC_TABLES, newOffsets));
+ }
+
+ oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES);
+ if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
+ tableOffsets = getValueAsLongArray(oldJpegTable);
+ byteCounts = new long[tableOffsets.length];
+ Arrays.fill(byteCounts, 64);
+ newOffsets = writeData(tableOffsets, byteCounts, outputStream);
+ newIFD.remove(oldJpegTable);
+ newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_Q_TABLES, newOffsets));
+ }
+
+ oldJpegTable = IFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES);
+ if (oldJpegTable != null && oldJpegTable.valueCount() > 0) {
+ tableOffsets = getValueAsLongArray(oldJpegTable);
+ byteCounts = new long[tableOffsets.length];
+ Arrays.fill(byteCounts, 64);
+ newOffsets = writeData(tableOffsets, byteCounts, outputStream);
+ newIFD.remove(oldJpegTable);
+ newIFD.add(new TIFFEntry(TIFF.TAG_OLD_JPEG_DC_TABLES, newOffsets));
+ }
+
+ return newIFD;
+ }
+
+ private int[] writeData(long[] offsets, long[] byteCounts, ImageOutputStream outputStream) throws IOException {
+ int[] newOffsets = new int[offsets.length];
+ for (int i = 0; i < offsets.length; i++) {
+ newOffsets[i] = (int) outputStream.getStreamPosition();
+ stream.seek(offsets[i]);
+
+ byte[] buffer = new byte[(int) byteCounts[i]];
+ stream.readFully(buffer);
+ outputStream.write(buffer);
+ }
+ return newOffsets;
+ }
+
+ private long[] getValueAsLongArray(Entry entry) throws IIOException {
+ //TODO: code duplication from TIFFReader, should be extracted to metadata api
+ long[] value;
+
+ if (entry.valueCount() == 1) {
+ // For single entries, this will be a boxed type
+ value = new long[] {((Number) entry.getValue()).longValue()};
+ }
+ else if (entry.getValue() instanceof short[]) {
+ short[] shorts = (short[]) entry.getValue();
+ value = new long[shorts.length];
+
+ for (int i = 0, length = value.length; i < length; i++) {
+ value[i] = shorts[i];
+ }
+ }
+ else if (entry.getValue() instanceof int[]) {
+ int[] ints = (int[]) entry.getValue();
+ value = new long[ints.length];
+
+ for (int i = 0, length = value.length; i < length; i++) {
+ value[i] = ints[i];
+ }
+ }
+ else if (entry.getValue() instanceof long[]) {
+ value = (long[]) entry.getValue();
+ }
+ else {
+ throw new IIOException(String.format("Unsupported %s type: %s (%s)", entry.getFieldName(), entry.getTypeName(), entry.getValue().getClass()));
+ }
+
+ return value;
+ }
+
+ /**
+ * Rotates the image by changing TIFF.TAG_ORIENTATION.
+ *
+ * NOTICE: TIFF.TAG_ORIENTATION is an advice how the image is meant do
+ * be displayed. Other metadata, such as width and height, relate to the
+ * image as how it is stored. The ImageIO TIFF plugin does not handle
+ * orientation. Use
+ * {@link TIFFUtilities#applyOrientation(BufferedImage, int)} for
+ * applying TIFF.TAG_ORIENTATION.
+ *
+ *
+ * @param degree Rotation amount, supports 90�, 180� and 270�.
+ */
+ public void rotate(int degree) {
+ Validate.isTrue(degree % 90 == 0 && degree > 0 && degree < 360,
+ "Only rotations by 90, 180 and 270 degree are supported");
+
+ ArrayList newIDFData = new ArrayList<>();
+ Iterator it = IFD.iterator();
+ while (it.hasNext()) {
+ newIDFData.add(it.next());
+ }
+
+ short orientation = TIFFBaseline.ORIENTATION_TOPLEFT;
+ Entry orientationEntry = IFD.getEntryById(TIFF.TAG_ORIENTATION);
+ if (orientationEntry != null) {
+ orientation = ((Number) orientationEntry.getValue()).shortValue();
+ newIDFData.remove(orientationEntry);
+ }
+
+ int steps = degree / 90;
+ for (int i = 0; i < steps; i++) {
+ switch (orientation) {
+ case TIFFBaseline.ORIENTATION_TOPLEFT:
+ orientation = TIFFExtension.ORIENTATION_RIGHTTOP;
+ break;
+ case TIFFExtension.ORIENTATION_TOPRIGHT:
+ orientation = TIFFExtension.ORIENTATION_RIGHTBOT;
+ break;
+ case TIFFExtension.ORIENTATION_BOTRIGHT:
+ orientation = TIFFExtension.ORIENTATION_LEFTBOT;
+ break;
+ case TIFFExtension.ORIENTATION_BOTLEFT:
+ orientation = TIFFExtension.ORIENTATION_LEFTTOP;
+ break;
+ case TIFFExtension.ORIENTATION_LEFTTOP:
+ orientation = TIFFExtension.ORIENTATION_TOPRIGHT;
+ break;
+ case TIFFExtension.ORIENTATION_RIGHTTOP:
+ orientation = TIFFExtension.ORIENTATION_BOTRIGHT;
+ break;
+ case TIFFExtension.ORIENTATION_RIGHTBOT:
+ orientation = TIFFExtension.ORIENTATION_BOTLEFT;
+ break;
+ case TIFFExtension.ORIENTATION_LEFTBOT:
+ orientation = TIFFBaseline.ORIENTATION_TOPLEFT;
+ break;
+ }
+ }
+ newIDFData.add(new TIFFEntry(TIFF.TAG_ORIENTATION, (short) orientation));
+ IFD = new AbstractDirectory(newIDFData) {
+ };
+ }
+ }
+
+ /**
+ * TODO: Temporary clone, to be removed after TMI204 has been closed
+ */
+ public static final class TIFFEntry extends AbstractEntry {
+ // TODO: Expose a merge of this and the EXIFEntry class...
+ private final short type;
+
+ private static short guessType(final Object val) {
+ // TODO: This code is duplicated in EXIFWriter.getType, needs refactor!
+ Object value = Validate.notNull(val);
+
+ boolean array = value.getClass().isArray();
+ if (array) {
+ value = Array.get(value, 0);
+ }
+
+ // Note: This "narrowing" is to keep data consistent between read/write.
+ // TODO: Check for negative values and use signed types?
+ if (value instanceof Byte) {
+ return TIFF.TYPE_BYTE;
+ }
+ if (value instanceof Short) {
+ if (!array && (Short) value < Byte.MAX_VALUE) {
+ return TIFF.TYPE_BYTE;
+ }
+
+ return TIFF.TYPE_SHORT;
+ }
+ if (value instanceof Integer) {
+ if (!array && (Integer) value < Short.MAX_VALUE) {
+ return TIFF.TYPE_SHORT;
+ }
+
+ return TIFF.TYPE_LONG;
+ }
+ if (value instanceof Long) {
+ if (!array && (Long) value < Integer.MAX_VALUE) {
+ return TIFF.TYPE_LONG;
+ }
+ }
+
+ if (value instanceof Rational) {
+ return TIFF.TYPE_RATIONAL;
+ }
+
+ if (value instanceof String) {
+ return TIFF.TYPE_ASCII;
+ }
+
+ // TODO: More types
+
+ throw new UnsupportedOperationException(String.format("Method guessType not implemented for value of type %s", value.getClass()));
+ }
+
+ public TIFFEntry(final int identifier, final Object value) {
+ this(identifier, guessType(value), value);
+ }
+
+ TIFFEntry(int identifier, short type, Object value) {
+ super(identifier, value);
+ this.type = type;
+ }
+
+ @Override
+ public String getTypeName() {
+ return TIFF.TYPE_NAMES[type];
+ }
+ }
+
+ /**
+ * TODO: Temporary clone, to be removed after TMI204 has been closed
+ */
+ public interface TIFFExtension {
+ int ORIENTATION_TOPRIGHT = 2;
+ int ORIENTATION_BOTRIGHT = 3;
+ int ORIENTATION_BOTLEFT = 4;
+ int ORIENTATION_LEFTTOP = 5;
+ int ORIENTATION_RIGHTTOP = 6;
+ int ORIENTATION_RIGHTBOT = 7;
+ int ORIENTATION_LEFTBOT = 8;
+ }
+
+ /**
+ * TODO: Temporary clone, to be removed after TMI204 has been closed
+ */
+ public interface TIFFBaseline {
+ int ORIENTATION_TOPLEFT = 1;
+ }
+}
diff --git a/contrib/src/test/java/com/twelvemonkeys/contrib/tiff/TIFFUtilitiesTest.java b/contrib/src/test/java/com/twelvemonkeys/contrib/tiff/TIFFUtilitiesTest.java
new file mode 100644
index 00000000..4c3b8732
--- /dev/null
+++ b/contrib/src/test/java/com/twelvemonkeys/contrib/tiff/TIFFUtilitiesTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2013, 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.contrib.tiff;
+
+import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
+import com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat;
+import com.twelvemonkeys.io.FileUtil;
+import org.junit.Assert;
+import org.junit.Test;
+import org.w3c.dom.Node;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import javax.xml.xpath.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferByte;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * TIFFUtilitiesTest
+ *
+ * @author Oliver Schmidtmer
+ * @author last modified by $Author$
+ * @version $Id$
+ */
+public class TIFFUtilitiesTest {
+
+ @Test
+ public void testMerge() throws IOException {
+ // Files from ImageIO TIFF Plugin
+ InputStream stream1 = getClassLoaderResource("/tiff/ccitt/group3_1d.tif").openStream();
+ InputStream stream2 = getClassLoaderResource("/tiff/ccitt/group3_2d.tif").openStream();
+ InputStream stream3 = getClassLoaderResource("/tiff/ccitt/group4.tif").openStream();
+
+ File file1 = File.createTempFile("imageiotest", ".tif");
+ File file2 = File.createTempFile("imageiotest", ".tif");
+ File file3 = File.createTempFile("imageiotest", ".tif");
+ File output = File.createTempFile("imageiotest", ".tif");
+
+ byte[] data;
+
+ data = FileUtil.read(stream1);
+ FileUtil.write(file1, data);
+ stream1.close();
+
+ data = FileUtil.read(stream2);
+ FileUtil.write(file2, data);
+ stream2.close();
+
+ data = FileUtil.read(stream3);
+ FileUtil.write(file3, data);
+ stream3.close();
+
+ List input = Arrays.asList(file1, file2, file3);
+ TIFFUtilities.merge(input, output);
+
+ ImageInputStream iis = ImageIO.createImageInputStream(output);
+ ImageReader reader = ImageIO.getImageReaders(iis).next();
+ reader.setInput(iis);
+ Assert.assertEquals(3, reader.getNumImages(true));
+
+ iis.close();
+ output.delete();
+ file1.delete();
+ file2.delete();
+ file3.delete();
+ }
+
+ @Test
+ public void testSplit() throws IOException {
+ InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
+ File inputFile = File.createTempFile("imageiotest", "tif");
+ byte[] data = FileUtil.read(inputStream);
+ FileUtil.write(inputFile, data);
+ inputStream.close();
+
+ File outputDirectory = Files.createTempDirectory("imageio").toFile();
+
+ TIFFUtilities.split(inputFile, outputDirectory);
+
+ ImageReader reader = ImageIO.getImageReadersByFormatName("TIF").next();
+
+ File[] outputFiles = outputDirectory.listFiles();
+ Assert.assertEquals(3, outputFiles.length);
+ for (File outputFile : outputFiles) {
+ ImageInputStream iis = ImageIO.createImageInputStream(outputFile);
+ reader.setInput(iis);
+ Assert.assertEquals(1, reader.getNumImages(true));
+ iis.close();
+ outputFile.delete();
+ }
+ outputDirectory.delete();
+ inputFile.delete();
+ }
+
+ @Test
+ public void testRotate() throws IOException, XPathExpressionException {
+ ImageReader reader = ImageIO.getImageReadersByFormatName("TIF").next();
+
+ InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
+ File inputFile = File.createTempFile("imageiotest", ".tif");
+ byte[] data = FileUtil.read(inputStream);
+ FileUtil.write(inputFile, data);
+ inputStream.close();
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ XPathExpression expression = xPath.compile("TIFFIFD/TIFFField[@number='274']/TIFFBytes/TIFFByte/@value");
+
+ // rotate all pages
+ ImageInputStream inputTest1 = ImageIO.createImageInputStream(inputFile);
+ File outputTest1 = File.createTempFile("imageiotest", ".tif");
+ ImageOutputStream iosTest1 = ImageIO.createImageOutputStream(outputTest1);
+ TIFFUtilities.rotatePages(inputTest1, iosTest1, 90);
+ iosTest1.close();
+
+ ImageInputStream checkTest1 = ImageIO.createImageInputStream(outputTest1);
+ reader.setInput(checkTest1);
+ for (int i = 0; i < 3; i++) {
+ Node metaData = reader.getImageMetadata(i)
+ .getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
+ short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
+ Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
+ }
+ checkTest1.close();
+
+ // rotate single page further
+ ImageInputStream inputTest2 = ImageIO.createImageInputStream(outputTest1);
+ File outputTest2 = File.createTempFile("imageiotest", ".tif");
+ ImageOutputStream iosTest2 = ImageIO.createImageOutputStream(outputTest2);
+ TIFFUtilities.rotatePage(inputTest2, iosTest2, 90, 1);
+ iosTest2.close();
+
+ ImageInputStream checkTest2 = ImageIO.createImageInputStream(outputTest2);
+ reader.setInput(checkTest2);
+ for (int i = 0; i < 3; i++) {
+ Node metaData = reader.getImageMetadata(i)
+ .getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
+ short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
+ Assert.assertEquals(orientation, i == 1
+ ? TIFFExtension.ORIENTATION_BOTRIGHT
+ : TIFFExtension.ORIENTATION_RIGHTTOP);
+ }
+ checkTest2.close();
+ }
+
+ @Test
+ public void testApplyOrientation() throws IOException {
+ InputStream inputStream = getClassLoaderResource("/contrib/tiff/multipage.tif").openStream();
+ File inputFile = File.createTempFile("imageiotest", "tif");
+ byte[] data = FileUtil.read(inputStream);
+ FileUtil.write(inputFile, data);
+ inputStream.close();
+
+ BufferedImage image = ImageIO.read(inputFile);
+
+ // rotate by 90�
+ BufferedImage image90 = TIFFUtilities.applyOrientation(image, TIFFExtension.ORIENTATION_RIGHTTOP);
+ // rotate by 270�
+ BufferedImage image360 = TIFFUtilities.applyOrientation(image90, TIFFExtension.ORIENTATION_LEFTBOT);
+
+ byte[] original = ((DataBufferByte) image.getData().getDataBuffer()).getData();
+ byte[] rotated = ((DataBufferByte) image360.getData().getDataBuffer()).getData();
+
+ Assert.assertArrayEquals(original, rotated);
+ }
+
+ protected URL getClassLoaderResource(final String pName) {
+ return getClass().getResource(pName);
+ }
+}
diff --git a/contrib/src/test/resources/contrib/tiff/multipage.tif b/contrib/src/test/resources/contrib/tiff/multipage.tif
new file mode 100644
index 00000000..2579cb34
Binary files /dev/null and b/contrib/src/test/resources/contrib/tiff/multipage.tif differ
diff --git a/imageio/imageio-batik/pom.xml b/imageio/imageio-batik/pom.xml
index 0b773fe3..1f31c074 100644
--- a/imageio/imageio-batik/pom.xml
+++ b/imageio/imageio-batik/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-batik
TwelveMonkeys :: ImageIO :: Batik Plugin
@@ -23,27 +23,54 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
- batik
+ org.apache.xmlgraphics
batik-rasterizer-ext
- 1.6-1
+ ${batik.version}
+ provided
+
+
+ org.apache.xmlgraphics
+ batik-extensions
+
+
+
+
+
+ org.apache.xmlgraphics
+ batik-extension
+ ${batik.version}
provided
- batik
+ org.apache.xmlgraphics
+ xmlgraphics-commons
+ 2.0.1
+ provided
+
+
+
+ org.apache.xmlgraphics
+ batik-anim
+ ${batik.version}
+ provided
+
+
+
+ org.apache.xmlgraphics
batik-svggen
- 1.6-1
+ ${batik.version}
provided
- batik
+ org.apache.xmlgraphics
batik-transcoder
- 1.6-1
+ ${batik.version}
provided
" and start over
+ // - else if next is "!DOCTYPE " skip any whitespace
+ // - compare next 3 bytes against "svg", return result
+ // - else
+ // - compare next 3 bytes against "svg", return result
- // If this is not a comment, or the DOCTYPE declaration, the doc
- // has no DOCTYPE and it can't be svg
- if (pInput.read() != '!') {
+ byte[] buffer = new byte[4];
+ while (true) {
+ pInput.readFully(buffer);
+
+ if (buffer[0] == '?') {
+ // This is the XML declaration or a processing instruction
+ while (!(pInput.read() == '?' && pInput.read() == '>')) {
+ // Skip until end of XML declaration or processing instruction
+ }
+ }
+ else if (buffer[0] == '!') {
+ if (buffer[1] == '-' && buffer[2] == '-') {
+ // This is a comment
+ while (!(pInput.read() == '-' && pInput.read() == '-' && pInput.read() == '>')) {
+ // Skip until end of comment
+ }
+ }
+ else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
+ && pInput.read() == 'T' && pInput.read() == 'Y'
+ && pInput.read() == 'P' && pInput.read() == 'E') {
+ // This is the DOCTYPE declaration
+ while (Character.isWhitespace((char) (b = pInput.read()))) {
+ // Skip over WS
+ }
+
+ if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
+ // It's SVG, identified by DOCTYPE
+ return true;
+ }
+
+ // DOCTYPE found, but not SVG
+ return false;
+ }
+
+ // Something else, we'll skip
+ }
+ else {
+ // This is a normal tag
+ if (buffer[0] == 's' && buffer[1] == 'v' && buffer[2] == 'g'
+ && (Character.isWhitespace((char) buffer[3]) || buffer[3] == ':')) {
+ // It's SVG, identified by root tag
+ // TODO: Support svg with prefix + recognize namespace (http://www.w3.org/2000/svg)!
+ return true;
+ }
+
+ // If the tag is not "svg", this isn't SVG
return false;
}
- // There might be comments before the doctype, unfortunately...
- // If next is "--", this is a comment
- if ((b = pInput.read()) == '-' && pInput.read() == '-') {
- while (!(pInput.read() == '-' && pInput.read() == '-' && pInput.read() == '>')) {
- // Skip until end of comment
- }
- }
-
- // If we are lucky, this is DOCTYPE declaration
- if (b == 'D' && pInput.read() == 'O' && pInput.read() == 'C'
- && pInput.read() == 'T' && pInput.read() == 'Y' && pInput.read() == 'P'
- && pInput.read() == 'E') {
- docTypeFound = true;
- while (Character.isWhitespace((char) (b = pInput.read()))) {
- // Skip over WS
- }
- if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
- //System.out.println("It's svg!");
- return true;
- }
+ while (pInput.read() != '<') {
+ // Skip over, until next begin tag
}
}
+ }
+ catch (EOFException ignore) {
+ // Possible for small files...
return false;
}
finally {
@@ -146,18 +151,17 @@ public class SVGImageReaderSpi extends ImageReaderSpi {
}
}
-
- public ImageReader createReaderInstance(Object extension) throws IOException {
+ public ImageReader createReaderInstance(final Object extension) throws IOException {
return new SVGImageReader(this);
}
- public String getDescription(Locale locale) {
- return "Scaleable Vector Graphics (SVG) format image reader";
+ public String getDescription(final Locale locale) {
+ return "Scalable Vector Graphics (SVG) format image reader";
}
@SuppressWarnings({"deprecation"})
@Override
- public void onRegistration(ServiceRegistry registry, Class> category) {
+ public void onRegistration(final ServiceRegistry registry, final Class> category) {
if (!SVG_READER_AVAILABLE) {
try {
// NOTE: This will break, but it gives us some useful debug info
@@ -170,5 +174,6 @@ public class SVGImageReaderSpi extends ImageReaderSpi {
IIOUtil.deregisterProvider(registry, this, category);
}
- }}
+ }
+}
diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfo.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfo.java
new file mode 100644
index 00000000..5e83e1af
--- /dev/null
+++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGProviderInfo.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2015, 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.svg;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.lang.SystemUtil;
+
+/**
+ * SVGProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: SVGProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class SVGProviderInfo extends ReaderWriterProviderInfo {
+ final static boolean SVG_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
+
+ protected SVGProviderInfo() {
+ super(
+ SVGProviderInfo.class,
+ SVG_READER_AVAILABLE ? new String[]{"svg", "SVG"} : new String[]{""}, // Names
+ SVG_READER_AVAILABLE ? new String[]{"svg"} : null, // Suffixes
+ SVG_READER_AVAILABLE ? new String[]{"image/svg", "image/x-svg", "image/svg+xml", "image/svg-xml"} : null, // Mime-types
+ "com.twelvemonkeys.imageio.plugins.svg.SVGImageReader", // Reader class name
+ new String[] {"com.twelvemonkeys.imageio.plugins.svg.SVGImageReaderSpi"},
+ null,
+ null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderSpi.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderSpi.java
index d9aecc2e..a8106b91 100755
--- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderSpi.java
+++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderSpi.java
@@ -28,64 +28,38 @@
package com.twelvemonkeys.imageio.plugins.wmf;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.lang.SystemUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
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;
+import static com.twelvemonkeys.imageio.plugins.wmf.WMFProviderInfo.WMF_READER_AVAILABLE;
+
/**
* WMFImageReaderSpi
*
- *
+ *
* @author Harald Kuhr
* @version $Id: WMFImageReaderSpi.java,v 1.1 2003/12/02 16:45:00 wmhakur Exp $
*/
-public class WMFImageReaderSpi extends ImageReaderSpi {
-
- // This is correct, as we rely on the SVG reader
- private final static boolean WMF_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
+public final class WMFImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates a {@code WMFImageReaderSpi}.
*/
public WMFImageReaderSpi() {
- this(IIOUtil.getProviderInfo(WMFImageReaderSpi.class));
+ super(new WMFProviderInfo());
}
- private WMFImageReaderSpi(final ProviderInfo pProviderInfo) {
- super(
- pProviderInfo.getVendorName(), // Vendor name
- pProviderInfo.getVersion(), // Version
- WMF_READER_AVAILABLE ? new String[]{"wmf", "WMF"} : new String[]{""}, // Names
- WMF_READER_AVAILABLE ? new String[]{"wmf", "emf"} : null, // Suffixes
- WMF_READER_AVAILABLE ? new String[]{"application/x-msmetafile", "image/x-wmf"} : null, // Mime-types
- "com.twelvemonkeys.imageio.plugins.wmf.WMFImageReader", // Reader class name..?
- new Class[] {ImageInputStream.class}, // Input types
- null, // 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 {
+ public boolean canDecodeInput(final Object source) throws IOException {
return source instanceof ImageInputStream && WMF_READER_AVAILABLE && canDecode((ImageInputStream) source);
}
- public static boolean canDecode(ImageInputStream pInput) throws IOException {
+ public static boolean canDecode(final ImageInputStream pInput) throws IOException {
if (pInput == null) {
throw new IllegalArgumentException("input == null");
}
@@ -96,7 +70,6 @@ public class WMFImageReaderSpi extends ImageReaderSpi {
for (byte header : WMF.HEADER) {
int read = (byte) pInput.read();
if (header != read) {
- // System.out.println("--> " + i + ": " + read + " (expected " + header + ")");
return false;
}
}
@@ -108,18 +81,17 @@ public class WMFImageReaderSpi extends ImageReaderSpi {
}
}
-
- public ImageReader createReaderInstance(Object extension) throws IOException {
+ public ImageReader createReaderInstance(final Object extension) throws IOException {
return new WMFImageReader(this);
}
- public String getDescription(Locale locale) {
+ public String getDescription(final Locale locale) {
return "Windows Meta File (WMF) image reader";
}
@SuppressWarnings({"deprecation"})
@Override
- public void onRegistration(ServiceRegistry registry, Class> category) {
+ public void onRegistration(final ServiceRegistry registry, final Class> category) {
if (!WMF_READER_AVAILABLE) {
IIOUtil.deregisterProvider(registry, this, category);
}
diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfo.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfo.java
new file mode 100644
index 00000000..6db9a085
--- /dev/null
+++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfo.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2015, 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.wmf;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.lang.SystemUtil;
+
+/**
+ * WMFProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: WMFProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
+ */
+final class WMFProviderInfo extends ReaderWriterProviderInfo {
+ // This is correct, as we rely on the SVG reader
+ final static boolean WMF_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
+
+ protected WMFProviderInfo() {
+ super(
+ WMFProviderInfo.class,
+ WMF_READER_AVAILABLE ? new String[]{"wmf", "WMF"} : new String[]{""}, // Names
+ WMF_READER_AVAILABLE ? new String[]{"wmf", "emf"} : null, // Suffixes
+ WMF_READER_AVAILABLE ? new String[]{"application/x-msmetafile", "image/x-wmf"} : null, // Mime-types
+ "com.twelvemonkeys.imageio.plugins.wmf.WMFImageReader", // Reader class name..?
+ new String[] {"com.twelvemonkeys.imageio.plugins.wmf.WMFImageReaderSpi"},
+ null,
+ null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTestCase.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java
similarity index 67%
rename from imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTestCase.java
rename to imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java
index 8324c311..9f0e8b52 100755
--- a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTestCase.java
+++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReaderTest.java
@@ -28,30 +28,39 @@
package com.twelvemonkeys.imageio.plugins.svg;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
import org.junit.Test;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
+import java.awt.image.BufferedImage;
import java.awt.image.ImagingOpException;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import static org.junit.Assert.assertEquals;
+
/**
- * SVGImageReaderTestCase
+ * SVGImageReaderTest
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
- * @version $Id: SVGImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
+ * @version $Id: SVGImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
-public class SVGImageReaderTestCase extends ImageReaderAbstractTestCase {
+public class SVGImageReaderTest extends ImageReaderAbstractTest {
private SVGImageReaderSpi provider = new SVGImageReaderSpi();
protected List getTestData() {
return Arrays.asList(
- new TestData(getClassLoaderResource("/svg/batikLogo.svg"), new Dimension(450, 500))
+ new TestData(getClassLoaderResource("/svg/batikLogo.svg"), new Dimension(450, 500)),
+ new TestData(getClassLoaderResource("/svg/red-square.svg"), new Dimension(100, 100)),
+ new TestData(getClassLoaderResource("/svg/blue-square.svg"), new Dimension(100, 100)),
+ new TestData(getClassLoaderResource("/svg/Android_robot.svg"), new Dimension(400, 400))
);
}
@@ -69,15 +78,15 @@ public class SVGImageReaderTestCase extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("svg");
+ return Collections.singletonList("svg");
}
protected List getSuffixes() {
- return Arrays.asList("svg");
+ return Collections.singletonList("svg");
}
protected List getMIMETypes() {
- return Arrays.asList("image/svg+xml");
+ return Collections.singletonList("image/svg+xml");
}
@Test
@@ -110,4 +119,22 @@ public class SVGImageReaderTestCase extends ImageReaderAbstractTestCaseHarald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: SVGProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class SVGProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new SVGProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTestCase.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTest.java
similarity index 87%
rename from imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTestCase.java
rename to imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTest.java
index 23c55193..9220d115 100755
--- a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTestCase.java
+++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReaderTest.java
@@ -28,7 +28,7 @@
package com.twelvemonkeys.imageio.plugins.wmf;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
import org.junit.Test;
@@ -36,22 +36,22 @@ import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
- * SVGImageReaderTestCase
+ * WMFImageReaderTest
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
- * @version $Id: SVGImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
+ * @version $Id: WMFImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
-public class WMFImageReaderTestCase extends ImageReaderAbstractTestCase {
+public class WMFImageReaderTest extends ImageReaderAbstractTest {
private WMFImageReaderSpi provider = new WMFImageReaderSpi();
protected List getTestData() {
- return Arrays.asList(
- // TODO: Dimensions does not look right...
- new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(841, 673))
+ return Collections.singletonList(
+ new TestData(getClassLoaderResource("/wmf/test.wmf"), new Dimension(133, 106))
);
}
@@ -69,7 +69,7 @@ public class WMFImageReaderTestCase extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("wmf");
+ return Collections.singletonList("wmf");
}
protected List getSuffixes() {
diff --git a/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfoTest.java b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfoTest.java
new file mode 100644
index 00000000..6b96d2cf
--- /dev/null
+++ b/imageio/imageio-batik/src/test/java/com/twelvemonkeys/imageio/plugins/wmf/WMFProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.wmf;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * WMFProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: WMFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class WMFProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new WMFProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-batik/src/test/resources/svg/Android_robot.svg b/imageio/imageio-batik/src/test/resources/svg/Android_robot.svg
new file mode 100644
index 00000000..e32e907b
--- /dev/null
+++ b/imageio/imageio-batik/src/test/resources/svg/Android_robot.svg
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/imageio/imageio-batik/src/test/resources/svg/blue-square.svg b/imageio/imageio-batik/src/test/resources/svg/blue-square.svg
new file mode 100644
index 00000000..fdf43634
--- /dev/null
+++ b/imageio/imageio-batik/src/test/resources/svg/blue-square.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/imageio/imageio-batik/src/test/resources/svg/red-square.svg b/imageio/imageio-batik/src/test/resources/svg/red-square.svg
new file mode 100644
index 00000000..2ae064db
--- /dev/null
+++ b/imageio/imageio-batik/src/test/resources/svg/red-square.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/imageio/imageio-bmp/pom.xml b/imageio/imageio-bmp/pom.xml
index 50234fdf..8b6aa174 100644
--- a/imageio/imageio-bmp/pom.xml
+++ b/imageio/imageio-bmp/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-bmp
TwelveMonkeys :: ImageIO :: BMP plugin
@@ -18,7 +18,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java
index 9abbff29..1988d376 100755
--- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java
+++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BMPMetadata.java
@@ -28,17 +28,16 @@
package com.twelvemonkeys.imageio.plugins.bmp;
+import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.lang.Validate;
import org.w3c.dom.Node;
-import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
/**
* BMPMetadata.
*/
-final class BMPMetadata extends IIOMetadata {
+final class BMPMetadata extends AbstractMetadata {
/** We return metadata in the exact same form as the JRE built-in, to be compatible with the BMPImageWriter. */
public static final String nativeMetadataFormatName = "javax_imageio_bmp_1.0";
@@ -46,46 +45,13 @@ final class BMPMetadata extends IIOMetadata {
private final int[] colorMap;
BMPMetadata(final DIBHeader header, final int[] colorMap) {
+ super(true, nativeMetadataFormatName, "com.sun.imageio.plugins.bmp.BMPMetadataFormat", null, null);
this.header = Validate.notNull(header, "header");
this.colorMap = colorMap == null || colorMap.length == 0 ? null : colorMap;
-
- standardFormatSupported = true;
- }
-
- @Override public boolean isReadOnly() {
- return true;
- }
-
- @Override public Node getAsTree(final String formatName) {
- if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
- return getStandardTree();
- }
- else if (nativeMetadataFormatName.equals(formatName)) {
- return getNativeTree();
- }
- else {
- throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
- }
- }
-
- @Override public void mergeTree(final String formatName, final Node root) {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
- @Override public void reset() {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
}
@Override
- public String getNativeMetadataFormatName() {
- return nativeMetadataFormatName;
- }
-
- private Node getNativeTree() {
+ protected Node getNativeTree() {
IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
addChildNode(root, "BMPVersion", header.getBMPVersion());
@@ -170,7 +136,8 @@ final class BMPMetadata extends IIOMetadata {
return child;
}
- @Override protected IIOMetadataNode getStandardChromaNode() {
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
// NOTE: BMP files may contain a color map, even if true color...
// Not sure if this is a good idea to expose to the meta data,
// as it might be unexpected... Then again...
@@ -197,7 +164,8 @@ final class BMPMetadata extends IIOMetadata {
return null;
}
- @Override protected IIOMetadataNode getStandardCompressionNode() {
+ @Override
+ protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
compressionTypeName.setAttribute("value", "NONE");
@@ -229,7 +197,8 @@ final class BMPMetadata extends IIOMetadata {
// }
}
- @Override protected IIOMetadataNode getStandardDataNode() {
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
// IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
@@ -294,7 +263,8 @@ final class BMPMetadata extends IIOMetadata {
return buffer.toString();
}
- @Override protected IIOMetadataNode getStandardDimensionNode() {
+ @Override
+ protected IIOMetadataNode getStandardDimensionNode() {
if (header.xPixelsPerMeter > 0 || header.yPixelsPerMeter > 0) {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
@@ -302,16 +272,16 @@ final class BMPMetadata extends IIOMetadata {
addChildNode(dimension, "HorizontalPhysicalPixelSpacing", null);
addChildNode(dimension, "VerticalPhysicalPixelSpacing", null);
- // IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
- //
- // if (header.topDown) {
- // imageOrientation.setAttribute("value", "FlipH");
- // }
- // else {
- // imageOrientation.setAttribute("value", "Normal");
- // }
- //
- // dimension.appendChild(imageOrientation);
+// IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
+//
+// if (header.topDown) {
+// imageOrientation.setAttribute("value", "FlipH");
+// }
+// else {
+// imageOrientation.setAttribute("value", "Normal");
+// }
+//
+// dimension.appendChild(imageOrientation);
return dimension;
}
@@ -325,7 +295,8 @@ final class BMPMetadata extends IIOMetadata {
// No tiling
- @Override protected IIOMetadataNode getStandardTransparencyNode() {
+ @Override
+ protected IIOMetadataNode getStandardTransparencyNode() {
return null;
// IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapDescriptor.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapDescriptor.java
index 3587d662..a5a00f6a 100755
--- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapDescriptor.java
+++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapDescriptor.java
@@ -43,6 +43,7 @@ abstract class BitmapDescriptor {
protected final DIBHeader header;
protected BufferedImage image;
+ protected BitmapMask mask;
public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
Validate.notNull(pEntry, "entry");
@@ -69,4 +70,17 @@ abstract class BitmapDescriptor {
protected final int getBitCount() {
return entry.getBitCount() != 0 ? entry.getBitCount() : header.getBitCount();
}
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
+ }
+
+ public final void setMask(final BitmapMask mask) {
+ this.mask = mask;
+ }
+
+ public final boolean hasMask() {
+ return header.getHeight() == getHeight() * 2;
+ }
}
diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapIndexed.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapIndexed.java
index c5d33293..cb2c413a 100755
--- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapIndexed.java
+++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapIndexed.java
@@ -28,8 +28,6 @@
package com.twelvemonkeys.imageio.plugins.bmp;
-import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
-
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
@@ -46,8 +44,6 @@ class BitmapIndexed extends BitmapDescriptor {
protected final int[] bits;
protected final int[] colors;
- private BitmapMask mask;
-
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
super(pEntry, pHeader);
bits = new int[getWidth() * getHeight()];
@@ -65,7 +61,7 @@ class BitmapIndexed extends BitmapDescriptor {
// This is slightly obscure, and should probably be moved..
Hashtable properties = null;
if (entry instanceof DirectoryEntry.CUREntry) {
- properties = new Hashtable(1);
+ properties = new Hashtable<>(1);
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
}
@@ -89,8 +85,6 @@ class BitmapIndexed extends BitmapDescriptor {
raster.setSamples(0, 0, getWidth(), getHeight(), 0, bits);
- //System.out.println("Image: " + image);
-
return image;
}
@@ -100,40 +94,40 @@ class BitmapIndexed extends BitmapDescriptor {
IndexColorModel createColorModel() {
// NOTE: This is a hack to make room for transparent pixel for mask
int bits = getBitCount();
-
+
int colors = this.colors.length;
- int trans = -1;
+ int transparent = -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 = findTransIndexMaybeRemap(this.colors, this.bits);
+ int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
if (index == -1) {
// No duplicate found, increase bitcount
bits++;
- trans = this.colors.length - 1;
+ transparent = this.colors.length - 1;
}
else {
- // Found a duplicate, use it as trans
- trans = index;
+ // Found a duplicate, use it as transparent
+ transparent = index;
colors--;
}
}
// NOTE: Setting hasAlpha to true, makes things work on 1.2
- return new InverseColorMapIndexColorModel(
- bits, colors, this.colors, 0, true, trans,
+ return new IndexColorModel(
+ bits, colors, this.colors, 0, true, transparent,
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
);
}
- private static int findTransIndexMaybeRemap(final int[] pColors, final int[] pBits) {
+ private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
// Look for unused colors, to use as transparent
- final boolean[] used = new boolean[pColors.length - 1];
- for (int pBit : pBits) {
- if (!used[pBit]) {
- used[pBit] = true;
+ boolean[] used = new boolean[colors.length - 1];
+ for (int bit : bits) {
+ if (!used[bit]) {
+ used[bit] = true;
}
}
@@ -144,38 +138,35 @@ class BitmapIndexed extends BitmapDescriptor {
}
// Try to find duplicates in colormap, and remap
- int trans = -1;
+ int transparent = -1;
int duplicate = -1;
- for (int i = 0; trans == -1 && i < pColors.length - 1; i++) {
- for (int j = i + 1; j < pColors.length - 1; j++) {
- if (pColors[i] == pColors[j]) {
- trans = j;
+ for (int i = 0; transparent == -1 && i < colors.length - 1; i++) {
+ for (int j = i + 1; j < colors.length - 1; j++) {
+ if (colors[i] == colors[j]) {
+ transparent = j;
duplicate = i;
break;
}
}
}
- if (trans != -1) {
+ if (transparent != -1) {
// Remap duplicate
- for (int i = 0; i < pBits.length; i++) {
- if (pBits[i] == trans) {
- pBits[i] = duplicate;
+ for (int i = 0; i < bits.length; i++) {
+ if (bits[i] == transparent) {
+ bits[i] = duplicate;
}
}
}
- return trans;
+ return transparent;
}
public BufferedImage getImage() {
if (image == null) {
image = createImageIndexed();
}
+
return image;
}
-
- public void setMask(final BitmapMask pMask) {
- mask = pMask;
- }
}
diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapMask.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapMask.java
index c4831680..5d619cf6 100755
--- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapMask.java
+++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapMask.java
@@ -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 mask;
+ protected final BitmapIndexed bitMask;
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
super(pParent, pHeader);
- mask = new BitmapIndexed(pParent, pHeader);
+ bitMask = new BitmapIndexed(pParent, pHeader);
}
boolean isTransparent(final int pX, final int pY) {
// NOTE: 1: Fully transparent, 0: Opaque...
- return mask.bits[pX + pY * getWidth()] != 0;
+ return bitMask.bits[pX + pY * getWidth()] != 0;
}
public BufferedImage getImage() {
- return mask.getImage();
+ return bitMask.getImage();
}
}
diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapRGB.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapRGB.java
index 11dc17d4..05f43600 100755
--- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapRGB.java
+++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/BitmapRGB.java
@@ -28,7 +28,9 @@
package com.twelvemonkeys.imageio.plugins.bmp;
+import java.awt.*;
import java.awt.image.BufferedImage;
+import java.awt.image.WritableRaster;
/**
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
@@ -43,6 +45,38 @@ class BitmapRGB extends BitmapDescriptor {
}
public BufferedImage getImage() {
+ // Test is mask != null rather than hasMask(), as 32 bit (w/alpha)
+ // might still have bitmask, but we don't read or use it.
+ if (mask != null) {
+ image = createMaskedImage();
+ mask = null;
+ }
+
return image;
}
+
+ private BufferedImage createMaskedImage() {
+ BufferedImage masked = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
+
+ Graphics2D graphics = masked.createGraphics();
+ try {
+ graphics.drawImage(image, 0, 0, null);
+ }
+ finally {
+ graphics.dispose();
+ }
+
+ WritableRaster alphaRaster = masked.getAlphaRaster();
+
+ byte[] trans = {0x0};
+ for (int y = 0; y < getHeight(); y++) {
+ for (int x = 0; x < getWidth(); x++) {
+ if (mask.isTransparent(x, y)) {
+ alphaRaster.setDataElements(x, y, trans);
+ }
+ }
+ }
+
+ return masked;
+ }
}
diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfo.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfo.java
index 45831e58..6444d5a6 100644
--- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfo.java
+++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.bmp;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageReader.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageReader.java
index bd8087ff..12d4e5fe 100644
--- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageReader.java
+++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/DIBImageReader.java
@@ -66,14 +66,15 @@ import java.util.List;
// TODO: Decide whether DirectoryEntry or DIBHeader should be primary source for color count/bit count
// TODO: Support loading icons from DLLs, see
// MSDN
-// Known issue: 256x256 PNG encoded icons does not have IndexColorModel even if stated in DirectoryEntry (seem impossible as the PNGs are all true color)
+// 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 headers = new WeakHashMap();
- private Map descriptors = new WeakWeakMap();
+ private Map headers = new WeakHashMap<>();
+ private Map descriptors = new WeakWeakMap<>();
private ImageReader pngImageReader;
@@ -101,7 +102,7 @@ abstract class DIBImageReader extends ImageReaderBase {
return getImageTypesPNG(entry);
}
- List types = new ArrayList();
+ List types = new ArrayList<>();
DIBHeader header = getHeader(entry);
// Use data from header to create specifier
@@ -121,10 +122,13 @@ abstract class DIBImageReader extends ImageReaderBase {
specifier = ImageTypeSpecifiers.createFromIndexColorModel(indexed.createColorModel());
break;
case 16:
+ // TODO: May have mask?!
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
break;
case 24:
- specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
+ specifier = new BitmapRGB(entry, header).hasMask()
+ ? ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)
+ : ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
break;
case 32:
specifier = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
@@ -184,6 +188,7 @@ abstract class DIBImageReader extends ImageReaderBase {
}
else {
Graphics2D g = destination.createGraphics();
+
try {
g.setComposite(AlphaComposite.Src);
g.drawImage(image, 0, 0, null);
@@ -335,7 +340,7 @@ abstract class DIBImageReader extends ImageReaderBase {
}
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
- readBitmapIndexed1(mask.mask, true);
+ readBitmapIndexed1(mask.bitMask, true);
pBitmap.setMask(mask);
}
@@ -370,7 +375,7 @@ abstract class DIBImageReader extends ImageReaderBase {
}
}
- // NOTE: If we are reading the mask, we don't abort or progress
+ // NOTE: If we are reading the mask, we don't abort or report progress
if (!pAsMask) {
if (abortRequested()) {
processReadAborted();
@@ -455,7 +460,7 @@ abstract class DIBImageReader extends ImageReaderBase {
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
- // Will create TYPE_USHORT_555;
+ // Will create TYPE_USHORT_555
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
DataBuffer buffer = new DataBufferShort(pixels, pixels.length);
WritableRaster raster = Raster.createPackedRaster(
@@ -480,6 +485,8 @@ abstract class DIBImageReader extends ImageReaderBase {
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
+
+ // TODO: Might be mask!?
}
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
@@ -494,16 +501,19 @@ abstract class DIBImageReader extends ImageReaderBase {
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
);
+ int scanlineStride = pBitmap.getWidth() * 3;
+ // BMP rows are padded to 4 byte boundary
+ int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
+
WritableRaster raster = Raster.createInterleavedRaster(
- buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), 3, bOffs, null
+ buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 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!
+ for (int y = 0; y < pBitmap.getHeight(); y++) {
+ int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
+ imageInput.readFully(pixels, offset, scanlineStride);
+ imageInput.skipBytes(rowSizeBytes - scanlineStride);
if (abortRequested()) {
processReadAborted();
@@ -512,6 +522,14 @@ abstract class DIBImageReader extends ImageReaderBase {
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
+
+ // 24 bit icons usually have a bit mask
+ if (pBitmap.hasMask()) {
+ BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
+ readBitmapIndexed1(mask.bitMask, true);
+
+ pBitmap.setMask(mask);
+ }
}
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
@@ -535,6 +553,9 @@ abstract class DIBImageReader extends ImageReaderBase {
}
processImageProgress(100 * y / (float) pBitmap.getHeight());
}
+
+ // There might be a mask here as well, but we'll ignore it,
+ // and use the 8 bit alpha channel in the ARGB pixel data
}
private Directory getDirectory() throws IOException {
diff --git a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfo.java b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfo.java
index c6d8fd36..9b29e1fc 100644
--- a/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfo.java
+++ b/imageio/imageio-bmp/src/main/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.bmp;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderTest.java
index 80ae803b..c426d7ee 100755
--- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderTest.java
+++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/BMPImageReaderTest.java
@@ -1,6 +1,6 @@
package com.twelvemonkeys.imageio.plugins.bmp;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import org.mockito.InOrder;
import org.w3c.dom.Node;
@@ -19,10 +19,12 @@ import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URISyntaxException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.*;
+import static org.junit.Assume.assumeNoException;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
@@ -34,7 +36,7 @@ import static org.mockito.Mockito.*;
* @author last modified by $Author: haraldk$
* @version $Id: BMPImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
-public class BMPImageReaderTest extends ImageReaderAbstractTestCase {
+public class BMPImageReaderTest extends ImageReaderAbstractTest {
protected List getTestData() {
return Arrays.asList(
// BMP Suite "Good"
@@ -90,7 +92,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("bmp");
+ return Collections.singletonList("bmp");
}
protected List getSuffixes() {
@@ -132,7 +134,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCase getMIMETypes() {
- return Arrays.asList("image/bmp");
+ return Collections.singletonList("image/bmp");
}
@Override
@@ -234,7 +236,6 @@ public class BMPImageReaderTest extends ImageReaderAbstractTestCaseHarald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: BMPProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class BMPProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new BMPProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderTest.java
index 45a20c02..fe885d86 100755
--- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderTest.java
+++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURImageReaderTest.java
@@ -1,6 +1,6 @@
package com.twelvemonkeys.imageio.plugins.bmp;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
import org.junit.Test;
@@ -10,6 +10,7 @@ import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import static org.junit.Assert.*;
@@ -21,7 +22,7 @@ import static org.junit.Assert.*;
* @author last modified by $Author: haraldk$
* @version $Id: CURImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
-public class CURImageReaderTest extends ImageReaderAbstractTestCase {
+public class CURImageReaderTest extends ImageReaderAbstractTest {
protected List getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/cur/hand.cur"), new Dimension(32, 32)),
@@ -43,11 +44,11 @@ public class CURImageReaderTest extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("cur");
+ return Collections.singletonList("cur");
}
protected List getSuffixes() {
- return Arrays.asList("cur");
+ return Collections.singletonList("cur");
}
protected List getMIMETypes() {
diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfoTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfoTest.java
new file mode 100644
index 00000000..61a814b2
--- /dev/null
+++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/CURProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.bmp;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * CURProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: CURProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class CURProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new CURProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderTest.java
index 858a26a5..ed5a2adc 100755
--- a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderTest.java
+++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOImageReaderTest.java
@@ -1,6 +1,6 @@
package com.twelvemonkeys.imageio.plugins.bmp;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
import org.junit.Test;
@@ -8,6 +8,7 @@ import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -17,7 +18,7 @@ import java.util.List;
* @author last modified by $Author: haraldk$
* @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
-public class ICOImageReaderTest extends ImageReaderAbstractTestCase {
+public class ICOImageReaderTest extends ImageReaderAbstractTest {
protected List getTestData() {
return Arrays.asList(
new TestData(
@@ -38,7 +39,9 @@ public class ICOImageReaderTest extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("ico");
+ return Collections.singletonList("ico");
}
protected List getSuffixes() {
- return Arrays.asList("ico");
+ return Collections.singletonList("ico");
}
protected List getMIMETypes() {
diff --git a/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfoTest.java b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfoTest.java
new file mode 100644
index 00000000..832d0ec9
--- /dev/null
+++ b/imageio/imageio-bmp/src/test/java/com/twelvemonkeys/imageio/plugins/bmp/ICOProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.bmp;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * ICOProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: ICOProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class ICOProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new ICOProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-bmp/src/test/resources/ico/rgb24bitmask.ico b/imageio/imageio-bmp/src/test/resources/ico/rgb24bitmask.ico
new file mode 100644
index 00000000..a2bcda1c
Binary files /dev/null and b/imageio/imageio-bmp/src/test/resources/ico/rgb24bitmask.ico differ
diff --git a/imageio/imageio-clippath/pom.xml b/imageio/imageio-clippath/pom.xml
index d7ab389c..85d4852a 100755
--- a/imageio/imageio-clippath/pom.xml
+++ b/imageio/imageio-clippath/pom.xml
@@ -1,12 +1,10 @@
-
+
4.0.0
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-clippath
TwelveMonkeys :: ImageIO :: Photoshop Path Support
@@ -22,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
com.twelvemonkeys.imageio
diff --git a/imageio/imageio-core/pom.xml b/imageio/imageio-core/pom.xml
index 6d78c59d..3c295d76 100644
--- a/imageio/imageio-core/pom.xml
+++ b/imageio/imageio-core/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-core
TwelveMonkeys :: ImageIO :: Core
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java
similarity index 66%
rename from imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java
rename to imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java
index 091246c7..d8ff9607 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/AbstractMetadata.java
@@ -26,7 +26,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package com.twelvemonkeys.imageio.plugins.psd;
+package com.twelvemonkeys.imageio;
import org.w3c.dom.Node;
@@ -42,13 +42,15 @@ import java.util.Arrays;
* @author last modified by $Author: haraldk$
* @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$
*/
-abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
- // TODO: Move to core...
+public abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
+ protected AbstractMetadata(final boolean standardFormatSupported,
+ final String nativeFormatName, final String nativeFormatClassName,
+ final String[] extraFormatNames, final String[] extraFormatClassNames) {
+ super(standardFormatSupported, nativeFormatName, nativeFormatClassName, extraFormatNames, extraFormatClassNames);
+ }
- protected AbstractMetadata(final boolean pStandardFormatSupported,
- final String pNativeFormatName, final String pNativeFormatClassName,
- final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) {
- super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames);
+ protected AbstractMetadata() {
+ super(true, null, null, null, null);
}
/**
@@ -63,36 +65,38 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
}
@Override
- public Node getAsTree(final String pFormatName) {
- validateFormatName(pFormatName);
+ public Node getAsTree(final String formatName) {
+ validateFormatName(formatName);
- if (pFormatName.equals(nativeMetadataFormatName)) {
+ if (formatName.equals(nativeMetadataFormatName)) {
return getNativeTree();
}
- else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
+ else if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
return getStandardTree();
}
- // TODO: What about extra formats??
- throw new AssertionError("Unreachable");
+ // Subclasses that supports extra formats need to check for these formats themselves...
+ return null;
+ }
+
+ /**
+ * Default implementation that throws {@code UnsupportedOperationException}.
+ * Subclasses that supports formats other than standard metadata should override this method.
+ *
+ * @throws UnsupportedOperationException
+ */
+ protected Node getNativeTree() {
+ throw new UnsupportedOperationException("getNativeTree");
}
@Override
- public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException {
+ public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException {
assertMutable();
- validateFormatName(pFormatName);
+ validateFormatName(formatName);
- if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) {
- throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot);
- }
-
- Node node = pRoot.getFirstChild();
- while (node != null) {
- // TODO: Merge values from node into this
-
- // Move to the next sibling
- node = node.getNextSibling();
+ if (!root.getNodeName().equals(formatName)) {
+ throw new IIOInvalidTreeException("Root must be " + formatName, root);
}
}
@@ -112,21 +116,19 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
}
}
- protected abstract Node getNativeTree();
-
- protected final void validateFormatName(final String pFormatName) {
+ protected final void validateFormatName(final String formatName) {
String[] metadataFormatNames = getMetadataFormatNames();
if (metadataFormatNames != null) {
for (String metadataFormatName : metadataFormatNames) {
- if (metadataFormatName.equals(pFormatName)) {
+ if (metadataFormatName.equals(formatName)) {
return; // Found, we're ok!
}
}
}
throw new IllegalArgumentException(
- String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames))
+ String.format("Bad format name: \"%s\". Expected one of %s", formatName, Arrays.toString(metadataFormatNames))
);
}
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java
index fbfa7825..90e16063 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java
@@ -29,6 +29,7 @@
package com.twelvemonkeys.imageio;
import com.twelvemonkeys.image.BufferedImageIcon;
+import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.util.IIOUtil;
import javax.imageio.*;
@@ -37,6 +38,9 @@ import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
@@ -424,6 +428,8 @@ public abstract class ImageReaderBase extends ImageReader {
static final String ZOOM_OUT = "zoom-out";
static final String ZOOM_ACTUAL = "zoom-actual";
+ private BufferedImage image;
+
Paint backgroundPaint;
final Paint checkeredBG;
@@ -434,6 +440,7 @@ public abstract class ImageReaderBase extends ImageReader {
setOpaque(false);
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+ image = pImage;
checkeredBG = createTexture();
// For indexed color, default to the color of the transparent pixel, if any
@@ -441,7 +448,7 @@ public abstract class ImageReaderBase extends ImageReader {
backgroundPaint = defaultBG != null ? defaultBG : checkeredBG;
- setupActions(pImage);
+ setupActions();
setComponentPopupMenu(createPopupMenu());
addMouseListener(new MouseAdapter() {
@Override
@@ -451,16 +458,59 @@ public abstract class ImageReaderBase extends ImageReader {
}
}
});
+
+ setTransferHandler(new TransferHandler() {
+ @Override
+ public int getSourceActions(JComponent c) {
+ return COPY;
+ }
+
+ @Override
+ protected Transferable createTransferable(JComponent c) {
+ return new ImageTransferable(image);
+ }
+
+ @Override
+ public boolean importData(JComponent comp, Transferable t) {
+ if (canImport(comp, t.getTransferDataFlavors())) {
+ try {
+ Image transferData = (Image) t.getTransferData(DataFlavor.imageFlavor);
+ image = ImageUtil.toBuffered(transferData);
+ setIcon(new BufferedImageIcon(image));
+
+ return true;
+ }
+ catch (UnsupportedFlavorException | IOException ignore) {
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
+ for (DataFlavor flavor : transferFlavors) {
+ if (flavor.equals(DataFlavor.imageFlavor)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ });
}
- private void setupActions(final BufferedImage pImage) {
+ private void setupActions() {
// Mac weirdness... VK_MINUS/VK_PLUS seems to map to english key map always...
- bindAction(new ZoomAction("Zoom in", pImage, 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0));
- bindAction(new ZoomAction("Zoom out", pImage, .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
- bindAction(new ZoomAction("Zoom actual", pImage), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0));
+ bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0));
+ bindAction(new ZoomAction("Zoom out", .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
+ bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0));
+
+ bindAction(TransferHandler.getCopyAction(), (String) TransferHandler.getCopyAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
+ bindAction(TransferHandler.getPasteAction(), (String) TransferHandler.getPasteAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
}
- private void bindAction(final AbstractAction action, final String key, final KeyStroke... keyStrokes) {
+ private void bindAction(final Action action, final String key, final KeyStroke... keyStrokes) {
for (KeyStroke keyStroke : keyStrokes) {
getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, key);
}
@@ -588,20 +638,18 @@ public abstract class ImageReaderBase extends ImageReader {
}
private class ZoomAction extends AbstractAction {
- private final BufferedImage image;
private final double zoomFactor;
- public ZoomAction(final String name, final BufferedImage image, final double zoomFactor) {
+ public ZoomAction(final String name, final double zoomFactor) {
super(name);
- this.image = image;
this.zoomFactor = zoomFactor;
}
- public ZoomAction(final String name, final BufferedImage image) {
- this(name, image, 0);
+ public ZoomAction(final String name) {
+ this(name, 0);
}
- public void actionPerformed(ActionEvent e) {
+ public void actionPerformed(final ActionEvent e) {
if (zoomFactor <= 0) {
setIcon(new BufferedImageIcon(image));
}
@@ -614,6 +662,33 @@ public abstract class ImageReaderBase extends ImageReader {
}
}
}
+
+ private static class ImageTransferable implements Transferable {
+ private final BufferedImage image;
+
+ public ImageTransferable(final BufferedImage image) {
+ this.image = image;
+ }
+
+ @Override
+ public DataFlavor[] getTransferDataFlavors() {
+ return new DataFlavor[] {DataFlavor.imageFlavor};
+ }
+
+ @Override
+ public boolean isDataFlavorSupported(final DataFlavor flavor) {
+ return DataFlavor.imageFlavor.equals(flavor);
+ }
+
+ @Override
+ public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+ if (isDataFlavorSupported(flavor)) {
+ return image;
+ }
+
+ throw new UnsupportedFlavorException(flavor);
+ }
+ }
}
private static class ExitIfNoWindowPresentHandler extends WindowAdapter {
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CIELabColorConverter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CIELabColorConverter.java
new file mode 100644
index 00000000..3abe5f47
--- /dev/null
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/CIELabColorConverter.java
@@ -0,0 +1,151 @@
+package com.twelvemonkeys.imageio.color;
+
+import com.twelvemonkeys.lang.Validate;
+
+/**
+ * Converts between CIE L*a*b* and sRGB color spaces.
+ */
+// Code adapted from ImageJ's Color_Space_Converter.java (Public Domain):
+// http://rsb.info.nih.gov/ij/plugins/download/Color_Space_Converter.java
+public final class CIELabColorConverter {
+ // TODO: Create interface in the color package?
+ // TODO: Make YCbCr/YCCK -> RGB/CMYK implement same interface?
+
+ public enum Illuminant {
+ D50(new float[] {96.4212f, 100.0f, 82.5188f}),
+ D65(new float[] {95.0429f, 100.0f, 108.8900f});
+
+ private final float[] whitePoint;
+
+ Illuminant(final float[] wp) {
+ whitePoint = Validate.isTrue(wp != null && wp.length == 3, wp, "Bad white point definition: %s");
+ }
+
+ public float[] getWhitePoint() {
+ return whitePoint;
+ }
+ }
+
+ private final float[] whitePoint;
+
+ public CIELabColorConverter(final Illuminant illuminant) {
+ whitePoint = Validate.notNull(illuminant, "illuminant").getWhitePoint();
+ }
+
+ private float clamp(float x) {
+ if (x < 0.0f) {
+ return 0.0f;
+ }
+ else if (x > 255.0f) {
+ return 255.0f;
+ }
+ else {
+ return x;
+ }
+ }
+
+ public void toRGB(float l, float a, float b, float[] rgbResult) {
+ XYZtoRGB(LABtoXYZ(l, a, b, rgbResult), rgbResult);
+ }
+
+ /**
+ * Convert LAB to XYZ.
+ * @param L
+ * @param a
+ * @param b
+ * @return XYZ values
+ */
+ private float[] LABtoXYZ(float L, float a, float b, float[] xyzResult) {
+ // Significant speedup: Removing Math.pow
+ float y = (L + 16.0f) / 116.0f;
+ float y3 = y * y * y; // Math.pow(y, 3.0);
+ float x = (a / 500.0f) + y;
+ float x3 = x * x * x; // Math.pow(x, 3.0);
+ float z = y - (b / 200.0f);
+ float z3 = z * z * z; // Math.pow(z, 3.0);
+
+ if (y3 > 0.008856f) {
+ y = y3;
+ }
+ else {
+ y = (y - (16.0f / 116.0f)) / 7.787f;
+ }
+
+ if (x3 > 0.008856f) {
+ x = x3;
+ }
+ else {
+ x = (x - (16.0f / 116.0f)) / 7.787f;
+ }
+
+ if (z3 > 0.008856f) {
+ z = z3;
+ }
+ else {
+ z = (z - (16.0f / 116.0f)) / 7.787f;
+ }
+
+ xyzResult[0] = x * whitePoint[0];
+ xyzResult[1] = y * whitePoint[1];
+ xyzResult[2] = z * whitePoint[2];
+
+ return xyzResult;
+ }
+
+ /**
+ * Convert XYZ to RGB
+ * @param xyz
+ * @return RGB values
+ */
+ private float[] XYZtoRGB(final float[] xyz, final float[] rgbResult) {
+ return XYZtoRGB(xyz[0], xyz[1], xyz[2], rgbResult);
+ }
+
+ private float[] XYZtoRGB(final float X, final float Y, final float Z, float[] rgbResult) {
+ float x = X / 100.0f;
+ float y = Y / 100.0f;
+ float z = Z / 100.0f;
+
+ float r = x * 3.2406f + y * -1.5372f + z * -0.4986f;
+ float g = x * -0.9689f + y * 1.8758f + z * 0.0415f;
+ float b = x * 0.0557f + y * -0.2040f + z * 1.0570f;
+
+ // assume sRGB
+ if (r > 0.0031308f) {
+ r = ((1.055f * (float) pow(r, 1.0 / 2.4)) - 0.055f);
+ }
+ else {
+ r = (r * 12.92f);
+ }
+
+ if (g > 0.0031308f) {
+ g = ((1.055f * (float) pow(g, 1.0 / 2.4)) - 0.055f);
+ }
+ else {
+ g = (g * 12.92f);
+ }
+
+ if (b > 0.0031308f) {
+ b = ((1.055f * (float) pow(b, 1.0 / 2.4)) - 0.055f);
+ }
+ else {
+ b = (b * 12.92f);
+ }
+
+ // convert 0..1 into 0..255
+ rgbResult[0] = clamp(r * 255);
+ rgbResult[1] = clamp(g * 255);
+ rgbResult[2] = clamp(b * 255);
+
+ return rgbResult;
+ }
+
+ // TODO: Test, to figure out if accuracy is good enough.
+ // Visual inspection looks good! The author claims 5-12% error, worst case up to 25%...
+ // http://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/
+ static double pow(final double a, final double b) {
+ long tmp = Double.doubleToLongBits(a);
+ long tmp2 = (long) (b * (tmp - 4606921280493453312L)) + 4606921280493453312L;
+ return Double.longBitsToDouble(tmp2);
+ }
+}
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java
index 953c8cbd..1e5337fa 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ColorSpaces.java
@@ -52,7 +52,7 @@ import java.util.Properties;
*
* 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.
+ * on the classpath, specifying the full path to the profiles.
* ICC color profiles are probably already present on your system, or
* can be downloaded from
* ICC ,
@@ -84,11 +84,11 @@ public final class ColorSpaces {
public static final int CS_GENERIC_CMYK = 5001;
// Weak references to hold the color spaces while cached
- private static WeakReference adobeRGB1998 = new WeakReference(null);
- private static WeakReference genericCMYK = new WeakReference(null);
+ private static WeakReference adobeRGB1998 = new WeakReference<>(null);
+ private static WeakReference genericCMYK = new WeakReference<>(null);
// Cache for the latest used color spaces
- private static final Map cache = new LRUHashMap(10);
+ private static final Map cache = new LRUHashMap<>(10);
private ColorSpaces() {}
@@ -100,7 +100,8 @@ public final class ColorSpaces {
*
* @param profile the ICC color profile. May not be {@code null}.
* @return an ICC color space
- * @throws IllegalArgumentException if {@code profile} is {@code null}
+ * @throws IllegalArgumentException if {@code profile} is {@code null}.
+ * @throws java.awt.color.CMMException if {@code profile} is invalid.
*/
public static ICC_ColorSpace createColorSpace(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
@@ -161,6 +162,11 @@ public final class ColorSpaces {
if (cs == null) {
cs = new ICC_ColorSpace(profile);
+
+ // Validate the color space, to avoid caching bad color spaces
+ // Will throw IllegalArgumentException or CMMException if the profile is bad
+ cs.fromRGB(new float[] {1f, 0f, 0f});
+
cache.put(key, cs);
}
@@ -195,7 +201,7 @@ public final class ColorSpaces {
* @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) {
+ static boolean isOffendingColorProfile(final ICC_Profile profile) {
Validate.notNull(profile, "profile");
// NOTE:
@@ -213,6 +219,26 @@ public final class ColorSpaces {
return data[ICC_Profile.icHdrRenderingIntent] != 0;
}
+ /**
+ * Tests whether an ICC color profile is valid.
+ * Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
+ *
+ *
+ * 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}.
+ *
+ *
+ * @param profile the ICC color profile. May not be {@code null}.
+ * @return {@code profile} if valid.
+ * @throws IllegalArgumentException if {@code profile} is {@code null}
+ * @throws java.awt.color.CMMException if {@code profile} is invalid.
+ */
+ public static ICC_Profile validateProfile(final ICC_Profile profile) {
+ createColorSpace(profile); // Creating a color space will fail if the profile is bad
+
+ return profile;
+ }
+
/**
* Returns the color space specified by the given color space constant.
*
@@ -249,7 +275,7 @@ public final class ColorSpaces {
}
}
- adobeRGB1998 = new WeakReference(profile);
+ adobeRGB1998 = new WeakReference<>(profile);
}
}
@@ -272,7 +298,7 @@ public final class ColorSpaces {
return CMYKColorSpace.getInstance();
}
- genericCMYK = new WeakReference(profile);
+ genericCMYK = new WeakReference<>(profile);
}
}
@@ -317,7 +343,7 @@ public final class ColorSpaces {
try {
return ICC_Profile.getInstance(profilePath);
}
- catch (IOException ignore) {
+ catch (SecurityException | IOException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
@@ -367,15 +393,18 @@ public final class ColorSpaces {
}
private static class Profiles {
- private static final Properties PROFILES = loadProfiles(Platform.os());
+ private static final Properties PROFILES = loadProfiles();
- private static Properties loadProfiles(final Platform.OperatingSystem os) {
+ private static Properties loadProfiles() {
Properties systemDefaults;
try {
- systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + os.id());
+ systemDefaults = SystemUtil.loadProperties(
+ ColorSpaces.class,
+ "com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
+ );
}
- catch (IOException ignore) {
+ catch (SecurityException | IOException ignore) {
System.err.printf(
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
ignore.getMessage()
@@ -392,10 +421,14 @@ public final class ColorSpaces {
Properties profiles = new Properties(systemDefaults);
try {
- Properties userOverrides = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles");
+ Properties userOverrides = SystemUtil.loadProperties(
+ ColorSpaces.class,
+ "com/twelvemonkeys/imageio/color/icc_profiles"
+ );
profiles.putAll(userOverrides);
}
- catch (IOException ignore) {
+ catch (SecurityException | IOException ignore) {
+ // Most likely, this file won't be there
}
if (DEBUG) {
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ICCProfileSanitizer.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ICCProfileSanitizer.java
index 0156c5ec..10352384 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ICCProfileSanitizer.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/ICCProfileSanitizer.java
@@ -14,7 +14,7 @@ import java.awt.color.ICC_Profile;
interface ICCProfileSanitizer {
void fixProfile(ICC_Profile profile, byte[] profileHeader);
- static class Factory {
+ class Factory {
static ICCProfileSanitizer get() {
// Strategy pattern:
// - KCMSSanitizerStrategy - Current behaviour, default for Java 1.6 and Oracle JRE 1.7
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java
new file mode 100644
index 00000000..3a38023f
--- /dev/null
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/Int16ComponentColorModel.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2014, 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 java.awt.color.ColorSpace;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.DataBuffer;
+
+/**
+ * ComponentColorModel subclass that correctly handles full 16 bit {@code TYPE_SHORT} signed integral samples.
+ **
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: UInt32ColorModel.java,v 1.0 24.01.11 17.51 haraldk Exp$
+ */
+public final class Int16ComponentColorModel extends ComponentColorModel {
+ private final ComponentColorModel delegate;
+
+ public Int16ComponentColorModel(final ColorSpace cs, final boolean hasAlpha, boolean isAlphaPremultiplied) {
+ super(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_SHORT);
+
+ delegate = new ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied, hasAlpha ? TRANSLUCENT : OPAQUE, DataBuffer.TYPE_USHORT);
+ }
+
+ private void remap(final short[] s, final int i) {
+ // MIN ... -1 -> 0 ... MAX
+ // 0 ... MAX -> MIN ... -1
+ short sample = s[i];
+
+ if (sample < 0) {
+ s[i] = (short) (sample - Short.MIN_VALUE);
+ }
+ else {
+ s[i] = (short) (sample + Short.MIN_VALUE);
+ }
+ }
+
+ @Override
+ public int getRed(final Object inData) {
+ remap((short[]) inData, 0);
+
+ return delegate.getRed(inData);
+ }
+
+ @Override
+ public int getGreen(final Object inData) {
+ remap((short[]) inData, 1);
+
+ return delegate.getGreen(inData);
+ }
+
+ @Override
+ public int getBlue(final Object inData) {
+ remap((short[]) inData, 2);
+
+ return delegate.getBlue(inData);
+ }
+}
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/YCbCrConverter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/YCbCrConverter.java
new file mode 100644
index 00000000..f21f712c
--- /dev/null
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/color/YCbCrConverter.java
@@ -0,0 +1,81 @@
+package com.twelvemonkeys.imageio.color;
+
+/**
+ * Fast YCbCr to RGB conversion.
+ *
+ * @author Harald Kuhr
+ * @author Original code by Werner Randelshofer (used by permission).
+ */
+public final class YCbCrConverter {
+ /**
+ * Define tables for YCC->RGB color space conversion.
+ */
+ private final static int SCALEBITS = 16;
+ private final static int MAXJSAMPLE = 255;
+ private final static int CENTERJSAMPLE = 128;
+ private final static int ONE_HALF = 1 << (SCALEBITS - 1);
+
+ private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
+ private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
+ private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
+ private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
+
+ /**
+ * Initializes tables for YCC->RGB color space conversion.
+ */
+ private static void buildYCCtoRGBtable() {
+ if (ColorSpaces.DEBUG) {
+ System.err.println("Building YCC conversion table");
+ }
+
+ for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
+ // i is the actual input pixel value, in the range 0..MAXJSAMPLE
+ // The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
+ // Cr=>R value is nearest int to 1.40200 * x
+ Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
+ // Cb=>B value is nearest int to 1.77200 * x
+ Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
+ // Cr=>G value is scaled-up -0.71414 * x
+ Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
+ // Cb=>G value is scaled-up -0.34414 * x
+ // We also add in ONE_HALF so that need not do it in inner loop
+ Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
+ }
+ }
+
+ static {
+ buildYCCtoRGBtable();
+ }
+
+ public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
+ double y = (yCbCr[offset] & 0xff);
+ double cb = (yCbCr[offset + 1] & 0xff) - 128;
+ double cr = (yCbCr[offset + 2] & 0xff) - 128;
+
+ double lumaRed = coefficients[0];
+ double lumaGreen = coefficients[1];
+ double lumaBlue = coefficients[2];
+
+ int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
+ int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
+ int green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
+
+ rgb[offset] = clamp(red);
+ rgb[offset + 2] = clamp(blue);
+ rgb[offset + 1] = clamp(green);
+ }
+
+ public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
+ int y = yCbCr[offset] & 0xff;
+ int cr = yCbCr[offset + 2] & 0xff;
+ int cb = yCbCr[offset + 1] & 0xff;
+
+ rgb[offset] = clamp(y + Cr_R_LUT[cr]);
+ rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
+ rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
+ }
+
+ private static byte clamp(int val) {
+ return (byte) Math.max(0, Math.min(255, val));
+ }
+}
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java
index a03fa076..61048791 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java
@@ -73,7 +73,7 @@ public class ProviderInfo {
return name.startsWith("com.twelvemonkeys") ? "TwelveMonkeys" : name;
}
- private String fakeVersion(Package pPackage) {
+ private String fakeVersion(final Package pPackage) {
String name = pPackage.getName();
return name.startsWith("com.twelvemonkeys") ? "DEV" : "Unspecified";
}
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfo.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfo.java
index 3b30a3af..a6138816 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfo.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.spi;
import javax.imageio.stream.ImageInputStream;
@@ -24,7 +52,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
private final String[] writerSpiClassNames;
private final Class[] outputTypes = new Class[] {ImageOutputStream.class};
private final boolean supportsStandardStreamMetadata;
- private final String nativeStreameMetadataFormatName;
+ private final String nativeStreamMetadataFormatName;
private final String nativeStreamMetadataFormatClassName;
private final String[] extraStreamMetadataFormatNames;
private final String[] extraStreamMetadataFormatClassNames;
@@ -38,7 +66,8 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
* Creates a provider information instance based on the given class.
*
* @param infoClass the class to get provider information from.
- * The provider info will be taken from the class' package. @throws IllegalArgumentException if {@code pPackage == null}
+ * The provider info will be taken from the class' package.
+ * @throws IllegalArgumentException if {@code pPackage == null}
*/
protected ReaderWriterProviderInfo(final Class extends ReaderWriterProviderInfo> infoClass,
final String[] formatNames,
@@ -68,7 +97,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
this.writerClassName = writerClassName;
this.writerSpiClassNames = writerSpiClassNames;
this.supportsStandardStreamMetadata = supportsStandardStreamMetadata;
- this.nativeStreameMetadataFormatName = nativeStreameMetadataFormatName;
+ this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName;
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
@@ -120,7 +149,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
}
public String nativeStreamMetadataFormatName() {
- return nativeStreameMetadataFormatName;
+ return nativeStreamMetadataFormatName;
}
public String nativeStreamMetadataFormatClassName() {
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java
new file mode 100644
index 00000000..372e5758
--- /dev/null
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageOutputStream.java
@@ -0,0 +1,55 @@
+package com.twelvemonkeys.imageio.stream;
+
+import javax.imageio.stream.ImageOutputStream;
+import javax.imageio.stream.ImageOutputStreamImpl;
+import java.io.IOException;
+
+/**
+ * SubImageOutputStream.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: SubImageOutputStream.java,v 1.0 30/03/15 harald.kuhr Exp$
+ */
+public class SubImageOutputStream extends ImageOutputStreamImpl {
+ private final ImageOutputStream stream;
+
+ public SubImageOutputStream(final ImageOutputStream stream) {
+ this.stream = stream;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ stream.write(b);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ stream.write(b, off, len);
+ }
+
+ @Override
+ public int read() throws IOException {
+ return stream.read();
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ return stream.read(b, off, len);
+ }
+
+ @Override
+ public boolean isCached() {
+ return stream.isCached();
+ }
+
+ @Override
+ public boolean isCachedMemory() {
+ return stream.isCachedMemory();
+ }
+
+ @Override
+ public boolean isCachedFile() {
+ return stream.isCachedFile();
+ }
+}
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java
index 165e4152..05cf4468 100644
--- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiers.java
@@ -28,14 +28,19 @@
package com.twelvemonkeys.imageio.util;
+import com.twelvemonkeys.lang.Validate;
+
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.ColorSpace;
-import java.awt.image.DataBuffer;
-import java.awt.image.IndexColorModel;
+import java.awt.image.*;
+
+import static com.twelvemonkeys.lang.Validate.isTrue;
+import static com.twelvemonkeys.lang.Validate.notNull;
/**
* Factory class for creating {@code ImageTypeSpecifier}s.
- * In most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
+ * Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but
+ * in most cases, this class will delegate to the corresponding methods in {@link ImageTypeSpecifier}.
*
* @see javax.imageio.ImageTypeSpecifier
* @author Harald Kuhr
@@ -54,6 +59,20 @@ public final class ImageTypeSpecifiers {
final int redMask, final int greenMask,
final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) {
+ if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
+ // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
+ notNull(colorSpace, "colorSpace");
+ isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
+ isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
+
+ int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
+
+ ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
+ isAlphaPremultiplied, transferType);
+
+ return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
+ }
+
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
}
@@ -79,7 +98,11 @@ public final class ImageTypeSpecifiers {
}
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType) {
- if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
+ if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) {
+ // As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version
+ return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false);
+ }
+ else if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
// As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version
return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false);
}
@@ -89,7 +112,11 @@ public final class ImageTypeSpecifiers {
}
public static ImageTypeSpecifier createGrayscale(final int bits, final int dataType, final boolean isAlphaPremultiplied) {
- if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
+ if (bits == 16 && dataType == DataBuffer.TYPE_SHORT) {
+ // As the ComponentColorModel is broken for 16 bit signed int, we'll use our own version
+ return new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied);
+ }
+ else if (bits == 32 && dataType == DataBuffer.TYPE_INT) {
// As the ComponentColorModel is broken for 32 bit unsigned int, we'll use our own version
return new UInt32ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, isAlphaPremultiplied);
}
@@ -98,6 +125,34 @@ public final class ImageTypeSpecifiers {
return ImageTypeSpecifier.createGrayscale(bits, dataType, false, isAlphaPremultiplied);
}
+ public static ImageTypeSpecifier createPackedGrayscale(final ColorSpace colorSpace, final int bits, final int dataType) {
+ notNull(colorSpace, "colorSpace");
+ isTrue(colorSpace.getType() == ColorSpace.TYPE_GRAY, colorSpace, "ColorSpace must be TYPE_GRAY");
+ isTrue(bits == 1 || bits == 2 || bits == 4, bits, "bits must be 1, 2, or 4: %s");
+ isTrue(dataType == DataBuffer.TYPE_BYTE, dataType, "dataType must be TYPE_BYTE: %s");
+
+ int numEntries = 1 << bits;
+
+ byte[] arr = new byte[numEntries];
+ byte[] arg = new byte[numEntries];
+ byte[] arb = new byte[numEntries];
+
+ // Scale array values according to color profile..
+ for (int i = 0; i < numEntries; i++) {
+ float[] gray = new float[]{i / (float) (numEntries - 1)};
+ float[] rgb = colorSpace.toRGB(gray);
+
+ arr[i] = (byte) (rgb[0] * 255);
+ arg[i] = (byte) (rgb[1] * 255);
+ arb[i] = (byte) (rgb[2]* 255);
+ }
+
+ ColorModel colorModel = new IndexColorModel(bits, numEntries, arr, arg, arb);
+ SampleModel sampleModel = new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
+
+ return new ImageTypeSpecifier(colorModel, sampleModel);
+ }
+
public static ImageTypeSpecifier createIndexed(final byte[] redLUT, final byte[] greenLUT,
final byte[] blueLUT, final byte[] alphaLUT,
final int bits, final int dataType) {
diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java
new file mode 100644
index 00000000..e79ed3d3
--- /dev/null
+++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/Int16ImageTypeSpecifier.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2014, 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.imageio.color.Int16ComponentColorModel;
+
+import javax.imageio.ImageTypeSpecifier;
+import java.awt.color.ColorSpace;
+import java.awt.image.DataBuffer;
+import java.awt.image.PixelInterleavedSampleModel;
+
+/**
+ * ImageTypeSpecifier for interleaved 16 bit signed integral samples.
+ *
+ * @see com.twelvemonkeys.imageio.color.Int16ColorModel
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: Int16ImageTypeSpecifier.java,v 1.0 24.01.11 17.51 haraldk Exp$
+ */
+final class Int16ImageTypeSpecifier extends ImageTypeSpecifier {
+ Int16ImageTypeSpecifier(final ColorSpace cs, int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) {
+ super(
+ new Int16ComponentColorModel(cs, hasAlpha, isAlphaPremultiplied),
+ new PixelInterleavedSampleModel(
+ DataBuffer.TYPE_SHORT, 1, 1,
+ cs.getNumComponents() + (hasAlpha ? 1 : 0),
+ cs.getNumComponents() + (hasAlpha ? 1 : 0),
+ bandOffsets
+ )
+ );
+ }
+}
diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/CIELabColorConverterTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/CIELabColorConverterTest.java
new file mode 100644
index 00000000..ff3ddbeb
--- /dev/null
+++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/color/CIELabColorConverterTest.java
@@ -0,0 +1,68 @@
+package com.twelvemonkeys.imageio.color;
+
+import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+
+/**
+ * CIELabColorConverterTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: CIELabColorConverterTest.java,v 1.0 22/10/15 harald.kuhr Exp$
+ */
+public class CIELabColorConverterTest {
+ @Test(expected = IllegalArgumentException.class)
+ public void testNoIllumninant() {
+ new CIELabColorConverter(null);
+ }
+
+ @Test
+ public void testD50() {
+ CIELabColorConverter converter = new CIELabColorConverter(Illuminant.D50);
+ float[] rgb = new float[3];
+
+ converter.toRGB(100, -128, -128, rgb);
+ assertArrayEquals(new float[] {0, 255, 255}, rgb, 1);
+
+ converter.toRGB(100, 0, 0, rgb);
+ assertArrayEquals(new float[] {255, 252, 220}, rgb, 5);
+
+ converter.toRGB(0, 0, 0, rgb);
+ assertArrayEquals(new float[] {0, 0, 0}, rgb, 1);
+
+ converter.toRGB(100, 0, 127, rgb);
+ assertArrayEquals(new float[] {255, 249, 0}, rgb, 5);
+
+ converter.toRGB(50, -128, 127, rgb);
+ assertArrayEquals(new float[] {0, 152, 0}, rgb, 2);
+
+ converter.toRGB(50, 127, -128, rgb);
+ assertArrayEquals(new float[] {222, 0, 255}, rgb, 2);
+ }
+
+ @Test
+ public void testD65() {
+ CIELabColorConverter converter = new CIELabColorConverter(Illuminant.D65);
+ float[] rgb = new float[3];
+
+ converter.toRGB(100, -128, -128, rgb);
+ assertArrayEquals(new float[] {0, 255, 255}, rgb, 1);
+
+ converter.toRGB(100, 0, 0, rgb);
+ assertArrayEquals(new float[] {255, 252, 255}, rgb, 5);
+
+ converter.toRGB(0, 0, 0, rgb);
+ assertArrayEquals(new float[] {0, 0, 0}, rgb, 1);
+
+ converter.toRGB(100, 0, 127, rgb);
+ assertArrayEquals(new float[] {255, 250, 0}, rgb, 5);
+
+ converter.toRGB(50, -128, 127, rgb);
+ assertArrayEquals(new float[] {0, 152, 0}, rgb, 3);
+
+ converter.toRGB(50, 127, -128, rgb);
+ assertArrayEquals(new float[] {184, 0, 255}, rgb, 5);
+ }
+}
diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfoTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfoTest.java
new file mode 100644
index 00000000..af0df7ed
--- /dev/null
+++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/spi/ReaderWriterProviderInfoTest.java
@@ -0,0 +1,160 @@
+package com.twelvemonkeys.imageio.spi;
+
+import org.hamcrest.Description;
+import org.junit.Test;
+import org.junit.internal.matchers.TypeSafeMatcher;
+
+import javax.imageio.ImageReader;
+import javax.imageio.ImageWriter;
+import javax.imageio.metadata.IIOMetadataFormat;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ImageWriterSpi;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.*;
+
+/**
+ * ReaderWriterProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: ReaderWriterProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public abstract class ReaderWriterProviderInfoTest {
+
+ private final ReaderWriterProviderInfo providerInfo = createProviderInfo();
+
+ protected abstract ReaderWriterProviderInfo createProviderInfo();
+
+ protected final ReaderWriterProviderInfo getProviderInfo() {
+ return providerInfo;
+ }
+
+ @Test
+ public void readerClassName() throws Exception {
+ assertClassExists(providerInfo.readerClassName(), ImageReader.class);
+ }
+
+ @Test
+ public void readerSpiClassNames() throws Exception {
+ assertClassesExist(providerInfo.readerSpiClassNames(), ImageReaderSpi.class);
+ }
+
+ @Test
+ public void inputTypes() throws Exception {
+ assertNotNull(providerInfo.inputTypes());
+ }
+
+ @Test
+ public void writerClassName() throws Exception {
+ assertClassExists(providerInfo.writerClassName(), ImageWriter.class);
+ }
+
+ @Test
+ public void writerSpiClassNames() throws Exception {
+ assertClassesExist(providerInfo.writerSpiClassNames(), ImageWriterSpi.class);
+ }
+
+ @Test
+ public void outputTypes() throws Exception {
+ assertNotNull(providerInfo.outputTypes());
+ }
+
+ @Test
+ public void nativeStreamMetadataFormatClassName() throws Exception {
+ assertClassExists(providerInfo.nativeStreamMetadataFormatClassName(), IIOMetadataFormat.class);
+ }
+
+ @Test
+ public void extraStreamMetadataFormatClassNames() throws Exception {
+ assertClassesExist(providerInfo.extraStreamMetadataFormatClassNames(), IIOMetadataFormat.class);
+ }
+
+ @Test
+ public void nativeImageMetadataFormatClassName() throws Exception {
+ assertClassExists(providerInfo.nativeImageMetadataFormatClassName(), IIOMetadataFormat.class);
+ }
+
+ @Test
+ public void extraImageMetadataFormatClassNames() throws Exception {
+ assertClassesExist(providerInfo.extraImageMetadataFormatClassNames(), IIOMetadataFormat.class);
+ }
+
+ @Test
+ public void formatNames() {
+ String[] names = providerInfo.formatNames();
+ assertNotNull(names);
+ assertFalse(names.length == 0);
+
+ List list = asList(names);
+
+ for (String name : list) {
+ assertNotNull(name);
+ assertFalse(name.isEmpty());
+
+ assertTrue(list.contains(name.toLowerCase()));
+ assertTrue(list.contains(name.toUpperCase()));
+ }
+ }
+
+ @Test
+ public void suffixes() {
+ String[] suffixes = providerInfo.suffixes();
+ assertNotNull(suffixes);
+ assertFalse(suffixes.length == 0);
+
+ for (String suffix : suffixes) {
+ assertNotNull(suffix);
+ assertFalse(suffix.isEmpty());
+ }
+ }
+
+ @Test
+ public void mimeTypes() {
+ String[] mimeTypes = providerInfo.mimeTypes();
+ assertNotNull(mimeTypes);
+ assertFalse(mimeTypes.length == 0);
+
+ for (String mimeType : mimeTypes) {
+ assertNotNull(mimeType);
+ assertFalse(mimeType.isEmpty());
+
+ assertTrue(mimeType.length() > 1);
+ assertTrue(mimeType.indexOf('/') > 0);
+ assertTrue(mimeType.indexOf('/') < mimeType.length() - 1);
+ }
+ }
+
+ public static void assertClassExists(final String className, final Class type) {
+ if (className != null) {
+ try {
+ final Class> cl = Class.forName(className);
+
+ assertThat(cl, new TypeSafeMatcher>() {
+ @Override
+ public boolean matchesSafely(Class> item) {
+ return type.isAssignableFrom(cl);
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is subclass of ").appendValue(type);
+ }
+ });
+ }
+ catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ fail("Class not found: " + e.getMessage());
+ }
+ }
+ }
+
+ public static void assertClassesExist(final String[] classNames, final Class type) {
+ if (classNames != null) {
+ for (String className : classNames) {
+ assertClassExists(className, type);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java
similarity index 98%
rename from imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java
rename to imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java
index 7b1f2a14..a92f75c2 100644
--- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java
+++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTest.java
@@ -57,13 +57,13 @@ import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
- * ImageReaderAbstractTestCase
+ * ImageReaderAbstractTest
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
- * @version $Id: ImageReaderAbstractTestCase.java,v 1.0 Apr 1, 2008 10:36:46 PM haraldk Exp$
+ * @version $Id: ImageReaderAbstractTest.java,v 1.0 Apr 1, 2008 10:36:46 PM haraldk Exp$
*/
-public abstract class ImageReaderAbstractTestCase {
+public abstract class ImageReaderAbstractTest {
// TODO: Should we really test if the provider is installed?
// - Pro: Tests the META-INF/services config
// - Con: Not all providers should be installed at runtime...
@@ -83,10 +83,7 @@ public abstract class ImageReaderAbstractTestCase {
try {
return getReaderClass().newInstance();
}
- catch (InstantiationException e) {
- throw new RuntimeException(e);
- }
- catch (IllegalAccessException e) {
+ catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@@ -1002,7 +999,7 @@ public abstract class ImageReaderAbstractTestCase {
/*aspectRatio =*/ reader.getAspectRatio(-1);
//assertEquals("Wrong aspect ratio", data.getDimension().width / (float) data.getDimension().height, aspectRatio, 0f);
}
- catch (IndexOutOfBoundsException expected) {
+ catch (IndexOutOfBoundsException ignore) {
}
catch (IOException e) {
fail("Could not read image aspect ratio" + e);
@@ -1391,7 +1388,7 @@ public abstract class ImageReaderAbstractTestCase {
reader.read(0, param);
fail("Expected to throw exception with illegal type specifier");
}
- catch (IIOException expected) {
+ catch (IIOException | IllegalArgumentException expected) {
// TODO: This is thrown by ImageReader.getDestination. But are we happy with that?
String message = expected.getMessage().toLowerCase();
if (!(message.contains("destination") && message.contains("type"))) {
@@ -1399,23 +1396,16 @@ public abstract class ImageReaderAbstractTestCase {
throw expected;
}
}
- catch (IllegalArgumentException expected) {
- String message = expected.getMessage().toLowerCase();
- if (!(message.contains("destination") && message.contains("type"))) {
- // Allow this to bubble up, du to a bug in the Sun PNGImageReader
- throw expected;
- }
- }
}
}
private List createIllegalTypes(Iterator pValidTypes) {
- List allTypes = new ArrayList();
+ List allTypes = new ArrayList<>();
for (int i = BufferedImage.TYPE_INT_RGB; i < BufferedImage.TYPE_BYTE_INDEXED; i++) {
allTypes.add(ImageTypeSpecifier.createFromBufferedImageType(i));
}
- List illegalTypes = new ArrayList(allTypes);
+ List illegalTypes = new ArrayList<>(allTypes);
while (pValidTypes.hasNext()) {
ImageTypeSpecifier valid = pValidTypes.next();
boolean removed = illegalTypes.remove(valid);
@@ -1454,6 +1444,7 @@ public abstract class ImageReaderAbstractTestCase {
assertEquals(reader.getHeight(0) + point.y, image.getHeight());
}
+ @SuppressWarnings("ConstantConditions")
@Test
public void testSetDestinationOffsetNull() throws IOException {
final ImageReader reader = createReader();
@@ -1629,12 +1620,12 @@ public abstract class ImageReaderAbstractTestCase {
throw new IllegalArgumentException("input == null");
}
- sizes = new ArrayList();
- images = new ArrayList();
+ sizes = new ArrayList<>();
+ images = new ArrayList<>();
List sizes = pSizes;
if (sizes == null) {
- sizes = new ArrayList();
+ sizes = new ArrayList<>();
if (pImages != null) {
for (BufferedImage image : pImages) {
sizes.add(new Dimension(image.getWidth(), image.getHeight()));
@@ -1690,6 +1681,7 @@ public abstract class ImageReaderAbstractTestCase {
return sizes.get(pIndex);
}
+ @SuppressWarnings("unused")
public BufferedImage getImage(final int pIndex) {
return images.get(pIndex);
}
diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java
index 88451917..9a286bbf 100644
--- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java
+++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageTypeSpecifiersTest.java
@@ -1,12 +1,11 @@
package com.twelvemonkeys.imageio.util;
+import com.twelvemonkeys.lang.Validate;
import org.junit.Test;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.ColorSpace;
-import java.awt.image.BufferedImage;
-import java.awt.image.DataBuffer;
-import java.awt.image.IndexColorModel;
+import java.awt.image.*;
import static org.junit.Assert.assertEquals;
@@ -40,7 +39,7 @@ public class ImageTypeSpecifiersTest {
}
@Test
- public void testCreatePacked() {
+ public void testCreatePacked32() {
// TYPE_INT_RGB
assertEquals(
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
@@ -61,31 +60,70 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
);
+ }
+
+ @Test
+ public void testCreatePacked16() {
// TYPE_USHORT_555_RGB
assertEquals(
- ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
+ createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
);
- // "SHORT 555 RGB" (impossible for some reason)
-// assertEquals(
-// ImageTypeSpecifier.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false),
-// ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_SHORT, false)
-// );
+ // "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
+
// TYPE_USHORT_565_RGB
assertEquals(
- ImageTypeSpecifier.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
+ createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
);
// "USHORT 4444 ARGB"
assertEquals(
- ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
+ createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false)
);
// "USHORT 4444 ARGB PRE"
assertEquals(
- ImageTypeSpecifier.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
+ createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true)
);
+
+ // Extra: Make sure color models bits is actually 16 (ImageTypeSpecifier equivalent returns 32)
+ assertEquals(16, ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize());
+ }
+
+ @Test
+ public void testCreatePacked8() {
+ // "BYTE 332 RGB"
+ assertEquals(
+ createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
+ ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false)
+ );
+ // "BYTE 2222 ARGB"
+ assertEquals(
+ createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
+ ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false)
+ );
+ // "BYTE 2222 ARGB PRE"
+ assertEquals(
+ createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
+ ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true)
+ );
+
+ // Extra: Make sure color models bits is actually 8 (ImageTypeSpecifiers equivalent returns 32)
+ assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize());
+ }
+
+ private ImageTypeSpecifier createPacked(final ColorSpace colorSpace,
+ final int redMask, final int greenMask, final int blueMask, final int alphaMask,
+ final int transferType, final boolean isAlphaPremultiplied) {
+ Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT, transferType, "transferType: %s");
+
+ int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
+
+ ColorModel colorModel =
+ new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
+
+ return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
@Test
@@ -337,11 +375,7 @@ public class ImageTypeSpecifiersTest {
);
assertEquals(
- ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false), // NOTE: Unsigned TYPE_SHORT makes no sense...
- ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
- );
- assertEquals(
- ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true),
+ new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0}, false, false),
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT)
);
}
@@ -400,19 +434,11 @@ public class ImageTypeSpecifiersTest {
);
assertEquals(
- ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, false),
+ new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, false),
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
);
assertEquals(
- ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, false, true),
- ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
- );
- assertEquals(
- ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, false),
- ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, false)
- );
- assertEquals(
- ImageTypeSpecifier.createGrayscale(16, DataBuffer.TYPE_SHORT, true, true),
+ new Int16ImageTypeSpecifier(ColorSpace.getInstance(ColorSpace.CS_GRAY), new int[] {0, 1}, true, true),
ImageTypeSpecifiers.createGrayscale(16, DataBuffer.TYPE_SHORT, true)
);
}
@@ -437,6 +463,30 @@ public class ImageTypeSpecifiersTest {
);
}
+ @Test
+ public void testCreatePackedGrayscale1() {
+ assertEquals(
+ ImageTypeSpecifier.createGrayscale(1, DataBuffer.TYPE_BYTE, false),
+ ImageTypeSpecifiers.createPackedGrayscale(GRAY, 1, DataBuffer.TYPE_BYTE)
+ );
+ }
+
+ @Test
+ public void testCreatePackedGrayscale2() {
+ assertEquals(
+ ImageTypeSpecifier.createGrayscale(2, DataBuffer.TYPE_BYTE, false),
+ ImageTypeSpecifiers.createPackedGrayscale(GRAY, 2, DataBuffer.TYPE_BYTE)
+ );
+ }
+
+ @Test
+ public void testCreatePackedGrayscale4() {
+ assertEquals(
+ ImageTypeSpecifier.createGrayscale(4, DataBuffer.TYPE_BYTE, false),
+ ImageTypeSpecifiers.createPackedGrayscale(GRAY, 4, DataBuffer.TYPE_BYTE)
+ );
+ }
+
@Test
public void testCreateIndexedByteArrays1to8() {
for (int bits = 1; bits <= 8; bits <<= 1) {
@@ -562,7 +612,6 @@ public class ImageTypeSpecifiersTest {
}
-
private static byte[] createByteLut(final int count) {
byte[] lut = new byte[count];
for (int i = 0; i < count; i++) {
diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTestCase.java
index 67ecd60f..15f9a31d 100755
--- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTestCase.java
+++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageWriterAbstractTestCase.java
@@ -63,6 +63,7 @@ public abstract class ImageWriterAbstractTestCase {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
+ ImageIO.setUseCache(false);
}
protected abstract ImageWriter createImageWriter();
@@ -120,23 +121,20 @@ public abstract class ImageWriterAbstractTestCase {
for (RenderedImage testData : getTestData()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
- writer.setOutput(stream);
- try {
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
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);
}
}
+ @SuppressWarnings("ConstantConditions")
@Test
public void testWriteNull() throws IOException {
ImageWriter writer = createImageWriter();
diff --git a/imageio/imageio-core/todo.txt b/imageio/imageio-core/todo.txt
index 4e48a7fa..fd469c86 100755
--- a/imageio/imageio-core/todo.txt
+++ b/imageio/imageio-core/todo.txt
@@ -1,8 +1,7 @@
- Rename to imageio-common?
-- Separate modules for more for more plugins
- - 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?
+- Add stream support for NIO and NIO2 stuff? Path, ByteChannel
+- Faster RAF support (need to buffer the slow readShort/readInt/readLong/readUnsignedShort/readUnsignedInt)
+- See if it's possible to use FileImageInputStream for FileChannels and FileInputStream to avoid the caching
+- And the above for output streams too...
DONE:
- Split up into separate plugins (modules), to allow easier configuration
\ No newline at end of file
diff --git a/imageio/imageio-hdr/pom.xml b/imageio/imageio-hdr/pom.xml
new file mode 100644
index 00000000..20efae92
--- /dev/null
+++ b/imageio/imageio-hdr/pom.xml
@@ -0,0 +1,30 @@
+
+
+ 4.0.0
+
+ com.twelvemonkeys.imageio
+ imageio
+ 3.3-SNAPSHOT
+
+ imageio-hdr
+ TwelveMonkeys :: ImageIO :: HDR plugin
+
+ ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR).
+
+
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+
+
+ com.twelvemonkeys.imageio
+ imageio-core
+ test-jar
+
+
+ com.twelvemonkeys.imageio
+ imageio-metadata
+
+
+
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java
new file mode 100644
index 00000000..170b8da0
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDR.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015, 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.hdr;
+
+/**
+ * HDR.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDR.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+interface HDR {
+ byte[] RADIANCE_MAGIC = new byte[] {'#', '?', 'R', 'A', 'D', 'I', 'A', 'N', 'C', 'E'};
+ byte[] RGBE_MAGIC = new byte[] {'#', '?', 'R', 'G', 'B', 'E'};
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java
new file mode 100644
index 00000000..7ce3ffa3
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRHeader.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2015, 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.hdr;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+
+/**
+ * HDRHeader.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRHeader.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+final class HDRHeader {
+ private static final String KEY_FORMAT = "FORMAT=";
+ private static final String KEY_PRIMARIES = "PRIMARIES=";
+ private static final String KEY_EXPOSURE = "EXPOSURE=";
+ private static final String KEY_GAMMA = "GAMMA=";
+ private static final String KEY_SOFTWARE = "SOFTWARE=";
+
+ private int width;
+ private int height;
+
+ private String software;
+
+ public static HDRHeader read(final ImageInputStream stream) throws IOException {
+ HDRHeader header = new HDRHeader();
+
+ while (true) {
+ String line = stream.readLine().trim();
+
+ if (line.isEmpty()) {
+ // This is the last line before the dimensions
+ break;
+ }
+
+ if (line.startsWith("#?")) {
+ // Program specifier, don't need that...
+ }
+ else if (line.startsWith("#")) {
+ // Comment (ignore)
+ }
+ else if (line.startsWith(KEY_FORMAT)) {
+ String format = line.substring(KEY_FORMAT.length()).trim();
+
+ if (!format.equals("32-bit_rle_rgbe")) {
+ throw new IIOException("Unsupported format \"" + format + "\"(expected \"32-bit_rle_rgbe\")");
+ }
+ // TODO: Support the 32-bit_rle_xyze format
+ }
+ else if (line.startsWith(KEY_PRIMARIES)) {
+ // TODO: We are going to need these values...
+ // Should contain 8 (RGB + white point) coordinates
+ }
+ else if (line.startsWith(KEY_EXPOSURE)) {
+ // TODO: We are going to need these values...
+ }
+ else if (line.startsWith(KEY_GAMMA)) {
+ // TODO: We are going to need these values...
+ }
+ else if (line.startsWith(KEY_SOFTWARE)) {
+ header.software = line.substring(KEY_SOFTWARE.length()).trim();
+ }
+ else {
+ // ...ignore
+ }
+ }
+
+ // TODO: Proper parsing of width/height and orientation!
+ String dimensionsLine = stream.readLine().trim();
+ String[] dims = dimensionsLine.split("\\s");
+
+ if (dims[0].equals("-Y") && dims[2].equals("+X")) {
+ header.height = Integer.parseInt(dims[1]);
+ header.width = Integer.parseInt(dims[3]);
+
+ return header;
+ }
+ else {
+ throw new IIOException("Unsupported RGBE orientation (expected \"-Y ... +X ...\")");
+ }
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public String getSoftware() {
+ return software;
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java
new file mode 100644
index 00000000..32490da5
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReadParam.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015, 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.hdr;
+
+import com.twelvemonkeys.imageio.plugins.hdr.tonemap.DefaultToneMapper;
+import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
+
+import javax.imageio.ImageReadParam;
+
+/**
+ * HDRImageReadParam.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRImageReadParam.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public final class HDRImageReadParam extends ImageReadParam {
+ static final ToneMapper DEFAULT_TONE_MAPPER = new DefaultToneMapper(.1f);
+
+ private ToneMapper toneMapper = DEFAULT_TONE_MAPPER;
+
+ public ToneMapper getToneMapper() {
+ return toneMapper;
+ }
+
+ public void setToneMapper(final ToneMapper toneMapper) {
+ this.toneMapper = toneMapper != null ? toneMapper : DEFAULT_TONE_MAPPER;
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java
new file mode 100644
index 00000000..e24fb5fc
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReader.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2015, 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.hdr;
+
+import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.plugins.hdr.tonemap.ToneMapper;
+import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.awt.color.ColorSpace;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBuffer;
+import java.awt.image.Raster;
+import java.awt.image.WritableRaster;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * HDRImageReader.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRImageReader.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+public final class HDRImageReader extends ImageReaderBase {
+ // Specs: http://radsite.lbl.gov/radiance/refer/filefmts.pdf
+
+ private HDRHeader header;
+
+ protected HDRImageReader(final ImageReaderSpi provider) {
+ super(provider);
+ }
+
+ @Override
+ protected void resetMembers() {
+ header = null;
+ }
+
+ private void readHeader() throws IOException {
+ if (header == null) {
+ header = HDRHeader.read(imageInput);
+
+ imageInput.flushBefore(imageInput.getStreamPosition());
+ }
+
+ imageInput.seek(imageInput.getFlushedPosition());
+ }
+
+ @Override
+ public int getWidth(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return header.getWidth();
+ }
+
+ @Override
+ public int getHeight(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return header.getHeight();
+ }
+
+ @Override
+ public Iterator getImageTypes(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ return Collections.singletonList(ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {0, 1, 2}, DataBuffer.TYPE_FLOAT, false, false)).iterator();
+ }
+
+ @Override
+ public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ int width = getWidth(imageIndex);
+ int height = getHeight(imageIndex);
+
+ BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
+
+ Rectangle srcRegion = new Rectangle();
+ Rectangle destRegion = new Rectangle();
+ computeRegions(param, width, height, destination, srcRegion, destRegion);
+
+ WritableRaster raster = destination.getRaster()
+ .createWritableChild(destRegion.x, destRegion.y, destRegion.width, destRegion.height, 0, 0, null);
+
+ int xSub = param != null ? param.getSourceXSubsampling() : 1;
+ int ySub = param != null ? param.getSourceYSubsampling() : 1;
+
+ // Allow pluggable tone mapper via ImageReadParam
+ ToneMapper toneMapper = param instanceof HDRImageReadParam
+ ? ((HDRImageReadParam) param).getToneMapper()
+ : HDRImageReadParam.DEFAULT_TONE_MAPPER;
+
+ byte[] rowRGBE = new byte[width * 4];
+ float[] rgb = new float[3];
+
+ processImageStarted(imageIndex);
+
+ // Process one scanline of RGBE data at a time
+ for (int srcY = 0; srcY < height; srcY++) {
+ int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
+ if (dstY >= destRegion.height) {
+ break;
+ }
+
+ RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
+
+ if (srcY % ySub == 0 && dstY >= destRegion.y) {
+ for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
+ int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
+ if (dstX >= destRegion.width) {
+ break;
+ }
+
+ RGBE.rgbe2float(rgb, rowRGBE, srcX * 4);
+
+ // Map/clamp RGB values into visible range, normally [0...1]
+ toneMapper.map(rgb);
+
+ raster.setDataElements(dstX, dstY, rgb);
+ }
+ }
+
+ processImageProgress(srcY * 100f / height);
+
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+ }
+
+ processImageComplete();
+
+ return destination;
+ }
+
+ @Override
+ public boolean canReadRaster() {
+ return true;
+ }
+
+ @Override
+ public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ int width = getWidth(imageIndex);
+ int height = getHeight(imageIndex);
+
+ Rectangle srcRegion = new Rectangle();
+ Rectangle destRegion = new Rectangle();
+ computeRegions(param, width, height, null, srcRegion, destRegion);
+ destRegion = srcRegion; // We don't really care about destination for raster
+
+ BufferedImage destination = new BufferedImage(srcRegion.width, srcRegion.height, BufferedImage.TYPE_4BYTE_ABGR);
+ WritableRaster raster = destination.getRaster();
+
+ int xSub = param != null ? param.getSourceXSubsampling() : 1;
+ int ySub = param != null ? param.getSourceYSubsampling() : 1;
+
+ byte[] rowRGBE = new byte[width * 4];
+ byte[] pixelRGBE = new byte[width];
+
+ processImageStarted(imageIndex);
+
+ // Process one scanline of RGBE data at a time
+ for (int srcY = 0; srcY < height; srcY++) {
+ int dstY = ((srcY - srcRegion.y) / ySub) + destRegion.y;
+ if (dstY >= destRegion.height) {
+ break;
+ }
+
+ RGBE.readPixelsRawRLE(imageInput, rowRGBE, 0, width, 1);
+
+ if (srcY % ySub == 0 && dstY >= destRegion.y) {
+ for (int srcX = srcRegion.x; srcX < srcRegion.x + srcRegion.width; srcX += xSub) {
+ int dstX = ((srcX - srcRegion.x) / xSub) + destRegion.x;
+ if (dstX >= destRegion.width) {
+ break;
+ }
+
+ System.arraycopy(rowRGBE, srcX * 4, pixelRGBE, 0, 4);
+ raster.setDataElements(dstX, dstY, pixelRGBE);
+ }
+ }
+
+ processImageProgress(srcY * 100f / height);
+
+ if (abortRequested()) {
+ processReadAborted();
+ break;
+ }
+ }
+
+ processImageComplete();
+
+ return destination.getRaster();
+ }
+
+ @Override
+ public ImageReadParam getDefaultReadParam() {
+ return new HDRImageReadParam();
+ }
+
+ @Override
+ public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return new HDRMetadata(header);
+ }
+
+ public static void main(final String[] args) throws IOException {
+ File file = new File(args[0]);
+
+ BufferedImage image = ImageIO.read(file);
+
+ showIt(image, file.getName());
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java
new file mode 100644
index 00000000..380313be
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderSpi.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2015, 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.hdr;
+
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
+
+import javax.imageio.ImageReader;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * HDRImageReaderSpi.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRImageReaderSpi.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+public final class HDRImageReaderSpi extends ImageReaderSpiBase {
+ public HDRImageReaderSpi() {
+ super(new HDRProviderInfo());
+ }
+
+ @Override
+ public boolean canDecodeInput(final Object source) throws IOException {
+ if (!(source instanceof ImageInputStream)) {
+ return false;
+ }
+
+ ImageInputStream stream = (ImageInputStream) source;
+
+ stream.mark();
+
+ try {
+ // NOTE: All images I have found starts with #?RADIANCE (or has no #? line at all),
+ // although some sources claim that #?RGBE is also used.
+ byte[] magic = new byte[HDR.RADIANCE_MAGIC.length];
+ stream.readFully(magic);
+
+ return Arrays.equals(HDR.RADIANCE_MAGIC, magic)
+ || Arrays.equals(HDR.RGBE_MAGIC, Arrays.copyOf(magic, 6));
+ }
+ finally {
+ stream.reset();
+ }
+ }
+
+ @Override
+ public ImageReader createReaderInstance(Object extension) throws IOException {
+ return new HDRImageReader(this);
+ }
+
+ @Override
+ public String getDescription(final Locale locale) {
+ return "Radiance RGBE High Dynaimc Range (HDR) image reader";
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java
new file mode 100755
index 00000000..cd5422eb
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRMetadata.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2015, 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.hdr;
+
+import com.twelvemonkeys.imageio.AbstractMetadata;
+
+import javax.imageio.metadata.IIOMetadataNode;
+
+final class HDRMetadata extends AbstractMetadata {
+ private final HDRHeader header;
+
+ HDRMetadata(final HDRHeader header) {
+ this.header = header;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
+ IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
+
+ IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
+ chroma.appendChild(csType);
+ csType.setAttribute("name", "RGB");
+ // TODO: Support XYZ
+
+ IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
+ numChannels.setAttribute("value", "3");
+ chroma.appendChild(numChannels);
+
+ IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
+ blackIsZero.setAttribute("value", "TRUE");
+ chroma.appendChild(blackIsZero);
+
+ return chroma;
+ }
+
+ // No compression
+
+ @Override
+ protected IIOMetadataNode getStandardCompressionNode() {
+ IIOMetadataNode node = new IIOMetadataNode("Compression");
+
+ IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
+ compressionTypeName.setAttribute("value", "RLE");
+ node.appendChild(compressionTypeName);
+
+ IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
+ lossless.setAttribute("value", "TRUE");
+ node.appendChild(lossless);
+
+ return node;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
+ IIOMetadataNode node = new IIOMetadataNode("Data");
+
+ IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
+ sampleFormat.setAttribute("value", "UnsignedIntegral");
+ node.appendChild(sampleFormat);
+
+ IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
+ bitsPerSample.setAttribute("value", "8 8 8 8");
+ node.appendChild(bitsPerSample);
+
+ return node;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardDimensionNode() {
+ IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
+
+ // TODO: Support other orientations
+ IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
+ imageOrientation.setAttribute("value", "Normal");
+ dimension.appendChild(imageOrientation);
+
+ return dimension;
+ }
+
+ // No document node
+
+ @Override
+ protected IIOMetadataNode getStandardTextNode() {
+ if (header.getSoftware() != null) {
+ IIOMetadataNode text = new IIOMetadataNode("Text");
+
+ IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
+ textEntry.setAttribute("keyword", "Software");
+ textEntry.setAttribute("value", header.getSoftware());
+ text.appendChild(textEntry);
+
+ return text;
+ }
+
+ return null;
+ }
+
+ // No tiling
+
+ // No transparency
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java
new file mode 100644
index 00000000..a11a215f
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfo.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2015, 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.hdr;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+
+/**
+ * HDRProviderInfo.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRProviderInfo.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+final class HDRProviderInfo extends ReaderWriterProviderInfo {
+ protected HDRProviderInfo() {
+ super(
+ HDRProviderInfo.class,
+ new String[] {"HDR", "hdr", "RGBE", "rgbe"},
+ new String[] {"hdr", "rgbe", "xyze", "pic"},
+ new String[] {"image/vnd.radiance"},
+ "com.twelvemonkeys.imageio.plugins.hdr.HDRImageReader",
+ new String[]{"com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi"},
+ null,
+ null,
+ false, null, null, null, null,
+ true, null, null, null, null
+ );
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java
new file mode 100644
index 00000000..b64607e1
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/RGBE.java
@@ -0,0 +1,494 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import java.io.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This file contains code to read and write four byte rgbe file format
+ * developed by Greg Ward. It handles the conversions between rgbe and
+ * pixels consisting of floats. The data is assumed to be an array of floats.
+ * By default there are three floats per pixel in the order red, green, blue.
+ * (RGBE_DATA_??? values control this.) Only the mimimal header reading and
+ * writing is implemented. Each routine does error checking and will return
+ * a status value as defined below. This code is intended as a skeleton so
+ * feel free to modify it to suit your needs.
+ *
+ * Ported to Java and restructured by Kenneth Russell.
+ * posted to http://www.graphics.cornell.edu/~bjw/
+ * written by Bruce Walter (bjw@graphics.cornell.edu) 5/26/95
+ * based on code written by Greg Ward
+ *
+ * Source: https://java.net/projects/jogl-demos/sources/svn/content/trunk/src/demos/hdr/RGBE.java
+ */
+final class RGBE {
+ // Flags indicating which fields in a Header are valid
+ private static final int VALID_PROGRAMTYPE = 0x01;
+ private static final int VALID_GAMMA = 0x02;
+ private static final int VALID_EXPOSURE = 0x04;
+
+ private static final String gammaString = "GAMMA=";
+ private static final String exposureString = "EXPOSURE=";
+
+ private static final Pattern widthHeightPattern = Pattern.compile("-Y (\\d+) \\+X (\\d+)");
+
+ public static class Header {
+ // Indicates which fields are valid
+ private int valid;
+
+ // Listed at beginning of file to identify it after "#?".
+ // Defaults to "RGBE"
+ private String programType;
+
+ // Image has already been gamma corrected with given gamma.
+ // Defaults to 1.0 (no correction)
+ private float gamma;
+
+ // A value of 1.0 in an image corresponds to
+ // watts/steradian/m^2. Defaults to 1.0.
+ private float exposure;
+
+ // Width and height of image
+ private int width;
+ private int height;
+
+ private Header(int valid,
+ String programType,
+ float gamma,
+ float exposure,
+ int width,
+ int height) {
+ this.valid = valid;
+ this.programType = programType;
+ this.gamma = gamma;
+ this.exposure = exposure;
+ this.width = width;
+ this.height = height;
+ }
+
+ public boolean isProgramTypeValid() {
+ return ((valid & VALID_PROGRAMTYPE) != 0);
+ }
+
+ public boolean isGammaValid() {
+ return ((valid & VALID_GAMMA) != 0);
+ }
+
+ public boolean isExposureValid() {
+ return ((valid & VALID_EXPOSURE) != 0);
+ }
+
+ public String getProgramType() {
+ return programType;
+ }
+
+ public float getGamma() {
+ return gamma;
+ }
+
+ public float getExposure() {
+ return exposure;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer();
+ if (isProgramTypeValid()) {
+ buf.append(" Program type: ");
+ buf.append(getProgramType());
+ }
+ buf.append(" Gamma");
+ if (isGammaValid()) {
+ buf.append(" [valid]");
+ }
+ buf.append(": ");
+ buf.append(getGamma());
+ buf.append(" Exposure");
+ if (isExposureValid()) {
+ buf.append(" [valid]");
+ }
+ buf.append(": ");
+ buf.append(getExposure());
+ buf.append(" Width: ");
+ buf.append(getWidth());
+ buf.append(" Height: ");
+ buf.append(getHeight());
+ return buf.toString();
+ }
+ }
+
+ public static Header readHeader(final DataInput in) throws IOException {
+ int valid = 0;
+ String programType = null;
+ float gamma = 1.0f;
+ float exposure = 1.0f;
+ int width = 0;
+ int height = 0;
+
+ String buf = in.readLine();
+ if (buf == null) {
+ throw new IOException("Unexpected EOF reading magic token");
+ }
+ if (buf.charAt(0) == '#' && buf.charAt(1) == '?') {
+ valid |= VALID_PROGRAMTYPE;
+ programType = buf.substring(2);
+ buf = in.readLine();
+ if (buf == null) {
+ throw new IOException("Unexpected EOF reading line after magic token");
+ }
+ }
+
+ boolean foundFormat = false;
+ boolean done = false;
+ while (!done) {
+ if (buf.equals("FORMAT=32-bit_rle_rgbe")) {
+ foundFormat = true;
+ }
+ else if (buf.startsWith(gammaString)) {
+ valid |= VALID_GAMMA;
+ gamma = Float.parseFloat(buf.substring(gammaString.length()));
+ }
+ else if (buf.startsWith(exposureString)) {
+ valid |= VALID_EXPOSURE;
+ exposure = Float.parseFloat(buf.substring(exposureString.length()));
+ }
+ else {
+ Matcher m = widthHeightPattern.matcher(buf);
+ if (m.matches()) {
+ width = Integer.parseInt(m.group(2));
+ height = Integer.parseInt(m.group(1));
+ done = true;
+ }
+ }
+
+ if (!done) {
+ buf = in.readLine();
+ if (buf == null) {
+ throw new IOException("Unexpected EOF reading header");
+ }
+ }
+ }
+
+ if (!foundFormat) {
+ throw new IOException("No FORMAT specifier found");
+ }
+
+ return new Header(valid, programType, gamma, exposure, width, height);
+ }
+
+ /**
+ * Simple read routine. Will not correctly handle run length encoding.
+ */
+ public static void readPixels(DataInput in, float[] data, int numpixels) throws IOException {
+ byte[] rgbe = new byte[4];
+ float[] rgb = new float[3];
+ int offset = 0;
+
+ while (numpixels-- > 0) {
+ in.readFully(rgbe);
+
+ rgbe2float(rgb, rgbe, 0);
+
+ data[offset++] = rgb[0];
+ data[offset++] = rgb[1];
+ data[offset++] = rgb[2];
+ }
+ }
+
+ public static void readPixelsRaw(DataInput in, byte[] data, int offset, int numpixels) throws IOException {
+ int numExpected = 4 * numpixels;
+ in.readFully(data, offset, numExpected);
+ }
+
+ public static void readPixelsRawRLE(DataInput in, byte[] data, int offset,
+ int scanline_width, int num_scanlines) throws IOException {
+ byte[] rgbe = new byte[4];
+ byte[] scanline_buffer = null;
+ int ptr, ptr_end;
+ int count;
+ byte[] buf = new byte[2];
+
+ if ((scanline_width < 8) || (scanline_width > 0x7fff)) {
+ // run length encoding is not allowed so read flat
+ readPixelsRaw(in, data, offset, scanline_width * num_scanlines);
+ }
+
+ // read in each successive scanline
+ while (num_scanlines > 0) {
+ in.readFully(rgbe);
+
+ if ((rgbe[0] != 2) || (rgbe[1] != 2) || ((rgbe[2] & 0x80) != 0)) {
+ // this file is not run length encoded
+ data[offset++] = rgbe[0];
+ data[offset++] = rgbe[1];
+ data[offset++] = rgbe[2];
+ data[offset++] = rgbe[3];
+ readPixelsRaw(in, data, offset, scanline_width * num_scanlines - 1);
+ }
+
+ if ((((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) != scanline_width) {
+ throw new IOException("Wrong scanline width " +
+ (((rgbe[2] & 0xFF) << 8) | (rgbe[3] & 0xFF)) +
+ ", expected " + scanline_width);
+ }
+
+ if (scanline_buffer == null) {
+ scanline_buffer = new byte[4 * scanline_width];
+ }
+
+ ptr = 0;
+ // read each of the four channels for the scanline into the buffer
+ for (int i = 0; i < 4; i++) {
+ ptr_end = (i + 1) * scanline_width;
+ while (ptr < ptr_end) {
+ in.readFully(buf);
+
+ if ((buf[0] & 0xFF) > 128) {
+ // a run of the same value
+ count = (buf[0] & 0xFF) - 128;
+ if ((count == 0) || (count > ptr_end - ptr)) {
+ throw new IOException("Bad scanline data");
+ }
+ while (count-- > 0) {
+ scanline_buffer[ptr++] = buf[1];
+ }
+ }
+ else {
+ // a non-run
+ count = buf[0] & 0xFF;
+ if ((count == 0) || (count > ptr_end - ptr)) {
+ throw new IOException("Bad scanline data");
+ }
+ scanline_buffer[ptr++] = buf[1];
+ if (--count > 0) {
+ in.readFully(scanline_buffer, ptr, count);
+ ptr += count;
+ }
+ }
+ }
+ }
+ // copy byte data to output
+ for (int i = 0; i < scanline_width; i++) {
+ data[offset++] = scanline_buffer[i];
+ data[offset++] = scanline_buffer[i + scanline_width];
+ data[offset++] = scanline_buffer[i + 2 * scanline_width];
+ data[offset++] = scanline_buffer[i + 3 * scanline_width];
+ }
+ num_scanlines--;
+ }
+ }
+
+ /**
+ * Standard conversion from float pixels to rgbe pixels.
+ */
+ public static void float2rgbe(byte[] rgbe, float red, float green, float blue) {
+ float v;
+ int e;
+
+ v = red;
+ if (green > v) {
+ v = green;
+ }
+ if (blue > v) {
+ v = blue;
+ }
+ if (v < 1e-32f) {
+ rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0;
+ }
+ else {
+ FracExp fe = frexp(v);
+ v = (float) (fe.getFraction() * 256.0 / v);
+ rgbe[0] = (byte) (red * v);
+ rgbe[1] = (byte) (green * v);
+ rgbe[2] = (byte) (blue * v);
+ rgbe[3] = (byte) (fe.getExponent() + 128);
+ }
+ }
+
+ /**
+ * Standard conversion from rgbe to float pixels. Note: Ward uses
+ * ldexp(col+0.5,exp-(128+8)). However we wanted pixels in the
+ * range [0,1] to map back into the range [0,1].
+ */
+ public static void rgbe2float(float[] rgb, byte[] rgbe, int startRGBEOffset) {
+ float f;
+
+ if (rgbe[startRGBEOffset + 3] != 0) { // nonzero pixel
+ f = (float) ldexp(1.0, (rgbe[startRGBEOffset + 3] & 0xFF) - (128 + 8));
+ rgb[0] = (rgbe[startRGBEOffset + 0] & 0xFF) * f;
+ rgb[1] = (rgbe[startRGBEOffset + 1] & 0xFF) * f;
+ rgb[2] = (rgbe[startRGBEOffset + 2] & 0xFF) * f;
+ }
+ else {
+ rgb[0] = 0;
+ rgb[1] = 0;
+ rgb[2] = 0;
+ }
+ }
+
+ public static double ldexp(double value, int exp) {
+ if (!finite(value) || value == 0.0) {
+ return value;
+ }
+ value = scalbn(value, exp);
+ // No good way to indicate errno (want to avoid throwing
+ // exceptions because don't know about stability of calculations)
+ // if(!finite(value)||value==0.0) errno = ERANGE;
+ return value;
+ }
+
+ //----------------------------------------------------------------------
+ // Internals only below this point
+ //
+
+ //----------------------------------------------------------------------
+ // Math routines, some fdlibm-derived
+ //
+
+ static class FracExp {
+ private double fraction;
+ private int exponent;
+
+ public FracExp(double fraction, int exponent) {
+ this.fraction = fraction;
+ this.exponent = exponent;
+ }
+
+ public double getFraction() {
+ return fraction;
+ }
+
+ public int getExponent() {
+ return exponent;
+ }
+ }
+
+ private static final double two54 = 1.80143985094819840000e+16; // 43500000 00000000
+ private static final double twom54 = 5.55111512312578270212e-17; // 0x3C900000 0x00000000
+ private static final double huge = 1.0e+300;
+ private static final double tiny = 1.0e-300;
+
+ private static int hi(double x) {
+ long bits = Double.doubleToRawLongBits(x);
+ return (int) (bits >>> 32);
+ }
+
+ private static int lo(double x) {
+ long bits = Double.doubleToRawLongBits(x);
+ return (int) bits;
+ }
+
+ private static double fromhilo(int hi, int lo) {
+ return Double.longBitsToDouble((((long) hi) << 32) |
+ (((long) lo) & 0xFFFFFFFFL));
+ }
+
+ private static FracExp frexp(double x) {
+ int hx = hi(x);
+ int ix = 0x7fffffff & hx;
+ int lx = lo(x);
+ int e = 0;
+ if (ix >= 0x7ff00000 || ((ix | lx) == 0)) {
+ return new FracExp(x, e); // 0,inf,nan
+ }
+ if (ix < 0x00100000) { // subnormal
+ x *= two54;
+ hx = hi(x);
+ ix = hx & 0x7fffffff;
+ e = -54;
+ }
+ e += (ix >> 20) - 1022;
+ hx = (hx & 0x800fffff) | 0x3fe00000;
+ lx = lo(x);
+ return new FracExp(fromhilo(hx, lx), e);
+ }
+
+ private static boolean finite(double x) {
+ int hx;
+ hx = hi(x);
+ return (((hx & 0x7fffffff) - 0x7ff00000) >> 31) != 0;
+ }
+
+ /**
+ * copysign(double x, double y)
+ * copysign(x,y) returns a value with the magnitude of x and
+ * with the sign bit of y.
+ */
+ private static double copysign(double x, double y) {
+ return fromhilo((hi(x) & 0x7fffffff) | (hi(y) & 0x80000000), lo(x));
+ }
+
+ /**
+ * scalbn (double x, int n)
+ * scalbn(x,n) returns x* 2**n computed by exponent
+ * manipulation rather than by actually performing an
+ * exponentiation or a multiplication.
+ */
+ private static double scalbn(double x, int n) {
+ int hx = hi(x);
+ int lx = lo(x);
+ int k = (hx & 0x7ff00000) >> 20; // extract exponent
+ if (k == 0) { // 0 or subnormal x
+ if ((lx | (hx & 0x7fffffff)) == 0) {
+ return x; // +-0
+ }
+ x *= two54;
+ hx = hi(x);
+ k = ((hx & 0x7ff00000) >> 20) - 54;
+ if (n < -50000) {
+ return tiny * x; // underflow
+ }
+ }
+ if (k == 0x7ff) {
+ return x + x; // NaN or Inf
+ }
+ k = k + n;
+ if (k > 0x7fe) {
+ return huge * copysign(huge, x); // overflow
+ }
+ if (k > 0) {
+ // normal result
+ return fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
+ }
+ if (k <= -54) {
+ if (n > 50000) {
+ // in case integer overflow in n+k
+ return huge * copysign(huge, x); // overflow
+ }
+ else {
+ return tiny * copysign(tiny, x); // underflow
+ }
+ }
+ k += 54; // subnormal result
+ x = fromhilo((hx & 0x800fffff) | (k << 20), lo(x));
+ return x * twom54;
+ }
+
+ //----------------------------------------------------------------------
+ // Test harness
+ //
+
+ public static void main(String[] args) {
+ for (int i = 0; i < args.length; i++) {
+ try {
+ DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(args[i])));
+ Header header = RGBE.readHeader(in);
+ System.err.println("Header for file \"" + args[i] + "\":");
+ System.err.println(" " + header);
+ byte[] data = new byte[header.getWidth() * header.getHeight() * 4];
+ readPixelsRawRLE(in, data, 0, header.getWidth(), header.getHeight());
+ in.close();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java
new file mode 100644
index 00000000..1a211886
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/DefaultToneMapper.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2015, 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.hdr.tonemap;
+
+/**
+ * DefaultToneMapper.
+ *
+ * Normalizes values to range [0...1] using:
+ *
+ * Vout = Vin / (Vin + C)
+ *
+ * Where C is constant.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: DefaultToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public final class DefaultToneMapper implements ToneMapper {
+
+ private final float constant;
+
+ public DefaultToneMapper() {
+ this(1);
+ }
+
+ public DefaultToneMapper(final float constant) {
+ this.constant = constant;
+ }
+
+ @Override
+ public void map(final float[] rgb) {
+ // Default Vo = Vi / (Vi + 1)
+ for (int i = 0; i < rgb.length; i++) {
+ rgb[i] = rgb[i] / (rgb[i] + constant);
+ }
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java
new file mode 100644
index 00000000..285b6955
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/GammaToneMapper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2015, 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.hdr.tonemap;
+
+/**
+ * GammaToneMapper.
+ *
+ * Normalizes values to range [0...1] using:
+ *
+ * Vout = A Vin \u03b3
+ *
+ * Where A is constant and \u03b3 is the gamma.
+ * Values > 1 are clamped.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: GammaToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public final class GammaToneMapper implements ToneMapper {
+
+ private final float constant;
+ private final float gamma;
+
+ public GammaToneMapper() {
+ this(0.5f, .25f);
+ }
+
+ public GammaToneMapper(final float constant, final float gamma) {
+ this.constant = constant;
+ this.gamma = gamma;
+ }
+
+ @Override
+ public void map(final float[] rgb) {
+ // Gamma Vo = A * Vi^y
+ for (int i = 0; i < rgb.length; i++) {
+ rgb[i] = Math.min(1f, (float) (constant * Math.pow(rgb[i], gamma)));
+ }
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java
new file mode 100644
index 00000000..33de6478
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/NullToneMapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2015, 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.hdr.tonemap;
+
+/**
+ * NullToneMapper.
+ *
+ * This {@code ToneMapper} does *not* normalize or clamp values
+ * to range [0...1], but leaves the values as-is.
+ * Useful for applications that implements custom tone mapping.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: NullToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public final class NullToneMapper implements ToneMapper {
+ @Override
+ public void map(float[] rgb) {
+ }
+}
diff --git a/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java
new file mode 100644
index 00000000..37d8f222
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/java/com/twelvemonkeys/imageio/plugins/hdr/tonemap/ToneMapper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2015, 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.hdr.tonemap;
+
+/**
+ * ToneMapper.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: ToneMapper.java,v 1.0 28/07/15 harald.kuhr Exp$
+ */
+public interface ToneMapper {
+ void map(float[] rgb);
+}
diff --git a/imageio/imageio-hdr/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-hdr/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
new file mode 100755
index 00000000..7af7febe
--- /dev/null
+++ b/imageio/imageio-hdr/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.hdr.HDRImageReaderSpi
diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java
new file mode 100755
index 00000000..7baae6be
--- /dev/null
+++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRImageReaderTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2015, 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.hdr;
+
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
+
+import javax.imageio.spi.ImageReaderSpi;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * TGAImageReaderTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
+ */
+public class HDRImageReaderTest extends ImageReaderAbstractTest {
+ @Override
+ protected List getTestData() {
+ return Arrays.asList(
+ new TestData(getClassLoaderResource("/hdr/memorial_o876.hdr"), new Dimension(512, 768))
+ );
+ }
+
+ @Override
+ protected ImageReaderSpi createProvider() {
+ return new HDRImageReaderSpi();
+ }
+
+ @Override
+ protected Class getReaderClass() {
+ return HDRImageReader.class;
+ }
+
+ @Override
+ protected HDRImageReader createReader() {
+ return new HDRImageReader(createProvider());
+ }
+
+ @Override
+ protected List getFormatNames() {
+ return Arrays.asList("HDR", "hdr", "RGBE", "rgbe");
+ }
+
+ @Override
+ protected List getSuffixes() {
+ return Arrays.asList("hdr", "rgbe", "xyze");
+ }
+
+ @Override
+ protected List getMIMETypes() {
+ return Collections.singletonList(
+ "image/vnd.radiance"
+ );
+ }
+}
diff --git a/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfoTest.java b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfoTest.java
new file mode 100644
index 00000000..153f3b45
--- /dev/null
+++ b/imageio/imageio-hdr/src/test/java/com/twelvemonkeys/imageio/plugins/hdr/HDRProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.hdr;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * HDRProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: HDRProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class HDRProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new HDRProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr b/imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr
new file mode 100644
index 00000000..c135ae99
Binary files /dev/null and b/imageio/imageio-hdr/src/test/resources/hdr/memorial_o876.hdr differ
diff --git a/imageio/imageio-icns/pom.xml b/imageio/imageio-icns/pom.xml
index 3389a889..37937b29 100644
--- a/imageio/imageio-icns/pom.xml
+++ b/imageio/imageio-icns/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-icns
TwelveMonkeys :: ImageIO :: ICNS plugin
@@ -18,7 +18,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java
index d72ca7bb..59cb4e9e 100644
--- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java
+++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.ReaderWriterProviderInfo;
@@ -19,7 +47,7 @@ final class ICNSProviderInfo extends ReaderWriterProviderInfo {
"image/x-apple-icons", // Common extension MIME
},
"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReader",
- new String[] {"com.twelvemonkeys.imageio.plugins.ics.ICNImageReaderSpi"},
+ new String[] {"com.twelvemonkeys.imageio.plugins.icns.ICNSImageReaderSpi"},
null, null,
false, null, null, null, null,
true, null, null, null, null
diff --git a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java
index 8dc95484..84131b60 100644
--- a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java
+++ b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReaderTest.java
@@ -28,7 +28,7 @@
package com.twelvemonkeys.imageio.plugins.icns;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Ignore;
import org.junit.Test;
@@ -37,6 +37,7 @@ import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -46,7 +47,7 @@ import java.util.List;
* @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 {
+public class ICNSImageReaderTest extends ImageReaderAbstractTest {
@Override
protected List getTestData() {
return Arrays.asList(
@@ -119,17 +120,17 @@ public class ICNSImageReaderTest extends ImageReaderAbstractTestCase {
@Override
protected List getFormatNames() {
- return Arrays.asList("icns");
+ return Collections.singletonList("icns");
}
@Override
protected List getSuffixes() {
- return Arrays.asList("icns");
+ return Collections.singletonList("icns");
}
@Override
protected List getMIMETypes() {
- return Arrays.asList("image/x-apple-icons");
+ return Collections.singletonList("image/x-apple-icons");
}
@Test
diff --git a/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfoTest.java b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfoTest.java
new file mode 100644
index 00000000..c5772ae6
--- /dev/null
+++ b/imageio/imageio-icns/src/test/java/com/twelvemonkeys/imageio/plugins/icns/ICNSProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.icns;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * ICNSProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: ICNSProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class ICNSProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new ICNSProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-iff/pom.xml b/imageio/imageio-iff/pom.xml
index 5bf6aeb5..1bac81f2 100644
--- a/imageio/imageio-iff/pom.xml
+++ b/imageio/imageio-iff/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-iff
TwelveMonkeys :: ImageIO :: IFF plugin
@@ -21,7 +21,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java
index cb47612c..62f8cf89 100644
--- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java
+++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.spi.ReaderWriterProviderInfo;
diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java
index 11d88b74..90783b11 100755
--- a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java
+++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReaderTest.java
@@ -28,7 +28,7 @@
package com.twelvemonkeys.imageio.plugins.iff;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import javax.imageio.ImageIO;
@@ -39,11 +39,10 @@ import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* IFFImageReaderTestCase
@@ -52,7 +51,7 @@ import static org.junit.Assert.assertTrue;
* @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 {
+public class IFFImageReaderTest extends ImageReaderAbstractTest {
protected List getTestData() {
return Arrays.asList(
// 32 bit - Ok
@@ -93,7 +92,7 @@ public class IFFImageReaderTest extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("iff");
+ return Collections.singletonList("iff");
}
protected List getSuffixes() {
diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfoTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfoTest.java
new file mode 100644
index 00000000..ec666c9a
--- /dev/null
+++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.iff;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * IFFProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: IFFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class IFFProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new IFFProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-jpeg/pom.xml b/imageio/imageio-jpeg/pom.xml
index 90e89fa1..1a2c62e0 100644
--- a/imageio/imageio-jpeg/pom.xml
+++ b/imageio/imageio-jpeg/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-jpeg
TwelveMonkeys :: ImageIO :: JPEG plugin
@@ -20,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
com.twelvemonkeys.imageio
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java
index 704dc1c9..20b5b50b 100644
--- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/EXIFThumbnailReader.java
@@ -28,6 +28,7 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
+import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
@@ -102,7 +103,7 @@ final class EXIFThumbnailReader extends ThumbnailReader {
thumbnail = readJPEG();
}
- cachedThumbnail = pixelsExposed ? null : new SoftReference(thumbnail);
+ cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail);
return thumbnail;
}
@@ -132,14 +133,10 @@ final class EXIFThumbnailReader extends ThumbnailReader {
input = new SequenceInputStream(new ByteArrayInputStream(fakeEmptyExif), input);
try {
- MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input);
- try {
+ try (MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(input)) {
return readJPEGThumbnail(reader, stream);
}
- finally {
- stream.close();
- }
}
finally {
input.close();
@@ -195,15 +192,15 @@ final class EXIFThumbnailReader extends ThumbnailReader {
break;
case 6:
// YCbCr
- for (int i = 0, thumbDataLength = thumbData.length; i < thumbDataLength; i += 3) {
- JPEGImageReader.YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
+ for (int i = 0; i < thumbSize; i += 3) {
+ YCbCrConverter.convertYCbCr2RGB(thumbData, thumbData, i);
}
break;
default:
throw new IIOException("Unknown PhotometricInterpretation value for uncompressed EXIF thumbnail (expected 2 or 6): " + interpretation);
}
- return ThumbnailReader.readRawThumbnail(thumbData, thumbData.length, 0, w, h);
+ return ThumbnailReader.readRawThumbnail(thumbData, thumbSize, 0, w, h);
}
throw new IIOException("Missing StripOffsets tag for uncompressed EXIF thumbnail");
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java
index 1c2a82c7..6264cb81 100644
--- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java
@@ -28,9 +28,9 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
-import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorSpaces;
+import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
@@ -112,7 +112,7 @@ public class JPEGImageReader extends ImageReaderBase {
private static final Map> SEGMENT_IDENTIFIERS = createSegmentIds();
private static Map> createSegmentIds() {
- Map> map = new LinkedHashMap>();
+ Map> map = new LinkedHashMap<>();
// Need all APP markers to be able to re-generate proper metadata later
for (int appMarker = JPEG.APP0; appMarker <= JPEG.APP15; appMarker++) {
@@ -152,10 +152,10 @@ public class JPEGImageReader extends ImageReaderBase {
/** Cached list of JPEG segments we filter from the underlying stream */
private List segments;
- JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
+ protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
super(provider);
- this.delegate = Validate.notNull(delegate);
+ this.delegate = Validate.notNull(delegate);
progressDelegator = new ProgressDelegator();
}
@@ -224,7 +224,7 @@ public class JPEGImageReader extends ImageReaderBase {
JPEGColorSpace csType = getSourceCSType(getJFIF(), getAdobeDCT(), getSOF());
if (types == null || !types.hasNext() || csType == JPEGColorSpace.CMYK || csType == JPEGColorSpace.YCCK) {
- ArrayList typeList = new ArrayList();
+ ArrayList typeList = new ArrayList<>();
// Add the standard types, we can always convert to these
typeList.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
typeList.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
@@ -268,10 +268,15 @@ public class JPEGImageReader extends ImageReaderBase {
@Override
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
// If delegate can determine the spec, we'll just go with that
- ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
+ try {
+ ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
- if (rawType != null) {
- return rawType;
+ if (rawType != null) {
+ return rawType;
+ }
+ }
+ catch (NullPointerException ignore) {
+ // Fall through
}
// Otherwise, consult the image metadata
@@ -298,7 +303,9 @@ public class JPEGImageReader extends ImageReaderBase {
super.setInput(input, seekForwardOnly, ignoreMetadata);
// JPEGSegmentImageInputStream that filters out/skips bad/unnecessary segments
- delegate.setInput(imageInput != null ? new JPEGSegmentImageInputStream(imageInput) : null, seekForwardOnly, ignoreMetadata);
+ delegate.setInput(imageInput != null
+ ? new JPEGSegmentImageInputStream(imageInput)
+ : null, seekForwardOnly, ignoreMetadata);
}
@Override
@@ -311,22 +318,10 @@ public class JPEGImageReader extends ImageReaderBase {
assertInput();
checkBounds(imageIndex);
-// CompoundDirectory exif = getExif();
-// if (exif != null) {
-// System.err.println("exif: " + exif);
-// System.err.println("Orientation: " + exif.getEntryById(TIFF.TAG_ORIENTATION));
-// Entry exifIFDEntry = exif.getEntryById(TIFF.TAG_EXIF_IFD);
-//
-// if (exifIFDEntry != null) {
-// Directory exifIFD = (Directory) exifIFDEntry.getValue();
-// System.err.println("PixelXDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_X_DIMENSION));
-// System.err.println("PixelYDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_Y_DIMENSION));
-// }
-// }
-
SOFSegment sof = getSOF();
ICC_Profile profile = getEmbeddedICCProfile(false);
AdobeDCTSegment adobeDCT = getAdobeDCT();
+ boolean bogusAdobeDCT = false;
if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 ||
adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) {
@@ -337,6 +332,7 @@ public class JPEGImageReader extends ImageReaderBase {
sof.marker & 0xf, sof.componentsInFrame()
));
+ bogusAdobeDCT = true;
adobeDCT = null;
}
@@ -345,11 +341,11 @@ public class JPEGImageReader extends ImageReaderBase {
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
if (delegate.canReadRaster() && (
+ bogusAdobeDCT ||
sourceCSType == JPEGColorSpace.CMYK ||
sourceCSType == JPEGColorSpace.YCCK ||
- adobeDCT != null && adobeDCT.getTransform() == AdobeDCTSegment.YCCK ||
- profile != null && !ColorSpaces.isCS_sRGB(profile)) ||
- sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null) { // TODO: Issue warning?
+ profile != null && !ColorSpaces.isCS_sRGB(profile) ||
+ sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null)) { // TODO: Issue warning?
if (DEBUG) {
System.out.println("Reading using raster and extra conversion");
System.out.println("ICC color profile: " + profile);
@@ -371,6 +367,10 @@ public class JPEGImageReader extends ImageReaderBase {
int origHeight = getHeight(imageIndex);
Iterator imageTypes = getImageTypes(imageIndex);
+ // TODO: Avoid creating destination here, if possible (as it saves time and memory)
+ // If YCbCr or RGB, we could instead create a BufferedImage around the converted raster directly.
+ // If YCCK or CMYK, we could instead create a BufferedImage around the converted raster,
+ // leaving the fourth band as alpha (or pretend it's not there, by creating a child raster).
BufferedImage image = getDestination(param, imageTypes, origWidth, origHeight);
WritableRaster destination = image.getRaster();
@@ -404,6 +404,7 @@ public class JPEGImageReader extends ImageReaderBase {
if (DEBUG) {
System.err.println("Converting from " + intendedCS + " to " + (image.getColorModel().getColorSpace().isCS_sRGB() ? "sRGB" : image.getColorModel().getColorSpace()));
}
+
convert = new ColorConvertOp(intendedCS, image.getColorModel().getColorSpace(), null);
}
// Else, pass through with no conversion
@@ -462,10 +463,13 @@ public class JPEGImageReader extends ImageReaderBase {
// Apply source color conversion from implicit color space
if (csType == JPEGColorSpace.YCbCr || csType == JPEGColorSpace.YCbCrA) {
- YCbCrConverter.convertYCbCr2RGB(raster);
+ convertYCbCr2RGB(raster);
}
else if (csType == JPEGColorSpace.YCCK) {
- YCbCrConverter.convertYCCK2CMYK(raster);
+ // TODO: Need to rethink this (non-) inversion, see #147
+ // TODO: Allow param to specify inversion, or possibly the PDF decode array
+ // flag0 bit 15, blend = 1 see http://graphicdesign.stackexchange.com/questions/12894/cmyk-jpegs-extracted-from-pdf-appear-inverted
+ convertYCCK2CMYK(raster);
}
else if (csType == JPEGColorSpace.CMYK) {
invertCMYK(raster);
@@ -620,7 +624,7 @@ public class JPEGImageReader extends ImageReaderBase {
}
}
- private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
+ protected ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
// NOTE: This is probably not the right way to do it... :-P
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
@@ -670,16 +674,11 @@ public class JPEGImageReader extends ImageReaderBase {
segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
}
- catch (IIOException ignore) {
+ catch (IIOException | IllegalArgumentException ignore) {
if (DEBUG) {
ignore.printStackTrace();
}
}
- catch (IllegalArgumentException foo) {
- if (DEBUG) {
- foo.printStackTrace();
- }
- }
finally {
imageInput.reset();
}
@@ -699,7 +698,7 @@ public class JPEGImageReader extends ImageReaderBase {
if ((marker == ALL_APP_MARKERS && segment.marker() >= JPEG.APP0 && segment.marker() <= JPEG.APP15 || segment.marker() == marker)
&& (identifier == null || identifier.equals(segment.identifier()))) {
if (appSegments == Collections.EMPTY_LIST) {
- appSegments = new ArrayList(segments.size());
+ appSegments = new ArrayList<>(segments.size());
}
appSegments.add(segment);
@@ -831,7 +830,7 @@ public class JPEGImageReader extends ImageReaderBase {
return data;
}
- ICC_Profile getEmbeddedICCProfile(final boolean allowBadIndexes) throws IOException {
+ protected ICC_Profile getEmbeddedICCProfile(final boolean allowBadIndexes) throws IOException {
// ICC v 1.42 (2006) annex B:
// APP2 marker (0xFFE2) + 2 byte length + ASCII 'ICC_PROFILE' + 0 (termination)
// + 1 byte chunk number + 1 byte chunk count (allows ICC profiles chunked in multiple APP2 segments)
@@ -853,7 +852,7 @@ public class JPEGImageReader extends ImageReaderBase {
return null;
}
- return readICCProfileSafe(stream);
+ return readICCProfileSafe(stream, allowBadIndexes);
}
else if (!segments.isEmpty()) {
// NOTE: This is probably over-complicated, as I've never encountered ICC_PROFILE chunks out of order...
@@ -900,15 +899,17 @@ public class JPEGImageReader extends ImageReaderBase {
streams[badICC ? i : chunkNumber - 1] = stream;
}
- return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))));
+ return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
}
return null;
}
- private ICC_Profile readICCProfileSafe(final InputStream stream) throws IOException {
+ private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) throws IOException {
try {
- return ICC_Profile.getInstance(stream);
+ ICC_Profile profile = ICC_Profile.getInstance(stream);
+
+ return allowBadProfile ? profile : ColorSpaces.validateProfile(profile);
}
catch (RuntimeException e) {
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
@@ -940,6 +941,11 @@ public class JPEGImageReader extends ImageReaderBase {
delegate.abort();
}
+ @Override
+ public ImageReadParam getDefaultReadParam() {
+ return delegate.getDefaultReadParam();
+ }
+
@Override
public boolean readerSupportsThumbnails() {
return true; // We support EXIF, JFIF and JFXX style thumbnails
@@ -949,7 +955,7 @@ public class JPEGImageReader extends ImageReaderBase {
checkBounds(imageIndex);
if (thumbnails == null) {
- thumbnails = new ArrayList();
+ thumbnails = new ArrayList<>();
ThumbnailReadProgressListener thumbnailProgressDelegator = new ThumbnailProgressDelegate();
// Read JFIF thumbnails if present
@@ -1101,105 +1107,32 @@ public class JPEGImageReader extends ImageReaderBase {
}
}
- /**
- * Static inner class for lazy-loading of conversion tables.
- *
- * @author Harald Kuhr
- * @author Original code by Werner Randelshofer
- */
- static final class YCbCrConverter {
- /** Define tables for YCC->RGB color space conversion. */
- private final static int SCALEBITS = 16;
- private final static int MAXJSAMPLE = 255;
- private final static int CENTERJSAMPLE = 128;
- private final static int ONE_HALF = 1 << (SCALEBITS - 1);
+ public static void convertYCbCr2RGB(final Raster raster) {
+ final int height = raster.getHeight();
+ final int width = raster.getWidth();
+ final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
- private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
- private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
- private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
- private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
-
- /**
- * Initializes tables for YCC->RGB color space conversion.
- */
- private static void buildYCCtoRGBtable() {
- if (DEBUG) {
- System.err.println("Building YCC conversion table");
- }
-
- for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
- // i is the actual input pixel value, in the range 0..MAXJSAMPLE
- // The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
- // Cr=>R value is nearest int to 1.40200 * x
- Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
- // Cb=>B value is nearest int to 1.77200 * x
- Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
- // Cr=>G value is scaled-up -0.71414 * x
- Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
- // Cb=>G value is scaled-up -0.34414 * x
- // We also add in ONE_HALF so that need not do it in inner loop
- Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ YCbCrConverter.convertYCbCr2RGB(data, data, (x + y * width) * 3);
}
}
+ }
- static {
- buildYCCtoRGBtable();
- }
+ public static void convertYCCK2CMYK(final Raster raster) {
+ final int height = raster.getHeight();
+ final int width = raster.getWidth();
+ final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
- static void convertYCbCr2RGB(final Raster raster) {
- final int height = raster.getHeight();
- final int width = raster.getWidth();
- final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
-
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- convertYCbCr2RGB(data, data, (x + y * width) * 3);
- }
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int offset = (x + y * width) * 4;
+ // YCC -> CMY
+ YCbCrConverter.convertYCbCr2RGB(data, data, offset);
+ // Inverse K
+ data[offset + 3] = (byte) (0xff - data[offset + 3] & 0xff);
}
}
-
- static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
- int y = yCbCr[offset ] & 0xff;
- int cr = yCbCr[offset + 2] & 0xff;
- int cb = yCbCr[offset + 1] & 0xff;
-
- rgb[offset ] = clamp(y + Cr_R_LUT[cr]);
- rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
- rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
- }
-
- static void convertYCCK2CMYK(final Raster raster) {
- final int height = raster.getHeight();
- final int width = raster.getWidth();
- final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
-
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- convertYCCK2CMYK(data, data, (x + y * width) * 4);
- }
- }
- }
-
- private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
- // Inverted
- int y = 255 - ycck[offset ] & 0xff;
- int cb = 255 - ycck[offset + 1] & 0xff;
- int cr = 255 - ycck[offset + 2] & 0xff;
- int k = 255 - ycck[offset + 3] & 0xff;
-
- int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
- int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
- int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
-
- cmyk[offset ] = clamp(cmykC);
- cmyk[offset + 1] = clamp(cmykM);
- cmyk[offset + 2] = clamp(cmykY);
- cmyk[offset + 3] = (byte) k; // K passes through unchanged
- }
-
- private static byte clamp(int val) {
- return (byte) Math.max(0, Math.min(255, val));
- }
}
private class ProgressDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
@@ -1297,7 +1230,68 @@ public class JPEGImageReader extends ImageReaderBase {
}
public static void main(final String[] args) throws IOException {
- for (final String arg : args) {
+ ImageIO.setUseCache(false);
+
+ int subX = 1;
+ int subY = 1;
+ int xOff = 0;
+ int yOff = 0;
+ Rectangle roi = null;
+ boolean metadata = false;
+ boolean thumbnails = false;
+
+ for (int argIdx = 0; argIdx < args.length; argIdx++) {
+ final String arg = args[argIdx];
+
+ if (arg.charAt(0) == '-') {
+ if (arg.equals("-s") || arg.equals("--subsample") && args.length > argIdx) {
+ String[] sub = args[++argIdx].split(",");
+
+ try {
+ if (sub.length >= 4) {
+ subX = Integer.parseInt(sub[0]);
+ subY = Integer.parseInt(sub[1]);
+ xOff = Integer.parseInt(sub[2]);
+ yOff = Integer.parseInt(sub[3]);
+ }
+ else {
+ subX = Integer.parseInt(sub[0]);
+ subY = sub.length > 1 ? Integer.parseInt(sub[1]) : subX;
+ }
+ }
+ catch (NumberFormatException e) {
+ System.err.println("Bad sub sampling (x,y): '" + args[argIdx] + "'");
+ }
+ }
+ else if (arg.equals("-r") || arg.equals("--roi") && args.length > argIdx) {
+ String[] region = args[++argIdx].split(",");
+
+ try {
+ if (region.length >= 4) {
+ roi = new Rectangle(Integer.parseInt(region[0]), Integer.parseInt(region[2]), Integer.parseInt(region[2]), Integer.parseInt(region[3]));
+ }
+ else {
+ roi = new Rectangle(Integer.parseInt(region[0]), Integer.parseInt(region[2]));
+ }
+ }
+ catch (IndexOutOfBoundsException | NumberFormatException e) {
+ System.err.println("Bad source region ([x,y,]w, h): '" + args[argIdx] + "'");
+ }
+ }
+ else if (arg.equals("-m") || arg.equals("--metadata")) {
+ metadata = true;
+ }
+ else if (arg.equals("-t") || arg.equals("--thumbnails")) {
+ thumbnails = true;
+ }
+ else {
+ System.err.println("Unknown argument: '" + arg + "'");
+ System.exit(-1);
+ }
+
+ continue;
+ }
+
File file = new File(arg);
ImageInputStream input = ImageIO.createImageInputStream(file);
@@ -1313,15 +1307,15 @@ public class JPEGImageReader extends ImageReaderBase {
continue;
}
- ImageReader reader = readers.next();
-// System.err.println("Reading using: " + reader);
+ final ImageReader reader = readers.next();
+ System.err.println("Reading using: " + reader);
reader.addIIOReadWarningListener(new IIOReadWarningListener() {
public void warningOccurred(ImageReader source, String warning) {
System.err.println("Warning: " + arg + ": " + warning);
}
});
- reader.addIIOReadProgressListener(new ProgressListenerBase() {
+ final ProgressListenerBase listener = new ProgressListenerBase() {
private static final int MAX_W = 78;
int lastProgress = 0;
@@ -1350,29 +1344,35 @@ public class JPEGImageReader extends ImageReaderBase {
System.out.println("]");
}
- });
+ };
+ reader.addIIOReadProgressListener(listener);
reader.setInput(input);
- // For a tables-only image, we can't read image, but we should get metadata.
- if (reader.getNumImages(true) == 0) {
- IIOMetadata streamMetadata = reader.getStreamMetadata();
- IIOMetadataNode streamNativeTree = (IIOMetadataNode) streamMetadata.getAsTree(streamMetadata.getNativeMetadataFormatName());
- new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(streamNativeTree, false);
- continue;
- }
-
try {
+ // For a tables-only image, we can't read image, but we should get metadata.
+ if (reader.getNumImages(true) == 0) {
+ IIOMetadata streamMetadata = reader.getStreamMetadata();
+ IIOMetadataNode streamNativeTree = (IIOMetadataNode) streamMetadata.getAsTree(streamMetadata.getNativeMetadataFormatName());
+ new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(streamNativeTree, false);
+ continue;
+ }
+
+ BufferedImage image;
ImageReadParam param = reader.getDefaultReadParam();
-// if (args.length > 1) {
-// int sub = Integer.parseInt(args[1]);
-// int sub = 4;
-// param.setSourceSubsampling(sub, sub, 0, 0);
-// }
- BufferedImage image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
+ if (subX > 1 || subY > 1 || roi != null) {
+ param.setSourceSubsampling(subX, subY, xOff, yOff);
+ param.setSourceRegion(roi);
+
+ image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY);
+ }
+ else {
+ image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0));
+ }
param.setDestination(image);
-// long start = System.currentTimeMillis();
+ long start = DEBUG ? System.currentTimeMillis() : 0;
+
try {
image = reader.read(0, param);
}
@@ -1383,12 +1383,13 @@ public class JPEGImageReader extends ImageReaderBase {
continue;
}
}
-// System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
-// System.err.println("image: " + image);
+ if (DEBUG) {
+ System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
+ System.err.println("image: " + image);
+ }
-// image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
-
+ /*
int maxW = 1280;
int maxH = 800;
if (image.getWidth() > maxW || image.getHeight() > maxH) {
@@ -1402,28 +1403,45 @@ public class JPEGImageReader extends ImageReaderBase {
}
// System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
}
+ */
showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(0), reader.getHeight(0)));
- try {
- IIOMetadata imageMetadata = reader.getImageMetadata(0);
- System.out.println("Metadata for File: " + file.getName());
- System.out.println("Native:");
- new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()), false);
- System.out.println("Standard:");
- new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
- System.out.println();
+ if (metadata) {
+ try {
+ IIOMetadata imageMetadata = reader.getImageMetadata(0);
+ System.out.println("Metadata for File: " + file.getName());
- int numThumbnails = reader.getNumThumbnails(0);
- for (int i = 0; i < numThumbnails; i++) {
- BufferedImage thumbnail = reader.readThumbnail(0, i);
-// System.err.println("thumbnail: " + thumbnail);
- showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
+ if (imageMetadata.getNativeMetadataFormatName() != null) {
+ System.out.println("Native:");
+ new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(imageMetadata.getNativeMetadataFormatName()), false);
+ }
+ if (imageMetadata.isStandardMetadataFormatSupported()) {
+ System.out.println("Standard:");
+ new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
+ }
+
+ System.out.println();
+ }
+ catch (IIOException e) {
+ System.err.println("Could not read thumbnails: " + arg + ": " + e.getMessage());
+ e.printStackTrace();
}
}
- catch (IIOException e) {
- System.err.println("Could not read thumbnails: " + arg + ": " + e.getMessage());
- e.printStackTrace();
+
+ if (thumbnails) {
+ try {
+ int numThumbnails = reader.getNumThumbnails(0);
+ for (int i = 0; i < numThumbnails; i++) {
+ BufferedImage thumbnail = reader.readThumbnail(0, i);
+ // System.err.println("thumbnail: " + thumbnail);
+ showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
+ }
+ }
+ catch (IIOException e) {
+ System.err.println("Could not read thumbnails: " + arg + ": " + e.getMessage());
+ e.printStackTrace();
+ }
}
}
catch (Throwable t) {
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderSpi.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderSpi.java
index efe0f4de..adf926e5 100644
--- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderSpi.java
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderSpi.java
@@ -29,6 +29,7 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.Validate;
@@ -48,14 +49,14 @@ import java.util.Locale;
* @version $Id: JPEGImageReaderSpi.java,v 1.0 24.01.11 22.12 haraldk Exp$
*/
public class JPEGImageReaderSpi extends ImageReaderSpiBase {
- private ImageReaderSpi delegateProvider;
+ protected ImageReaderSpi delegateProvider;
/**
* Constructor for use by {@link javax.imageio.spi.IIORegistry} only.
* The instance created will not work without being properly registered.
*/
public JPEGImageReaderSpi() {
- super(new JPEGProviderInfo());
+ this(new JPEGProviderInfo());
}
/**
@@ -69,6 +70,15 @@ public class JPEGImageReaderSpi extends ImageReaderSpiBase {
this.delegateProvider = Validate.notNull(delegateProvider);
}
+ /**
+ * Constructor for subclasses.
+ *
+ * @param info
+ */
+ protected JPEGImageReaderSpi(final ReaderWriterProviderInfo info) {
+ super(info);
+ }
+
static ImageReaderSpi lookupDelegateProvider(final ServiceRegistry registry) {
Iterator providers = registry.getServiceProviders(ImageReaderSpi.class, true);
@@ -83,7 +93,7 @@ public class JPEGImageReaderSpi extends ImageReaderSpiBase {
return null;
}
- @SuppressWarnings({"unchecked"})
+ @SuppressWarnings({"unchecked", "deprecation"})
@Override
public void onRegistration(final ServiceRegistry registry, final Class> category) {
if (delegateProvider == null) {
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGProviderInfo.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGProviderInfo.java
index 961a877c..2865efaf 100644
--- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGProviderInfo.java
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.jpeg;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java
index a5f11ecb..f7a2a727 100644
--- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java
+++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStream.java
@@ -240,16 +240,21 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
private void streamInit() throws IOException {
stream.seek(0);
- int soi = stream.readUnsignedShort();
- if (soi != JPEG.SOI) {
- throw new IIOException(String.format("Not a JPEG stream (starts with: 0x%04x, expected SOI: 0x%04x)", soi, JPEG.SOI));
- }
- else {
+ try {
+ int soi = stream.readUnsignedShort();
+
+ if (soi != JPEG.SOI) {
+ throw new IIOException(String.format("Not a JPEG stream (starts with: 0x%04x, expected SOI: 0x%04x)", soi, JPEG.SOI));
+ }
+
segment = new Segment(soi, 0, 0, 2);
segments.add(segment);
currentSegment = segments.size() - 1; // 0
}
+ catch (EOFException eof) {
+ throw new IIOException(String.format("Not a JPEG stream (short stream. expected SOI: 0x%04x)", JPEG.SOI), eof);
+ }
}
static boolean isAppSegmentMarker(final int marker) {
diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java
index 30969f76..1f495e35 100644
--- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java
+++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java
@@ -28,7 +28,7 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.hamcrest.core.IsInstanceOf;
import org.junit.Ignore;
import org.junit.Test;
@@ -59,10 +59,11 @@ import java.util.*;
import java.util.List;
import static org.junit.Assert.*;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.*;
/**
* JPEGImageReaderTest
@@ -71,11 +72,11 @@ import static org.mockito.Mockito.verify;
* @author last modified by $Author: haraldk$
* @version $Id: JPEGImageReaderTest.java,v 1.0 24.01.11 22.04 haraldk Exp$
*/
-public class JPEGImageReaderTest extends ImageReaderAbstractTestCase {
+public class JPEGImageReaderTest extends ImageReaderAbstractTest {
- private static final JPEGImageReaderSpi SPI = new JPEGImageReaderSpi(lookupDelegateProvider());
+ protected static final JPEGImageReaderSpi SPI = new JPEGImageReaderSpi(lookupDelegateProvider());
- private static ImageReaderSpi lookupDelegateProvider() {
+ protected static ImageReaderSpi lookupDelegateProvider() {
return JPEGImageReaderSpi.lookupDelegateProvider(IIORegistry.getDefaultInstance());
}
@@ -85,6 +86,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase getMIMETypes() {
- return Arrays.asList("image/jpeg");
+ return Collections.singletonList("image/jpeg");
}
// TODO: Test that subsampling is actually reading something
@@ -368,7 +370,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase(0));
+ assertThat(markerSequence.getChildNodes().getLength(), new GreaterThan<>(0));
NodeList unknowns = markerSequence.getElementsByTagName("unknown");
for (int j = 0; j < unknowns.getLength(); j++) {
@@ -1157,10 +1159,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase spiClass = (Class) Class.forName("com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi");
ImageReaderSpi provider = spiClass.newInstance();
- return provider.createReaderInstance();
+ ImageReader reader = provider.createReaderInstance();
+ assumeNotNull(reader);
+ return reader;
}
catch (Throwable t) {
- System.err.println("WARNING: Could not create ImageReader for reference (missing dependency): " + t.getMessage());
-
- return null;
+ assumeNoException(t);
}
+
+ return null;
}
private void assertTreesEquals(String message, Node expectedTree, Node actualTree) {
@@ -1290,7 +1290,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase sortNodes(final NodeList nodes) {
- ArrayList sortedNodes = new ArrayList(new AbstractList() {
+ ArrayList sortedNodes = new ArrayList<>(new AbstractList() {
@Override
public IIOMetadataNode get(int index) {
return (IIOMetadataNode) nodes.item(index);
@@ -1438,7 +1438,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCaseHarald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: JPEGProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class JPEGProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new JPEGProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStreamTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStreamTest.java
index f6af554b..2def2e06 100644
--- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStreamTest.java
+++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGSegmentImageInputStreamTest.java
@@ -74,6 +74,24 @@ public class JPEGSegmentImageInputStreamTest {
stream.read();
}
+ @Test(expected = IIOException.class)
+ public void testStreamNonJPEGArray() throws IOException {
+ ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[] {42, 42, 0, 0, 77, 99})));
+ stream.readFully(new byte[1]);
+ }
+
+ @Test(expected = IIOException.class)
+ public void testStreamEmpty() throws IOException {
+ ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[0])));
+ stream.read();
+ }
+
+ @Test(expected = IIOException.class)
+ public void testStreamEmptyArray() throws IOException {
+ ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(new ByteArrayInputStream(new byte[0])));
+ stream.readFully(new byte[1]);
+ }
+
@Test
public void testStreamRealData() throws IOException {
ImageInputStream stream = new JPEGSegmentImageInputStream(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/invalid-icc-duplicate-sequence-numbers-rgb-internal-kodak-srgb-jfif.jpg")));
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/corrupted-icc-srgb.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/corrupted-icc-srgb.jpg
new file mode 100644
index 00000000..c775a0f7
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/corrupted-icc-srgb.jpg differ
diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/exif-jfif-app13-app14ycck-3channel.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/exif-jfif-app13-app14ycck-3channel.jpg
new file mode 100644
index 00000000..754292d8
Binary files /dev/null and b/imageio/imageio-jpeg/src/test/resources/jpeg/exif-jfif-app13-app14ycck-3channel.jpg differ
diff --git a/imageio/imageio-metadata/pom.xml b/imageio/imageio-metadata/pom.xml
index 77b62d9f..40993b3f 100644
--- a/imageio/imageio-metadata/pom.xml
+++ b/imageio/imageio-metadata/pom.xml
@@ -3,7 +3,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
4.0.0
imageio-metadata
@@ -20,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java
index ce7c6074..be03f90c 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java
@@ -64,4 +64,8 @@ public interface Entry {
// For arrays only
int valueCount();
+
+ // TODO: getValueAsInt, UnsignedInt, Short, UnsignedShort, Byte, UnsignedByte etc
+ // TODO: getValueAsIntArray, ShortArray, ByteArray, StringArray etc (also for non-arrays, to return a single element array)
+
}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataWriter.java
new file mode 100644
index 00000000..325fbdad
--- /dev/null
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataWriter.java
@@ -0,0 +1,15 @@
+package com.twelvemonkeys.imageio.metadata;
+
+import javax.imageio.stream.ImageOutputStream;
+import java.io.IOException;
+
+/**
+ * MetadataWriter.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: MetadataWriter.java,v 1.0 28/05/15 harald.kuhr Exp$
+ */
+public abstract class MetadataWriter {
+ abstract public boolean write(Directory directory, ImageOutputStream stream) throws IOException;
+}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java
index 381c0913..0c6f5403 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java
@@ -38,13 +38,14 @@ import com.twelvemonkeys.imageio.metadata.AbstractEntry;
* @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
*/
final class EXIFEntry extends AbstractEntry {
+ // TODO: Expose as TIFFEntry
final private short type;
EXIFEntry(final int identifier, final Object value, final short type) {
super(identifier, value);
- if (type < 1 || type > TIFF.TYPE_NAMES.length) {
- throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", type));
+ if (type < 1 || type >= TIFF.TYPE_NAMES.length) {
+ throw new IllegalArgumentException(String.format("Illegal TIFF type: %s", type));
}
// TODO: Validate that type is applicable to value?
@@ -71,6 +72,8 @@ final class EXIFEntry extends AbstractEntry {
return "IPTC";
case TIFF.TAG_PHOTOSHOP:
return "Adobe";
+ case TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA:
+ return "ImageSourceData";
case TIFF.TAG_ICC_PROFILE:
return "ICCProfile";
@@ -84,8 +87,16 @@ final class EXIFEntry extends AbstractEntry {
return "Compression";
case TIFF.TAG_PHOTOMETRIC_INTERPRETATION:
return "PhotometricInterpretation";
+ case TIFF.TAG_FILL_ORDER:
+ return "FillOrder";
+ case TIFF.TAG_DOCUMENT_NAME:
+ return "DocumentName";
case TIFF.TAG_IMAGE_DESCRIPTION:
return "ImageDescription";
+ case TIFF.TAG_MAKE:
+ return "Make";
+ case TIFF.TAG_MODEL:
+ return "Model";
case TIFF.TAG_STRIP_OFFSETS:
return "StripOffsets";
case TIFF.TAG_ORIENTATION:
@@ -104,14 +115,10 @@ final class EXIFEntry extends AbstractEntry {
return "PlanarConfiguration";
case TIFF.TAG_RESOLUTION_UNIT:
return "ResolutionUnit";
- case TIFF.TAG_JPEG_INTERCHANGE_FORMAT:
- return "JPEGInterchangeFormat";
- case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
- return "JPEGInterchangeFormatLength";
- case TIFF.TAG_MAKE:
- return "Make";
- case TIFF.TAG_MODEL:
- return "Model";
+ case TIFF.TAG_PAGE_NAME:
+ return "PageName";
+ case TIFF.TAG_PAGE_NUMBER:
+ return "PageNumber";
case TIFF.TAG_SOFTWARE:
return "Software";
case TIFF.TAG_DATE_TIME:
@@ -138,10 +145,20 @@ final class EXIFEntry extends AbstractEntry {
return "YCbCrPositioning";
case TIFF.TAG_COLOR_MAP:
return "ColorMap";
+ case TIFF.TAG_INK_SET:
+ return "InkSet";
+ case TIFF.TAG_INK_NAMES:
+ return "InkNames";
case TIFF.TAG_EXTRA_SAMPLES:
return "ExtraSamples";
case TIFF.TAG_SAMPLE_FORMAT:
return "SampleFormat";
+ case TIFF.TAG_JPEG_TABLES:
+ return "JPEGTables";
+ case TIFF.TAG_JPEG_INTERCHANGE_FORMAT:
+ return "JPEGInterchangeFormat";
+ case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
+ return "JPEGInterchangeFormatLength";
case TIFF.TAG_SUB_IFD:
return "SubIFD";
@@ -176,6 +193,8 @@ final class EXIFEntry extends AbstractEntry {
return "Flash";
case EXIF.TAG_FOCAL_LENGTH:
return "FocalLength";
+ case EXIF.TAG_SENSING_METHOD:
+ return "SensingMethod";
case EXIF.TAG_FILE_SOURCE:
return "FileSource";
case EXIF.TAG_SCENE_TYPE:
@@ -189,7 +208,7 @@ final class EXIFEntry extends AbstractEntry {
case EXIF.TAG_WHITE_BALANCE:
return "WhiteBalance";
case EXIF.TAG_DIGITAL_ZOOM_RATIO:
- return "DigitalZoomRation";
+ return "DigitalZoomRatio";
case EXIF.TAG_FOCAL_LENGTH_IN_35_MM_FILM:
return "FocalLengthIn35mmFilm";
case EXIF.TAG_SCENE_CAPTURE_TYPE:
@@ -202,6 +221,8 @@ final class EXIFEntry extends AbstractEntry {
return "Saturation";
case EXIF.TAG_SHARPNESS:
return "Sharpness";
+ case EXIF.TAG_IMAGE_UNIQUE_ID:
+ return "ImageUniqueID";
case EXIF.TAG_FLASHPIX_VERSION:
return "FlashpixVersion";
@@ -214,6 +235,8 @@ final class EXIFEntry extends AbstractEntry {
return "DateTimeDigitized";
case EXIF.TAG_IMAGE_NUMBER:
return "ImageNumber";
+ case EXIF.TAG_MAKER_NOTE:
+ return "MakerNote";
case EXIF.TAG_USER_COMMENT:
return "UserComment";
@@ -259,6 +282,6 @@ final class EXIFEntry extends AbstractEntry {
@Override
public String getTypeName() {
- return TIFF.TYPE_NAMES[type - 1];
+ return TIFF.TYPE_NAMES[type];
}
}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
index ac13be42..2c872b45 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java
@@ -71,7 +71,7 @@ public final class EXIFReader extends MetadataReader {
else if (bom[0] == 'M' && bom[1] == 'M') {
input.setByteOrder(ByteOrder.BIG_ENDIAN);
}
- else {
+ else {
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
}
@@ -79,7 +79,7 @@ public final class EXIFReader extends MetadataReader {
// http://www.awaresystems.be/imaging/tiff/bigtiff.html
int magic = input.readUnsignedShort();
if (magic != TIFF.TIFF_MAGIC) {
- throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
+ throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
}
long directoryOffset = input.readUnsignedInt();
@@ -89,8 +89,8 @@ public final class EXIFReader extends MetadataReader {
// TODO: Consider re-writing so that the linked IFD parsing is done externally to the method
protected Directory readDirectory(final ImageInputStream pInput, final long pOffset, final boolean readLinked) throws IOException {
- List ifds = new ArrayList();
- List entries = new ArrayList();
+ List ifds = new ArrayList<>();
+ List entries = new ArrayList<>();
pInput.seek(pOffset);
long nextOffset = -1;
@@ -105,21 +105,27 @@ public final class EXIFReader extends MetadataReader {
}
for (int i = 0; i < entryCount; i++) {
- EXIFEntry entry = readEntry(pInput);
+ try {
+ EXIFEntry entry = readEntry(pInput);
- if (entry == null) {
-// System.err.println("Expected: " + entryCount + " values, found only " + i);
- // TODO: Log warning?
- nextOffset = 0;
+ if (entry != null) {
+ entries.add(entry);
+ }
+ }
+ catch (IIOException e) {
break;
}
-
- entries.add(entry);
}
if (readLinked) {
if (nextOffset == -1) {
- nextOffset = pInput.readUnsignedInt();
+ try {
+ nextOffset = pInput.readUnsignedInt();
+ }
+ catch (EOFException e) {
+ // catch EOF here as missing EOF marker
+ nextOffset = 0;
+ }
}
// Read linked IFDs
@@ -138,7 +144,7 @@ public final class EXIFReader extends MetadataReader {
);
ifds.add(0, new IFD(entries));
-
+
return new EXIFDirectory(ifds);
}
@@ -156,7 +162,7 @@ public final class EXIFReader extends MetadataReader {
try {
if (KNOWN_IFDS.contains(tagId)) {
long[] pointerOffsets = getPointerOffsets(entry);
- List subIFDs = new ArrayList(pointerOffsets.length);
+ List subIFDs = new ArrayList<>(pointerOffsets.length);
for (long pointerOffset : pointerOffsets) {
CompoundDirectory subDirectory = (CompoundDirectory) readDirectory(input, pointerOffset, false);
@@ -170,15 +176,18 @@ public final class EXIFReader extends MetadataReader {
// Replace the entry with parsed data
entries.set(i, new EXIFEntry(tagId, subIFDs.get(0), entry.getType()));
}
- else {
+ else {
// Replace the entry with parsed data
entries.set(i, new EXIFEntry(tagId, subIFDs.toArray(new IFD[subIFDs.size()]), entry.getType()));
}
}
}
catch (IIOException e) {
- // TODO: Issue warning without crashing...?
- e.printStackTrace();
+ if (DEBUG) {
+ // TODO: Issue warning without crashing...?
+ System.err.println("Error parsing sub-IFD: " + tagId);
+ e.printStackTrace();
+ }
}
}
}
@@ -204,7 +213,9 @@ public final class EXIFReader extends MetadataReader {
offsets = (long[]) value;
}
else {
- throw new IIOException(String.format("Unknown pointer type: %s", (value != null ? value.getClass() : null)));
+ throw new IIOException(String.format("Unknown pointer type: %s", (value != null
+ ? value.getClass()
+ : null)));
}
return offsets;
@@ -215,11 +226,6 @@ public final class EXIFReader extends MetadataReader {
int tagId = pInput.readUnsignedShort();
short type = pInput.readShort();
- // This isn't really an entry, and the directory entry count was wrong OR bad data...
- if (tagId == 0 && type == 0) {
- return null;
- }
-
int count = pInput.readInt(); // Number of values
// It's probably a spec violation to have count 0, but we'll be lenient about it
@@ -228,32 +234,33 @@ public final class EXIFReader extends MetadataReader {
}
if (type <= 0 || type > 13) {
+ pInput.skipBytes(4); // read Value
+
// Invalid tag, this is just for debugging
- long offset = pInput.getStreamPosition() - 8l;
+ long offset = pInput.getStreamPosition() - 12l;
if (DEBUG) {
System.err.printf("Bad EXIF data @%08x\n", pInput.getStreamPosition());
System.err.println("tagId: " + tagId + (tagId <= 0 ? " (INVALID)" : ""));
System.err.println("type: " + type + " (INVALID)");
System.err.println("count: " + count);
- }
- pInput.mark();
- pInput.seek(offset);
+ pInput.mark();
+ pInput.seek(offset);
- try {
- byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
- int len = pInput.read(bytes);
+ try {
+ byte[] bytes = new byte[8 + Math.min(120, Math.max(24, count))];
+ int len = pInput.read(bytes);
- if (DEBUG) {
- System.err.print(HexDump.dump(offset, bytes, 0, len));
- System.err.println(len < count ? "[...]" : "");
+ if (DEBUG) {
+ System.err.print(HexDump.dump(offset, bytes, 0, len));
+ System.err.println(len < count ? "[...]" : "");
+ }
+ }
+ finally {
+ pInput.reset();
}
}
- finally {
- pInput.reset();
- }
-
return null;
}
@@ -446,8 +453,8 @@ public final class EXIFReader extends MetadataReader {
}
static int getValueLength(final int pType, final int pCount) {
- if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) {
- return TIFF.TYPE_LENGTHS[pType - 1] * pCount;
+ if (pType > 0 && pType < TIFF.TYPE_LENGTHS.length) {
+ return TIFF.TYPE_LENGTHS[pType] * pCount;
}
return -1;
@@ -501,7 +508,8 @@ public final class EXIFReader extends MetadataReader {
//////////////////////
// TODO: Stream based hex dump util?
public static class HexDump {
- private HexDump() {}
+ private HexDump() {
+ }
private static final int WIDTH = 32;
@@ -515,7 +523,7 @@ public final class EXIFReader extends MetadataReader {
int i;
for (i = 0; i < len; i++) {
if (i % WIDTH == 0) {
- if (i > 0 ) {
+ if (i > 0) {
builder.append("\n");
}
builder.append(String.format("%08x: ", i + off + offset));
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java
index 1983ac04..af0ac041 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriter.java
@@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.metadata.MetadataWriter;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
@@ -39,6 +40,7 @@ import java.io.IOException;
import java.lang.reflect.Array;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.*;
/**
@@ -48,7 +50,7 @@ import java.util.*;
* @author last modified by $Author: haraldk$
* @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$
*/
-public class EXIFWriter {
+public final class EXIFWriter extends MetadataWriter {
static final int WORD_LENGTH = 2;
static final int LONGWORD_LENGTH = 4;
@@ -58,6 +60,7 @@ public class EXIFWriter {
return write(new IFD(entries), stream);
}
+ @Override
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
Validate.notNull(directory);
Validate.notNull(stream);
@@ -92,11 +95,11 @@ public class EXIFWriter {
stream.writeShort(42);
}
- public long writeIFD(final Collection entries, ImageOutputStream stream) throws IOException {
+ public long writeIFD(final Collection entries, final ImageOutputStream stream) throws IOException {
return writeIFD(new IFD(entries), stream, false);
}
- private long writeIFD(final Directory original, ImageOutputStream stream, boolean isSubIFD) throws IOException {
+ private long writeIFD(final Directory original, final ImageOutputStream stream, final boolean isSubIFD) throws IOException {
// TIFF spec says tags should be in increasing order, enforce that when writing
Directory ordered = ensureOrderedDirectory(original);
@@ -155,6 +158,10 @@ public class EXIFWriter {
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
}
+ public long computeIFDOffsetSize(final Collection directory) {
+ return computeDataSize(new IFD(directory)) + LONGWORD_LENGTH;
+ }
+
private long computeDataSize(final Directory directory) {
long dataSize = 0;
@@ -181,7 +188,7 @@ public class EXIFWriter {
private Directory ensureOrderedDirectory(final Directory directory) {
if (!isSorted(directory)) {
- List entries = new ArrayList(directory.size());
+ List entries = new ArrayList<>(directory.size());
for (Entry entry : directory) {
entries.add(entry);
@@ -215,7 +222,7 @@ public class EXIFWriter {
return true;
}
- private long writeValue(Entry entry, long dataOffset, ImageOutputStream stream) throws IOException {
+ private long writeValue(final Entry entry, final long dataOffset, final ImageOutputStream stream) throws IOException {
short type = getType(entry);
int valueLength = EXIFReader.getValueLength(type, getCount(entry));
@@ -236,18 +243,22 @@ public class EXIFWriter {
}
}
- private int getCount(Entry entry) {
+ private int getCount(final Entry entry) {
Object value = entry.getValue();
return value instanceof String ? ((String) value).getBytes(Charset.forName("UTF-8")).length + 1 : entry.valueCount();
}
- private void writeValueInline(Object value, short type, ImageOutputStream stream) throws IOException {
+ private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
if (value.getClass().isArray()) {
switch (type) {
+ case TIFF.TYPE_UNDEFINED:
case TIFF.TYPE_BYTE:
+ case TIFF.TYPE_SBYTE:
stream.write((byte[]) value);
break;
+
case TIFF.TYPE_SHORT:
+ case TIFF.TYPE_SSHORT:
short[] shorts;
if (value instanceof short[]) {
@@ -276,7 +287,9 @@ public class EXIFWriter {
stream.writeShorts(shorts, 0, shorts.length);
break;
+
case TIFF.TYPE_LONG:
+ case TIFF.TYPE_SLONG:
int[] ints;
if (value instanceof int[]) {
@@ -291,21 +304,49 @@ public class EXIFWriter {
}
}
else {
- throw new IllegalArgumentException("Unsupported type for TIFF SHORT: " + value.getClass());
+ throw new IllegalArgumentException("Unsupported type for TIFF LONG: " + value.getClass());
}
stream.writeInts(ints, 0, ints.length);
-
break;
case TIFF.TYPE_RATIONAL:
+ case TIFF.TYPE_SRATIONAL:
Rational[] rationals = (Rational[]) value;
for (Rational rational : rationals) {
stream.writeInt((int) rational.numerator());
stream.writeInt((int) rational.denominator());
}
- // TODO: More types
+ break;
+
+ case TIFF.TYPE_FLOAT:
+ float[] floats;
+
+ if (value instanceof float[]) {
+ floats = (float[]) value;
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
+ }
+
+ stream.writeFloats(floats, 0, floats.length);
+
+ break;
+
+ case TIFF.TYPE_DOUBLE:
+ double[] doubles;
+
+ if (value instanceof double[]) {
+ doubles = (double[]) value;
+ }
+ else {
+ throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
+ }
+
+ stream.writeDoubles(doubles, 0, doubles.length);
+
+ break;
default:
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
@@ -317,25 +358,35 @@ public class EXIFWriter {
else {
switch (type) {
case TIFF.TYPE_BYTE:
- stream.writeByte((Integer) value);
+ case TIFF.TYPE_SBYTE:
+ case TIFF.TYPE_UNDEFINED:
+ stream.writeByte(((Number) value).intValue());
break;
case TIFF.TYPE_ASCII:
- byte[] bytes = ((String) value).getBytes(Charset.forName("UTF-8"));
+ byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
stream.write(bytes);
stream.write(0);
break;
case TIFF.TYPE_SHORT:
- stream.writeShort((Integer) value);
+ case TIFF.TYPE_SSHORT:
+ stream.writeShort(((Number) value).intValue());
break;
case TIFF.TYPE_LONG:
+ case TIFF.TYPE_SLONG:
stream.writeInt(((Number) value).intValue());
break;
case TIFF.TYPE_RATIONAL:
+ case TIFF.TYPE_SRATIONAL:
Rational rational = (Rational) value;
stream.writeInt((int) rational.numerator());
stream.writeInt((int) rational.denominator());
break;
- // TODO: More types
+ case TIFF.TYPE_FLOAT:
+ stream.writeFloat(((Number) value).floatValue());
+ break;
+ case TIFF.TYPE_DOUBLE:
+ stream.writeDouble(((Number) value).doubleValue());
+ break;
default:
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
@@ -343,7 +394,7 @@ public class EXIFWriter {
}
}
- private void writeValueAt(long dataOffset, Object value, short type, ImageOutputStream stream) throws IOException {
+ private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
stream.writeInt(assertIntegerOffset(dataOffset));
long position = stream.getStreamPosition();
stream.seek(dataOffset);
@@ -351,12 +402,27 @@ public class EXIFWriter {
stream.seek(position);
}
- private short getType(Entry entry) {
+ private short getType(final Entry entry) {
+ // TODO: What a MESS! Rewrite and expose EXIFEntry as TIFFEntry or so...
+
+ // For internal entries use type directly
if (entry instanceof EXIFEntry) {
EXIFEntry exifEntry = (EXIFEntry) entry;
return exifEntry.getType();
}
+ // For other entries, use name if it matches
+ String typeName = entry.getTypeName();
+
+ if (typeName != null) {
+ for (int i = 1; i < TIFF.TYPE_NAMES.length; i++) {
+ if (typeName.equals(TIFF.TYPE_NAMES[i])) {
+ return (short) i;
+ }
+ }
+ }
+
+ // Otherwise, fall back to the native Java type
Object value = Validate.notNull(entry.getValue());
boolean array = value.getClass().isArray();
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
index d169e690..aeca1fd0 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java
@@ -88,13 +88,16 @@ public interface TIFF {
Should probably all map to Java long (and fail if high bit is set for the unsigned types???)
*/
String[] TYPE_NAMES = {
+ null,
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
"IFD",
null, null,
"LONG8", "SLONG8", "IFD8"
};
+ /** Length of the corresponding type, in bytes. */
int[] TYPE_LENGTHS = {
+ -1,
1, 1, 2, 4, 8,
1, 1, 2, 4, 8, 4, 8,
4,
@@ -124,6 +127,8 @@ public interface TIFF {
int TAG_YCBCR_POSITIONING = 531;
int TAG_X_RESOLUTION = 282;
int TAG_Y_RESOLUTION = 283;
+ int TAG_X_POSITION = 286;
+ int TAG_Y_POSITION = 287;
int TAG_RESOLUTION_UNIT = 296;
/// B. Tags relating to recording offset
@@ -131,9 +136,13 @@ public interface TIFF {
int TAG_STRIP_OFFSETS = 273;
int TAG_ROWS_PER_STRIP = 278;
int TAG_STRIP_BYTE_COUNTS = 279;
+ int TAG_FREE_OFFSETS = 288; // "Not recommended for general interchange."
// "Old-style" JPEG (still used as EXIF thumbnail)
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
+
+ int TAG_GROUP3OPTIONS = 292;
+ int TAG_GROUP4OPTIONS = 293;
/// C. Tags relating to image data characteristics
@@ -153,9 +162,11 @@ public interface TIFF {
/// D. Other tags
int TAG_DATE_TIME = 306;
+ int TAG_DOCUMENT_NAME = 269;
int TAG_IMAGE_DESCRIPTION = 270;
int TAG_MAKE = 271;
int TAG_MODEL = 272;
+ int TAG_PAGE_NAME = 285;
int TAG_PAGE_NUMBER = 297;
int TAG_SOFTWARE = 305;
int TAG_ARTIST = 315;
@@ -184,6 +195,18 @@ public interface TIFF {
*/
int TAG_PHOTOSHOP = 34377;
+ /**
+ * Photoshop layer and mask information (byte order follows TIFF container).
+ * Layer and mask information found in a typical layered Photoshop file.
+ * Starts with a character string of "Adobe Photoshop Document Data Block"
+ * (or "Adobe Photoshop Document Data V0002" for PSB)
+ * including the null termination character.
+ * @see com.twelvemonkeys.imageio.metadata.psd.PSD
+ */
+ int TAG_PHOTOSHOP_IMAGE_SOURCE_DATA = 37724;
+
+ int TAG_PHOTOSHOP_ANNOTATIONS = 50255;
+
/**
* ICC Color Profile.
* @see java.awt.color.ICC_Profile
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java
index a149aa80..adee2645 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java
@@ -36,110 +36,115 @@ package com.twelvemonkeys.imageio.metadata.iptc;
* @version $Id: IPTC.java,v 1.0 Nov 11, 2009 6:20:21 PM haraldk Exp$
*/
public interface IPTC {
- static final int ENVELOPE_RECORD = 1 << 8;
- static final int APPLICATION_RECORD = 2 << 8;
+ int ENVELOPE_RECORD = 1 << 8;
+ int APPLICATION_RECORD = 2 << 8;
- static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
+ /** 1:05: Destination */
+ int TAG_DESTINATION = ENVELOPE_RECORD | 5;
+ /** 1:50: Product ID */
+ int TAG_PRODUCT_ID = ENVELOPE_RECORD | 50;
+ /** 1:90: Coded Character Set */
+ int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
/** 2:00 Record Version (mandatory) */
- public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
+ int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
/** 2:03 Object Type Reference */
- public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
+ int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
/** 2:04 Object Attribute Reference (repeatable) */
- public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
+ int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
/** 2:05 Object Name */
- public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
+ int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
/** 2:07 Edit Status */
- public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
+ int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
/** 2:08 Editorial Update */
- public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
+ int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
/** 2:10 Urgency */
- public static final int TAG_URGENCY = APPLICATION_RECORD | 10;
+ int TAG_URGENCY = APPLICATION_RECORD | 10;
/** 2:12 Subect Reference (repeatable) */
- public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
+ int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
/** 2:15 Category */
- public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
+ int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
/** 2:20 Supplemental Category (repeatable) */
- public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
+ int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
/** 2:22 Fixture Identifier */
- public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
+ int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
/** 2:25 Keywords (repeatable) */
- public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25;
+ int TAG_KEYWORDS = APPLICATION_RECORD | 25;
/** 2:26 Content Locataion Code (repeatable) */
- public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
+ int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
/** 2:27 Content Locataion Name (repeatable) */
- public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
+ int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
/** 2:30 Release Date */
- public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
+ int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
/** 2:35 Release Time */
- public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
+ int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
/** 2:37 Expiration Date */
- public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
+ int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
/** 2:38 Expiration Time */
- public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
+ int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
/** 2:40 Special Instructions */
- public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
+ int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
/** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */
- public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
+ int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
/** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */
- public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
+ int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
/** 2:47 Reference Date (mandatory if 2:45 present) */
- public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
+ int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
/** 2:50 Reference Number (mandatory if 2:45 present) */
- public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
+ int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
/** 2:55 Date Created */
- public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
+ int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
/** 2:60 Time Created */
- public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
+ int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
/** 2:62 Digital Creation Date */
- public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
+ int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
/** 2:63 Digital Creation Date */
- public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
+ int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
/** 2:65 Originating Program */
- public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
+ int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
/** 2:70 Program Version (only valid if 2:65 present) */
- public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
+ int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
/** 2:75 Object Cycle (a: morning, p: evening, b: both) */
- public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
+ int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
/** 2:80 By-line (repeatable) */
- public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
+ int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
/** 2:85 By-line Title (repeatable) */
- public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
+ int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
/** 2:90 City */
- public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
+ int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
/** 2:92 Sub-location */
- public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
+ int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
/** 2:95 Province/State */
- public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
+ int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
/** 2:100 Country/Primary Location Code */
- public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
+ int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
/** 2:101 Country/Primary Location Name */
- public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
+ int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
/** 2:103 Original Transmission Reference */
- public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
+ int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
/** 2:105 Headline */
- public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
+ int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
/** 2:110 Credit */
- public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
+ int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
/** 2:115 Source */
- public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
+ int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
/** 2:116 Copyright Notice */
- public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
+ int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
/** 2:118 Contact */
- public static final int TAG_CONTACT = APPLICATION_RECORD | 118;
+ int TAG_CONTACT = APPLICATION_RECORD | 118;
/** 2:120 Catption/Abstract */
- public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
+ int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
/** 2:122 Writer/Editor (repeatable) */
- public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
+ int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
/** 2:125 Rasterized Caption (binary data) */
- public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
+ int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
/** 2:130 Image Type */
- public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
+ int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
/** 2:131 Image Orientation */
- public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
+ int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
/** 2:135 Language Identifier */
- public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
+ int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
// TODO: 2:150-2:154 Audio
@@ -150,9 +155,28 @@ public interface IPTC {
*
* @see JobMinder Homepage
*/
- static final int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199;
+ int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199;
// TODO: Other custom fields in 155-200 range?
// TODO: 2:200-2:202 Object Preview Data
+
+ final class Tags {
+ static boolean isArray(final short tagId) {
+ switch (tagId) {
+ case IPTC.TAG_DESTINATION:
+ case IPTC.TAG_PRODUCT_ID:
+ case IPTC.TAG_SUBJECT_REFERENCE:
+ case IPTC.TAG_SUPPLEMENTAL_CATEGORIES:
+ case IPTC.TAG_KEYWORDS:
+ case IPTC.TAG_CONTENT_LOCATION_CODE:
+ case IPTC.TAG_CONTENT_LOCATION_NAME:
+ case IPTC.TAG_BY_LINE:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java
index 133f07bc..54a644ca 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java
@@ -42,6 +42,7 @@ import java.util.Collection;
*/
final class IPTCDirectory extends AbstractDirectory {
IPTCDirectory(final Collection extends Entry> entries) {
+ // TODO: Normalize multiple entries with same key to single entry w/array
super(entries);
}
}
\ No newline at end of file
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java
index 6bca3f91..956b6ac1 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java
@@ -47,6 +47,30 @@ class IPTCEntry extends AbstractEntry {
switch ((Integer) getIdentifier()) {
case IPTC.TAG_RECORD_VERSION:
return "RecordVersion";
+ case IPTC.TAG_KEYWORDS:
+ return "Keywords";
+ case IPTC.TAG_SPECIAL_INSTRUCTIONS:
+ return "Instructions";
+ case IPTC.TAG_DIGITAL_CREATION_DATE:
+ return "DigitalCreationDate";
+ case IPTC.TAG_DIGITAL_CREATION_TIME:
+ return "DigitalCreationTime";
+ case IPTC.TAG_DATE_CREATED:
+ return "DateCreated";
+ case IPTC.TAG_TIME_CREATED:
+ return "TimeCreated";
+ case IPTC.TAG_BY_LINE_TITLE:
+ return "ByLineTitle";
+ case IPTC.TAG_CITY:
+ return "City";
+ case IPTC.TAG_SUB_LOCATION:
+ return "SubLocation";
+ case IPTC.TAG_PROVINCE_OR_STATE:
+ return "StateProvince";
+ case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE:
+ return "CountryCode";
+ case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION:
+ return "Country";
case IPTC.TAG_SOURCE:
return "Source";
case IPTC.TAG_CAPTION:
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java
index a9fad969..34a8f1a5 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java
@@ -43,8 +43,9 @@ import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
/**
* IPTCReader
@@ -60,61 +61,88 @@ public final class IPTCReader extends MetadataReader {
private int encoding = ENCODING_UNSPECIFIED;
-
@Override
public Directory read(final ImageInputStream input) throws IOException {
Validate.notNull(input, "input");
- List entries = new ArrayList();
+ Map entries = new LinkedHashMap<>();
// 0x1c identifies start of a tag
while (input.read() == 0x1c) {
short tagId = input.readShort();
int tagByteCount = input.readUnsignedShort();
- Entry entry = readEntry(input, tagId, tagByteCount);
+
+ boolean array = IPTC.Tags.isArray(tagId);
+ Entry entry = readEntry(input, tagId, tagByteCount, array, array ? entries.get(tagId) : null);
if (entry != null) {
- entries.add(entry);
+ entries.put(tagId, entry);
}
}
- return new IPTCDirectory(entries);
+ return new IPTCDirectory(entries.values());
}
- private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException {
- Object value = null;
+ private IPTCEntry mergeEntries(final short tagId, final Object newValue, final Entry oldEntry) {
+ Object[] oldValue = oldEntry != null ? (Object[]) oldEntry.getValue() : null;
+ Object[] value;
+
+ if (newValue instanceof String) {
+ if (oldValue == null) {
+ value = new String[] {(String) newValue};
+ }
+ else {
+ String[] array = (String[]) oldValue;
+ value = Arrays.copyOf(array, array.length + 1);
+ value[value.length - 1] = newValue;
+ }
+ }
+ else {
+ if (oldValue == null) {
+ value = new Object[] {newValue};
+ }
+ else {
+ value = Arrays.copyOf(oldValue, oldValue.length + 1);
+ value [value .length - 1] = newValue;
+ }
+ }
+
+ return new IPTCEntry(tagId, value);
+ }
+
+ private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength, final boolean array, final Entry oldEntry) throws IOException {
+ Object value;
switch (pTagId) {
case IPTC.TAG_CODED_CHARACTER_SET:
// TODO: Mapping from ISO 646 to Java supported character sets?
- // TODO: Move somewhere else?
encoding = parseEncoding(pInput, pLength);
return null;
case IPTC.TAG_RECORD_VERSION:
+ // TODO: Assert length == 2?
// A single unsigned short value
value = pInput.readUnsignedShort();
break;
default:
- // Skip non-Application fields, as they are typically not human readable
- if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) {
- pInput.skipBytes(pLength);
- return null;
+ // TODO: Create Tags.getType(tag), to allow for more flexible types
+ if ((pTagId & 0xff00) == IPTC.APPLICATION_RECORD) {
+ // Treat Application records as Strings
+ if (pLength < 1) {
+ value = null;
+ }
+ else {
+ value = parseString(pInput, pLength);
+ }
+ }
+ else {
+ // Non-Application fields, typically not human readable
+ byte[] data = new byte[pLength];
+ pInput.readFully(data);
+ value = data;
}
-
- // fall through
}
- // If we don't have a value, treat it as a string
- if (value == null) {
- if (pLength < 1) {
- value = null;
- }
- else {
- value = parseString(pInput, pLength);
- }
- }
-
- return new IPTCEntry(pTagId, value);
+ return array ? mergeEntries(pTagId, value, oldEntry) : new IPTCEntry(pTagId, value);
}
private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException {
@@ -148,7 +176,7 @@ public final class IPTCReader extends MetadataReader {
}
// Fall back to use ISO-8859-1
- // This will not fail, but may may create wrong fallback-characters
+ // This will not fail, but may create wrong fallback-characters
return StringUtil.decode(data, 0, data.length, "ISO8859_1");
}
}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriter.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriter.java
new file mode 100644
index 00000000..11904a7f
--- /dev/null
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriter.java
@@ -0,0 +1,69 @@
+package com.twelvemonkeys.imageio.metadata.iptc;
+
+import com.twelvemonkeys.imageio.metadata.Directory;
+import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.metadata.MetadataWriter;
+
+import javax.imageio.stream.ImageOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static com.twelvemonkeys.lang.Validate.notNull;
+
+/**
+ * IPTCWriter.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: IPTCWriter.java,v 1.0 28/05/15 harald.kuhr Exp$
+ */
+public final class IPTCWriter extends MetadataWriter {
+ @Override
+ public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
+ notNull(directory, "directory");
+ notNull(stream, "stream");
+
+ // TODO: Make sure we always write application record version (2.00)
+ // TODO: Write encoding UTF8?
+
+ for (Entry entry : directory) {
+ int tag = (Integer) entry.getIdentifier();
+ Object value = entry.getValue();
+
+ if (IPTC.Tags.isArray((short) tag)) {
+ Object[] values = (Object[]) value;
+
+ for (Object v : values) {
+ stream.write(0x1c);
+ stream.writeShort(tag);
+ writeValue(stream, v);
+ }
+ }
+ else {
+ stream.write(0x1c);
+ stream.writeShort(tag);
+ writeValue(stream, value);
+ }
+ }
+
+ return false;
+ }
+
+ private void writeValue(final ImageOutputStream stream, final Object value) throws IOException {
+ if (value instanceof String) {
+ byte[] data = ((String) value).getBytes(StandardCharsets.UTF_8);
+ stream.writeShort(data.length);
+ stream.write(data);
+ }
+ else if (value instanceof byte[]) {
+ byte[] data = (byte[]) value;
+ stream.writeShort(data.length);
+ stream.write(data);
+ }
+ else if (value instanceof Integer) {
+ // TODO: Need to know types from tag
+ stream.writeShort(2);
+ stream.writeShort((Integer) value);
+ }
+ }
+}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java
index b89d4eb4..f07a4453 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java
@@ -66,7 +66,8 @@ class PSDEntry extends AbstractEntry {
field.setAccessible(true);
if (field.get(null).equals(getIdentifier())) {
- return StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true);
+ String fieldName = StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true);
+ return name != null ? fieldName + ": " + name : fieldName;
}
}
}
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java
index f6e67a46..a8904a50 100755
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDReader.java
@@ -59,7 +59,7 @@ public final class PSDReader extends MetadataReader {
public Directory read(final ImageInputStream input) throws IOException {
Validate.notNull(input, "input");
- List entries = new ArrayList();
+ List entries = new ArrayList<>();
while (true) {
try {
diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java
index 642ecf7f..28abce4f 100644
--- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java
+++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java
@@ -71,6 +71,7 @@ public final class XMPReader extends MetadataReader {
// TODO: Consider parsing using SAX?
// TODO: Determine encoding and parse using a Reader...
// TODO: Refactor scanner to return inputstream?
+ // TODO: Be smarter about ASCII-NULL termination/padding (the SAXParser aka Xerces DOMParser doesn't like it)...
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java
index 189d6c28..24c36fa7 100644
--- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataReaderAbstractTest.java
@@ -79,7 +79,7 @@ public abstract class MetadataReaderAbstractTest {
assertNotNull(directory);
}
- protected final Matcher hasValue(final Object value) {
+ protected static Matcher hasValue(final Object value) {
return new EntryHasValue(value);
}
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataWriterAbstractTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataWriterAbstractTest.java
new file mode 100644
index 00000000..845ebc97
--- /dev/null
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/MetadataWriterAbstractTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.metadata;
+
+import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
+import org.junit.Test;
+
+import javax.imageio.ImageIO;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.MemoryCacheImageOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+
+/**
+ * ReaderAbstractTest
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haraldk$
+ * @version $Id: ReaderAbstractTest.java,v 1.0 04.01.12 09:40 haraldk Exp$
+ */
+public abstract class MetadataWriterAbstractTest {
+ static {
+ IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
+ ImageIO.setUseCache(false);
+ }
+
+ protected final URL getResource(final String name) throws IOException {
+ return getClass().getResource(name);
+ }
+
+ protected final ImageInputStream getDataAsIIS() throws IOException {
+ return ImageIO.createImageInputStream(getData());
+ }
+
+ protected abstract InputStream getData() throws IOException;
+
+ protected abstract MetadataWriter createWriter();
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWriteNullDirectory() throws IOException {
+ createWriter().write(null, new MemoryCacheImageOutputStream(new ByteArrayOutputStream()));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWriteNullStream() throws IOException {
+ createWriter().write(new AbstractDirectory(new ArrayList()) {
+ }, null);
+ }
+}
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
index 1249de6c..2d2468ac 100644
--- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReaderTest.java
@@ -276,4 +276,20 @@ public class EXIFReaderTest extends MetadataReaderAbstractTest {
assertNotNull(interop);
assertEquals(0, interop.size());
}
+
+ @Test
+ public void testReadExifWithMissingEOFMarker() throws IOException {
+ try (ImageInputStream stream = ImageIO.createImageInputStream(getResource("/exif/noeof.tif"))) {
+ CompoundDirectory directory = (CompoundDirectory) createReader().read(stream);
+ assertEquals(15, directory.size());
+ assertEquals(1, directory.directoryCount());
+ }
+ }
+
+ public void testReadExifWithEmptyTag() throws IOException {
+ try (ImageInputStream stream = ImageIO.createImageInputStream(getResource("/exif/emptyexiftag.tif"))) {
+ CompoundDirectory directory = (CompoundDirectory) createReader().read(stream);
+ assertEquals(3, directory.directoryCount());
+ }
+ }
}
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java
index a425a7c7..f682fd00 100644
--- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/EXIFWriterTest.java
@@ -28,24 +28,17 @@
package com.twelvemonkeys.imageio.metadata.exif;
-import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
-import com.twelvemonkeys.imageio.metadata.AbstractEntry;
-import com.twelvemonkeys.imageio.metadata.Directory;
-import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.metadata.*;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
-import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test;
import javax.imageio.ImageIO;
-import javax.imageio.spi.IIORegistry;
-import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.ImageOutputStreamImpl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.net.URL;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
@@ -61,37 +54,25 @@ import static org.junit.Assert.assertNotNull;
* @author last modified by $Author: haraldk$
* @version $Id: EXIFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
*/
-public class EXIFWriterTest {
- static {
- IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
- ImageIO.setUseCache(false);
- }
+public class EXIFWriterTest extends MetadataWriterAbstractTest {
- protected final URL getResource(final String name) throws IOException {
- return getClass().getResource(name);
- }
-
- protected final ImageInputStream getDataAsIIS() throws IOException {
- return ImageIO.createImageInputStream(getData());
- }
-
- // @Override
+ @Override
protected InputStream getData() throws IOException {
return getResource("/exif/exif-jpeg-segment.bin").openStream();
}
-// @Override
protected EXIFReader createReader() {
return new EXIFReader();
}
+ @Override
protected EXIFWriter createWriter() {
return new EXIFWriter();
}
@Test
public void testWriteReadSimple() throws IOException {
- ArrayList entries = new ArrayList();
+ ArrayList entries = new ArrayList<>();
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
@@ -139,7 +120,7 @@ public class EXIFWriterTest {
@Test
public void testWriteMotorola() throws IOException {
- ArrayList entries = new ArrayList();
+ ArrayList entries = new ArrayList<>();
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
Directory directory = new AbstractDirectory(entries) {};
@@ -174,7 +155,7 @@ public class EXIFWriterTest {
@Test
public void testWriteIntel() throws IOException {
- ArrayList entries = new ArrayList();
+ ArrayList entries = new ArrayList<>();
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
Directory directory = new AbstractDirectory(entries) {};
@@ -254,7 +235,7 @@ public class EXIFWriterTest {
@Test
public void testComputeIFDSize() throws IOException {
- ArrayList entries = new ArrayList();
+ ArrayList entries = new ArrayList<>();
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriterTest.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriterTest.java
new file mode 100644
index 00000000..e5f277db
--- /dev/null
+++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCWriterTest.java
@@ -0,0 +1,71 @@
+package com.twelvemonkeys.imageio.metadata.iptc;
+
+import com.twelvemonkeys.imageio.metadata.Directory;
+import com.twelvemonkeys.imageio.metadata.MetadataWriter;
+import com.twelvemonkeys.imageio.metadata.MetadataWriterAbstractTest;
+import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
+import org.junit.Test;
+
+import javax.imageio.stream.MemoryCacheImageOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeNotNull;
+
+/**
+ * IPTCWriterTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: IPTCWriterTest.java,v 1.0 05/06/15 harald.kuhr Exp$
+ */
+public class IPTCWriterTest extends MetadataWriterAbstractTest {
+ @Override
+ protected InputStream getData() throws IOException {
+ return getResource("/iptc/iptc-jpeg-segment.bin").openStream();
+ }
+
+ @Override
+ protected MetadataWriter createWriter() {
+ return new IPTCWriter();
+ }
+
+ private IPTCReader createReader() {
+ return new IPTCReader();
+ }
+
+ @Test
+ public void testRewriteExisting() throws IOException {
+ IPTCReader reader = createReader();
+ Directory iptc = reader.read(getDataAsIIS());
+ assumeNotNull(iptc);
+
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ MemoryCacheImageOutputStream stream = new MemoryCacheImageOutputStream(bytes);
+ createWriter().write(iptc, stream);
+ stream.close();
+
+ Directory written = reader.read(new ByteArrayImageInputStream(bytes.toByteArray()));
+ assertEquals(iptc, written);
+ }
+
+ @Test
+ public void testWrite() throws IOException {
+ List entries = new ArrayList<>();
+ entries.add(new IPTCEntry(IPTC.TAG_KEYWORDS, new String[] {"Uno", "Due", "Tre"}));
+
+ Directory iptc = new IPTCDirectory(entries);
+
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ MemoryCacheImageOutputStream stream = new MemoryCacheImageOutputStream(bytes);
+ createWriter().write(iptc, stream);
+ stream.close();
+
+ Directory written = createReader().read(new ByteArrayImageInputStream(bytes.toByteArray()));
+ assertEquals(iptc, written);
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-metadata/src/test/resources/exif/emptyexiftag.tif b/imageio/imageio-metadata/src/test/resources/exif/emptyexiftag.tif
new file mode 100644
index 00000000..40fa5507
Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/exif/emptyexiftag.tif differ
diff --git a/imageio/imageio-metadata/src/test/resources/exif/noeof.tif b/imageio/imageio-metadata/src/test/resources/exif/noeof.tif
new file mode 100644
index 00000000..19e68a2d
Binary files /dev/null and b/imageio/imageio-metadata/src/test/resources/exif/noeof.tif differ
diff --git a/imageio/imageio-pcx/pom.xml b/imageio/imageio-pcx/pom.xml
index 511724a1..0f49aa20 100755
--- a/imageio/imageio-pcx/pom.xml
+++ b/imageio/imageio-pcx/pom.xml
@@ -1,12 +1,10 @@
-
+
4.0.0
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-pcx
TwelveMonkeys :: ImageIO :: PCX plugin
@@ -22,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderSpi.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderSpi.java
index 1a4e31b1..b5749027 100755
--- a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderSpi.java
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderSpi.java
@@ -28,49 +28,21 @@
package com.twelvemonkeys.imageio.plugins.dcx;
-import com.twelvemonkeys.imageio.spi.ProviderInfo;
-import com.twelvemonkeys.imageio.util.IIOUtil;
+import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
-import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Locale;
-public final class DCXImageReaderSpi extends ImageReaderSpi {
+public final class DCXImageReaderSpi extends ImageReaderSpiBase {
/**
* Creates a {@code DCXImageReaderSpi}.
*/
public DCXImageReaderSpi() {
- this(IIOUtil.getProviderInfo(DCXImageReaderSpi.class));
- }
-
- private DCXImageReaderSpi(final ProviderInfo providerInfo) {
- super(
- providerInfo.getVendorName(),
- providerInfo.getVersion(),
- new String[]{
- "dcx",
- "DCX"
- },
- new String[]{"dcx"},
- new String[]{
- // No official IANA record exists
- "image/dcx",
- "image/x-dcx",
- },
- "com.twelvemkonkeys.imageio.plugins.dcx.DCXImageReader",
- new Class[] {ImageInputStream.class},
- null,
- true, // supports standard stream metadata
- null, null, // native stream format name and class
- null, null, // extra stream formats
- true, // supports standard image metadata
- null, null,
- null, null // extra image metadata formats
- );
+ super(new DCXProviderInfo());
}
@Override public boolean canDecodeInput(final Object source) throws IOException {
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfo.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfo.java
index 264a7701..434119e3 100644
--- a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfo.java
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.dcx;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
@@ -23,8 +51,8 @@ final class DCXProviderInfo extends ReaderWriterProviderInfo {
"image/dcx",
"image/x-dcx",
},
- "com.twelvemkonkeys.imageio.plugins.dcx.DCXImageReader",
- new String[] {"com.twelvemkonkeys.imageio.plugins.dcx.DCXImageReaderSpi"},
+ "com.twelvemonkeys.imageio.plugins.dcx.DCXImageReader",
+ new String[] {"com.twelvemonkeys.imageio.plugins.dcx.DCXImageReaderSpi"},
null, null,
false, null, null, null, null,
true, null, null, null, null
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java
index afe9e496..604e012a 100755
--- a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReader.java
@@ -277,13 +277,13 @@ public final class PCXImageReader extends ImageReaderBase {
throw new AssertionError();
}
- processImageProgress(100f * y / height * c / header.getChannels());
-
if (abortRequested()) {
break;
}
}
+ processImageProgress(100f * y / height);
+
if (y >= srcRegion.y + srcRegion.height) {
break;
}
@@ -372,7 +372,7 @@ public final class PCXImageReader extends ImageReaderBase {
readPalette = true;
// Wee can't simply skip to an offset, as the RLE compression makes the file size unpredictable
- skiptToEOF(imageInput);
+ skipToEOF(imageInput);
// Seek backwards from EOF
long paletteStart = imageInput.getStreamPosition() - 769;
@@ -400,7 +400,7 @@ public final class PCXImageReader extends ImageReaderBase {
}
// TODO: Candidate util method
- private static long skiptToEOF(final ImageInputStream stream) throws IOException {
+ private static long skipToEOF(final ImageInputStream stream) throws IOException {
long length = stream.length();
if (length > 0) {
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java
index 25b376a7..2c6d1ca6 100755
--- a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXMetadata.java
@@ -28,52 +28,22 @@
package com.twelvemonkeys.imageio.plugins.pcx;
-import org.w3c.dom.Node;
+import com.twelvemonkeys.imageio.AbstractMetadata;
-import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.IndexColorModel;
-final class PCXMetadata extends IIOMetadata {
- // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
-
+final class PCXMetadata extends AbstractMetadata {
private final PCXHeader header;
private final IndexColorModel vgaPalette;
PCXMetadata(final PCXHeader header, final IndexColorModel vgaPalette) {
this.header = header;
this.vgaPalette = vgaPalette;
-
- standardFormatSupported = true;
}
- @Override public boolean isReadOnly() {
- return true;
- }
-
- @Override public Node getAsTree(final String formatName) {
- if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
- return getStandardTree();
- }
- else {
- throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
- }
- }
-
- @Override public void mergeTree(final String formatName, final Node root) {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
- @Override public void reset() {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
- @Override protected IIOMetadataNode getStandardChromaNode() {
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IndexColorModel palette = null;
@@ -141,7 +111,8 @@ final class PCXMetadata extends IIOMetadata {
// No compression
- @Override protected IIOMetadataNode getStandardCompressionNode() {
+ @Override
+ protected IIOMetadataNode getStandardCompressionNode() {
if (header.getCompression() != PCX.COMPRESSION_NONE) {
IIOMetadataNode node = new IIOMetadataNode("Compression");
@@ -159,7 +130,8 @@ final class PCXMetadata extends IIOMetadata {
return null;
}
- @Override protected IIOMetadataNode getStandardDataNode() {
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
// Planar configuration only makes sense for multi-channel images
@@ -202,7 +174,8 @@ final class PCXMetadata extends IIOMetadata {
return buffer.toString();
}
- @Override protected IIOMetadataNode getStandardDimensionNode() {
+ @Override
+ protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
@@ -218,7 +191,8 @@ final class PCXMetadata extends IIOMetadata {
// No tiling
- @Override protected IIOMetadataNode getStandardTransparencyNode() {
+ @Override
+ protected IIOMetadataNode getStandardTransparencyNode() {
// NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
diff --git a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfo.java b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfo.java
index e55e8c0c..8f734fda 100644
--- a/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfo.java
+++ b/imageio/imageio-pcx/src/main/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.pcx;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
@@ -23,8 +51,8 @@ final class PCXProviderInfo extends ReaderWriterProviderInfo {
"image/pcx",
"image/x-pcx",
},
- "com.twelvemkonkeys.imageio.plugins.pcx.PCXImageReader",
- new String[] {"com.twelvemkonkeys.imageio.plugins.pcx.PCXImageReaderSpi"},
+ "com.twelvemonkeys.imageio.plugins.pcx.PCXImageReader",
+ new String[] {"com.twelvemonkeys.imageio.plugins.pcx.PCXImageReaderSpi"},
null, null,
false, null, null, null, null,
true, null, null, null, null
diff --git a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderTest.java b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderTest.java
index e5a9cc5b..51ef59f3 100755
--- a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderTest.java
+++ b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXImageReaderTest.java
@@ -28,15 +28,13 @@
package com.twelvemonkeys.imageio.plugins.dcx;
-import java.awt.*;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import javax.imageio.spi.ImageReaderSpi;
-
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
-import org.junit.Test;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
/**
* DCXImageReaderTest
@@ -45,10 +43,10 @@ import org.junit.Test;
* @author last modified by $Author: haraldk$
* @version $Id: DCXImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
*/
-public class DCXImageReaderTest extends ImageReaderAbstractTestCase {
+public class DCXImageReaderTest extends ImageReaderAbstractTest {
@Override
protected List getTestData() {
- return Arrays.asList(
+ return Collections.singletonList(
new TestData(getClassLoaderResource("/dcx/input.dcx"), new Dimension(70, 46)) // RLE encoded RGB (the only sample I've found)
);
}
@@ -75,7 +73,7 @@ public class DCXImageReaderTest extends ImageReaderAbstractTestCase getSuffixes() {
- return Arrays.asList("dcx");
+ return Collections.singletonList("dcx");
}
@Override
diff --git a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfoTest.java b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfoTest.java
new file mode 100644
index 00000000..5e3893e7
--- /dev/null
+++ b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/dcx/DCXProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.dcx;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * DCXProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: DCXProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class DCXProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new DCXProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java
index 69d2cc86..078e6a05 100755
--- a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java
+++ b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXImageReaderTest.java
@@ -28,13 +28,14 @@
package com.twelvemonkeys.imageio.plugins.pcx;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -44,7 +45,7 @@ import java.util.List;
* @author last modified by $Author: haraldk$
* @version $Id: PCXImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
*/
-public class PCXImageReaderTest extends ImageReaderAbstractTestCase {
+public class PCXImageReaderTest extends ImageReaderAbstractTest {
@Override
protected List getTestData() {
return Arrays.asList(
@@ -91,7 +92,7 @@ public class PCXImageReaderTest extends ImageReaderAbstractTestCase getSuffixes() {
- return Arrays.asList("pcx");
+ return Collections.singletonList("pcx");
}
@Override
diff --git a/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfoTest.java b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfoTest.java
new file mode 100644
index 00000000..d82951a0
--- /dev/null
+++ b/imageio/imageio-pcx/src/test/java/com/twelvemonkeys/imageio/plugins/pcx/PCXProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.pcx;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * PCXProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PCXProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class PCXProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new PCXProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-pdf/pom.xml b/imageio/imageio-pdf/pom.xml
index 8d0628f9..9f19c32e 100644
--- a/imageio/imageio-pdf/pom.xml
+++ b/imageio/imageio-pdf/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-pdf
TwelveMonkeys :: ImageIO :: PDF plugin
@@ -21,7 +21,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-pict/pom.xml b/imageio/imageio-pict/pom.xml
index 43f2abbd..1b9afc34 100644
--- a/imageio/imageio-pict/pom.xml
+++ b/imageio/imageio-pict/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-pict
TwelveMonkeys :: ImageIO :: PICT plugin
@@ -18,7 +18,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfo.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfo.java
index d5117e2f..20b16eb2 100644
--- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfo.java
+++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.pict;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
@@ -16,10 +44,10 @@ final class PICTProviderInfo extends ReaderWriterProviderInfo {
new String[] {"pct", "PCT", "pict", "PICT"},
new String[] {"pct", "pict"},
new String[] {"image/pict", "image/x-pict"},
- "com.twelvemkonkeys.imageio.plugins.pict.PICTImageReader",
+ "com.twelvemonkeys.imageio.plugins.pict.PICTImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi"},
"com.twelvemonkeys.imageio.plugins.pict.PICTImageWriter",
- new String[] {"com.twelvemkonkeys.imageio.plugins.pict.PICTImageWriterSpi"},
+ new String[] {"com.twelvemonkeys.imageio.plugins.pict.PICTImageWriterSpi"},
false, null, null, null, null,
true, null, null, null, null
);
diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java
index 41efe9f5..9c46904c 100644
--- a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java
+++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderTest.java
@@ -2,7 +2,7 @@ package com.twelvemonkeys.imageio.plugins.pict;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStreamSpi;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import javax.imageio.spi.IIORegistry;
@@ -11,6 +11,7 @@ import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertFalse;
@@ -22,7 +23,7 @@ import static org.junit.Assert.assertFalse;
* @author last modified by $Author: haraldk$
* @version $Id: ICOImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
-public class PICTImageReaderTest extends ImageReaderAbstractTestCase {
+public class PICTImageReaderTest extends ImageReaderAbstractTest {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new ByteArrayImageInputStreamSpi());
@@ -71,7 +72,7 @@ public class PICTImageReaderTest extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("pict");
+ return Collections.singletonList("pict");
}
protected List getSuffixes() {
diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfoTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfoTest.java
new file mode 100644
index 00000000..920e2c91
--- /dev/null
+++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/PICTProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.pict;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * PICTProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PICTProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class PICTProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new PICTProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-pnm/pom.xml b/imageio/imageio-pnm/pom.xml
index 2d808f38..647d5ae1 100755
--- a/imageio/imageio-pnm/pom.xml
+++ b/imageio/imageio-pnm/pom.xml
@@ -1,12 +1,10 @@
-
+
4.0.0
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-pnm
TwelveMonkeys :: ImageIO :: PNM plugin
@@ -23,7 +21,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java
index c35e64d0..6b10f6d2 100755
--- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java
+++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpi.java
@@ -60,11 +60,11 @@ public final class PNMImageReaderSpi extends ImageReaderSpi {
"image/x-portable-anymap",
"image/x-portable-arbitrarymap" // PAM
},
- "com.twelvemkonkeys.imageio.plugins.pnm.PNMImageReader",
+ "com.twelvemonkeys.imageio.plugins.pnm.PNMImageReader",
new Class[] {ImageInputStream.class},
new String[] {
- "com.twelvemkonkeys.imageio.plugins.pnm.PNMImageWriterSpi",
- "com.twelvemkonkeys.imageio.plugins.pnm.PAMImageWriterSpi"
+ "com.twelvemonkeys.imageio.plugins.pnm.PNMImageWriterSpi",
+ "com.twelvemonkeys.imageio.plugins.pnm.PAMImageWriterSpi"
},
true, // supports standard stream metadata
null, null, // native stream format name and class
diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java
index 267439b5..58578603 100755
--- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java
+++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMMetadata.java
@@ -28,51 +28,22 @@
package com.twelvemonkeys.imageio.plugins.pnm;
-import org.w3c.dom.Node;
+import com.twelvemonkeys.imageio.AbstractMetadata;
-import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.*;
import java.awt.image.DataBuffer;
import java.nio.ByteOrder;
-final class PNMMetadata extends IIOMetadata {
- // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
-
+final class PNMMetadata extends AbstractMetadata {
private final PNMHeader header;
PNMMetadata(final PNMHeader header) {
this.header = header;
- standardFormatSupported = true;
}
- @Override public boolean isReadOnly() {
- return true;
- }
-
- @Override public Node getAsTree(final String formatName) {
- if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
- return getStandardTree();
- }
- else {
- throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
- }
- }
-
- @Override public void mergeTree(final String formatName, final Node root) {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
- @Override public void reset() {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
- @Override protected IIOMetadataNode getStandardChromaNode() {
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
@@ -105,7 +76,9 @@ final class PNMMetadata extends IIOMetadata {
// TODO: Might make sense to set gamma?
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
- blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO ? "FALSE" : "TRUE");
+ blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO
+ ? "FALSE"
+ : "TRUE");
chroma.appendChild(blackIsZero);
return chroma;
@@ -113,11 +86,14 @@ final class PNMMetadata extends IIOMetadata {
// No compression
- @Override protected IIOMetadataNode getStandardDataNode() {
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
- sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT ? "Real" : "UnsignedIntegral");
+ sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT
+ ? "Real"
+ : "UnsignedIntegral");
node.appendChild(sampleFormat);
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
@@ -128,7 +104,9 @@ final class PNMMetadata extends IIOMetadata {
significantBitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(computeSignificantBits())));
node.appendChild(significantBitsPerSample);
- String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN ? "0" : Integer.toString(header.getBitsPerSample() - 1);
+ String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN
+ ? "0"
+ : Integer.toString(header.getBitsPerSample() - 1);
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb));
@@ -166,7 +144,8 @@ final class PNMMetadata extends IIOMetadata {
return buffer.toString();
}
- @Override protected IIOMetadataNode getStandardDimensionNode() {
+ @Override
+ protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
@@ -178,7 +157,8 @@ final class PNMMetadata extends IIOMetadata {
// No document node
- @Override protected IIOMetadataNode getStandardTextNode() {
+ @Override
+ protected IIOMetadataNode getStandardTextNode() {
if (!header.getComments().isEmpty()) {
IIOMetadataNode text = new IIOMetadataNode("Text");
@@ -197,7 +177,8 @@ final class PNMMetadata extends IIOMetadata {
// No tiling
- @Override protected IIOMetadataNode getStandardTransparencyNode() {
+ @Override
+ protected IIOMetadataNode getStandardTransparencyNode() {
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
diff --git a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageWriterSpiTest.java b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageWriterSpiTest.java
new file mode 100644
index 00000000..c6efb07b
--- /dev/null
+++ b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PAMImageWriterSpiTest.java
@@ -0,0 +1,38 @@
+package com.twelvemonkeys.imageio.plugins.pnm;
+
+import org.junit.Test;
+
+import javax.imageio.ImageWriter;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ImageWriterSpi;
+
+import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists;
+import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * PAMImageWriterSpiTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PAMImageWriterSpiTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class PAMImageWriterSpiTest {
+
+ private final ImageWriterSpi spi = new PAMImageWriterSpi();
+
+ @Test
+ public void getPluginClassName() {
+ assertClassExists(spi.getPluginClassName(), ImageWriter.class);
+ }
+
+ @Test
+ public void getImageReaderSpiNames() {
+ assertClassesExist(spi.getImageReaderSpiNames(), ImageReaderSpi.class);
+ }
+
+ @Test
+ public void getOutputTypes() {
+ assertNotNull(spi.getOutputTypes());
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpiTest.java b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpiTest.java
new file mode 100644
index 00000000..2ed5daef
--- /dev/null
+++ b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderSpiTest.java
@@ -0,0 +1,38 @@
+package com.twelvemonkeys.imageio.plugins.pnm;
+
+import org.junit.Test;
+
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ImageWriterSpi;
+
+import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists;
+import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * PNMImageReaderSpiTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PNMImageReaderSpiTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class PNMImageReaderSpiTest {
+
+ private final ImageReaderSpi spi = new PNMImageReaderSpi();
+
+ @Test
+ public void getPluginClassName() {
+ assertClassExists(spi.getPluginClassName(), ImageReader.class);
+ }
+
+ @Test
+ public void getImageWriterSpiNames() {
+ assertClassesExist(spi.getImageWriterSpiNames(), ImageWriterSpi.class);
+ }
+
+ @Test
+ public void getInputTypes() {
+ assertNotNull(spi.getInputTypes());
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java
index 2efceaa6..c8a3dde1 100755
--- a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java
+++ b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReaderTest.java
@@ -28,7 +28,7 @@
package com.twelvemonkeys.imageio.plugins.pnm;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import javax.imageio.ImageReader;
@@ -39,9 +39,10 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.List;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
-public class PNMImageReaderTest extends ImageReaderAbstractTestCase{
+public class PNMImageReaderTest extends ImageReaderAbstractTest {
@Override protected List getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/ppm/lena.ppm"), new Dimension(128, 128)), // P6 (PPM RAW)
diff --git a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageWriterSpiTest.java b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageWriterSpiTest.java
new file mode 100644
index 00000000..4bbb4936
--- /dev/null
+++ b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageWriterSpiTest.java
@@ -0,0 +1,38 @@
+package com.twelvemonkeys.imageio.plugins.pnm;
+
+import org.junit.Test;
+
+import javax.imageio.ImageWriter;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.spi.ImageWriterSpi;
+
+import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists;
+import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * PNMImageWriterSpiTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PNMImageWriterSpiTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class PNMImageWriterSpiTest {
+
+ private final ImageWriterSpi spi = new PNMImageWriterSpi();
+
+ @Test
+ public void getPluginClassName() {
+ assertClassExists(spi.getPluginClassName(), ImageWriter.class);
+ }
+
+ @Test
+ public void getImageReaderSpiNames() {
+ assertClassesExist(spi.getImageReaderSpiNames(), ImageReaderSpi.class);
+ }
+
+ @Test
+ public void getOutputTypes() {
+ assertNotNull(spi.getOutputTypes());
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMProviderInfoTest.java b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMProviderInfoTest.java
new file mode 100644
index 00000000..7ae7e892
--- /dev/null
+++ b/imageio/imageio-pnm/src/test/java/com/twelvemonkeys/imageio/plugins/pnm/PNMProviderInfoTest.java
@@ -0,0 +1,21 @@
+package com.twelvemonkeys.imageio.plugins.pnm;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * PNMProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PNMProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class PNMProviderInfoTest {
+ @Test
+ public void vendorVersion() {
+ PNMProviderInfo providerInfo = new PNMProviderInfo();
+ assertNotNull(providerInfo.getVendorName());
+ assertNotNull(providerInfo.getVersion());
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-psd/pom.xml b/imageio/imageio-psd/pom.xml
index c02f7e83..c58d19b4 100644
--- a/imageio/imageio-psd/pom.xml
+++ b/imageio/imageio-psd/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-psd
TwelveMonkeys :: ImageIO :: PSD plugin
@@ -20,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
com.twelvemonkeys.imageio
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java
index b1c0539a..82f506de 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java
@@ -51,6 +51,7 @@ interface PSD {
/** PSD Resource type identifier "8BIM" */
int RESOURCE_TYPE = ('8' << 24) + ('B' << 16) + ('I' << 8) + 'M';
+ int RESOURCE_TYPE_LONG = ('8' << 24) + ('B' << 16) + ('6' << 8) + '4';;
// Blending modes
/** Pass through blending mode "pass"*/
@@ -578,6 +579,94 @@ interface PSD {
*/
int RES_ALTERNATE_SPOT_COLORS = 0x042B;
+ /**
+ * (Photoshop CS2) Layer Selection ID(s).
+ * 2 bytes count, following is repeated for each count: 4 bytes layer ID.
+ */
+ int RES_LAYER_SELECTION_IDS = 0x042D;
+
+ /**
+ * (Photoshop CS2) HDR Toning information
+ */
+ int RES_HDR_TONING_INFO = 0x042E;
+
+ /**
+ * (Photoshop CS2) Print info
+ */
+ int RES_PRINT_INFO = 0x042F;
+
+ /**
+ * (Photoshop CS2) Layer Group(s) Enabled ID.
+ * 1 byte for each layer in the document, repeated by length of the resource.
+ * NOTE: Layer groups have start and end markers.
+ */
+ int RES_LAYER_GROUPS_ENABLED = 0x0430;
+
+ /**
+ * (Photoshop CS3) Color samplers resource.
+ * Also see ID 1038 for old format.
+ * See Color samplers resource format.
+ */
+ int RES_COLOR_SAMPLERS_RESOURCE = 0x0431;
+
+ /**
+ * (Photoshop CS3) Measurement Scale.
+ * 4 bytes (descriptor version = 16), Descriptor (see Descriptor structure)
+ */
+ int RES_MEASUREMENT_SCALE = 0x0432;
+
+ /**
+ * (Photoshop CS3) Timeline Information.
+ * 4 bytes (descriptor version = 16), Descriptor (see Descriptor structure)
+ */
+ int RES_TIMELINE_INFO = 0x0433;
+
+ /**
+ * (Photoshop CS3) Sheet Disclosure.
+ * 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure)
+ */
+ int RES_SHEET_DISCLOSURE = 0x0434;
+
+ /**
+ * (Photoshop CS3) DisplayInfo structure to support floating point colors.
+ * Also see ID 1007. See Appendix A in Photoshop API Guide.pdf .
+ */
+ int RES_DISPLAY_INFO_FP = 0x0435;
+
+ /**
+ * (Photoshop CS3) Onion Skins.
+ * 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure)
+ */
+ int RES_ONION_SKINS = 0x0436;
+
+ /**
+ * (Photoshop CS4) Count Information.
+ * 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure).
+ * Information about the count in the document. See the Count Tool.
+ */
+ int RES_COUNT_INFO = 0x0438;
+
+ /**
+ * (Photoshop CS5) Print Information.
+ * 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure).
+ * Information about the current print settings in the document. The color management options.
+ */
+ int RES_PRINT_INFO_CMM = 0x043A;
+
+ /**
+ * (Photoshop CS5) Print Style.
+ * 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure).
+ * Information about the current print style in the document. The printing marks, labels, ornaments, etc.
+ */
+ int RES_PRINT_STYLE = 0x043B;
+
+ /**
+ * (Photoshop CC) Path Selection State.
+ * 4 bytes (descriptor version = 16), Descriptor (see See Descriptor structure).
+ * Information about the current path selection state.
+ */
+ int RES_PATH_SELECTION_STATE = 0x0440;
+
// 07d0-0bb6
/* Saved path information */
@@ -601,4 +690,22 @@ interface PSD {
/** Plug-In resource(s). Resources added by a plug-in. See the plug-in API found in the SDK documentation */
int RES_PLUGIN_MAX = 0x1387;
+
+ // TODO: Better naming of these.. It's a kind of resource blocks as well..
+ // "Additional Layer Information"
+ int LMsk = 'L' << 24 | 'M' << 16 | 's' << 8 | 'k';
+ int Lr16 = 'L' << 24 | 'r' << 16 | '1' << 8 | '6';
+ int Lr32 = 'L' << 24 | 'r' << 16 | '3' << 8 | '2';
+ int Layr = 'L' << 24 | 'a' << 16 | 'y' << 8 | 'r';
+ int Mt16 = 'M' << 24 | 't' << 16 | '1' << 8 | '6';
+ int Mt32 = 'M' << 24 | 't' << 16 | '3' << 8 | '2';
+ int Mtrn = 'M' << 24 | 't' << 16 | 'r' << 8 | 'n';
+ int Alph = 'A' << 24 | 'l' << 16 | 'p' << 8 | 'h';
+ int FMsk = 'F' << 24 | 'M' << 16 | 's' << 8 | 'k';
+ int lnk2 = 'l' << 24 | 'n' << 16 | 'k' << 8 | '2';
+ int FEid = 'F' << 24 | 'E' << 16 | 'i' << 8 | 'd';
+ int FXid = 'F' << 24 | 'X' << 16 | 'i' << 8 | 'd';
+ int PxSD = 'P' << 24 | 'x' << 16 | 'S' << 8 | 'D';
+ int luni = 'l' << 24 | 'u' << 16 | 'n' << 8 | 'i';
+ int lyid = 'l' << 24 | 'y' << 16 | 'i' << 8 | 'd';
}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java
index b3f71c20..993929af 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java
@@ -28,8 +28,6 @@
package com.twelvemonkeys.imageio.plugins.psd;
-import com.twelvemonkeys.image.InverseColorMapIndexColorModel;
-
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.DataBuffer;
@@ -66,7 +64,7 @@ final class PSDColorData {
IndexColorModel getIndexColorModel() {
if (colorModel == null) {
int[] rgb = toInterleavedRGB(colors);
- colorModel = new InverseColorMapIndexColorModel(8, rgb.length, rgb, 0, false, -1, DataBuffer.TYPE_BYTE);
+ colorModel = new IndexColorModel(8, rgb.length, rgb, 0, false, -1, DataBuffer.TYPE_BYTE);
}
return colorModel;
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
old mode 100755
new mode 100644
index ea1d40d3..d5f45e16
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java
@@ -54,11 +54,11 @@ import java.util.List;
/**
* ImageReader for Adobe Photoshop Document (PSD) format.
*
- * @see Adobe Photoshop File Formats Specification
- * @see Adobe Photoshop File Format Summary
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
* @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
+ * @see Adobe Photoshop File Formats Specification
+ * @see Adobe Photoshop File Format Summary
*/
// TODO: Implement ImageIO meta data interface
// TODO: Figure out of we should assume Adobe RGB (1998) color model, if no embedded profile?
@@ -171,13 +171,13 @@ public final class PSDImageReader extends ImageReaderBase {
}
if (header.channels == 1 && header.bits == 8) {
- return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
+ return ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_BYTE, false, false);
}
else if (header.channels == 2 && header.bits == 8) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_BYTE, true, false);
}
else if (header.channels == 1 && header.bits == 16) {
- return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY);
+ return ImageTypeSpecifiers.createBanded(cs, new int[] {0}, new int[] {0}, DataBuffer.TYPE_USHORT, false, false);
}
else if (header.channels == 2 && header.bits == 16) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, DataBuffer.TYPE_USHORT, true, false);
@@ -224,22 +224,22 @@ public final class PSDImageReader extends ImageReaderBase {
cs = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
}
- if (header.channels == 4 && header.bits == 8) {
+ if (header.channels == 4 && header.bits == 8) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_BYTE, false, false);
}
- else if (header.channels == 5 && header.bits == 8) {
+ else if (header.channels == 5 && header.bits == 8) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_BYTE, true, false);
}
- else if (header.channels == 4 && header.bits == 16) {
+ else if (header.channels == 4 && header.bits == 16) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_USHORT, false, false);
}
- else if (header.channels == 5 && header.bits == 16) {
+ else if (header.channels == 5 && header.bits == 16) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_USHORT, true, false);
}
- else if (header.channels == 4 && header.bits == 32) {
+ else if (header.channels == 4 && header.bits == 32) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, DataBuffer.TYPE_INT, false, false);
}
- else if (header.channels == 5 && header.bits == 32) {
+ else if (header.channels == 5 && header.bits == 32) {
return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, DataBuffer.TYPE_INT, true, false);
}
@@ -250,6 +250,7 @@ public final class PSDImageReader extends ImageReaderBase {
case PSD.COLOR_MODE_LAB:
// TODO: Implement
// TODO: If there's a color profile embedded, it should be easy, otherwise we're out of luck...
+ // TODO: See the LAB color handling in TIFF
default:
throw new IIOException(String.format("Unsupported PSD MODE: %s (%d channels/%d bits)", header.mode, header.channels, header.bits));
}
@@ -263,7 +264,7 @@ public final class PSDImageReader extends ImageReaderBase {
ImageTypeSpecifier rawType = getRawImageTypeInternal(imageIndex);
ColorSpace cs = rawType.getColorModel().getColorSpace();
- List types = new ArrayList();
+ List types = new ArrayList<>();
switch (header.mode) {
case PSD.COLOR_MODE_RGB:
@@ -308,7 +309,7 @@ public final class PSDImageReader extends ImageReaderBase {
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false));
}
else if (rawType.getNumBands() == 4 && rawType.getBitsPerBand(0) == 16) {
- types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[]{3, 2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
+ types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_USHORT, false, false));
}
else if (rawType.getNumBands() == 5 && rawType.getBitsPerBand(0) == 16) {
types.add(ImageTypeSpecifiers.createInterleaved(cs, new int[] {4, 3, 2, 1, 0}, DataBuffer.TYPE_USHORT, true, false));
@@ -362,25 +363,6 @@ public final class PSDImageReader extends ImageReaderBase {
final Rectangle dest = new Rectangle();
computeRegions(param, header.width, header.height, image, source, dest);
- /*
- NOTE: It seems safe to just leave this out for now. The only thing we need is to support sub sampling.
- Sun's readers does not support arbitrary destination formats.
-
- // TODO: Create temp raster in native format w * 1
- // Read (sub-sampled) row into temp raster (skip other rows)
- // Copy pixels from temp raster
- // If possible, leave the destination image "untouched" (accelerated)
- // See Jim Grahams comments:
- // http://forums.java.net/jive/message.jspa?messageID=295758#295758
-
- // TODO: Banding...
-
- ImageTypeSpecifier spec = getRawImageType(imageIndex);
- BufferedImage temp = spec.createBufferedImage(getWidth(imageIndex), 1);
- temp.getRaster();
-
- */
-
final int xSub;
final int ySub;
@@ -449,20 +431,19 @@ public final class PSDImageReader extends ImageReaderBase {
return layersStart;
}
- private void readImageData(final BufferedImage pImage,
+ private void readImageData(final BufferedImage destination,
final ColorModel pSourceCM, final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int[] pByteCounts, final int pCompression) throws IOException {
- final WritableRaster raster = pImage.getRaster();
- final ColorModel destCM = pImage.getColorModel();
+ WritableRaster destRaster = destination.getRaster();
+ ColorModel destCM = destination.getColorModel();
- // TODO: This raster is 3-5 times longer than needed, depending on number of channels...
- final WritableRaster rowRaster = pSourceCM.createCompatibleWritableRaster(header.width, 1);
-
- final int channels = rowRaster.getNumBands();
- final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
- final int interleavedBands = banded ? 1 : raster.getNumBands();
+ int channels = pSourceCM.createCompatibleSampleModel(1, 1).getNumBands();
+ ImageTypeSpecifier singleBandRowSpec = ImageTypeSpecifiers.createGrayscale(header.bits, pSourceCM.getTransferType());
+ WritableRaster rowRaster = singleBandRowSpec.createBufferedImage(header.width, 1).getRaster();
+ boolean banded = destRaster.getDataBuffer().getNumBanks() > 1;
+ int interleavedBands = banded ? 1 : destRaster.getNumBands();
for (int c = 0; c < channels; c++) {
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
@@ -470,19 +451,19 @@ public final class PSDImageReader extends ImageReaderBase {
switch (header.bits) {
case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
- read1bitChannel(c, header.channels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, pCompression == PSD.COMPRESSION_RLE);
+ read1bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, pCompression == PSD.COMPRESSION_RLE);
break;
case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
- read8bitChannel(c, header.channels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
+ read8bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
break;
case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
- read16bitChannel(c, header.channels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
+ read16bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
break;
case 32:
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
- read32bitChannel(c, header.channels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
+ read32bitChannel(c, header.channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
break;
default:
throw new IIOException(String.format("Unsupported PSD bit depth: %s", header.bits));
@@ -495,12 +476,12 @@ public final class PSDImageReader extends ImageReaderBase {
if (header.bits == 8) {
// Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in
- decomposeAlpha(destCM, raster.getDataBuffer(), pDest.width, pDest.height, raster.getNumBands());
+ decomposeAlpha(destCM, destRaster.getDataBuffer(), pDest.width, pDest.height, destRaster.getNumBands());
}
// NOTE: ColorSpace uses Object.equals(), so we rely on using same instances!
- if (!pSourceCM.getColorSpace().equals(pImage.getColorModel().getColorSpace())) {
- convertToDestinationCS(pSourceCM, pImage.getColorModel(), raster);
+ if (!pSourceCM.getColorSpace().equals(destination.getColorModel().getColorSpace())) {
+ convertToDestinationCS(pSourceCM, destination.getColorModel(), destRaster);
}
}
@@ -546,28 +527,24 @@ public final class PSDImageReader extends ImageReaderBase {
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
- final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
- final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
+ boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
+ int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
+ final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
- // NOTE: Length is in *16 bit values* (shorts)
- int length = 2 * (pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth);
+ int length = (pRLECompressed ? pRowByteCounts[pRowOffset + y] : 4 * pChannelWidth);
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
- DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
- try {
+ try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
for (int x = 0; x < pChannelWidth; x++) {
pRow[x] = input.readInt();
}
}
- finally {
- input.close();
- }
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
@@ -580,7 +557,7 @@ public final class PSDImageReader extends ImageReaderBase {
int value = pRow[pSource.x + x * pXSub];
// CMYK values are stored inverted, but alpha is not
- if (isCMYK && pChannel < colorComponents) {
+ if (invert) {
value = 0xffffffff - value;
}
@@ -609,27 +586,23 @@ public final class PSDImageReader extends ImageReaderBase {
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
- final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
- final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
+ boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
+ int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
+ final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
- // NOTE: Length is in *16 bit values* (shorts)
- int length = 2 * (pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth);
+ int length = (pRLECompressed ? pRowByteCounts[pRowOffset + y] : 2 * pChannelWidth);
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
- DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
- try {
+ try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
for (int x = 0; x < pChannelWidth; x++) {
pRow[x] = input.readShort();
}
}
- finally {
- input.close();
- }
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
@@ -642,8 +615,8 @@ public final class PSDImageReader extends ImageReaderBase {
short value = pRow[pSource.x + x * pXSub];
// CMYK values are stored inverted, but alpha is not
- if (isCMYK && pChannel < colorComponents) {
- value = (short) (65535 - value & 0xffff);
+ if (invert) {
+ value = (short) (0xffff - value & 0xffff);
}
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
@@ -671,8 +644,9 @@ public final class PSDImageReader extends ImageReaderBase {
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
- final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
- final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
+ boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
+ int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
+ final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
@@ -682,13 +656,9 @@ public final class PSDImageReader extends ImageReaderBase {
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
- DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
- try {
+ try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
input.readFully(pRow, 0, pChannelWidth);
}
- finally {
- input.close();
- }
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
@@ -701,8 +671,8 @@ public final class PSDImageReader extends ImageReaderBase {
byte value = pRow[pSource.x + x * pXSub];
// CMYK values are stored inverted, but alpha is not
- if (isCMYK && pChannel < colorComponents) {
- value = (byte) (255 - value & 0xff);
+ if (invert) {
+ value = (byte) (0xff - value & 0xff);
}
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
@@ -741,13 +711,9 @@ public final class PSDImageReader extends ImageReaderBase {
// Read entire line, if within source region and sampling
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
- DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length);
- try {
+ try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
input.readFully(pRow, 0, pRow.length);
}
- finally {
- input.close();
- }
}
else {
imageInput.readFully(pRow, 0, pRow.length);
@@ -868,8 +834,8 @@ public final class PSDImageReader extends ImageReaderBase {
if (!header.hasValidDimensions()) {
processWarningOccurred(String.format("Dimensions exceed maximum allowed for %s: %dx%d (max %dx%d)",
- header.largeFormat ? "PSB" : "PSD",
- header.width, header.height, header.getMaxSize(), header.getMaxSize()));
+ header.largeFormat ? "PSB" : "PSD",
+ header.width, header.height, header.getMaxSize(), header.getMaxSize()));
}
metadata = new PSDMetadata();
@@ -898,6 +864,10 @@ public final class PSDImageReader extends ImageReaderBase {
// Don't need the header again
imageInput.flushBefore(imageInput.getStreamPosition());
+
+ if (DEBUG) {
+ System.out.println("header: " + header);
+ }
}
}
@@ -913,7 +883,7 @@ public final class PSDImageReader extends ImageReaderBase {
if (pParseData && imageResourcesLength > 0) {
if (metadata.imageResources == null) {
- metadata.imageResources = new ArrayList();
+ metadata.imageResources = new ArrayList<>();
long expectedEnd = imageInput.getStreamPosition() + imageResourcesLength;
while (imageInput.getStreamPosition() < expectedEnd) {
@@ -921,6 +891,10 @@ public final class PSDImageReader extends ImageReaderBase {
metadata.imageResources.add(resource);
}
+ if (DEBUG) {
+ System.out.println("imageResources: " + metadata.imageResources);
+ }
+
if (imageInput.getStreamPosition() != expectedEnd) {
throw new IIOException("Corrupt PSD document"); // ..or maybe just a bug in the reader.. ;-)
}
@@ -970,10 +944,13 @@ public final class PSDImageReader extends ImageReaderBase {
long read = imageInput.getStreamPosition() - pos;
- long diff = layerInfoLength - (read - (header.largeFormat ? 8 : 4)); // - 4 for the layerInfoLength field itself
+ long diff = layerInfoLength - (read - (header.largeFormat
+ ? 8
+ : 4)); // - 4 for the layerInfoLength field itself
imageInput.skipBytes(diff);
- } else {
+ }
+ else {
metadata.layerInfo = Collections.emptyList();
}
@@ -990,6 +967,10 @@ public final class PSDImageReader extends ImageReaderBase {
// TODO: We should now be able to flush input
// imageInput.seek(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
// imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
+
+ if (DEBUG) {
+ System.out.println("layerInfo: " + metadata.layerInfo);
+ }
}
}
@@ -1001,20 +982,18 @@ public final class PSDImageReader extends ImageReaderBase {
final int width = getLayerWidth(layerIndex);
final int height = getLayerHeight(layerIndex);
+ // TODO: This behaviour must be documented!
+ // If layer has no pixel data, return null
+ if (width <= 0 || height <= 0) {
+ return null;
+ }
+
PSDLayerInfo layerInfo = metadata.layerInfo.get(layerIndex);
// final int width = layerInfo.right - layerInfo.left;
// final int height = layerInfo.bottom - layerInfo.top;
// Even if raw/imageType has no alpha, the layers may still have alpha...
ImageTypeSpecifier imageType = getRawImageTypeForLayer(layerIndex);
-
- // TODO: Find a better way of handling layers of size 0
- // - Return null? Return a BufferedImage subclass that has no data (0 x 0)?
- // Create image (or dummy, if h/w are <= 0)
-// BufferedImage layer = imageType.createBufferedImage(Math.max(1, width), Math.max(1, height));
- if (width <= 0 || height <= 0) {
- return null;
- }
BufferedImage layer = getDestination(param, getImageTypes(layerIndex + 1), Math.max(1, width), Math.max(1, height));
imageInput.seek(findLayerStartPos(layerIndex));
@@ -1028,9 +1007,8 @@ public final class PSDImageReader extends ImageReaderBase {
final WritableRaster raster = layer.getRaster();
final ColorModel destCM = layer.getColorModel();
- // TODO: This raster is 3-5 times longer than needed, depending on number of channels...
ColorModel sourceCM = imageType.getColorModel();
- final WritableRaster rowRaster = width > 0 ? sourceCM.createCompatibleWritableRaster(width, 1) : null;
+ final WritableRaster rowRaster = sourceCM.createCompatibleWritableRaster((int) Math.ceil(width / (double) sourceCM.getNumComponents()), 1);
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
final int interleavedBands = banded ? 1 : raster.getNumBands();
@@ -1134,7 +1112,6 @@ public final class PSDImageReader extends ImageReaderBase {
}
}
-
// If there really is more channels, then create new imageTypeSpec
if (newBandNum > compositeType.getNumBands()) {
int[] indices = new int[newBandNum];
@@ -1228,7 +1205,7 @@ public final class PSDImageReader extends ImageReaderBase {
for (PSDImageResource resource : metadata.imageResources) {
if (resource instanceof PSDThumbnail) {
if (thumbnails == null) {
- thumbnails = new ArrayList();
+ thumbnails = new ArrayList<>();
}
thumbnails.add((PSDThumbnail) resource);
@@ -1386,7 +1363,7 @@ public final class PSDImageReader extends ImageReaderBase {
System.out.println("read time: " + (System.currentTimeMillis() - start));
System.out.println("image: " + image);
- if (image.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_CMYK) {
+ if (image.getType() == BufferedImage.TYPE_CUSTOM) {
try {
ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null);
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
@@ -1407,8 +1384,24 @@ public final class PSDImageReader extends ImageReaderBase {
for (int i = 1; i < images; i++) {
start = System.currentTimeMillis();
BufferedImage layer = imageReader.read(i);
+
System.out.println("layer read time: " + (System.currentTimeMillis() - start));
System.err.println("layer: " + layer);
+
+ if (layer != null && layer.getType() == BufferedImage.TYPE_CUSTOM) {
+ try {
+ ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_sRGB), null);
+ GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
+ layer = op.filter(layer, gc.createCompatibleImage(layer.getWidth(), layer.getHeight(), layer.getTransparency()));
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ layer = ImageUtil.accelerate(layer);
+ }
+ System.out.println("layer conversion time: " + (System.currentTimeMillis() - start));
+ System.out.println("layer: " + layer);
+ }
+
showIt(layer, "layer " + i);
}
}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java
index 1fc8fd82..7b92883c 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java
@@ -50,7 +50,10 @@ final class PSDLayerInfo {
final PSDLayerBlendMode blendMode;
final PSDLayerMaskData layerMaskData;
final PSDChannelSourceDestinationRange[] ranges;
- final String layerName;
+ private final String layerName;
+
+ private String unicodeLayerName;
+ private int layerId;
PSDLayerInfo(final boolean largeFormat, final ImageInputStream pInput) throws IOException {
top = pInput.readInt();
@@ -70,11 +73,11 @@ final class PSDLayerInfo {
blendMode = new PSDLayerBlendMode(pInput);
- // Length of layer mask data
+ // Length of layer extra data
long extraDataSize = pInput.readUnsignedInt();
// Layer mask/adjustment layer data
- int layerMaskDataSize = pInput.readInt(); // May be 0, 20 or 36 bytes...
+ int layerMaskDataSize = pInput.readInt(); // May be 0, 20 or variable (up to 55) bytes...
if (layerMaskDataSize != 0) {
layerMaskData = new PSDLayerMaskData(pInput, layerMaskDataSize);
}
@@ -92,19 +95,78 @@ final class PSDLayerInfo {
ranges[i] = new PSDChannelSourceDestinationRange(pInput, (i == 0 ? "Gray" : "Channel " + (i - 1)));
}
+ // Layer name
layerName = PSDUtil.readPascalString(pInput);
-
int layerNameSize = layerName.length() + 1;
// Skip pad bytes for long word alignment
if (layerNameSize % 4 != 0) {
- int skip = layerNameSize % 4;
+ int skip = 4 - (layerNameSize % 4);
pInput.skipBytes(skip);
layerNameSize += skip;
}
- // TODO: Consider reading this: Adjustment layer info etc...
- pInput.skipBytes(extraDataSize - layerMaskDataSize - 4 - layerBlendingDataSize - 4 - layerNameSize);
+ // Parse "Additional layer data"
+ long additionalLayerInfoStart = pInput.getStreamPosition();
+ long expectedEnd = additionalLayerInfoStart + extraDataSize - layerMaskDataSize - 4 - layerBlendingDataSize - 4 - layerNameSize;
+ while (pInput.getStreamPosition() < expectedEnd) {
+ // 8BIM or 8B64
+ int resourceSignature = pInput.readInt();
+
+ if (resourceSignature != PSD.RESOURCE_TYPE && resourceSignature != PSD.RESOURCE_TYPE_LONG) {
+ // Could be a corrupt document, or some new resource (type) we don't know about,
+ // we'll just leave it and carry on, as this is all secondary information for the reader.
+ break;
+ }
+
+ int resourceKey = pInput.readInt();
+
+ // NOTE: Only SOME resources have long length fields...
+ boolean largeResource = resourceSignature != PSD.RESOURCE_TYPE;
+ long resourceLength = largeResource ? pInput.readLong() : pInput.readUnsignedInt();
+ long resourceStart = pInput.getStreamPosition();
+
+// System.out.printf("signature: %s 0x%08x\n", PSDUtil.intToStr(resourceSignature), resourceSignature);
+// System.out.println("key: " + PSDUtil.intToStr(resourceKey));
+// System.out.println("length: " + resourceLength);
+
+ switch (resourceKey) {
+ case PSD.luni:
+ unicodeLayerName = PSDUtil.readUnicodeString(pInput);
+ // There's usually a 0-pad here, but it is skipped in the general re-aligning code below
+ break;
+
+ case PSD.lyid:
+ if (resourceLength != 4) {
+ throw new IIOException(String.format("Expected layerId length == 4: %d", resourceLength));
+ }
+ layerId = pInput.readInt();
+ break;
+
+ default:
+ // TODO: Parse more data...
+ pInput.skipBytes(resourceLength);
+ break;
+ }
+
+ // Re-align in case we got the length incorrect
+ if (pInput.getStreamPosition() != resourceStart + resourceLength) {
+ pInput.seek(resourceStart + resourceLength);
+ }
+ }
+
+ // Re-align in case we got the length incorrect
+ if (pInput.getStreamPosition() != expectedEnd) {
+ pInput.seek(expectedEnd);
+ }
+ }
+
+ String getLayerName() {
+ return unicodeLayerName != null ? unicodeLayerName : layerName;
+ }
+
+ int getLayerId() {
+ return layerId;
}
@Override
@@ -122,7 +184,7 @@ final class PSDLayerInfo {
builder.append(", layer mask data: ").append(layerMaskData);
}
builder.append(", ranges: ").append(Arrays.toString(ranges));
- builder.append(", layer name: \"").append(layerName).append("\"");
+ builder.append(", layer name: \"").append(getLayerName()).append("\"");
builder.append("]");
return builder.toString();
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java
index aa13943c..28e27b9c 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java
@@ -47,42 +47,90 @@ final class PSDLayerMaskData {
private int defaultColor;
private int flags;
- private boolean large;
- private int realFlags;
- private int realUserBackground;
- private int realTop;
- private int realLeft;
- private int realBottom;
- private int realRight;
+ private int maskParams;
+ private int userMaskDensity;
+ private double userMaskFeather;
+ private int vectorMaskDensity;
+ private double vectorMaskFeather;
PSDLayerMaskData(final ImageInputStream pInput, final int pSize) throws IOException {
- if (pSize != 20 && pSize != 36) {
- throw new IIOException("Illegal PSD Layer Mask data size: " + pSize + " (expected 20 or 36)");
+ if (pSize < 20 || pSize > 55) {
+ throw new IIOException("Illegal PSD Layer Mask data size: " + pSize + " (expected between 20 and 55)");
}
+ // Rectangle enclosing layer mask: Top, left, bottom, right.
top = pInput.readInt();
left = pInput.readInt();
bottom = pInput.readInt();
right = pInput.readInt();
+ // Default color. 0 or 255
defaultColor = pInput.readUnsignedByte();
+ // Flags.
+ // bit 0 = position relative to layer
+ // bit 1 = layer mask disabled
+ // bit 2 = invert layer mask when blending (Obsolete)
+ // bit 3 = indicates that the user mask actually came from rendering other data
+ // bit 4 = indicates that the user and/or vector masks have parameters applied to them
flags = pInput.readUnsignedByte();
- if (pSize == 20) {
+ int dataLeft = pSize - 18;
+
+ if ((flags & 0x10) != 0) {
+ // Mask Parameters. Only present if bit 4 of Flags set above.
+ maskParams = pInput.readUnsignedByte();
+ dataLeft--;
+
+ // Mask Parameters bit flags present as follows:
+ // bit 0 = user mask density, 1 byte
+ // bit 1 = user mask feather, 8 byte, double
+ // bit 2 = vector mask density, 1 byte
+ // bit 3 = vector mask feather, 8 bytes, double
+ if ((maskParams & 0x01) != 0) {
+ userMaskDensity = pInput.readByte();
+ dataLeft--;
+ }
+ if ((maskParams & 0x02) != 0) {
+ userMaskFeather = pInput.readDouble();
+ dataLeft -= 8;
+ }
+ if ((maskParams & 0x04) != 0) {
+ vectorMaskDensity = pInput.readByte();
+ dataLeft--;
+ }
+ if ((maskParams & 0x08) != 0) {
+ vectorMaskFeather = pInput.readDouble();
+ dataLeft -= 8;
+ }
+ }
+
+ // Padding. Only present if size = 20. Otherwise the following is present
+ if (pSize == 20 && dataLeft == 2) {
pInput.readShort(); // Pad
+ dataLeft -= 2;
}
else {
- // TODO: What to make out of this?
- large = true;
+ if (dataLeft >= 2) {
+ // Real Flags. Same as Flags information above.
+ flags = pInput.readUnsignedByte();
+ dataLeft--;
+ // Real user mask background. 0 or 255.
+ defaultColor = pInput.readUnsignedByte();
+ dataLeft--;
+ }
+ if (dataLeft >= 16) {
+ // Rectangle enclosing layer mask: Top, left, bottom, right.
+ top = pInput.readInt();
+ left = pInput.readInt();
+ bottom = pInput.readInt();
+ right = pInput.readInt();
+ dataLeft -= 16;
+ }
+ }
- realFlags = pInput.readUnsignedByte();
- realUserBackground = pInput.readUnsignedByte();
-
- realTop = pInput.readInt();
- realLeft = pInput.readInt();
- realBottom = pInput.readInt();
- realRight = pInput.readInt();
+ if (dataLeft > 0) {
+ pInput.skipBytes(dataLeft);
}
}
@@ -97,40 +145,54 @@ final class PSDLayerMaskData {
builder.append(", default color: ").append(defaultColor);
builder.append(", flags: ").append(Integer.toBinaryString(flags));
- // TODO: Maybe the flag bits have oposite order?
builder.append(" (");
if ((flags & 0x01) != 0) {
- builder.append("Pos. rel. to layer");
+ builder.append("relative");
}
else {
- builder.append("Pos. abs.");
+ builder.append("absolute");
}
if ((flags & 0x02) != 0) {
- builder.append(", Mask disabled");
+ builder.append(", disabled");
}
else {
- builder.append(", Mask enabled");
+ builder.append(", enabled");
}
if ((flags & 0x04) != 0) {
- builder.append(", Invert mask");
+ builder.append(", inverted");
}
if ((flags & 0x08) != 0) {
- builder.append(", Unknown bit 3");
+ builder.append(", from rendered data");
}
if ((flags & 0x10) != 0) {
- builder.append(", Unknown bit 4");
+ builder.append(", has parameters");
}
if ((flags & 0x20) != 0) {
- builder.append(", Unknown bit 5");
+ builder.append(", unknown flag (bit 5)");
}
if ((flags & 0x40) != 0) {
- builder.append(", Unknown bit 6");
+ builder.append(", unknown flag (bit 6)");
}
if ((flags & 0x80) != 0) {
- builder.append(", Unknown bit 7");
+ builder.append(", unknown flag (bit 7)");
}
builder.append(")");
+ if ((flags & 0x10) != 0) {
+ if ((maskParams & 0x01) != 0) {
+ builder.append(", userMaskDensity: ").append(userMaskDensity);
+ }
+ if ((maskParams & 0x02) != 0) {
+ builder.append(", userMaskFeather: ").append(userMaskFeather);
+ }
+ if ((maskParams & 0x04) != 0) {
+ builder.append(", vectorMaskDensity: ").append(vectorMaskDensity);
+ }
+ if ((maskParams & 0x08) != 0) {
+ builder.append(", vectorMaskFeather: ").append(vectorMaskFeather);
+ }
+ }
+
builder.append("]");
return builder.toString();
}
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java
index 19bd5d7f..f59cb0b3 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java
@@ -28,6 +28,7 @@
package com.twelvemonkeys.imageio.plugins.psd;
+import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
@@ -374,7 +375,7 @@ public final class PSDMetadata extends AbstractMetadata {
for (PSDLayerInfo psdLayerInfo : layerInfo) {
// TODO: Group in layer and use sub node for blend mode?
node = new IIOMetadataNode("LayerInfo");
- node.setAttribute("name", psdLayerInfo.layerName);
+ node.setAttribute("name", psdLayerInfo.getLayerName());
node.setAttribute("top", String.valueOf(psdLayerInfo.top));
node.setAttribute("left", String.valueOf(psdLayerInfo.left));
node.setAttribute("bottom", String.valueOf(psdLayerInfo.bottom));
@@ -571,9 +572,8 @@ public final class PSDMetadata extends AbstractMetadata {
compressionNode.appendChild(compressionTypeName);
if (compression != PSD.COMPRESSION_NONE) {
- IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
- lossless.setAttribute("value", "true");
- compressionNode.appendChild(lossless);
+ compressionNode.appendChild(new IIOMetadataNode("Lossless"));
+ // "value" defaults to TRUE, all PSD compressions are lossless
}
return compressionNode;
@@ -755,7 +755,7 @@ public final class PSDMetadata extends AbstractMetadata {
}
private void appendTextEntriesFlat(final IIOMetadataNode node, final Directory directory, final FilterIterator.Filter filter) {
- FilterIterator entries = new FilterIterator(directory.iterator(), filter);
+ FilterIterator entries = new FilterIterator<>(directory.iterator(), filter);
while (entries.hasNext()) {
Entry entry = entries.next();
@@ -807,7 +807,7 @@ public final class PSDMetadata extends AbstractMetadata {
@SuppressWarnings({"unchecked"})
Iterator iterator = (Iterator) imageResources.iterator();
- return new FilterIterator(iterator, new FilterIterator.Filter() {
+ return new FilterIterator<>(iterator, new FilterIterator.Filter() {
public boolean accept(final T pElement) {
return resourceType.isInstance(pElement);
}
@@ -817,7 +817,7 @@ public final class PSDMetadata extends AbstractMetadata {
Iterator getResources(final int... resourceTypes) {
Iterator iterator = imageResources.iterator();
- return new FilterIterator(iterator, new FilterIterator.Filter() {
+ return new FilterIterator<>(iterator, new FilterIterator.Filter() {
public boolean accept(final PSDImageResource pResource) {
for (int type : resourceTypes) {
if (type == pResource.id) {
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java
index 50e99677..1110ca86 100755
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java
@@ -34,7 +34,9 @@ import org.w3c.dom.Document;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import java.awt.image.BufferedImage;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -46,8 +48,6 @@ import java.util.List;
*/
public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
- private static final PSDMetadataFormat instance = new PSDMetadataFormat();
-
static final List PSD_BLEND_MODES = Arrays.asList(
PSDUtil.intToStr(PSD.BLEND_PASS), PSDUtil.intToStr(PSD.BLEND_NORM), PSDUtil.intToStr(PSD.BLEND_DISS),
PSDUtil.intToStr(PSD.BLEND_DARK), PSDUtil.intToStr(PSD.BLEND_MUL), PSDUtil.intToStr(PSD.BLEND_IDIV),
@@ -61,6 +61,8 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
PSDUtil.intToStr(PSD.BLEND_LUM)
);
+ private static final PSDMetadataFormat instance = new PSDMetadataFormat();
+
/**
* Private constructor.
*
@@ -79,7 +81,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
addElement("Header", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY);
addAttribute("Header", "type", DATATYPE_STRING, false, "PSD", Arrays.asList("PSD", "PSB"));
- addAttribute("Header", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1"));
+ addAttribute("Header", "version", DATATYPE_INTEGER, false, "1", Collections.singletonList("1"));
addAttribute("Header", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true);
// rows?
@@ -87,7 +89,8 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
// columns?
addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16"));
- addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES));
+
+ addAttribute("Header", "mode", DATATYPE_STRING, true, null, asListNoNulls(PSDMetadata.COLOR_MODES));
/*
Contains the required data to define the color mode.
@@ -133,7 +136,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY);
// TODO: Consider using human readable strings
// TODO: Limit values (0-8, 10, 11, 3000)
- addAttribute("DisplayInfo", "colorSpace", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_CS));
+ addAttribute("DisplayInfo", "colorSpace", DATATYPE_STRING, true, null, asListNoNulls(PSDMetadata.DISPLAY_INFO_CS));
addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4);
addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true);
// TODO: Consider using human readable strings
@@ -196,17 +199,16 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
// root -> ImageResources -> ResolutionInfo
addElement("ResolutionInfo", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("ResolutionInfo", "hRes", DATATYPE_FLOAT, true, null);
- addAttribute("ResolutionInfo", "hResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS));
- addAttribute("ResolutionInfo", "widthUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS));
+ addAttribute("ResolutionInfo", "hResUnit", DATATYPE_STRING, true, null, asListNoNulls(PSDMetadata.RESOLUTION_UNITS));
+ addAttribute("ResolutionInfo", "widthUnit", DATATYPE_STRING, true, null, asListNoNulls(PSDMetadata.DIMENSION_UNITS));
addAttribute("ResolutionInfo", "vRes", DATATYPE_FLOAT, true, null);
- addAttribute("ResolutionInfo", "vResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS));
- addAttribute("ResolutionInfo", "heightUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS));
+ addAttribute("ResolutionInfo", "vResUnit", DATATYPE_STRING, true, null, asListNoNulls(PSDMetadata.RESOLUTION_UNITS));
+ addAttribute("ResolutionInfo", "heightUnit", DATATYPE_STRING, true, null, asListNoNulls(PSDMetadata.DIMENSION_UNITS));
// root -> ImageResources -> UnicodeAlphaNames
addElement("UnicodeAlphaNames", "ImageResources", 0, Integer.MAX_VALUE);
addChildElement("UnicodeAlphaNames", "Name"); // TODO: Does this really work?
-
// root -> ImageResources -> VersionInfo
addElement("VersionInfo", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("VersionInfo", "version", DATATYPE_INTEGER, false, "1");
@@ -229,10 +231,10 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
addObjectValue("XMP", Document.class, true, null);
// root -> Layers
- addElement("Layers", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_REPEAT);
+ addElement("Layers", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, 0, Short.MAX_VALUE);
// root -> Layers -> LayerInfo
- addElement("LayerInfo", "Layers", CHILD_POLICY_REPEAT);
+ addElement("LayerInfo", "Layers", CHILD_POLICY_EMPTY);
addAttribute("LayerInfo", "name", DATATYPE_STRING, false, "");
addAttribute("LayerInfo", "top", DATATYPE_INTEGER, false, "0");
addAttribute("LayerInfo", "left", DATATYPE_INTEGER, false, "0");
@@ -258,6 +260,18 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
addAttribute("GlobalLayerMask", "kind", DATATYPE_STRING, false, "layer", Arrays.asList("selected", "protected", "layer"));
}
+ private static List asListNoNulls(final T[] values) {
+ List list = new ArrayList<>(values.length);
+
+ for (T value : values) {
+ if (value != null) {
+ list.add(value);
+ }
+ }
+
+ return list;
+ }
+
@Override
public boolean canNodeAppear(final String elementName, final ImageTypeSpecifier imageType) {
// TODO: PSDColorData and PaletteEntry only for indexed color model
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDProviderInfo.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDProviderInfo.java
index 2b52c770..1767cc50 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDProviderInfo.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.psd;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
@@ -22,10 +50,10 @@ final class PSDProviderInfo extends ReaderWriterProviderInfo {
"application/x-photoshop",
"image/x-photoshop"
},
- "com.twelvemkonkeys.imageio.plugins.psd.PSDImageReader",
+ "com.twelvemonkeys.imageio.plugins.psd.PSDImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi"},
null,
- null, // new String[] {"com.twelvemkonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
+ null, // new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
false, null, null, null, null,
true, PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME, null, null
);
diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java
index 7945f837..5cd5f0c2 100644
--- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java
+++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java
@@ -51,7 +51,7 @@ final class PSDUtil {
static String intToStr(int value) {
return new String(
new byte[]{
- (byte) ((value & 0xff000000) >> 24),
+ (byte) ((value & 0xff000000) >>> 24),
(byte) ((value & 0x00ff0000) >> 16),
(byte) ((value & 0x0000ff00) >> 8),
(byte) ((value & 0x000000ff))
diff --git a/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTestCase.java b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java
similarity index 91%
rename from imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTestCase.java
rename to imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java
index a5808c20..73ca0e32 100755
--- a/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTestCase.java
+++ b/imageio/imageio-psd/src/test/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderTest.java
@@ -28,32 +28,35 @@
package com.twelvemonkeys.imageio.plugins.psd;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import org.junit.Test;
+import org.w3c.dom.NodeList;
+import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
-import javax.imageio.ImageTypeSpecifier;
-import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.ImageReader;
+import javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ArrayList;
import java.io.IOException;
+import java.util.*;
+import java.util.List;
import static org.junit.Assert.*;
/**
- * PSDImageReaderTestCase
+ * PSDImageReaderTest
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
- * @version $Id: PSDImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
+ * @version $Id: PSDImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
-public class PSDImageReaderTestCase extends ImageReaderAbstractTestCase {
+public class PSDImageReaderTest extends ImageReaderAbstractTest {
private static final ImageReaderSpi provider = new PSDImageReaderSpi();
@@ -110,11 +113,11 @@ public class PSDImageReaderTestCase extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("psd");
+ return Collections.singletonList("psd");
}
protected List getSuffixes() {
- return Arrays.asList("psd");
+ return Collections.singletonList("psd");
}
protected List getMIMETypes() {
@@ -243,7 +246,7 @@ public class PSDImageReaderTestCase extends ImageReaderAbstractTestCase sequnce = new ArrayList();
+ final List sequnce = new ArrayList<>();
imageReader.addIIOReadProgressListener(new ProgressListenerBase() {
private float mLastPercentageDone = 0;
@@ -387,4 +390,20 @@ public class PSDImageReaderTestCase extends ImageReaderAbstractTestCaseHarald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: PSDProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class PSDProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new PSDProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-psd/src/test/resources/psd/long-layer-names.psd b/imageio/imageio-psd/src/test/resources/psd/long-layer-names.psd
new file mode 100644
index 00000000..452d61e6
Binary files /dev/null and b/imageio/imageio-psd/src/test/resources/psd/long-layer-names.psd differ
diff --git a/imageio/imageio-psd/todo.txt b/imageio/imageio-psd/todo.txt
index 7d731b62..2b0ec7fb 100755
--- a/imageio/imageio-psd/todo.txt
+++ b/imageio/imageio-psd/todo.txt
@@ -1,4 +1,3 @@
-- Implement source subsampling and region of interest
- Separate package for the resources (seems to be a lot)?
- Move to metadata package, or implement metadata interface, as this is (similar|equivalent) to TIFF/JPEG APP13 segment tag?
- Possibility to read only some resources? readResources(int[] resourceKeys)?
diff --git a/imageio/imageio-reference/pom.xml b/imageio/imageio-reference/pom.xml
index c636b8ed..00899b72 100644
--- a/imageio/imageio-reference/pom.xml
+++ b/imageio/imageio-reference/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-reference
TwelveMonkeys :: ImageIO :: reference test cases
@@ -20,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/JPEGImageReaderTestCase.java b/imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/JPEGImageReaderTest.java
similarity index 68%
rename from imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/JPEGImageReaderTestCase.java
rename to imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/JPEGImageReaderTest.java
index 69da521b..be36f205 100644
--- a/imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/JPEGImageReaderTestCase.java
+++ b/imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/JPEGImageReaderTest.java
@@ -1,34 +1,47 @@
package com.twelvemonkeys.imageio.reference;
-import com.sun.imageio.plugins.jpeg.JPEGImageReader;
-import com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.lang.SystemUtil;
import org.junit.Ignore;
import org.junit.Test;
import javax.imageio.IIOException;
+import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import static org.junit.Assume.assumeNoException;
+
/**
- * JPEGImageReaderTestCase
+ * JPEGImageReaderTest
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
- * @version $Id: JPEGImageReaderTestCase.java,v 1.0 Oct 9, 2009 3:37:25 PM haraldk Exp$
+ * @version $Id: JPEGImageReaderTest.java,v 1.0 Oct 9, 2009 3:37:25 PM haraldk Exp$
*/
-public class JPEGImageReaderTestCase extends ImageReaderAbstractTestCase {
+public class JPEGImageReaderTest extends ImageReaderAbstractTest {
private static final boolean IS_JAVA_6 = SystemUtil.isClassAvailable("java.util.Deque");
- protected JPEGImageReaderSpi provider = new JPEGImageReaderSpi();
+ protected final ImageReaderSpi provider = lookupSpi();
+
+ private ImageReaderSpi lookupSpi() {
+ try {
+ return (ImageReaderSpi) Class.forName("com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi").newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+ assumeNoException(e);
+ }
+
+ return null;
+ }
@Override
protected List getTestData() {
- return Arrays.asList(
+ return Collections.singletonList(
new TestData(getClassLoaderResource("/jpeg/R-7439-1151526181.jpeg"), new Dimension(386, 396))
);
}
@@ -39,14 +52,21 @@ public class JPEGImageReaderTestCase extends ImageReaderAbstractTestCase getReaderClass() {
- return JPEGImageReader.class;
+ protected Class getReaderClass() {
+ try {
+ return Class.forName("com.sun.imageio.plugins.jpeg.JPEGImageReader");
+ }
+ catch (ClassNotFoundException e) {
+ assumeNoException(e);
+ }
+
+ return null;
}
@Override
- protected JPEGImageReader createReader() {
+ protected ImageReader createReader() {
try {
- return (JPEGImageReader) provider.createReaderInstance();
+ return provider.createReaderInstance();
}
catch (IOException e) {
throw new RuntimeException(e);
diff --git a/imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/PNGImageReaderTestCase.java b/imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/PNGImageReaderTest.java
similarity index 56%
rename from imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/PNGImageReaderTestCase.java
rename to imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/PNGImageReaderTest.java
index 8b1a7269..4bbf5522 100644
--- a/imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/PNGImageReaderTestCase.java
+++ b/imageio/imageio-reference/src/test/java/com/twelvemonkeys/imageio/reference/PNGImageReaderTest.java
@@ -1,30 +1,43 @@
package com.twelvemonkeys.imageio.reference;
-import com.sun.imageio.plugins.png.PNGImageReader;
-import com.sun.imageio.plugins.png.PNGImageReaderSpi;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import javax.imageio.IIOException;
+import javax.imageio.ImageReader;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import static org.junit.Assume.assumeNoException;
+
/**
- * PNGImageReaderTestCase
+ * PNGImageReaderTest
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
- * @version $Id: PNGImageReaderTestCase.java,v 1.0 Oct 9, 2009 3:37:25 PM haraldk Exp$
+ * @version $Id: PNGImageReaderTest.java,v 1.0 Oct 9, 2009 3:37:25 PM haraldk Exp$
*/
-public class PNGImageReaderTestCase extends ImageReaderAbstractTestCase {
- protected PNGImageReaderSpi provider = new PNGImageReaderSpi();
+public class PNGImageReaderTest extends ImageReaderAbstractTest {
+ protected final ImageReaderSpi provider = lookupSpi();
+
+ private ImageReaderSpi lookupSpi() {
+ try {
+ return (ImageReaderSpi) Class.forName("com.sun.imageio.plugins.png.PNGImageReaderSpi").newInstance();
+ }
+ catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
+ assumeNoException(e);
+ }
+
+ return null;
+ }
@Override
protected List getTestData() {
- return Arrays.asList(
+ return Collections.singletonList(
new TestData(getClassLoaderResource("/png/12monkeys-splash.png"), new Dimension(300, 410))
);
}
@@ -35,14 +48,21 @@ public class PNGImageReaderTestCase extends ImageReaderAbstractTestCase getReaderClass() {
- return PNGImageReader.class;
+ protected Class getReaderClass() {
+ try {
+ return Class.forName("com.sun.imageio.plugins.png.PNGImageReader");
+ }
+ catch (ClassNotFoundException e) {
+ assumeNoException(e);
+ }
+
+ return null;
}
@Override
- protected PNGImageReader createReader() {
+ protected ImageReader createReader() {
try {
- return (PNGImageReader) provider.createReaderInstance();
+ return provider.createReaderInstance();
}
catch (IOException e) {
throw new RuntimeException(e);
diff --git a/imageio/imageio-sgi/pom.xml b/imageio/imageio-sgi/pom.xml
index 6dee4429..0296d502 100755
--- a/imageio/imageio-sgi/pom.xml
+++ b/imageio/imageio-sgi/pom.xml
@@ -1,12 +1,10 @@
-
+
4.0.0
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-sgi
TwelveMonkeys :: ImageIO :: SGI plugin
@@ -22,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIMetadata.java b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIMetadata.java
index 16e8b8b6..5e4614fd 100755
--- a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIMetadata.java
+++ b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIMetadata.java
@@ -28,48 +28,19 @@
package com.twelvemonkeys.imageio.plugins.sgi;
-import org.w3c.dom.Node;
+import com.twelvemonkeys.imageio.AbstractMetadata;
-import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
-final class SGIMetadata extends IIOMetadata {
- // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
-
+final class SGIMetadata extends AbstractMetadata {
private final SGIHeader header;
SGIMetadata(final SGIHeader header) {
this.header = header;
- standardFormatSupported = true;
}
- @Override public boolean isReadOnly() {
- return true;
- }
-
- @Override public Node getAsTree(final String formatName) {
- if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
- return getStandardTree();
- }
- else {
- throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
- }
- }
-
- @Override public void mergeTree(final String formatName, final Node root) {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
- @Override public void reset() {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
- @Override protected IIOMetadataNode getStandardChromaNode() {
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
// NOTE: There doesn't seem to be any god way to determine color space, other than by convention
@@ -117,12 +88,15 @@ final class SGIMetadata extends IIOMetadata {
// No compression
- @Override protected IIOMetadataNode getStandardCompressionNode() {
+ @Override
+ protected IIOMetadataNode getStandardCompressionNode() {
if (header.getCompression() != SGI.COMPRESSION_NONE) {
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
- compressionTypeName.setAttribute("value", header.getCompression() == SGI.COMPRESSION_RLE ? "RLE" : "Uknown");
+ compressionTypeName.setAttribute("value", header.getCompression() == SGI.COMPRESSION_RLE
+ ? "RLE"
+ : "Uknown");
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
@@ -135,7 +109,8 @@ final class SGIMetadata extends IIOMetadata {
return null;
}
- @Override protected IIOMetadataNode getStandardDataNode() {
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
@@ -183,7 +158,8 @@ final class SGIMetadata extends IIOMetadata {
return buffer.toString();
}
- @Override protected IIOMetadataNode getStandardDimensionNode() {
+ @Override
+ protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
@@ -195,7 +171,8 @@ final class SGIMetadata extends IIOMetadata {
// No document node
- @Override protected IIOMetadataNode getStandardTextNode() {
+ @Override
+ protected IIOMetadataNode getStandardTextNode() {
if (!header.getName().isEmpty()) {
IIOMetadataNode text = new IIOMetadataNode("Text");
@@ -212,14 +189,17 @@ final class SGIMetadata extends IIOMetadata {
// No tiling
- @Override protected IIOMetadataNode getStandardTransparencyNode() {
+ @Override
+ protected IIOMetadataNode getStandardTransparencyNode() {
// NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
- alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3 ? "none" : "nonpremultiplied");
+ alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3
+ ? "none"
+ : "nonpremultiplied");
transparency.appendChild(alpha);
return transparency;
diff --git a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfo.java b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfo.java
index bcfd503b..d49a948c 100644
--- a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfo.java
+++ b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.sgi;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
@@ -23,7 +51,7 @@ final class SGIProviderInfo extends ReaderWriterProviderInfo {
"image/sgi",
"image/x-sgi",
},
- "com.twelvemkonkeys.imageio.plugins.sgi.SGIImageReader",
+ "com.twelvemonkeys.imageio.plugins.sgi.SGIImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.sgi.SGIImageReaderSpi"},
null,
null,
diff --git a/imageio/imageio-sgi/src/test/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReaderTest.java b/imageio/imageio-sgi/src/test/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReaderTest.java
index 209d8efd..8d6d8112 100755
--- a/imageio/imageio-sgi/src/test/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReaderTest.java
+++ b/imageio/imageio-sgi/src/test/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReaderTest.java
@@ -28,11 +28,12 @@
package com.twelvemonkeys.imageio.plugins.sgi;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -42,10 +43,10 @@ import java.util.List;
* @author last modified by $Author: haraldk$
* @version $Id: SGIImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
*/
-public class SGIImageReaderTest extends ImageReaderAbstractTestCase {
+public class SGIImageReaderTest extends ImageReaderAbstractTest {
@Override
protected List getTestData() {
- return Arrays.asList(
+ return Collections.singletonList(
new TestData(getClassLoaderResource("/sgi/MARBLES.SGI"), new Dimension(1419, 1001)) // RLE encoded RGB
);
}
@@ -72,9 +73,7 @@ public class SGIImageReaderTest extends ImageReaderAbstractTestCase getSuffixes() {
- return Arrays.asList(
- "sgi"
- );
+ return Collections.singletonList("sgi");
}
@Override
diff --git a/imageio/imageio-sgi/src/test/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfoTest.java b/imageio/imageio-sgi/src/test/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfoTest.java
new file mode 100644
index 00000000..7a84ff48
--- /dev/null
+++ b/imageio/imageio-sgi/src/test/java/com/twelvemonkeys/imageio/plugins/sgi/SGIProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.sgi;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * SGIProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: SGIProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class SGIProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new SGIProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-tga/pom.xml b/imageio/imageio-tga/pom.xml
index fe5f36eb..2683485a 100755
--- a/imageio/imageio-tga/pom.xml
+++ b/imageio/imageio-tga/pom.xml
@@ -1,12 +1,10 @@
-
+
4.0.0
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-tga
TwelveMonkeys :: ImageIO :: TGA plugin
@@ -22,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGA.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGA.java
index e845fcb0..116ec2df 100755
--- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGA.java
+++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGA.java
@@ -29,6 +29,8 @@
package com.twelvemonkeys.imageio.plugins.tga;
interface TGA {
+ byte[] MAGIC = {'T', 'R', 'U', 'E', 'V', 'I', 'S', 'I', 'O', 'N', '-', 'X', 'F', 'I', 'L', 'E', '.', 0};
+
/** Fixed header size: 18.*/
int HEADER_SIZE = 18;
diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAExtensions.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAExtensions.java
new file mode 100644
index 00000000..ad806de9
--- /dev/null
+++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAExtensions.java
@@ -0,0 +1,187 @@
+package com.twelvemonkeys.imageio.plugins.tga;
+
+import javax.imageio.IIOException;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
+
+/**
+ * TGAExtensions.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TGAExtensions.java,v 1.0 27/07/15 harald.kuhr Exp$
+ */
+final class TGAExtensions {
+ public static final int EXT_AREA_SIZE = 495;
+
+ private String authorName;
+ private String authorComments;
+
+ private Calendar creationDate;
+ private String jobId;
+
+ private String softwareId;
+ private String softwareVersion;
+
+ private int backgroundColor;
+ private double pixelAspectRatio;
+ private double gamma;
+
+ private long colorCorrectionOffset;
+ private long postageStampOffset;
+ private long scanLineOffset;
+
+ private int attributeType;
+
+ private TGAExtensions() {
+ }
+
+ static TGAExtensions read(final ImageInputStream stream) throws IOException {
+ int extSize = stream.readUnsignedShort();
+
+ // Should always be 495 for version 2.0, no newer version exists...
+ if (extSize < EXT_AREA_SIZE) {
+ throw new IIOException(String.format("TGA Extension Area size less than %d: %d", EXT_AREA_SIZE, extSize));
+ }
+
+ TGAExtensions extensions = new TGAExtensions();
+ extensions.authorName = readString(stream, 41);;
+ extensions.authorComments = readString(stream, 324);
+ extensions.creationDate = readDate(stream);
+ extensions.jobId = readString(stream, 41);
+
+ stream.skipBytes(6); // Job time, 3 shorts, hours/minutes/seconds elapsed
+
+ extensions.softwareId = readString(stream, 41);
+
+ // Software version (* 100) short + single byte ASCII (ie. 101 'b' for 1.01b)
+ int softwareVersion = stream.readUnsignedShort();
+ int softwareLetter = stream.readByte();
+
+ extensions.softwareVersion = softwareVersion != 0 && softwareLetter != ' '
+ ? String.format("%d.%d%d", softwareVersion / 100, softwareVersion % 100, softwareLetter).trim()
+ : null;
+
+ extensions.backgroundColor = stream.readInt(); // ARGB
+
+ extensions.pixelAspectRatio = readRational(stream);
+ extensions.gamma = readRational(stream);
+
+ extensions.colorCorrectionOffset = stream.readUnsignedInt();
+ extensions.postageStampOffset = stream.readUnsignedInt();
+ extensions.scanLineOffset = stream.readUnsignedInt();
+
+ // Offset 494 specifies Attribute type:
+ // 0: no Alpha data included (bits 3-0 of field 5.6 should also be set to zero)
+ // 1: undefined data in the Alpha field, can be ignored
+ // 2: undefined data in the Alpha field, but should be retained
+ // 3: useful Alpha channel data is present
+ // 4: pre-multiplied Alpha (see description below)
+ // 5 -127: RESERVED
+ // 128-255: Un-assigned
+ extensions.attributeType = stream.readUnsignedByte();
+
+ return extensions;
+ }
+
+ private static double readRational(final ImageInputStream stream) throws IOException {
+ int numerator = stream.readUnsignedShort();
+ int denominator = stream.readUnsignedShort();
+
+ return denominator != 0 ? numerator / (double) denominator : 1;
+ }
+
+ private static Calendar readDate(final ImageInputStream stream) throws IOException {
+ Calendar calendar = Calendar.getInstance();
+ calendar.clear();
+
+ int month = stream.readUnsignedShort();
+ int date = stream.readUnsignedShort();
+ int year = stream.readUnsignedShort();
+
+ int hourOfDay = stream.readUnsignedShort();
+ int minute = stream.readUnsignedShort();
+ int second = stream.readUnsignedShort();
+
+ // Unused
+ if (month == 0 && year == 0 && date == 0 && hourOfDay == 0 && minute == 0 && second == 0) {
+ return null;
+ }
+
+ calendar.set(year, month - 1, date, hourOfDay, minute, second);
+
+ return calendar;
+ }
+
+ private static String readString(final ImageInputStream stream, final int maxLength) throws IOException {
+ byte[] data = new byte[maxLength];
+ stream.readFully(data);
+
+ return asZeroTerminatedASCIIString(data);
+ }
+
+ private static String asZeroTerminatedASCIIString(final byte[] data) {
+ int len = data.length;
+
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] == 0) {
+ len = i;
+ }
+ }
+
+ return new String(data, 0, len, StandardCharsets.US_ASCII);
+ }
+
+ public boolean hasAlpha() {
+ switch (attributeType) {
+ case 3:
+ case 4:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public boolean isAlphaPremultiplied() {
+ switch (attributeType) {
+ case 4:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public long getThumbnailOffset() {
+ return postageStampOffset;
+ }
+
+ public String getAuthorName() {
+ return authorName;
+ }
+
+ public String getAuthorComments() {
+ return authorComments;
+ }
+
+ public Calendar getCreationDate() {
+ return creationDate;
+ }
+
+ public String getSoftware() {
+ return softwareId;
+ }
+
+ public String getSoftwareVersion() {
+ return softwareVersion;
+ }
+
+ public double getPixelAspectRatio() {
+ return pixelAspectRatio;
+ }
+
+ public int getBackgroundColor() {
+ return backgroundColor;
+ }
+}
diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReader.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReader.java
index 5465ed03..bceccb8b 100755
--- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReader.java
+++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReader.java
@@ -33,6 +33,7 @@ import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
+import com.twelvemonkeys.lang.Validate;
import com.twelvemonkeys.xml.XMLSerializer;
import javax.imageio.IIOException;
@@ -51,6 +52,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
@@ -59,6 +61,7 @@ public final class TGAImageReader extends ImageReaderBase {
// http://www.gamers.org/dEngine/quake3/TGA.txt
private TGAHeader header;
+ private TGAExtensions extensions;
protected TGAImageReader(final ImageReaderSpi provider) {
super(provider);
@@ -67,6 +70,7 @@ public final class TGAImageReader extends ImageReaderBase {
@Override
protected void resetMembers() {
header = null;
+ extensions = null;
}
@Override
@@ -89,7 +93,7 @@ public final class TGAImageReader extends ImageReaderBase {
public Iterator getImageTypes(final int imageIndex) throws IOException {
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
- List specifiers = new ArrayList();
+ List specifiers = new ArrayList<>();
// TODO: Implement
specifiers.add(rawType);
@@ -110,19 +114,29 @@ public final class TGAImageReader extends ImageReaderBase {
return ImageTypeSpecifiers.createFromIndexColorModel(header.getColorMap());
case TGA.IMAGETYPE_MONOCHROME:
case TGA.IMAGETYPE_MONOCHROME_RLE:
- return ImageTypeSpecifiers.createGrayscale(1, DataBuffer.TYPE_BYTE);
+ return ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
case TGA.IMAGETYPE_TRUECOLOR:
case TGA.IMAGETYPE_TRUECOLOR_RLE:
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ boolean hasAlpha = header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha();
+ boolean isAlphaPremultiplied = extensions != null && extensions.isAlphaPremultiplied();
+
switch (header.getPixelDepth()) {
case 16:
+ if (hasAlpha) {
+ // USHORT_1555_ARGB...
+ return ImageTypeSpecifiers.createPacked(sRGB, 0x7C00, 0x03E0, 0x001F, 0x8000, DataBuffer.TYPE_USHORT, isAlphaPremultiplied);
+ }
+ // Default mask out alpha
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_USHORT_555_RGB);
case 24:
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
case 32:
- // 4BYTE_BGRA...
- return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false);
+ // 4BYTE_BGRX...
+ // Can't mask out alpha (efficiently) for 4BYTE, so we'll ignore it while reading instead,
+ // if hasAlpha is false
+ return ImageTypeSpecifiers.createInterleaved(sRGB, new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, isAlphaPremultiplied);
default:
throw new IIOException("Unknown pixel depth for truecolor: " + header.getPixelDepth());
}
@@ -166,31 +180,32 @@ public final class TGAImageReader extends ImageReaderBase {
DataInput input;
if (imageType == TGA.IMAGETYPE_COLORMAPPED_RLE || imageType == TGA.IMAGETYPE_TRUECOLOR_RLE || imageType == TGA.IMAGETYPE_MONOCHROME_RLE) {
input = new LittleEndianDataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new RLEDecoder(header.getPixelDepth())));
- } else {
+ }
+ else {
input = imageInput;
}
for (int y = 0; y < height; y++) {
switch (header.getPixelDepth()) {
- case 8:
- case 24:
- case 32:
- byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
- readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y);
- break;
- case 16:
- short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
- readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
- break;
- default:
- throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
- }
-
- processImageProgress(100f * y / height);
-
- if (height - 1 - y < srcRegion.y) {
+ case 8:
+ case 24:
+ case 32:
+ byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
+ readRowByte(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataByte, destRaster, clippedRow, y);
break;
- }
+ case 16:
+ short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
+ readRowUShort(input, height, srcRegion, header.getOrigin(), xSub, ySub, rowDataUShort, destRaster, clippedRow, y);
+ break;
+ default:
+ throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
+ }
+
+ processImageProgress(100f * y / height);
+
+ if (height - 1 - y < srcRegion.y) {
+ break;
+ }
if (abortRequested()) {
processReadAborted();
@@ -212,11 +227,11 @@ public final class TGAImageReader extends ImageReaderBase {
return;
}
-
input.readFully(rowDataByte, 0, rowDataByte.length);
- if (srcChannel.getNumBands() == 4) {
- invertAlpha(rowDataByte);
+ if (srcChannel.getNumBands() == 4 && (header.getAttributeBits() == 0 || extensions != null && !extensions.hasAlpha())) {
+ // Remove the alpha channel (make pixels opaque) if there are no "attribute bits" (alpha bits)
+ removeAlpha32(rowDataByte);
}
// Subsample horizontal
@@ -240,9 +255,9 @@ public final class TGAImageReader extends ImageReaderBase {
}
}
- private void invertAlpha(final byte[] rowDataByte) {
- for (int i = 3; i < rowDataByte.length; i += 4) {
- rowDataByte[i] = (byte) (0xFF - rowDataByte[i]);
+ private void removeAlpha32(final byte[] rowData) {
+ for (int i = 3; i < rowData.length; i += 4) {
+ rowData[i] = (byte) 0xFF;
}
}
@@ -313,21 +328,154 @@ public final class TGAImageReader extends ImageReaderBase {
private void readHeader() throws IOException {
if (header == null) {
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+
+ // Read header
header = TGAHeader.read(imageInput);
// System.err.println("header: " + header);
imageInput.flushBefore(imageInput.getStreamPosition());
+
+ // Read footer, if 2.0 format (ends with TRUEVISION-XFILE\0)
+ skipToEnd(imageInput);
+ imageInput.seek(imageInput.getStreamPosition() - 26);
+
+ long extOffset = imageInput.readInt();
+ /*long devOffset = */imageInput.readInt(); // Ignored for now
+
+ byte[] magic = new byte[18];
+ imageInput.readFully(magic);
+
+ if (Arrays.equals(magic, TGA.MAGIC)) {
+ if (extOffset > 0) {
+ imageInput.seek(extOffset);
+ extensions = TGAExtensions.read(imageInput);
+ }
+ }
}
imageInput.seek(imageInput.getFlushedPosition());
}
- @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
+ // TODO: Candidate util method
+ private static void skipToEnd(final ImageInputStream stream) throws IOException {
+ if (stream.length() > 0) {
+ // Seek to end of file
+ stream.seek(stream.length());
+ }
+ else {
+ // Skip to end
+ long lastGood = stream.getStreamPosition();
+
+ while (stream.read() != -1) {
+ lastGood = stream.getStreamPosition();
+ stream.skipBytes(1024);
+ }
+
+ stream.seek(lastGood);
+
+ while (true) {
+ if (stream.read() == -1) {
+ break;
+ }
+ // Just continue reading to EOF...
+ }
+ }
+ }
+
+ // Thumbnail support
+
+ @Override
+ public boolean readerSupportsThumbnails() {
+ return true;
+ }
+
+ @Override
+ public boolean hasThumbnails(final int imageIndex) throws IOException {
checkBounds(imageIndex);
readHeader();
- return new TGAMetadata(header);
+ return extensions != null && extensions.getThumbnailOffset() > 0;
+ }
+
+ @Override
+ public int getNumThumbnails(final int imageIndex) throws IOException {
+ return hasThumbnails(imageIndex) ? 1 : 0;
+ }
+
+ @Override
+ public int getThumbnailWidth(final int imageIndex, final int thumbnailIndex) throws IOException {
+ checkBounds(imageIndex);
+ Validate.isTrue(thumbnailIndex >= 0 && thumbnailIndex < getNumThumbnails(imageIndex), "thumbnailIndex >= numThumbnails");
+
+ imageInput.seek(extensions.getThumbnailOffset());
+
+ return imageInput.readUnsignedByte();
+ }
+
+ @Override
+ public int getThumbnailHeight(final int imageIndex, final int thumbnailIndex) throws IOException {
+ getThumbnailWidth(imageIndex, thumbnailIndex); // Laziness...
+
+ return imageInput.readUnsignedByte();
+ }
+
+ @Override
+ public BufferedImage readThumbnail(final int imageIndex, final int thumbnailIndex) throws IOException {
+ Iterator imageTypes = getImageTypes(imageIndex);
+ ImageTypeSpecifier rawType = getRawImageType(imageIndex);
+
+ int width = getThumbnailWidth(imageIndex, thumbnailIndex);
+ int height = getThumbnailHeight(imageIndex, thumbnailIndex);
+
+ // For thumbnail, always read entire image
+ Rectangle srcRegion = new Rectangle(width, height);
+
+ BufferedImage destination = getDestination(null, imageTypes, width, height);
+ WritableRaster destRaster = destination.getRaster();
+ WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
+
+ processThumbnailStarted(imageIndex, thumbnailIndex);
+
+ // Thumbnail is always stored non-compressed, no need for RLE support
+ imageInput.seek(extensions.getThumbnailOffset() + 2);
+
+ for (int y = 0; y < height; y++) {
+ switch (header.getPixelDepth()) {
+ case 8:
+ case 24:
+ case 32:
+ byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
+ readRowByte(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataByte, destRaster, rowRaster, y);
+ break;
+ case 16:
+ short[] rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
+ readRowUShort(imageInput, height, srcRegion, header.getOrigin(), 1, 1, rowDataUShort, destRaster, rowRaster, y);
+ break;
+ default:
+ throw new AssertionError("Unsupported pixel depth: " + header.getPixelDepth());
+ }
+
+ processThumbnailProgress(100f * y / height);
+
+ if (height - 1 - y < srcRegion.y) {
+ break;
+ }
+ }
+
+ processThumbnailComplete();
+
+ return destination;
+ }
+
+ // Metadata support
+
+ @Override
+ public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
+ checkBounds(imageIndex);
+ readHeader();
+
+ return new TGAMetadata(header, extensions);
}
public static void main(String[] args) throws IOException {
diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderSpi.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderSpi.java
index 2948915a..5a741ba3 100755
--- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderSpi.java
+++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderSpi.java
@@ -45,7 +45,8 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
super(new TGAProviderInfo());
}
- @Override public boolean canDecodeInput(final Object source) throws IOException {
+ @Override
+ public boolean canDecodeInput(final Object source) throws IOException {
if (!(source instanceof ImageInputStream)) {
return false;
}
@@ -58,7 +59,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
try {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
- // NOTE: The TGA format does not have a magic identifier, so this is guesswork...
+ // NOTE: The original TGA format does not have a magic identifier, so this is guesswork...
// We'll try to match sane values, and hope no other files contains the same sequence.
stream.readUnsignedByte();
@@ -88,11 +89,11 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
int colorMapStart = stream.readUnsignedShort();
int colorMapSize = stream.readUnsignedShort();
- int colorMapDetph = stream.readUnsignedByte();
+ int colorMapDepth = stream.readUnsignedByte();
if (colorMapSize == 0) {
// No color map, all 3 fields should be 0
- if (colorMapStart!= 0 || colorMapDetph != 0) {
+ if (colorMapStart != 0 || colorMapDepth != 0) {
return false;
}
}
@@ -106,7 +107,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
if (colorMapStart >= colorMapSize) {
return false;
}
- if (colorMapDetph != 15 && colorMapDetph != 16 && colorMapDetph != 24 && colorMapDetph != 32) {
+ if (colorMapDepth != 15 && colorMapDepth != 16 && colorMapDepth != 24 && colorMapDepth != 32) {
return false;
}
}
@@ -134,6 +135,7 @@ public final class TGAImageReaderSpi extends ImageReaderSpiBase {
// We're pretty sure by now, but there can still be false positives...
// For 2.0 format, we could skip to end, and read "TRUEVISION-XFILE.\0" but it would be too slow
+ // unless we are working with a local file (and the file may still be a valid original TGA without it).
return true;
}
finally {
diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadata.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadata.java
index d78cf8f3..bcca2bb6 100755
--- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadata.java
+++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadata.java
@@ -28,53 +28,29 @@
package com.twelvemonkeys.imageio.plugins.tga;
-import org.w3c.dom.Node;
+import com.twelvemonkeys.imageio.AbstractMetadata;
-import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
+import java.awt.*;
import java.awt.image.IndexColorModel;
+import java.util.Calendar;
-final class TGAMetadata extends IIOMetadata {
- // TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
-
+final class TGAMetadata extends AbstractMetadata {
private final TGAHeader header;
+ private final TGAExtensions extensions;
- TGAMetadata(final TGAHeader header) {
+ TGAMetadata(final TGAHeader header, final TGAExtensions extensions) {
this.header = header;
- standardFormatSupported = true;
+ this.extensions = extensions;
}
- @Override public boolean isReadOnly() {
- return true;
- }
-
- @Override public Node getAsTree(final String formatName) {
- if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
- return getStandardTree();
- }
- else {
- throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
- }
- }
-
- @Override public void mergeTree(final String formatName, final Node root) {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
- @Override public void reset() {
- if (isReadOnly()) {
- throw new IllegalStateException("Metadata is read-only");
- }
- }
-
-
- @Override protected IIOMetadataNode getStandardChromaNode() {
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
+ chroma.appendChild(csType);
+
switch (header.getImageType()) {
case TGA.IMAGETYPE_MONOCHROME:
case TGA.IMAGETYPE_MONOCHROME_RLE:
@@ -92,15 +68,22 @@ final class TGAMetadata extends IIOMetadata {
default:
csType.setAttribute("name", "Unknown");
}
- chroma.appendChild(csType);
- // TODO: Channels in chroma node reflects channels in color model (see data node, for channels in data)
+ // NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
+ chroma.appendChild(numChannels);
switch (header.getPixelDepth()) {
case 8:
- case 16:
numChannels.setAttribute("value", Integer.toString(1));
break;
+ case 16:
+ if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
+ numChannels.setAttribute("value", Integer.toString(4));
+ }
+ else {
+ numChannels.setAttribute("value", Integer.toString(3));
+ }
+ break;
case 24:
numChannels.setAttribute("value", Integer.toString(3));
break;
@@ -108,11 +91,10 @@ final class TGAMetadata extends IIOMetadata {
numChannels.setAttribute("value", Integer.toString(4));
break;
}
- chroma.appendChild(numChannels);
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
- blackIsZero.setAttribute("value", "TRUE");
chroma.appendChild(blackIsZero);
+ blackIsZero.setAttribute("value", "TRUE");
// NOTE: TGA files may contain a color map, even if true color...
// Not sure if this is a good idea to expose to the meta data,
@@ -124,20 +106,31 @@ final class TGAMetadata extends IIOMetadata {
for (int i = 0; i < colorMap.getMapSize(); i++) {
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
+ palette.appendChild(paletteEntry);
paletteEntry.setAttribute("index", Integer.toString(i));
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
-
- palette.appendChild(paletteEntry);
}
}
+ if (extensions != null && extensions.getBackgroundColor() != 0) {
+ Color background = new Color(extensions.getBackgroundColor(), true);
+
+ IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor");
+ chroma.appendChild(backgroundColor);
+
+ backgroundColor.setAttribute("red", Integer.toString(background.getRed()));
+ backgroundColor.setAttribute("green", Integer.toString(background.getGreen()));
+ backgroundColor.setAttribute("blue", Integer.toString(background.getBlue()));
+ }
+
return chroma;
}
- @Override protected IIOMetadataNode getStandardCompressionNode() {
+ @Override
+ protected IIOMetadataNode getStandardCompressionNode() {
switch (header.getImageType()) {
case TGA.IMAGETYPE_COLORMAPPED_RLE:
case TGA.IMAGETYPE_TRUECOLOR_RLE:
@@ -145,15 +138,16 @@ final class TGAMetadata extends IIOMetadata {
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN:
case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE:
IIOMetadataNode node = new IIOMetadataNode("Compression");
+
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
- String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
- ? "Uknown" : "RLE";
- compressionTypeName.setAttribute("value", value);
node.appendChild(compressionTypeName);
+ String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE
+ ? "Uknown" : "RLE";
+ compressionTypeName.setAttribute("value", value);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
- lossless.setAttribute("value", "TRUE");
node.appendChild(lossless);
+ lossless.setAttribute("value", "TRUE");
return node;
default:
@@ -162,14 +156,17 @@ final class TGAMetadata extends IIOMetadata {
}
}
- @Override protected IIOMetadataNode getStandardDataNode() {
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
- planarConfiguration.setAttribute("value", "PixelInterleaved");
node.appendChild(planarConfiguration);
+ planarConfiguration.setAttribute("value", "PixelInterleaved");
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
+ node.appendChild(sampleFormat);
+
switch (header.getImageType()) {
case TGA.IMAGETYPE_COLORMAPPED:
case TGA.IMAGETYPE_COLORMAPPED_RLE:
@@ -182,13 +179,19 @@ final class TGAMetadata extends IIOMetadata {
break;
}
- node.appendChild(sampleFormat);
-
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
+ node.appendChild(bitsPerSample);
+
switch (header.getPixelDepth()) {
case 8:
- case 16:
bitsPerSample.setAttribute("value", createListValue(1, Integer.toString(header.getPixelDepth())));
+ case 16:
+ if (header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha()) {
+ bitsPerSample.setAttribute("value", "5, 5, 5, 1");
+ }
+ else {
+ bitsPerSample.setAttribute("value", createListValue(3, "5"));
+ }
break;
case 24:
bitsPerSample.setAttribute("value", createListValue(3, Integer.toString(8)));
@@ -198,12 +201,6 @@ final class TGAMetadata extends IIOMetadata {
break;
}
- node.appendChild(bitsPerSample);
-
- // TODO: Do we need MSB?
-// IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
-// sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
-
return node;
}
@@ -221,10 +218,12 @@ final class TGAMetadata extends IIOMetadata {
return buffer.toString();
}
- @Override protected IIOMetadataNode getStandardDimensionNode() {
+ @Override
+ protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
+ dimension.appendChild(imageOrientation);
switch (header.getOrigin()) {
case TGA.ORIGIN_LOWER_LEFT:
@@ -241,38 +240,90 @@ final class TGAMetadata extends IIOMetadata {
break;
}
- dimension.appendChild(imageOrientation);
+ IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
+ dimension.appendChild(pixelAspectRatio);
+ pixelAspectRatio.setAttribute("value", extensions != null ? String.valueOf(extensions.getPixelAspectRatio()) : "1.0");
return dimension;
}
- // No document node
+ @Override
+ protected IIOMetadataNode getStandardDocumentNode() {
+ IIOMetadataNode document = new IIOMetadataNode("Document");
- @Override protected IIOMetadataNode getStandardTextNode() {
- // TODO: Extra "developer area" and other stuff might go here...
- if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
- IIOMetadataNode text = new IIOMetadataNode("Text");
+ IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
+ document.appendChild(formatVersion);
+ formatVersion.setAttribute("value", extensions == null ? "1.0" : "2.0");
- IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
- textEntry.setAttribute("keyword", "identification");
- textEntry.setAttribute("value", header.getIdentification());
- text.appendChild(textEntry);
+ // ImageCreationTime from extensions date
+ if (extensions != null && extensions.getCreationDate() != null) {
+ IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime");
+ document.appendChild(imageCreationTime);
- return text;
+ Calendar date = extensions.getCreationDate();
+
+ imageCreationTime.setAttribute("year", String.valueOf(date.get(Calendar.YEAR)));
+ imageCreationTime.setAttribute("month", String.valueOf(date.get(Calendar.MONTH) + 1));
+ imageCreationTime.setAttribute("day", String.valueOf(date.get(Calendar.DAY_OF_MONTH)));
+ imageCreationTime.setAttribute("hour", String.valueOf(date.get(Calendar.HOUR_OF_DAY)));
+ imageCreationTime.setAttribute("minute", String.valueOf(date.get(Calendar.MINUTE)));
+ imageCreationTime.setAttribute("second", String.valueOf(date.get(Calendar.SECOND)));
}
- return null;
+ return document;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardTextNode() {
+ IIOMetadataNode text = new IIOMetadataNode("Text");
+
+ // NOTE: Names corresponds to equivalent fields in TIFF
+ if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
+ appendTextEntry(text, "DocumentName", header.getIdentification());
+ }
+
+ if (extensions != null) {
+ appendTextEntry(text, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : extensions.getSoftware() + " " + extensions.getSoftwareVersion());
+ appendTextEntry(text, "Artist", extensions.getAuthorName());
+ appendTextEntry(text, "UserComment", extensions.getAuthorComments());
+ }
+
+ return text.hasChildNodes() ? text : null;
+ }
+
+ private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) {
+ if (value != null) {
+ IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
+ parent.appendChild(textEntry);
+ textEntry.setAttribute("keyword", keyword);
+ textEntry.setAttribute("value", value);
+ }
}
// No tiling
- @Override protected IIOMetadataNode getStandardTransparencyNode() {
+ @Override
+ protected IIOMetadataNode getStandardTransparencyNode() {
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
- alpha.setAttribute("value", header.getPixelDepth() == 32 ? "nonpremultiplied" : "none");
transparency.appendChild(alpha);
+ if (extensions != null) {
+ if (extensions.hasAlpha()) {
+ alpha.setAttribute("value", extensions.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied");
+ }
+ else {
+ alpha.setAttribute("value", "none");
+ }
+ }
+ else if (header.getAttributeBits() == 8) {
+ alpha.setAttribute("value", "nonpremultiplied");
+ }
+ else {
+ alpha.setAttribute("value", "none");
+ }
+
return transparency;
}
}
diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfo.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfo.java
index c48d47e2..1009093b 100644
--- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfo.java
+++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.tga;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
@@ -23,7 +51,7 @@ final class TGAProviderInfo extends ReaderWriterProviderInfo {
"image/tga", "image/x-tga",
"image/targa", "image/x-targa",
},
- "com.twelvemkonkeys.imageio.plugins.tga.TGAImageReader",
+ "com.twelvemonkeys.imageio.plugins.tga.TGAImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.tga.TGAImageReaderSpi"},
null,
null,
diff --git a/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderTest.java b/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderTest.java
index 59a85d49..601df0a9 100755
--- a/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderTest.java
+++ b/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageReaderTest.java
@@ -28,7 +28,7 @@
package com.twelvemonkeys.imageio.plugins.tga;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
@@ -42,7 +42,7 @@ import java.util.List;
* @author last modified by $Author: haraldk$
* @version $Id: TGAImageReaderTest.java,v 1.0 03.07.14 22:28 haraldk Exp$
*/
-public class TGAImageReaderTest extends ImageReaderAbstractTestCase {
+public class TGAImageReaderTest extends ImageReaderAbstractTest {
@Override
protected List getTestData() {
return Arrays.asList(
diff --git a/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfoTest.java b/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfoTest.java
new file mode 100644
index 00000000..99a85127
--- /dev/null
+++ b/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.tga;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * TGAProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TGAProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class TGAProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new TGAProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-thumbsdb/pom.xml b/imageio/imageio-thumbsdb/pom.xml
index 1e674aaf..7ade94f8 100644
--- a/imageio/imageio-thumbsdb/pom.xml
+++ b/imageio/imageio-thumbsdb/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-thumbsdb
TwelveMonkeys :: ImageIO :: Thumbs.db plugin
@@ -20,7 +20,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java
index 13152c22..6d31b19f 100644
--- a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java
+++ b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java
@@ -62,7 +62,7 @@ import java.util.SortedSet;
* @see com.twelvemonkeys.io.ole2.CompoundDocument
* @see Harald Kuhr
* @version $Id: ThumbsDBImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$
*/
-public class ThumbsDBImageReaderSpi extends ImageReaderSpiBase {
+public final class ThumbsDBImageReaderSpi extends ImageReaderSpiBase {
private ImageReaderSpi jpegProvider;
/**
@@ -61,7 +61,7 @@ public class ThumbsDBImageReaderSpi extends ImageReaderSpiBase {
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
}
- public boolean canDecode(final ImageInputStream pInput) throws IOException {
+ boolean canDecode(final ImageInputStream pInput) throws IOException {
maybeInitJPEGProvider();
// If this is a OLE 2 CompoundDocument, we could try...
// TODO: How do we know it's thumbs.db format (structure), without reading quite a lot?
diff --git a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBProviderInfo.java b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBProviderInfo.java
index 861dab33..a7585f00 100644
--- a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBProviderInfo.java
+++ b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.thumbsdb;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
@@ -13,9 +41,9 @@ final class ThumbsDBProviderInfo extends ReaderWriterProviderInfo {
protected ThumbsDBProviderInfo() {
super(
ThumbsDBProviderInfo.class,
- new String[]{"thumbs", "THUMBS", "Thumbs DB"},
- new String[]{"db"},
- new String[]{"image/x-thumbs-db", "application/octet-stream"}, // TODO: Check IANA et al...
+ new String[] {"thumbs", "THUMBS", "Thumbs DB"},
+ new String[] {"db"},
+ new String[] {"image/x-thumbs-db", "application/octet-stream"}, // TODO: Check IANA et al...
"com.twelvemonkeys.imageio.plugins.thumbsdb.ThumbsDBImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.thumbsdb.ThumbsDBImageReaderSpi"},
null,
diff --git a/imageio/imageio-thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTestCase.java b/imageio/imageio-thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTest.java
similarity index 90%
rename from imageio/imageio-thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTestCase.java
rename to imageio/imageio-thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTest.java
index ee1e7440..8b6ad255 100644
--- a/imageio/imageio-thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTestCase.java
+++ b/imageio/imageio-thumbsdb/src/test/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReaderTest.java
@@ -29,7 +29,7 @@
package com.twelvemonkeys.imageio.plugins.thumbsdb;
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.io.ole2.CompoundDocument;
import com.twelvemonkeys.io.ole2.Entry;
import com.twelvemonkeys.lang.SystemUtil;
@@ -43,16 +43,19 @@ import java.awt.*;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import static org.junit.Assert.assertNotNull;
+
/**
- * ICOImageReaderTestCase
+ * ICOImageReaderTest
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
- * @version $Id: ICOImageReaderTestCase.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
+ * @version $Id: ICOImageReaderTest.java,v 1.0 Apr 1, 2008 10:39:17 PM haraldk Exp$
*/
-public class ThumbsDBImageReaderTestCase extends ImageReaderAbstractTestCase {
+public class ThumbsDBImageReaderTest extends ImageReaderAbstractTest {
private static final boolean IS_JAVA_6 = SystemUtil.isClassAvailable("java.util.Deque");
private ThumbsDBImageReaderSpi provider = new ThumbsDBImageReaderSpi();
@@ -97,15 +100,15 @@ public class ThumbsDBImageReaderTestCase extends ImageReaderAbstractTestCase getFormatNames() {
- return Arrays.asList("thumbs");
+ return Collections.singletonList("thumbs");
}
protected List getSuffixes() {
- return Arrays.asList("db");
+ return Collections.singletonList("db");
}
protected List getMIMETypes() {
- return Arrays.asList("image/x-thumbs-db");
+ return Collections.singletonList("image/x-thumbs-db");
}
@Test
@@ -115,7 +118,9 @@ public class ThumbsDBImageReaderTestCase extends ImageReaderAbstractTestCaseHarald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: ThumbsDBProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class ThumbsDBProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new ThumbsDBProviderInfo();
+ }
+
+ @Override
+ public void formatNames() {
+ String[] names = getProviderInfo().formatNames();
+ assertNotNull(names);
+ assertFalse(names.length == 0);
+
+ List list = new ArrayList<>(asList(names));
+ assertTrue(list.remove("Thumbs DB")); // No dupes of this name
+
+ for (String name : list) {
+ assertNotNull(name);
+ assertFalse(name.isEmpty());
+
+ assertTrue(list.contains(name.toLowerCase()));
+ assertTrue(list.contains(name.toUpperCase()));
+ }
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-tiff/pom.xml b/imageio/imageio-tiff/pom.xml
index 7f8f118f..11f66f34 100644
--- a/imageio/imageio-tiff/pom.xml
+++ b/imageio/imageio-tiff/pom.xml
@@ -4,7 +4,7 @@
com.twelvemonkeys.imageio
imageio
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
imageio-tiff
TwelveMonkeys :: ImageIO :: TIFF plugin
@@ -24,7 +24,7 @@
com.twelvemonkeys.imageio
imageio-core
- tests
+ test-jar
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java
index 79cec686..16ac23b8 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStream.java
@@ -34,11 +34,13 @@ import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.Arrays;
/**
* CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
*
* @author Harald Kuhr
+ * @author Oliver Schmidtmer
* @author last modified by $Author: haraldk$
* @version $Id: CCITTFaxDecoderStream.java,v 1.0 23.05.12 15:55 haraldk Exp$
*/
@@ -51,33 +53,59 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
private int decodedLength;
private int decodedPos;
- private int bitBuffer;
- private int bitBufferLength;
-
// Need to take fill order into account (?) (use flip table?)
private final int fillOrder;
private final int type;
- private final int[] changes;
- private int changesCount;
+ private int[] changesReferenceRow;
+ private int[] changesCurrentRow;
+ private int changesReferenceRowCount;
+ private int changesCurrentRowCount;
- private static final int EOL_CODE = 0x01; // 12 bit
+ private int lastChangingElement = 0;
- public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder) {
+ private boolean optionG32D = false;
+
+ @SuppressWarnings("unused") // Leading zeros for aligning EOL
+ private boolean optionG3Fill = false;
+
+ private boolean optionUncompressed = false;
+
+ public CCITTFaxDecoderStream(final InputStream stream, final int columns, final int type, final int fillOrder,
+ final long options) {
super(Validate.notNull(stream, "stream"));
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
// We know this is only used for b/w (1 bit)
this.decodedRow = new byte[(columns + 7) / 8];
- this.type = Validate.isTrue(type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, type, "Only CCITT Modified Huffman RLE compression (2) supported: %s"); // TODO: Implement group 3 and 4
- this.fillOrder = Validate.isTrue(fillOrder == 1, fillOrder, "Only fill order 1 supported: %s"); // TODO: Implement fillOrder == 2
+ this.type = Validate.isTrue(
+ type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE ||
+ type == TIFFExtension.COMPRESSION_CCITT_T4 || type == TIFFExtension.COMPRESSION_CCITT_T6,
+ type, "Only CCITT Modified Huffman RLE compression (2), CCITT T4 (3) or CCITT T6 (4) supported: %s"
+ );
+ this.fillOrder = Validate.isTrue(
+ fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT || fillOrder == TIFFExtension.FILL_RIGHT_TO_LEFT,
+ fillOrder, "Expected fill order 1 or 2: %s"
+ );
- this.changes = new int[columns];
+ this.changesReferenceRow = new int[columns + 1];
+ this.changesCurrentRow = new int[columns + 1];
+
+ switch (type) {
+ case TIFFExtension.COMPRESSION_CCITT_T4:
+ optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0;
+ optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0;
+ optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0;
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T6:
+ optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0;
+ break;
+ }
+
+ Validate.isTrue(!optionUncompressed, optionUncompressed,
+ "CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
}
- // IDEA: Would it be faster to keep all bit combos of each length (>=2) that is NOT a code, to find bit length, then look up value in table?
- // -- If white run, start at 4 bits to determine length, if black, start at 2 bits
-
private void fetch() throws IOException {
if (decodedPos >= decodedLength) {
decodedLength = 0;
@@ -91,7 +119,8 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
throw e;
}
- // ..otherwise, just client code trying to read past the end of stream
+ // ..otherwise, just client code trying to read past the end of
+ // stream
decodedLength = -1;
}
@@ -99,154 +128,300 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
}
}
- private void decodeRow() throws IOException {
- resetBuffer();
+ private void decode1D() throws IOException {
+ int index = 0;
+ boolean white = true;
+ changesCurrentRowCount = 0;
- boolean literalRun = true;
+ do {
+ int completeRun;
- /*
- if (type == TIFFExtension.COMPRESSION_CCITT_T4) {
- int eol = readBits(12);
- System.err.println("eol: " + eol);
- while (eol != EOL_CODE) {
- eol = readBits(1);
- System.err.println("eol: " + eol);
-// throw new IOException("Missing EOL");
+ if (white) {
+ completeRun = decodeRun(whiteRunTree);
+ }
+ else {
+ completeRun = decodeRun(blackRunTree);
}
- literalRun = readBits(1) == 1;
+ index += completeRun;
+ changesCurrentRow[changesCurrentRowCount++] = index;
+
+ // Flip color for next run
+ white = !white;
+ } while (index < columns);
+ }
+
+ private void decode2D() throws IOException {
+ changesReferenceRowCount = changesCurrentRowCount;
+ int[] tmp = changesCurrentRow;
+ changesCurrentRow = changesReferenceRow;
+ changesReferenceRow = tmp;
+
+ boolean white = true;
+ int index = 0;
+ changesCurrentRowCount = 0;
+
+ mode: while (index < columns) {
+ // read mode
+ Node n = codeTree.root;
+
+ while (true) {
+ n = n.walk(readBit());
+
+ if (n == null) {
+ continue mode;
+ }
+ else if (n.isLeaf) {
+ switch (n.value) {
+ case VALUE_HMODE:
+ int runLength;
+ runLength = decodeRun(white ? whiteRunTree : blackRunTree);
+ index += runLength;
+ changesCurrentRow[changesCurrentRowCount++] = index;
+
+ runLength = decodeRun(white ? blackRunTree : whiteRunTree);
+ index += runLength;
+ changesCurrentRow[changesCurrentRowCount++] = index;
+ break;
+
+ case VALUE_PASSMODE:
+ int pChangingElement = getNextChangingElement(index, white) + 1;
+
+ if (pChangingElement >= changesReferenceRowCount) {
+ index = columns;
+ }
+ else {
+ index = changesReferenceRow[pChangingElement];
+ }
+
+ break;
+
+ default:
+ // Vertical mode (-3 to 3)
+ int vChangingElement = getNextChangingElement(index, white);
+
+ if (vChangingElement >= changesReferenceRowCount || vChangingElement == -1) {
+ index = columns + n.value;
+ }
+ else {
+ index = changesReferenceRow[vChangingElement] + n.value;
+ }
+
+ changesCurrentRow[changesCurrentRowCount] = index;
+ changesCurrentRowCount++;
+ white = !white;
+
+ break;
+ }
+
+ continue mode;
+ }
+ }
+ }
+ }
+
+ private int getNextChangingElement(final int a0, final boolean white) throws IOException {
+ int start = (lastChangingElement & 0xFFFF_FFFE) + (white ? 0 : 1);
+ if (start > 2) {
+ start -= 2;
}
- System.err.println("literalRun: " + literalRun);
- */
- int index = 0;
+ if (a0 == 0) {
+ return start;
+ }
- if (literalRun) {
- changesCount = 0;
- boolean white = true;
-
- do {
- int completeRun = 0;
-
- int run;
- do {
- if (white) {
- run = decodeRun(WHITE_CODES, WHITE_RUN_LENGTHS, 4);
- }
- else {
- run = decodeRun(BLACK_CODES, BLACK_RUN_LENGTHS, 2);
- }
-
- completeRun += run;
- }
- while (run >= 64); // Additional makeup codes are packed into both b/w codes, terminating codes are < 64 bytes
-
- changes[changesCount++] = index + completeRun;
-
-// System.err.printf("%s run: %d\n", white ? "white" : "black", run);
-
- // TODO: Optimize with lookup for 0-7 bits?
- // Fill bits to byte boundary...
- while (index % 8 != 0 && completeRun-- > 0) {
- decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
- }
-
- // ...then fill complete bytes to either 0xff or 0x00...
- if (index % 8 == 0) {
- final byte value = (byte) (white ? 0xff : 0x00);
-
- while (completeRun > 7) {
- decodedRow[index / 8] = value;
- completeRun -= 8;
- index += 8;
- }
- }
-
- // ...finally fill any remaining bits
- while (completeRun-- > 0) {
- decodedRow[index++ / 8] |= (white ? 1 << 8 - (index % 8) : 0);
- }
-
- // Flip color for next run
- white = !white;
+ for (int i = start; i < changesReferenceRowCount; i += 2) {
+ if (a0 < changesReferenceRow[i]) {
+ lastChangingElement = i;
+ return i;
}
- while (index < columns);
+ }
+
+ return -1;
+ }
+
+ private void decodeRowType2() throws IOException {
+ resetBuffer();
+ decode1D();
+ }
+
+ private void decodeRowType4() throws IOException {
+ eof: while (true) {
+ // read till next EOL code
+ Node n = eolOnlyTree.root;
+
+ while (true) {
+ n = n.walk(readBit());
+
+ if (n == null) {
+ continue eof;
+ }
+
+ if (n.isLeaf) {
+ break eof;
+ }
+ }
+ }
+
+ if (!optionG32D || readBit()) {
+ decode1D();
}
else {
- // non-literal run
+ decode2D();
+ }
+ }
+
+ private void decodeRowType6() throws IOException {
+ decode2D();
+ }
+
+ private void decodeRow() throws IOException {
+ switch (type) {
+ case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
+ decodeRowType2();
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T4:
+ decodeRowType4();
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T6:
+ decodeRowType6();
+ break;
}
- if (type == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE && index != columns) {
+ int index = 0;
+ boolean white = true;
+
+ lastChangingElement = 0;
+ for (int i = 0; i <= changesCurrentRowCount; i++) {
+ int nextChange = columns;
+
+ if (i != changesCurrentRowCount) {
+ nextChange = changesCurrentRow[i];
+ }
+
+ if (nextChange > columns) {
+ nextChange = columns;
+ }
+
+ int byteIndex = index / 8;
+
+ while (index % 8 != 0 && (nextChange - index) > 0) {
+ decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
+ index++;
+ }
+
+ if (index % 8 == 0) {
+ byteIndex = index / 8;
+ final byte value = (byte) (white ? 0x00 : 0xff);
+
+ while ((nextChange - index) > 7) {
+ decodedRow[byteIndex] = value;
+ index += 8;
+ ++byteIndex;
+ }
+ }
+
+ while ((nextChange - index) > 0) {
+ if (index % 8 == 0) {
+ decodedRow[byteIndex] = 0;
+ }
+
+ decodedRow[byteIndex] |= (white ? 0 : 1 << (7 - ((index) % 8)));
+ index++;
+ }
+
+ white = !white;
+ }
+
+ if (index != columns) {
throw new IOException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
}
decodedLength = (index + 7) / 8;
}
- private int decodeRun(short[][] codes, short[][] runLengths, int minCodeSize) throws IOException {
- // TODO: Optimize...
- // Looping and comparing is the most straight-forward, but probably not the most effective way...
- int code = readBits(minCodeSize);
+ private int decodeRun(final Tree tree) throws IOException {
+ int total = 0;
- for (int bits = 0; bits < codes.length; bits++) {
- short[] bitCodes = codes[bits];
+ Node n = tree.root;
- for (int i = 0; i < bitCodes.length; i++) {
- if (bitCodes[i] == code) {
-// System.err.println("code: " + code);
+ while (true) {
+ boolean bit = readBit();
+ n = n.walk(bit);
- // Code found, return matching run length
- return runLengths[bits][i];
- }
+ if (n == null) {
+ throw new IOException("Unknown code in Huffman RLE stream");
}
- // No code found, read one more bit and try again
- code = fillOrder == 1 ? (code << 1) | readBits(1) : readBits(1) << (bits + minCodeSize) | code;
+ if (n.isLeaf) {
+ total += n.value;
+ if (n.value < 64) {
+ return total;
+ }
+ else {
+ n = tree.root;
+ }
+ }
}
-
- throw new IOException("Unknown code in Huffman RLE stream");
}
- private void resetBuffer() {
+ private void resetBuffer() throws IOException {
for (int i = 0; i < decodedRow.length; i++) {
decodedRow[i] = 0;
}
- bitBuffer = 0;
- bitBufferLength = 0;
+ while (true) {
+ if (bufferPos == -1) {
+ return;
+ }
+
+ readBit();
+ }
}
- private int readBits(int bitCount) throws IOException {
- while (bitBufferLength < bitCount) {
- int read = in.read();
- if (read == -1) {
+ int buffer = -1;
+ int bufferPos = -1;
+
+ private boolean readBit() throws IOException {
+ if (bufferPos < 0 || bufferPos > 7) {
+ buffer = in.read();
+
+ if (buffer == -1) {
throw new EOFException("Unexpected end of Huffman RLE stream");
}
- int bits = read & 0xff;
- bitBuffer = (bitBuffer << 8) | bits;
- bitBufferLength += 8;
+ bufferPos = 0;
}
- // TODO: Take fill order into account
- bitBufferLength -= bitCount;
- int result = bitBuffer >> bitBufferLength;
- bitBuffer &= (1 << bitBufferLength) - 1;
+ boolean isSet;
- return result;
+ if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
+ isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
+ }
+ else {
+ isSet = ((buffer >> (bufferPos)) & 1) == 1;
+ }
+
+ bufferPos++;
+
+ if (bufferPos > 7) {
+ bufferPos = -1;
+ }
+
+ return isSet;
}
@Override
public int read() throws IOException {
if (decodedLength < 0) {
- return -1;
+ return 0x0;
}
if (decodedPos >= decodedLength) {
fetch();
if (decodedLength < 0) {
- return -1;
+ return 0x0;
}
}
@@ -256,14 +431,16 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (decodedLength < 0) {
- return -1;
+ Arrays.fill(b, off, off + len, (byte) 0x0);
+ return len;
}
if (decodedPos >= decodedLength) {
fetch();
if (decodedLength < 0) {
- return -1;
+ Arrays.fill(b, off, off + len, (byte) 0x0);
+ return len;
}
}
@@ -304,150 +481,320 @@ final class CCITTFaxDecoderStream extends FilterInputStream {
throw new IOException("mark/reset not supported");
}
+ private static final class Node {
+ Node left;
+ Node right;
+
+ int value; // > 63 non term.
+
+ boolean canBeFill = false;
+ boolean isLeaf = false;
+
+ void set(final boolean next, final Node node) {
+ if (!next) {
+ left = node;
+ }
+ else {
+ right = node;
+ }
+ }
+
+ Node walk(final boolean next) {
+ return next ? right : left;
+ }
+
+ @Override
+ public String toString() {
+ return "[leaf=" + isLeaf + ", value=" + value + ", canBeFill=" + canBeFill + "]";
+ }
+ }
+
+ private static final class Tree {
+ final Node root = new Node();
+
+ void fill(final int depth, final int path, final int value) throws IOException {
+ Node current = root;
+
+ for (int i = 0; i < depth; i++) {
+ int bitPos = depth - 1 - i;
+ boolean isSet = ((path >> bitPos) & 1) == 1;
+ Node next = current.walk(isSet);
+
+ if (next == null) {
+ next = new Node();
+
+ if (i == depth - 1) {
+ next.value = value;
+ next.isLeaf = true;
+ }
+
+ if (path == 0) {
+ next.canBeFill = true;
+ }
+
+ current.set(isSet, next);
+ }
+ else {
+ if (next.isLeaf) {
+ throw new IOException("node is leaf, no other following");
+ }
+ }
+
+ current = next;
+ }
+ }
+
+ void fill(final int depth, final int path, final Node node) throws IOException {
+ Node current = root;
+
+ for (int i = 0; i < depth; i++) {
+ int bitPos = depth - 1 - i;
+ boolean isSet = ((path >> bitPos) & 1) == 1;
+ Node next = current.walk(isSet);
+
+ if (next == null) {
+ if (i == depth - 1) {
+ next = node;
+ }
+ else {
+ next = new Node();
+ }
+
+ if (path == 0) {
+ next.canBeFill = true;
+ }
+
+ current.set(isSet, next);
+ }
+ else {
+ if (next.isLeaf) {
+ throw new IOException("node is leaf, no other following");
+ }
+ }
+
+ current = next;
+ }
+ }
+ }
+
static final short[][] BLACK_CODES = {
{ // 2 bits
- 0x2, 0x3,
- },
+ 0x2, 0x3,
+ },
{ // 3 bits
- 0x2, 0x3,
- },
+ 0x2, 0x3,
+ },
{ // 4 bits
- 0x2, 0x3,
- },
+ 0x2, 0x3,
+ },
{ // 5 bits
- 0x3,
- },
+ 0x3,
+ },
{ // 6 bits
- 0x4, 0x5,
- },
+ 0x4, 0x5,
+ },
{ // 7 bits
- 0x4, 0x5, 0x7,
- },
+ 0x4, 0x5, 0x7,
+ },
{ // 8 bits
- 0x4, 0x7,
- },
+ 0x4, 0x7,
+ },
{ // 9 bits
- 0x18,
- },
+ 0x18,
+ },
{ // 10 bits
- 0x17, 0x18, 0x37, 0x8, 0xf,
- },
+ 0x17, 0x18, 0x37, 0x8, 0xf,
+ },
{ // 11 bits
- 0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
- },
+ 0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
+ },
{ // 12 bits
- 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
- 0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
- 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
- 0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
- },
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
+ 0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
+ 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
+ 0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
+ },
{ // 13 bits
- 0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
- 0x74, 0x75, 0x76, 0x77,
- }
+ 0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
+ 0x74, 0x75, 0x76, 0x77,
+ }
};
static final short[][] BLACK_RUN_LENGTHS = {
{ // 2 bits
- 3, 2,
- },
+ 3, 2,
+ },
{ // 3 bits
- 1, 4,
- },
+ 1, 4,
+ },
{ // 4 bits
- 6, 5,
- },
+ 6, 5,
+ },
{ // 5 bits
- 7,
- },
+ 7,
+ },
{ // 6 bits
- 9, 8,
- },
+ 9, 8,
+ },
{ // 7 bits
- 10, 11, 12,
- },
+ 10, 11, 12,
+ },
{ // 8 bits
- 13, 14,
- },
+ 13, 14,
+ },
{ // 9 bits
- 15,
- },
+ 15,
+ },
{ // 10 bits
- 16, 17, 0, 18, 64,
- },
+ 16, 17, 0, 18, 64,
+ },
{ // 11 bits
- 24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
- },
+ 24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
+ },
{ // 12 bits
- 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320,
- 384, 448, 53, 54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49,
- 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26, 27, 28, 29, 34, 35,
- 36, 37, 38, 39, 42, 43,
- },
+ 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, 384, 448, 53,
+ 54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26,
+ 27, 28, 29, 34, 35, 36, 37, 38, 39, 42, 43,
+ },
{ // 13 bits
- 640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960,
- 1024, 1088, 1152, 1216,
- }
+ 640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, 1024, 1088,
+ 1152, 1216,
+ }
};
public static final short[][] WHITE_CODES = {
{ // 4 bits
- 0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
- },
+ 0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
+ },
{ // 5 bits
- 0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
- },
+ 0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
+ },
{ // 6 bits
- 0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
- },
+ 0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
+ },
{ // 7 bits
- 0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
- },
+ 0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
+ },
{ // 8 bits
- 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c,
- 0x2d, 0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55,
- 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
- },
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
+ 0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, 0x58, 0x59,
+ 0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
+ },
{ // 9 bits
- 0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
- },
+ 0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
+ },
{ // 10 bits
},
{ // 11 bits
- 0x8, 0xc, 0xd,
- },
+ 0x8, 0xc, 0xd,
+ },
{ // 12 bits
- 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
- }
+ 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
+ }
};
public static final short[][] WHITE_RUN_LENGTHS = {
{ // 4 bits
- 2, 3, 4, 5, 6, 7,
- },
+ 2, 3, 4, 5, 6, 7,
+ },
{ // 5 bits
- 128, 8, 9, 64, 10, 11,
- },
+ 128, 8, 9, 64, 10, 11,
+ },
{ // 6 bits
- 192, 1664, 16, 17, 13, 14, 15, 1, 12,
- },
+ 192, 1664, 16, 17, 13, 14, 15, 1, 12,
+ },
{ // 7 bits
- 26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
- },
+ 26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
+ },
{ // 8 bits
- 33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43,
- 44, 30, 61, 62, 63, 0, 320, 384, 45, 59, 60, 46, 49, 50, 51,
- 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
- },
+ 33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, 44, 30, 61, 62, 63, 0, 320, 384, 45,
+ 59, 60, 46, 49, 50, 51, 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
+ },
{ // 9 bits
- 1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
- },
+ 1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
+ },
{ // 10 bits
},
{ // 11 bits
- 1792, 1856, 1920,
- },
+ 1792, 1856, 1920,
+ },
{ // 12 bits
- 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
- }
+ 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
+ }
};
+
+ final static Node EOL;
+ final static Node FILL;
+ final static Tree blackRunTree;
+ final static Tree whiteRunTree;
+ final static Tree eolOnlyTree;
+ final static Tree codeTree;
+
+ final static int VALUE_EOL = -2000;
+ final static int VALUE_FILL = -1000;
+ final static int VALUE_PASSMODE = -3000;
+ final static int VALUE_HMODE = -4000;
+
+ static {
+ EOL = new Node();
+ EOL.isLeaf = true;
+ EOL.value = VALUE_EOL;
+ FILL = new Node();
+ FILL.value = VALUE_FILL;
+ FILL.left = FILL;
+ FILL.right = EOL;
+
+ eolOnlyTree = new Tree();
+ try {
+ eolOnlyTree.fill(12, 0, FILL);
+ eolOnlyTree.fill(12, 1, EOL);
+ }
+ catch (IOException e) {
+ throw new AssertionError(e);
+ }
+
+ blackRunTree = new Tree();
+ try {
+ for (int i = 0; i < BLACK_CODES.length; i++) {
+ for (int j = 0; j < BLACK_CODES[i].length; j++) {
+ blackRunTree.fill(i + 2, BLACK_CODES[i][j], BLACK_RUN_LENGTHS[i][j]);
+ }
+ }
+ blackRunTree.fill(12, 0, FILL);
+ blackRunTree.fill(12, 1, EOL);
+ }
+ catch (IOException e) {
+ throw new AssertionError(e);
+ }
+
+ whiteRunTree = new Tree();
+ try {
+ for (int i = 0; i < WHITE_CODES.length; i++) {
+ for (int j = 0; j < WHITE_CODES[i].length; j++) {
+ whiteRunTree.fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]);
+ }
+ }
+
+ whiteRunTree.fill(12, 0, FILL);
+ whiteRunTree.fill(12, 1, EOL);
+ }
+ catch (IOException e) {
+ throw new AssertionError(e);
+ }
+
+ codeTree = new Tree();
+ try {
+ codeTree.fill(4, 1, VALUE_PASSMODE); // pass mode
+ codeTree.fill(3, 1, VALUE_HMODE); // H mode
+ codeTree.fill(1, 1, 0); // V(0)
+ codeTree.fill(3, 3, 1); // V_R(1)
+ codeTree.fill(6, 3, 2); // V_R(2)
+ codeTree.fill(7, 3, 3); // V_R(3)
+ codeTree.fill(3, 2, -1); // V_L(1)
+ codeTree.fill(6, 2, -2); // V_L(2)
+ codeTree.fill(7, 2, -3); // V_L(3)
+ }
+ catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStream.java
new file mode 100644
index 00000000..8a250ece
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStream.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright (c) 2013, 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.lang.Validate;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
+ *
+ * @author Oliver Schmidtmer
+ * @author last modified by $Author$
+ * @version $Id$
+ */
+final class CCITTFaxEncoderStream extends OutputStream {
+
+ private int currentBufferLength = 0;
+ private final byte[] inputBuffer;
+ private final int inputBufferLength;
+ private int columns;
+ private int rows;
+
+ private int[] changesCurrentRow;
+ private int[] changesReferenceRow;
+ private int currentRow = 0;
+ private int changesCurrentRowLength = 0;
+ private int changesReferenceRowLength = 0;
+ private byte outputBuffer = 0;
+ private byte outputBufferBitLength = 0;
+ private int type;
+ private int fillOrder;
+ private boolean optionG32D;
+ private boolean optionG3Fill;
+ private boolean optionUncompressed;
+ private OutputStream stream;
+
+ public CCITTFaxEncoderStream(final OutputStream stream, final int columns, final int rows, final int type, final int fillOrder,
+ final long options) {
+
+ this.stream = stream;
+ this.type = type;
+ this.columns = columns;
+ this.rows = rows;
+ this.fillOrder = fillOrder;
+
+ this.changesReferenceRow = new int[columns];
+ this.changesCurrentRow = new int[columns];
+
+ switch (type) {
+ case TIFFExtension.COMPRESSION_CCITT_T4:
+ optionG32D = (options & TIFFExtension.GROUP3OPT_2DENCODING) != 0;
+ optionG3Fill = (options & TIFFExtension.GROUP3OPT_FILLBITS) != 0;
+ optionUncompressed = (options & TIFFExtension.GROUP3OPT_UNCOMPRESSED) != 0;
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T6:
+ optionUncompressed = (options & TIFFExtension.GROUP4OPT_UNCOMPRESSED) != 0;
+ break;
+ }
+
+ inputBufferLength = (columns + 7) / 8;
+ inputBuffer = new byte[inputBufferLength];
+
+ Validate.isTrue(!optionUncompressed, optionUncompressed,
+ "CCITT GROUP 3/4 OPTION UNCOMPRESSED is not supported");
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ inputBuffer[currentBufferLength] = (byte) b;
+ currentBufferLength++;
+
+ if (currentBufferLength == inputBufferLength) {
+ encodeRow();
+ currentBufferLength = 0;
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ stream.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ stream.close();
+ }
+
+ private void encodeRow() throws IOException {
+ currentRow++;
+ int[] tmp = changesReferenceRow;
+ changesReferenceRow = changesCurrentRow;
+ changesCurrentRow = tmp;
+ changesReferenceRowLength = changesCurrentRowLength;
+ changesCurrentRowLength = 0;
+
+ int index = 0;
+ boolean white = true;
+ while (index < columns) {
+ int byteIndex = index / 8;
+ int bit = index % 8;
+ if ((((inputBuffer[byteIndex] >> (7 - bit)) & 1) == 1) == (white)) {
+ changesCurrentRow[changesCurrentRowLength] = index;
+ changesCurrentRowLength++;
+ white = !white;
+ }
+ index++;
+ }
+
+ switch (type) {
+ case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
+ encodeRowType2();
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T4:
+ encodeRowType4();
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T6:
+ encodeRowType6();
+ break;
+ }
+
+ if (currentRow == rows) {
+ if (type == TIFFExtension.COMPRESSION_CCITT_T6) {
+ writeEOL();
+ writeEOL();
+ }
+ fill();
+ }
+ }
+
+ private void encodeRowType2() throws IOException {
+ encode1D();
+ fill();
+ }
+
+ private void encodeRowType4() throws IOException {
+ writeEOL();
+ if (optionG32D) {
+ // do k=1 only on first line. Detect first line by missing reference
+ // line.
+ if (changesReferenceRowLength == 0) {
+ write(1, 1);
+ encode1D();
+ }
+ else {
+ write(0, 1);
+ encode2D();
+ }
+ }
+ else {
+ encode1D();
+ }
+ if (optionG3Fill) {
+ fill();
+ }
+ }
+
+ private void encodeRowType6() throws IOException {
+ encode2D();
+ }
+
+ private void encode1D() throws IOException {
+ int index = 0;
+ boolean white = true;
+ while (index < columns) {
+ int[] nextChanges = getNextChanges(index, white);
+ int runLength = nextChanges[0] - index;
+ writeRun(runLength, white);
+ index += runLength;
+ white = !white;
+ }
+ }
+
+ private int[] getNextChanges(int pos, boolean white) {
+ int[] result = new int[] {columns, columns};
+ for (int i = 0; i < changesCurrentRowLength; i++) {
+ if (pos < changesCurrentRow[i] || (pos == 0 && white)) {
+ result[0] = changesCurrentRow[i];
+ if ((i + 1) < changesCurrentRowLength) {
+ result[1] = changesCurrentRow[i + 1];
+ }
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ private void writeRun(int runLength, boolean white) throws IOException {
+ int nonterm = runLength / 64;
+ Code[] codes = white ? WHITE_NONTERMINATING_CODES : BLACK_NONTERMINATING_CODES;
+ while (nonterm > 0) {
+ if (nonterm >= codes.length) {
+ write(codes[codes.length - 1].code, codes[codes.length - 1].length);
+ nonterm -= codes.length;
+ }
+ else {
+ write(codes[nonterm - 1].code, codes[nonterm - 1].length);
+ nonterm = 0;
+ }
+ }
+
+ Code c = white ? WHITE_TERMINATING_CODES[runLength % 64] : BLACK_TERMINATING_CODES[runLength % 64];
+ write(c.code, c.length);
+ }
+
+ private void encode2D() throws IOException {
+ boolean white = true;
+ int index = 0; // a0
+ while (index < columns) {
+ int[] nextChanges = getNextChanges(index, white); // a1, a2
+
+ int[] nextRefs = getNextRefChanges(index, white); // b1, b2
+
+ int difference = nextChanges[0] - nextRefs[0];
+ if (nextChanges[0] > nextRefs[1]) {
+ // PMODE
+ write(1, 4);
+ index = nextRefs[1];
+ }
+ else if (difference > 3 || difference < -3) {
+ // HMODE
+ write(1, 3);
+ writeRun(nextChanges[0] - index, white);
+ writeRun(nextChanges[1] - nextChanges[0], !white);
+ index = nextChanges[1];
+
+ }
+ else {
+ // VMODE
+ switch (difference) {
+ case 0:
+ write(1, 1);
+ break;
+ case 1:
+ write(3, 3);
+ break;
+ case 2:
+ write(3, 6);
+ break;
+ case 3:
+ write(3, 7);
+ break;
+ case -1:
+ write(2, 3);
+ break;
+ case -2:
+ write(2, 6);
+ break;
+ case -3:
+ write(2, 7);
+ break;
+ }
+ white = !white;
+ index = nextRefs[0] + difference;
+ }
+ }
+ }
+
+ private int[] getNextRefChanges(int a0, boolean white) {
+ int[] result = new int[] {columns, columns};
+ for (int i = (white ? 0 : 1); i < changesReferenceRowLength; i += 2) {
+ if (changesReferenceRow[i] > a0 || (a0 == 0 && i == 0)) {
+ result[0] = changesReferenceRow[i];
+ if ((i + 1) < changesReferenceRowLength) {
+ result[1] = changesReferenceRow[i + 1];
+ }
+ break;
+ }
+ }
+ return result;
+ }
+
+ private void write(int code, int codeLength) throws IOException {
+
+ for (int i = 0; i < codeLength; i++) {
+ boolean codeBit = ((code >> (codeLength - i - 1)) & 1) == 1;
+ if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
+ outputBuffer |= (codeBit ? 1 << (7 - ((outputBufferBitLength) % 8)) : 0);
+ }
+ else {
+ outputBuffer |= (codeBit ? 1 << (((outputBufferBitLength) % 8)) : 0);
+ }
+ outputBufferBitLength++;
+
+ if (outputBufferBitLength == 8) {
+ stream.write(outputBuffer);
+ clearOutputBuffer();
+ }
+ }
+ }
+
+ private void writeEOL() throws IOException {
+ if (optionG3Fill) {
+ // Fill up so EOL ends on a byte-boundary
+ while (outputBufferBitLength != 4) {
+ write(0, 1);
+ }
+ }
+ write(1, 12);
+ }
+
+ private void fill() throws IOException {
+ if (outputBufferBitLength != 0) {
+ stream.write(outputBuffer);
+ }
+ clearOutputBuffer();
+ }
+
+ private void clearOutputBuffer() {
+ outputBuffer = 0;
+ outputBufferBitLength = 0;
+ }
+
+ public static class Code {
+ private Code(int code, int length) {
+ this.code = code;
+ this.length = length;
+ }
+
+ final int code;
+ final int length;
+ }
+
+ public static final Code[] WHITE_TERMINATING_CODES;
+
+ public static final Code[] WHITE_NONTERMINATING_CODES;
+
+ public static final Code[] BLACK_TERMINATING_CODES;
+
+ public static final Code[] BLACK_NONTERMINATING_CODES;
+
+ static {
+ // Setup HUFFMAN Codes
+ WHITE_TERMINATING_CODES = new Code[64];
+ WHITE_NONTERMINATING_CODES = new Code[40];
+ for (int i = 0; i < CCITTFaxDecoderStream.WHITE_CODES.length; i++) {
+ int bitLength = i + 4;
+ for (int j = 0; j < CCITTFaxDecoderStream.WHITE_CODES[i].length; j++) {
+ int value = CCITTFaxDecoderStream.WHITE_RUN_LENGTHS[i][j];
+ int code = CCITTFaxDecoderStream.WHITE_CODES[i][j];
+
+ if (value < 64) {
+ WHITE_TERMINATING_CODES[value] = new Code(code, bitLength);
+ }
+ else {
+ WHITE_NONTERMINATING_CODES[(value / 64) - 1] = new Code(code, bitLength);
+ }
+ }
+ }
+
+ BLACK_TERMINATING_CODES = new Code[64];
+ BLACK_NONTERMINATING_CODES = new Code[40];
+ for (int i = 0; i < CCITTFaxDecoderStream.BLACK_CODES.length; i++) {
+ int bitLength = i + 2;
+ for (int j = 0; j < CCITTFaxDecoderStream.BLACK_CODES[i].length; j++) {
+ int value = CCITTFaxDecoderStream.BLACK_RUN_LENGTHS[i][j];
+ int code = CCITTFaxDecoderStream.BLACK_CODES[i][j];
+
+ if (value < 64) {
+ BLACK_TERMINATING_CODES[value] = new Code(code, bitLength);
+ }
+ else {
+ BLACK_NONTERMINATING_CODES[(value / 64) - 1] = new Code(code, bitLength);
+ }
+ }
+ }
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
index 87c328c3..251b3812 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWDecoder.java
@@ -190,7 +190,6 @@ abstract class LZWDecoder implements Decoder {
public static Decoder create(boolean oldBitReversedStream) {
return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWSpecDecoder();
-// return oldBitReversedStream ? new LZWCompatibilityDecoder() : new LZWTreeDecoder();
}
static final class LZWSpecDecoder extends LZWDecoder {
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java
index ee3cdf17..47afa666 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/LZWEncoder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, Harald Kuhr
+ * Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -33,21 +33,22 @@ import com.twelvemonkeys.io.enc.Encoder;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
-import java.util.Map;
-import java.util.TreeMap;
-
-import static com.twelvemonkeys.imageio.plugins.tiff.LZWDecoder.LZWString;
+import java.util.Arrays;
/**
* LZWEncoder
+ *
+ * Inspired by LZWTreeEncoder by Wen Yu and the
+ * algorithm described by Bob Montgomery
+ * which
+ * "[...] uses a tree method to search if a new string is already in the table,
+ * which is much simpler, faster, and easier to understand than hashing."
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
* @version $Id: LZWEncoder.java,v 1.0 02.12.13 14:13 haraldk Exp$
*/
final class LZWEncoder implements Encoder {
- // TODO: Consider extracting LZWStringTable from LZWDecoder
-
/** Clear: Re-initialize tables. */
static final int CLEAR_CODE = 256;
/** End of Information. */
@@ -58,159 +59,136 @@ final class LZWEncoder implements Encoder {
private static final int TABLE_SIZE = 1 << MAX_BITS;
- private int remaining;
+ // A child is made up of a parent (or prefix) code plus a suffix byte
+ // and siblings are strings with a common parent(or prefix) and different
+ // suffix bytes
+ private final short[] CHILDREN = new short[TABLE_SIZE];
+ private final short[] SIBLINGS = new short[TABLE_SIZE];
+ private final short[] SUFFIXES = new short[TABLE_SIZE];
- private final LZWString[] table = new LZWString[TABLE_SIZE];
-// private final Map reverseTable = new HashMap<>(TABLE_SIZE - 256); // This is foobar
- private final Map reverseTable = new TreeMap<>(); // This is foobar
- private int tableLength;
- LZWString omega = LZWString.EMPTY;
+ // Initial setup
+ private int parent = -1;
+ private int bitsPerCode = MIN_BITS;
+ private int nextValidCode = EOI_CODE + 1;
+ private int maxCode = maxValue(bitsPerCode);
- int bitsPerCode;
- private int oldCode = CLEAR_CODE;
- private int maxCode;
- int bitMask;
+ // Buffer for partial codes
+ private int bits = 0;
+ private int bitPos = 0;
- int bits;
- int bitPos;
+ // Keep track of how many bytes we will write, to make sure we write EOI at correct position
+ private long remaining;
- protected LZWEncoder(final int length) {
- this.remaining = length;
-
- // First 258 entries of table is always fixed
- for (int i = 0; i < 256; i++) {
- table[i] = new LZWString((byte) i);
- }
-
- init();
- }
-
- private static int bitmaskFor(final int bits) {
- return (1 << bits) - 1;
- }
-
- private void init() {
- tableLength = 258;
- bitsPerCode = MIN_BITS;
- bitMask = bitmaskFor(bitsPerCode);
- maxCode = maxCode();
-// omega = LZWString.EMPTY;
- reverseTable.clear();
- }
-
- protected int maxCode() {
- return bitMask;
+ LZWEncoder(final long length) {
+ remaining = length;
}
+ @Override
public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException {
-// InitializeStringTable();
-// WriteCode(ClearCode);
-// Ω = the empty string;
-// for each character in the strip {
-// K = GetNextCharacter();
-// if Ω+K is in the string table {
-// Ω = Ω+K;/* string concatenation */
-// }
-// else{
-// WriteCode (CodeFromString( Ω));
-// AddTableEntry(Ω+K);
-// Ω=K;
-// } }/*end of for loop*/
-// WriteCode (CodeFromString(Ω));
-// WriteCode (EndOfInformation);
+ encodeBytes(stream, buffer);
- if (remaining < 0) {
- throw new IOException("Write past end of stream");
- }
-
- // TODO: Write 9 bit clear code ONLY first time!
- if (oldCode == CLEAR_CODE) {
- writeCode(stream, CLEAR_CODE);
- }
-
- int len = buffer.remaining();
-
- while (buffer.hasRemaining()) {
- byte k = buffer.get();
-
- LZWString string = omega.concatenate(k);
-
- int tableIndex = isInTable(string);
- if (tableIndex >= 0) {
- omega = string;
- oldCode = tableIndex;
- }
- else {
- writeCode(stream, oldCode);
- addStringToTable(string);
- oldCode = k & 0xff;
- omega = table[k & 0xff];
-
- // Handle table (almost) full
- if (tableLength >= TABLE_SIZE - 2) {
- writeCode(stream, CLEAR_CODE);
- init();
- }
- }
- }
-
- remaining -= len;
-
- // Write EOI when er are done (the API isn't very supportive of this)
if (remaining <= 0) {
- writeCode(stream, oldCode);
+ // Write EOI when er are done (the API isn't very supportive of this at the moment)
+ writeCode(stream, parent);
writeCode(stream, EOI_CODE);
+
+ // Flush partial codes by writing 0 pad
if (bitPos > 0) {
writeCode(stream, 0);
}
}
}
- private int isInTable(final LZWString string) {
- if (string.length == 1) {
- return string.value & 0xff;
+ void encodeBytes(final OutputStream stream, final ByteBuffer buffer) throws IOException {
+ int length = buffer.remaining();
+
+ if (length == 0) {
+ return;
}
- Integer index = reverseTable.get(string);
- return index != null ? index : -1;
+ if (parent == -1) {
+ // Init stream
+ writeCode(stream, CLEAR_CODE);
+ parent = buffer.get() & 0xff;
+ }
- // TODO: Needs optimization :-)
-// for (int i = 258; i < tableLength; i++) {
-// if (table[i].equals(string)) {
-// return i;
-// }
-// }
+ while (buffer.hasRemaining()) {
+ int value = buffer.get() & 0xff;
+ int child = CHILDREN[parent];
-// return -1;
+ if (child > 0) {
+ if (SUFFIXES[child] == value) {
+ parent = child;
+ }
+ else {
+ int sibling = child;
+
+ while (true) {
+ if (SIBLINGS[sibling] > 0) {
+ sibling = SIBLINGS[sibling];
+
+ if (SUFFIXES[sibling] == value) {
+ parent = sibling;
+ break;
+ }
+ }
+ else {
+ SIBLINGS[sibling] = (short) nextValidCode;
+ SUFFIXES[nextValidCode] = (short) value;
+ writeCode(stream, parent);
+ parent = value;
+ nextValidCode++;
+
+ increaseCodeSizeOrResetIfNeeded(stream);
+
+ break;
+ }
+ }
+ }
+ }
+ else {
+ CHILDREN[parent] = (short) nextValidCode;
+ SUFFIXES[nextValidCode] = (short) value;
+ writeCode(stream, parent);
+ parent = value;
+ nextValidCode++;
+
+ increaseCodeSizeOrResetIfNeeded(stream);
+ }
+ }
+
+ remaining -= length;
}
- private int addStringToTable(final LZWString string) {
-// System.err.println("LZWEncoder.addStringToTable: " + string);
- final int index = tableLength++;
- table[index] = string;
- reverseTable.put(string, index);
+ private void increaseCodeSizeOrResetIfNeeded(final OutputStream stream) throws IOException {
+ if (nextValidCode > maxCode) {
+ if (bitsPerCode == MAX_BITS) {
+ // Reset stream by writing Clear code
+ writeCode(stream, CLEAR_CODE);
- if (tableLength > maxCode) {
- bitsPerCode++;
-
- if (bitsPerCode > MAX_BITS) {
- throw new IllegalStateException(String.format("TIFF LZW with more than %d bits per code encountered (table overflow)", MAX_BITS));
+ // Reset tables
+ resetTables();
+ }
+ else {
+ // Increase code size
+ bitsPerCode++;
+ maxCode = maxValue(bitsPerCode);
}
-
- bitMask = bitmaskFor(bitsPerCode);
- maxCode = maxCode();
}
+ }
-// if (string.length > maxString) {
-// maxString = string.length;
-// }
+ private void resetTables() {
+ Arrays.fill(CHILDREN, (short) 0);
+ Arrays.fill(SIBLINGS, (short) 0);
- return index;
+ bitsPerCode = MIN_BITS;
+ maxCode = maxValue(bitsPerCode);
+ nextValidCode = EOI_CODE + 1;
}
private void writeCode(final OutputStream stream, final int code) throws IOException {
// System.err.printf("LZWEncoder.writeCode: 0x%04x\n", code);
- bits = (bits << bitsPerCode) | (code & bitMask);
+ bits = (bits << bitsPerCode) | (code & maxCode);
bitPos += bitsPerCode;
while (bitPos >= 8) {
@@ -222,4 +200,12 @@ final class LZWEncoder implements Encoder {
bits &= bitmaskFor(bitPos);
}
-}
+
+ private static int maxValue(final int codeLen) {
+ return (1 << codeLen) - 1;
+ }
+
+ private static int bitmaskFor(final int bits) {
+ return maxValue(bits);
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java
index 2fbd54e5..6d9a8cee 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFBaseline.java
@@ -59,4 +59,13 @@ interface TIFFBaseline {
int RESOLUTION_UNIT_NONE = 1;
int RESOLUTION_UNIT_DPI = 2; // Default
int RESOLUTION_UNIT_CENTIMETER = 3;
+
+ int FILL_LEFT_TO_RIGHT = 1; // Default
+
+ // NOTE: These are bit flags that can be ORed together!
+ int FILETYPE_REDUCEDIMAGE = 1;
+ int FILETYPE_PAGE = 2;
+ int FILETYPE_MASK = 4;
+
+ int ORIENTATION_TOPLEFT = 1;
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java
index 3a03ea31..d62652a2 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFCustom.java
@@ -50,6 +50,7 @@ interface TIFFCustom {
int COMPRESSION_SGILOG = 34676;
int COMPRESSION_SGILOG24 = 34677;
int COMPRESSION_JPEG2000 = 34712;
+ // TODO: Aperio SVS JPEG2000: 33003 (YCbCr) and 33005 (RGB), see http://openslide.org/formats/aperio/
int PHOTOMETRIC_LOGL = 32844;
int PHOTOMETRIC_LOGLUV = 32845;
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java
index de714606..f6444464 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFExtension.java
@@ -62,6 +62,8 @@ interface TIFFExtension {
int PREDICTOR_HORIZONTAL_DIFFERENCING = 2;
int PREDICTOR_HORIZONTAL_FLOATINGPOINT = 3;
+ int FILL_RIGHT_TO_LEFT = 2;
+
int SAMPLEFORMAT_INT = 2;
int SAMPLEFORMAT_FP = 3;
int SAMPLEFORMAT_UNDEFINED = 4;
@@ -83,4 +85,17 @@ interface TIFFExtension {
* description of the inks to be used.
*/
int INKSET_NOT_CMYK = 2;
+
+ int ORIENTATION_TOPRIGHT = 2;
+ int ORIENTATION_BOTRIGHT = 3;
+ int ORIENTATION_BOTLEFT = 4;
+ int ORIENTATION_LEFTTOP = 5;
+ int ORIENTATION_RIGHTTOP = 6;
+ int ORIENTATION_RIGHTBOT = 7;
+ int ORIENTATION_LEFTBOT = 8;
+
+ int GROUP3OPT_2DENCODING = 1;
+ int GROUP3OPT_UNCOMPRESSED = 2;
+ int GROUP3OPT_FILLBITS = 4;
+ int GROUP4OPT_UNCOMPRESSED = 2;
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java
new file mode 100644
index 00000000..d652d81e
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadata.java
@@ -0,0 +1,1347 @@
+/*
+ * Copyright (c) 2015, 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.AbstractMetadata;
+import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
+import com.twelvemonkeys.imageio.metadata.Directory;
+import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.metadata.exif.Rational;
+import com.twelvemonkeys.imageio.metadata.exif.TIFF;
+import com.twelvemonkeys.lang.Validate;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
+import java.lang.reflect.Array;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * TIFFImageMetadata.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TIFFImageMetadata.java,v 1.0 17/04/15 harald.kuhr Exp$
+ */
+public final class TIFFImageMetadata extends AbstractMetadata {
+
+ static final int RATIONAL_SCALE_FACTOR = 100000;
+
+ private final Directory original;
+ private Directory ifd;
+
+ /**
+ * Creates an empty TIFF metadata object.
+ *
+ * Client code can update or change the metadata using the
+ * {@link #setFromTree(String, Node)}
+ * or {@link #mergeTree(String, Node)} methods.
+ */
+ public TIFFImageMetadata() {
+ this(new TIFFIFD(Collections.emptyList()));
+ }
+
+ /**
+ * Creates a TIFF metadata object, using the values from the given IFD.
+ *
+ * Client code can update or change the metadata using the
+ * {@link #setFromTree(String, Node)}
+ * or {@link #mergeTree(String, Node)} methods.
+ */
+ public TIFFImageMetadata(final Directory ifd) {
+ super(true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, TIFFMedataFormat.class.getName(), null, null);
+ this.ifd = Validate.notNull(ifd, "IFD");
+ this.original = ifd;
+ }
+
+ /**
+ * Creates a TIFF metadata object, using the values from the given entries.
+ *
+ * Client code can update or change the metadata using the
+ * {@link #setFromTree(String, Node)}
+ * or {@link #mergeTree(String, Node)} methods.
+ */
+ public TIFFImageMetadata(final Collection entries) {
+ this(new TIFFIFD(entries));
+ }
+
+ protected IIOMetadataNode getNativeTree() {
+ IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
+ root.appendChild(asTree(ifd));
+
+ return root;
+ }
+
+ private IIOMetadataNode asTree(final Directory ifd) {
+ IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
+
+ for (Entry tag : ifd) {
+ IIOMetadataNode tagNode;
+ Object value = tag.getValue();
+
+ if (value instanceof Directory) {
+ // TODO: Don't expand non-TIFF IFDs...
+ tagNode = asTree((Directory) value);
+ tagNode.setAttribute("parentTagNumber", String.valueOf(tag.getIdentifier()));
+ String fieldName = tag.getFieldName();
+ if (fieldName != null) {
+ tagNode.setAttribute("parentTagName", fieldName);
+ }
+
+ // TODO: tagSets is REQUIRED!
+ }
+ else {
+ tagNode = new IIOMetadataNode("TIFFField");
+ tagNode.setAttribute("number", String.valueOf(tag.getIdentifier()));
+
+ String fieldName = tag.getFieldName();
+ if (fieldName != null) {
+ tagNode.setAttribute("name", fieldName);
+ }
+
+ int count = tag.valueCount();
+
+ if (TIFF.TYPE_NAMES[TIFF.TYPE_UNDEFINED].equals(tag.getTypeName())) {
+ // Why does "undefined" need special handling?! It's just a byte array.. :-P
+ // Or maybe rather, why isn't all types implemented like this..?
+ // TODO: Consider handling IPTC, Photoshop/Adobe, XMP and ICC Profile as Undefined always
+ // (even if older software wrote as Byte), as it's more compact?
+ IIOMetadataNode valueNode = new IIOMetadataNode("TIFFUndefined");
+ tagNode.appendChild(valueNode);
+
+ if (count == 1) {
+ valueNode.setAttribute("value", String.valueOf(value));
+ }
+ else {
+ valueNode.setAttribute("value", Arrays.toString((byte[]) value).replaceAll("\\[?\\]?", ""));
+ }
+ }
+ else {
+ String arrayTypeName = getMetadataArrayType(tag);
+ IIOMetadataNode valueNode = new IIOMetadataNode(arrayTypeName);
+ tagNode.appendChild(valueNode);
+
+ boolean unsigned = !isSignedType(tag);
+ String typeName = getMetadataType(tag);
+
+ // NOTE: ASCII/Strings have count 1, always. This seems consistent with the JAI ImageIO version.
+ if (count == 1) {
+ IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
+ valueNode.appendChild(elementNode);
+
+ setTIFFNativeValue(value, unsigned, elementNode);
+ }
+ else {
+ for (int i = 0; i < count; i++) {
+ Object val = Array.get(value, i);
+ IIOMetadataNode elementNode = new IIOMetadataNode(typeName);
+ valueNode.appendChild(elementNode);
+
+ setTIFFNativeValue(val, unsigned, elementNode);
+ }
+ }
+ }
+ }
+
+ ifdNode.appendChild(tagNode);
+ }
+
+ return ifdNode;
+ }
+
+ private void setTIFFNativeValue(final Object value, final boolean unsigned, final IIOMetadataNode elementNode) {
+ if (unsigned && value instanceof Byte) {
+ elementNode.setAttribute("value", String.valueOf((Byte) value & 0xFF));
+ }
+ else if (unsigned && value instanceof Short) {
+ elementNode.setAttribute("value", String.valueOf((Short) value & 0xFFFF));
+ }
+ else if (unsigned && value instanceof Integer) {
+ elementNode.setAttribute("value", String.valueOf((Integer) value & 0xFFFFFFFFl));
+ }
+ else {
+ elementNode.setAttribute("value", String.valueOf(value));
+ }
+ }
+
+ private boolean isSignedType(final Entry tag) {
+ String typeName = tag.getTypeName();
+
+ // Stupid special cases implementation, until we can access the type id...
+ if ("SBYTE".equals(typeName)) {
+ return true;
+ }
+ if ("SSHORT".equals(typeName)) {
+ return true;
+ }
+ if ("SLONG".equals(typeName)) {
+ return true;
+ }
+ if ("SRATIONAL".equals(typeName)) {
+ return true;
+ }
+ if ("FLOAT".equals(typeName)) {
+ return true;
+ }
+ if ("DOUBLE".equals(typeName)) {
+ return true;
+ }
+ if ("SLONG8".equals(typeName)) {
+ return true;
+ }
+ // IFD8 not used
+
+ return false;
+ }
+
+ private String getMetadataArrayType(final Entry tag) {
+ String typeName = tag.getTypeName();
+
+ // Stupid special cases implementation, until we can access the type id...
+ if ("BYTE".equals(typeName)) {
+ return "TIFFBytes";
+ }
+ if ("ASCII".equals(typeName)) {
+ return "TIFFAsciis";
+ }
+ if ("SHORT".equals(typeName)) {
+ return "TIFFShorts";
+ }
+ if ("LONG".equals(typeName)) {
+ return "TIFFLongs";
+ }
+ if ("RATIONAL".equals(typeName)) {
+ return "TIFFRationals";
+ }
+ // UNDEFINED not used...
+ if ("SBYTE".equals(typeName)) {
+ return "TIFFSBytes";
+ }
+ if ("SSHORT".equals(typeName)) {
+ return "TIFFSShorts";
+ }
+ if ("SLONG".equals(typeName)) {
+ return "TIFFSLongs";
+ }
+ if ("SRATIONAL".equals(typeName)) {
+ return "TIFFSRationals";
+ }
+ if ("FLOAT".equals(typeName)) {
+ return "TIFFFloats";
+ }
+ if ("DOUBLE".equals(typeName)) {
+ return "TIFFDoubles";
+ }
+ // IFD not used
+ if ("LONG8".equals(typeName)) {
+ return "TIFFLong8s";
+ }
+ if ("SLONG8".equals(typeName)) {
+ return "TIFFSLong8s";
+ }
+ // IFD8 not used
+
+ throw new IllegalArgumentException(typeName);
+ }
+
+ private String getMetadataType(final Entry tag) {
+ String typeName = tag.getTypeName();
+
+ // Stupid special cases implementation, until we can access the type id...
+ if ("BYTE".equals(typeName)) {
+ return "TIFFByte";
+ }
+ if ("ASCII".equals(typeName)) {
+ return "TIFFAscii";
+ }
+ if ("SHORT".equals(typeName)) {
+ return "TIFFShort";
+ }
+ if ("LONG".equals(typeName)) {
+ return "TIFFLong";
+ }
+ if ("RATIONAL".equals(typeName)) {
+ return "TIFFRational";
+ }
+ // UNDEFINED not used...
+ if ("SBYTE".equals(typeName)) {
+ return "TIFFSByte";
+ }
+ if ("SSHORT".equals(typeName)) {
+ return "TIFFSShort";
+ }
+ if ("SLONG".equals(typeName)) {
+ return "TIFFSLong";
+ }
+ if ("SRATIONAL".equals(typeName)) {
+ return "TIFFSRational";
+ }
+ if ("FLOAT".equals(typeName)) {
+ return "TIFFFloat";
+ }
+ if ("DOUBLE".equals(typeName)) {
+ return "TIFFDouble";
+ }
+ // IFD not used
+ if ("LONG8".equals(typeName)) {
+ return "TIFFLong8";
+ }
+ if ("SLONG8".equals(typeName)) {
+ return "TIFFSLong8";
+ }
+ // IFD8 not used
+
+ throw new IllegalArgumentException(typeName);
+ }
+
+ // TODO: Candidate superclass method!
+ private IIOMetadataNode addChildNode(final IIOMetadataNode parent,
+ final String name,
+ final Object object) {
+ IIOMetadataNode child = new IIOMetadataNode(name);
+
+ if (object != null) {
+ child.setUserObject(object); // TODO: Should we always store user object?!?!
+ child.setNodeValue(object.toString()); // TODO: Fix this line
+ }
+
+ parent.appendChild(child);
+
+ return child;
+ }
+
+ /// Standard metadata
+ // See: http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html
+
+ @Override
+ protected IIOMetadataNode getStandardChromaNode() {
+ IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
+
+ // Handle ColorSpaceType (RGB/CMYK/YCbCr etc)...
+ Entry photometricTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
+ int photometricValue = getValueAsInt(photometricTag); // No default for this tag!
+ int numChannelsValue = getSamplesPerPixelWithFallback();
+
+ IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType");
+ chroma.appendChild(colorSpaceType);
+ switch (photometricValue) {
+ case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
+ case TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO:
+ case TIFFBaseline.PHOTOMETRIC_MASK: // It's really a transparency mask/alpha channel, but...
+ colorSpaceType.setAttribute("value", "GRAY");
+ break;
+ case TIFFBaseline.PHOTOMETRIC_RGB:
+ case TIFFBaseline.PHOTOMETRIC_PALETTE:
+ colorSpaceType.setAttribute("value", "RGB");
+ break;
+ case TIFFExtension.PHOTOMETRIC_YCBCR:
+ colorSpaceType.setAttribute("value", "YCbCr");
+ break;
+ case TIFFExtension.PHOTOMETRIC_CIELAB:
+ case TIFFExtension.PHOTOMETRIC_ICCLAB:
+ case TIFFExtension.PHOTOMETRIC_ITULAB:
+ colorSpaceType.setAttribute("value", "Lab");
+ break;
+ case TIFFExtension.PHOTOMETRIC_SEPARATED:
+ // TODO: May be CMYK, or something else... Consult InkSet and NumberOfInks!
+ if (numChannelsValue == 3) {
+ colorSpaceType.setAttribute("value", "CMY");
+ }
+ else {
+ colorSpaceType.setAttribute("value", "CMYK");
+ }
+ break;
+ case TIFFCustom.PHOTOMETRIC_LOGL: // ..?
+ case TIFFCustom.PHOTOMETRIC_LOGLUV:
+ colorSpaceType.setAttribute("value", "Luv");
+ break;
+ case TIFFCustom.PHOTOMETRIC_CFA:
+ case TIFFCustom.PHOTOMETRIC_LINEAR_RAW: // ...or is this RGB?
+ colorSpaceType.setAttribute("value", "3CLR");
+ break;
+ default:
+ colorSpaceType.setAttribute("value", Integer.toHexString(numChannelsValue) + "CLR");
+ break;
+ }
+
+ // NumChannels
+ IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
+ chroma.appendChild(numChannels);
+ if (photometricValue == TIFFBaseline.PHOTOMETRIC_PALETTE) {
+ numChannels.setAttribute("value", "3");
+ }
+ else {
+ numChannels.setAttribute("value", Integer.toString(numChannelsValue));
+ }
+
+ // BlackIsZero (defaults to TRUE)
+ IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
+ chroma.appendChild(blackIsZero);
+ switch (photometricValue) {
+ case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
+ blackIsZero.setAttribute("value", "FALSE");
+ break;
+ default:
+ break;
+ }
+
+ Entry colorMapTag = ifd.getEntryById(TIFF.TAG_COLOR_MAP);
+
+ if (colorMapTag != null) {
+ int[] colorMapValues = (int[]) colorMapTag.getValue();
+
+ IIOMetadataNode palette = new IIOMetadataNode("Palette");
+ chroma.appendChild(palette);
+
+ int count = colorMapValues.length / 3;
+ for (int i = 0; i < count; i++) {
+ IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
+ paletteEntry.setAttribute("index", Integer.toString(i));
+
+ // TODO: See TIFFImageReader createIndexColorModel, to detect 8 bit colorMap
+ paletteEntry.setAttribute("red", Integer.toString((colorMapValues[i] >> 8) & 0xff));
+ paletteEntry.setAttribute("green", Integer.toString((colorMapValues[i + count] >> 8) & 0xff));
+ paletteEntry.setAttribute("blue", Integer.toString((colorMapValues[i + count * 2] >> 8) & 0xff));
+
+ palette.appendChild(paletteEntry);
+ }
+ }
+
+ return chroma;
+ }
+
+ private int getSamplesPerPixelWithFallback() {
+ // SamplePerPixel defaults to 1, but we'll check BitsPerSample to be sure
+ Entry samplesPerPixelTag = ifd.getEntryById(TIFF.TAG_SAMPLES_PER_PIXEL);
+ Entry bitsPerSampleTag = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
+
+ return samplesPerPixelTag != null
+ ? getValueAsInt(samplesPerPixelTag)
+ : bitsPerSampleTag != null ? bitsPerSampleTag.valueCount() : 1;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardCompressionNode() {
+ IIOMetadataNode compression = new IIOMetadataNode("Compression");
+ IIOMetadataNode compressionTypeName = addChildNode(compression, "CompressionTypeName", null);
+
+ Entry compressionTag = ifd.getEntryById(TIFF.TAG_COMPRESSION);
+ int compressionValue = compressionTag == null
+ ? TIFFBaseline.COMPRESSION_NONE
+ : getValueAsInt(compressionTag);
+
+ // Naming is identical to JAI ImageIO metadata as far as possible
+ switch (compressionValue) {
+ case TIFFBaseline.COMPRESSION_NONE:
+ compressionTypeName.setAttribute("value", "None");
+ break;
+ case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
+ compressionTypeName.setAttribute("value", "CCITT RLE");
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T4:
+ compressionTypeName.setAttribute("value", "CCITT T4");
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T6:
+ compressionTypeName.setAttribute("value", "CCITT T6");
+ break;
+ case TIFFExtension.COMPRESSION_LZW:
+ compressionTypeName.setAttribute("value", "LZW");
+ break;
+ case TIFFExtension.COMPRESSION_OLD_JPEG:
+ compressionTypeName.setAttribute("value", "Old JPEG");
+ break;
+ case TIFFExtension.COMPRESSION_JPEG:
+ compressionTypeName.setAttribute("value", "JPEG");
+ break;
+ case TIFFExtension.COMPRESSION_ZLIB:
+ compressionTypeName.setAttribute("value", "ZLib");
+ break;
+ case TIFFExtension.COMPRESSION_DEFLATE:
+ compressionTypeName.setAttribute("value", "Deflate");
+ break;
+ case TIFFBaseline.COMPRESSION_PACKBITS:
+ compressionTypeName.setAttribute("value", "PackBits");
+ break;
+ case TIFFCustom.COMPRESSION_CCITTRLEW:
+ compressionTypeName.setAttribute("value", "CCITT RLEW");
+ break;
+ case TIFFCustom.COMPRESSION_DCS:
+ compressionTypeName.setAttribute("value", "DCS");
+ break;
+ case TIFFCustom.COMPRESSION_IT8BL:
+ compressionTypeName.setAttribute("value", "IT8BL");
+ break;
+ case TIFFCustom.COMPRESSION_IT8CTPAD:
+ compressionTypeName.setAttribute("value", "IT8CTPAD");
+ break;
+ case TIFFCustom.COMPRESSION_IT8LW:
+ compressionTypeName.setAttribute("value", "IT8LW");
+ break;
+ case TIFFCustom.COMPRESSION_IT8MP:
+ compressionTypeName.setAttribute("value", "IT8MP");
+ break;
+ case TIFFCustom.COMPRESSION_JBIG:
+ compressionTypeName.setAttribute("value", "JBIG");
+ break;
+ case TIFFCustom.COMPRESSION_JPEG2000:
+ compressionTypeName.setAttribute("value", "JPEG 2000");
+ break;
+ case TIFFCustom.COMPRESSION_NEXT:
+ compressionTypeName.setAttribute("value", "NEXT");
+ break;
+ case TIFFCustom.COMPRESSION_PIXARFILM:
+ compressionTypeName.setAttribute("value", "Pixar Film");
+ break;
+ case TIFFCustom.COMPRESSION_PIXARLOG:
+ compressionTypeName.setAttribute("value", "Pixar Log");
+ break;
+ case TIFFCustom.COMPRESSION_SGILOG:
+ compressionTypeName.setAttribute("value", "SGI Log");
+ break;
+ case TIFFCustom.COMPRESSION_SGILOG24:
+ compressionTypeName.setAttribute("value", "SGI Log24");
+ break;
+ case TIFFCustom.COMPRESSION_THUNDERSCAN:
+ compressionTypeName.setAttribute("value", "ThunderScan");
+ break;
+ default:
+ compressionTypeName.setAttribute("value", "Unknown " + compressionValue);
+ break;
+ }
+
+ if (compressionValue != TIFFBaseline.COMPRESSION_NONE) {
+ // Lossless (defaults to TRUE)
+ IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
+ compression.appendChild(lossless);
+
+ switch (compressionValue) {
+ case TIFFExtension.COMPRESSION_OLD_JPEG:
+ case TIFFExtension.COMPRESSION_JPEG:
+ case TIFFCustom.COMPRESSION_JBIG:
+ case TIFFCustom.COMPRESSION_JPEG2000:
+ lossless.setAttribute("value", "FALSE");
+ break;
+ default:
+ break;
+ }
+ }
+
+ return compression;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardDataNode() {
+ IIOMetadataNode node = new IIOMetadataNode("Data");
+
+ IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
+ Entry planarConfigurationTag = ifd.getEntryById(TIFF.TAG_PLANAR_CONFIGURATION);
+ int planarConfigurationValue = planarConfigurationTag == null
+ ? TIFFBaseline.PLANARCONFIG_CHUNKY
+ : getValueAsInt(planarConfigurationTag);
+
+ switch (planarConfigurationValue) {
+ case TIFFBaseline.PLANARCONFIG_CHUNKY:
+ planarConfiguration.setAttribute("value", "PixelInterleaved");
+ break;
+ case TIFFExtension.PLANARCONFIG_PLANAR:
+ planarConfiguration.setAttribute("value", "PlaneInterleaved");
+ break;
+ default:
+ planarConfiguration.setAttribute("value", "Unknown " + planarConfigurationValue);
+ }
+ node.appendChild(planarConfiguration);
+
+ Entry photometricInterpretationTag = ifd.getEntryById(TIFF.TAG_PHOTOMETRIC_INTERPRETATION);
+ int photometricInterpretationValue = photometricInterpretationTag == null
+ ? TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO
+ : getValueAsInt(photometricInterpretationTag);
+
+ Entry samleFormatTag = ifd.getEntryById(TIFF.TAG_SAMPLE_FORMAT);
+ // TODO: Fix for sampleformat 1 1 1 (as int[]) ??!?!?
+ int sampleFormatValue = samleFormatTag == null
+ ? TIFFBaseline.SAMPLEFORMAT_UINT
+ : getValueAsInt(samleFormatTag);
+ IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
+ node.appendChild(sampleFormat);
+
+ switch (sampleFormatValue) {
+ case TIFFBaseline.SAMPLEFORMAT_UINT:
+ if (photometricInterpretationValue == TIFFBaseline.PHOTOMETRIC_PALETTE) {
+ sampleFormat.setAttribute("value", "Index");
+ }
+ else {
+ sampleFormat.setAttribute("value", "UnsignedIntegral");
+ }
+ break;
+ case TIFFExtension.SAMPLEFORMAT_INT:
+ sampleFormat.setAttribute("value", "SignedIntegral");
+ break;
+ case TIFFExtension.SAMPLEFORMAT_FP:
+ sampleFormat.setAttribute("value", "Real");
+ break;
+ default:
+ sampleFormat.setAttribute("value", "Unknown " + sampleFormatValue);
+ break;
+ }
+
+ // TODO: See TIFFImageReader.getBitsPerSample + fix the metadata to have getAsXxxArray methods.
+ // BitsPerSample (not required field for Class B/Bilevel, defaults to 1)
+ Entry bitsPerSampleTag = ifd.getEntryById(TIFF.TAG_BITS_PER_SAMPLE);
+ String bitsPerSampleValue = bitsPerSampleTag == null
+ ? "1"
+ : bitsPerSampleTag.getValueAsString().replaceAll("\\[?\\]?,?", "");
+
+ IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
+ node.appendChild(bitsPerSample);
+ bitsPerSample.setAttribute("value", bitsPerSampleValue);
+
+ int numChannelsValue = getSamplesPerPixelWithFallback();
+
+ // SampleMSB
+ Entry fillOrderTag = ifd.getEntryById(TIFF.TAG_FILL_ORDER);
+ int fillOrder = fillOrderTag != null
+ ? getValueAsInt(fillOrderTag)
+ : TIFFBaseline.FILL_LEFT_TO_RIGHT;
+ IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
+ node.appendChild(sampleMSB);
+ if (fillOrder == TIFFBaseline.FILL_LEFT_TO_RIGHT) {
+ sampleMSB.setAttribute("value", createListValue(numChannelsValue, "0"));
+ }
+ else {
+ if ("1".equals(bitsPerSampleValue)) {
+ sampleMSB.setAttribute("value", createListValue(numChannelsValue, "7"));
+ }
+ else {
+ // TODO: FixMe for bitsPerSample > 8
+ sampleMSB.setAttribute("value", createListValue(numChannelsValue, "7"));
+ }
+ }
+
+ return node;
+ }
+
+ private static int getValueAsInt(final Entry entry) {
+ Object value = entry.getValue();
+
+ if (value instanceof Number) {
+ return ((Number) value).intValue();
+ }
+ else if (value instanceof short[]) {
+ return ((short[]) value)[0];
+ }
+ else if (value instanceof int[]) {
+ return ((int[]) value)[0];
+ }
+
+ throw new IllegalArgumentException("Unsupported type: " + entry);
+ }
+
+ // TODO: Candidate superclass method!
+ private String createListValue(final int itemCount, final String... values) {
+ StringBuilder buffer = new StringBuilder();
+
+ for (int i = 0; i < itemCount; i++) {
+ if (buffer.length() > 0) {
+ buffer.append(' ');
+ }
+
+ buffer.append(values[i % values.length]);
+ }
+
+ return buffer.toString();
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardDimensionNode() {
+ IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
+
+ // PixelAspectRatio
+ Entry xResTag = ifd.getEntryById(TIFF.TAG_X_RESOLUTION);
+ Entry yResTag = ifd.getEntryById(TIFF.TAG_Y_RESOLUTION);
+ double xSizeValue = 1 / (xResTag == null ? 72.0 : ((Number) xResTag.getValue()).doubleValue());
+ double ySizeValue = 1 / (xResTag == null ? 72.0 : ((Number) yResTag.getValue()).doubleValue());
+
+ IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
+ dimension.appendChild(pixelAspectRatio);
+ pixelAspectRatio.setAttribute("value", String.valueOf(xSizeValue / ySizeValue));
+
+ // ImageOrientation
+ Entry orientationTag = ifd.getEntryById(TIFF.TAG_ORIENTATION);
+ if (orientationTag != null) {
+ int orientationValue = getValueAsInt(orientationTag);
+
+ String value = null;
+ switch (orientationValue) {
+ case TIFFBaseline.ORIENTATION_TOPLEFT:
+ value = "Normal";
+ break;
+ case TIFFExtension.ORIENTATION_TOPRIGHT:
+ value = "FlipH";
+ break;
+ case TIFFExtension.ORIENTATION_BOTRIGHT:
+ value = "Rotate180";
+ break;
+ case TIFFExtension.ORIENTATION_BOTLEFT:
+ value = "FlipV";
+ break;
+ case TIFFExtension.ORIENTATION_LEFTTOP:
+ value = "FlipHRotate90";
+ break;
+ case TIFFExtension.ORIENTATION_RIGHTTOP:
+ value = "Rotate270";
+ break;
+ case TIFFExtension.ORIENTATION_RIGHTBOT:
+ value = "FlipVRotate90";
+ break;
+ case TIFFExtension.ORIENTATION_LEFTBOT:
+ value = "Rotate90";
+ break;
+ }
+
+ if (value != null) {
+ IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
+ dimension.appendChild(imageOrientation);
+ imageOrientation.setAttribute("value", value);
+ }
+
+ }
+
+ Entry resUnitTag = ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
+ int resUnitValue = resUnitTag == null ? TIFFBaseline.RESOLUTION_UNIT_DPI : getValueAsInt(resUnitTag);
+ if (resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER || resUnitValue == TIFFBaseline.RESOLUTION_UNIT_DPI) {
+ // 10 mm in 1 cm or 25.4 mm in 1 inch
+ double scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4;
+
+ // HorizontalPixelSize
+ // VerticalPixelSize
+ IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
+ dimension.appendChild(horizontalPixelSize);
+ horizontalPixelSize.setAttribute("value", String.valueOf(xSizeValue * scale));
+
+ IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
+ dimension.appendChild(verticalPixelSize);
+ verticalPixelSize.setAttribute("value", String.valueOf(ySizeValue * scale));
+
+ // HorizontalPosition
+ // VerticalPosition
+ Entry xPosTag = ifd.getEntryById(TIFF.TAG_X_POSITION);
+ Entry yPosTag = ifd.getEntryById(TIFF.TAG_Y_POSITION);
+
+ if (xPosTag != null && yPosTag != null) {
+ double xPosValue = ((Number) xPosTag.getValue()).doubleValue();
+ double yPosValue = ((Number) yPosTag.getValue()).doubleValue();
+
+ IIOMetadataNode horizontalPosition = new IIOMetadataNode("HorizontalPosition");
+ dimension.appendChild(horizontalPosition);
+ horizontalPosition.setAttribute("value", String.valueOf(xPosValue * scale));
+
+ IIOMetadataNode verticalPosition = new IIOMetadataNode("VerticalPosition");
+ dimension.appendChild(verticalPosition);
+ verticalPosition.setAttribute("value", String.valueOf(yPosValue * scale));
+ }
+ }
+
+ return dimension;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardTransparencyNode() {
+ // Consult ExtraSamples
+ Entry extraSamplesTag = ifd.getEntryById(TIFF.TAG_EXTRA_SAMPLES);
+
+ if (extraSamplesTag != null) {
+ int extraSamplesValue = (extraSamplesTag.getValue() instanceof Number)
+ ? getValueAsInt(extraSamplesTag)
+ : ((Number) Array.get(extraSamplesTag.getValue(), 0)).intValue();
+
+ // Other values exists, these are not alpha
+ if (extraSamplesValue == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA || extraSamplesValue == TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA) {
+ IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
+ IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
+ transparency.appendChild(alpha);
+
+ alpha.setAttribute("value", extraSamplesValue == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA
+ ? "premultiplied"
+ : "nonpremultiplied");
+
+ return transparency;
+ }
+ }
+
+ return null;
+
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardDocumentNode() {
+ IIOMetadataNode document = new IIOMetadataNode("Document");
+
+ // FormatVersion, hardcoded to 6.0 (the current TIFF specification version),
+ // as there's no format information in the TIFF structure.
+ IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
+ document.appendChild(formatVersion);
+ formatVersion.setAttribute("value", "6.0");
+
+ // SubImageInterpretation from SubImageInterpretation (if applicable)
+ Entry subFileTypeTag = ifd.getEntryById(TIFF.TAG_SUBFILE_TYPE);
+ if (subFileTypeTag != null) {
+ // NOTE: The JAI metadata is somewhat broken here, as these are bit flags, not values...
+ String value = null;
+ int subFileTypeValue = getValueAsInt(subFileTypeTag);
+ if ((subFileTypeValue & TIFFBaseline.FILETYPE_MASK) != 0) {
+ value = "TransparencyMask";
+ }
+ else if ((subFileTypeValue & TIFFBaseline.FILETYPE_REDUCEDIMAGE) != 0) {
+ value = "ReducedResolution";
+ }
+ else if ((subFileTypeValue & TIFFBaseline.FILETYPE_PAGE) != 0) {
+ value = "SinglePage";
+ }
+
+ // If no flag is set, we don't know...
+ if (value != null) {
+ IIOMetadataNode subImageInterpretation = new IIOMetadataNode("SubImageInterpretation");
+ document.appendChild(subImageInterpretation);
+ subImageInterpretation.setAttribute("value", value);
+ }
+ }
+
+ // ImageCreationTime from DateTime
+ Entry dateTimeTag = ifd.getEntryById(TIFF.TAG_DATE_TIME);
+ if (dateTimeTag != null) {
+ DateFormat format = new SimpleDateFormat("yyyy:MM:dd hh:mm:ss");
+
+ try {
+ IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime");
+ document.appendChild(imageCreationTime);
+
+ Calendar date = Calendar.getInstance();
+ date.setTime(format.parse(dateTimeTag.getValueAsString()));
+
+ imageCreationTime.setAttribute("year", String.valueOf(date.get(Calendar.YEAR)));
+ imageCreationTime.setAttribute("month", String.valueOf(date.get(Calendar.MONTH) + 1));
+ imageCreationTime.setAttribute("day", String.valueOf(date.get(Calendar.DAY_OF_MONTH)));
+ imageCreationTime.setAttribute("hour", String.valueOf(date.get(Calendar.HOUR_OF_DAY)));
+ imageCreationTime.setAttribute("minute", String.valueOf(date.get(Calendar.MINUTE)));
+ imageCreationTime.setAttribute("second", String.valueOf(date.get(Calendar.SECOND)));
+ }
+ catch (ParseException ignore) {
+ // Bad format...
+ }
+ }
+
+ return document;
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardTextNode() {
+ IIOMetadataNode text = new IIOMetadataNode("Text");
+
+ // DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
+ // /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
+ addTextEntryIfPresent(text, TIFF.TAG_DOCUMENT_NAME);
+ addTextEntryIfPresent(text, TIFF.TAG_IMAGE_DESCRIPTION);
+ addTextEntryIfPresent(text, TIFF.TAG_MAKE);
+ addTextEntryIfPresent(text, TIFF.TAG_MODEL);
+ addTextEntryIfPresent(text, TIFF.TAG_PAGE_NAME);
+ addTextEntryIfPresent(text, TIFF.TAG_SOFTWARE);
+ addTextEntryIfPresent(text, TIFF.TAG_ARTIST);
+ addTextEntryIfPresent(text, TIFF.TAG_HOST_COMPUTER);
+ addTextEntryIfPresent(text, TIFF.TAG_INK_NAMES);
+ addTextEntryIfPresent(text, TIFF.TAG_COPYRIGHT);
+
+ return text.hasChildNodes() ? text : null;
+ }
+
+ private void addTextEntryIfPresent(final IIOMetadataNode text, final int tag) {
+ Entry entry = ifd.getEntryById(tag);
+ if (entry != null) {
+ IIOMetadataNode node = new IIOMetadataNode("TextEntry");
+ text.appendChild(node);
+ node.setAttribute("keyword", entry.getFieldName());
+ node.setAttribute("value", entry.getValueAsString());
+ }
+ }
+
+ @Override
+ protected IIOMetadataNode getStandardTileNode() {
+ // TODO! Woot?! This node is not documented in the DTD (although the page mentions a "tile" node)..?
+ // See http://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
+ // See http://stackoverflow.com/questions/30910719/javax-imageio-1-0-standard-plug-in-neutral-metadata-format-tiling-information
+ return super.getStandardTileNode();
+ }
+
+ /// Mutation
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ public void setFromTree(final String formatName, final Node root) throws IIOInvalidTreeException {
+ // Standard validation
+ super.mergeTree(formatName, root);
+
+ // Set by "merging" with empty map
+ LinkedHashMap entries = new LinkedHashMap<>();
+ mergeEntries(formatName, root, entries);
+
+ // TODO: Consistency validation?
+
+ // Finally create a new IFD from merged values
+ ifd = new TIFFIFD(entries.values());
+ }
+
+ @Override
+ public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException {
+ // Standard validation
+ super.mergeTree(formatName, root);
+
+ // Clone entries (shallow clone, as entries themselves are immutable)
+ LinkedHashMap entries = new LinkedHashMap<>(ifd.size() + 10);
+
+ for (Entry entry : ifd) {
+ entries.put((Integer) entry.getIdentifier(), entry);
+ }
+
+ mergeEntries(formatName, root, entries);
+
+ // TODO: Consistency validation?
+
+ // Finally create a new IFD from merged values
+ ifd = new TIFFIFD(entries.values());
+ }
+
+ private void mergeEntries(final String formatName, final Node root, final Map entries) throws IIOInvalidTreeException {
+ // Merge from both native and standard trees
+ if (getNativeMetadataFormatName().equals(formatName)) {
+ mergeNativeTree(root, entries);
+ }
+ else if (IIOMetadataFormatImpl.standardMetadataFormatName.equals(formatName)) {
+ mergeStandardTree(root, entries);
+ }
+ else {
+ // Should already be checked for
+ throw new AssertionError();
+ }
+ }
+
+ private void mergeStandardTree(final Node root, final Map entries) throws IIOInvalidTreeException {
+ NodeList nodes = root.getChildNodes();
+
+ // Merge selected values from standard tree
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+
+ if ("Dimension".equals(node.getNodeName())) {
+ mergeFromStandardDimensionNode(node, entries);
+ }
+ else if ("Document".equals(node.getNodeName())) {
+ mergeFromStandardDocumentNode(node, entries);
+ }
+ else if ("Text".equals(node.getNodeName())) {
+ mergeFromStandardTextNode(node, entries);
+ }
+ }
+ }
+
+ private void mergeFromStandardDimensionNode(final Node dimensionNode, final Map entries) {
+ // Dimension: xRes/yRes
+ // - If set, set res unit to pixels per cm as this better reflects values?
+ // - Or, convert to DPI, if we already had values in DPI??
+ // Also, if we have only aspect, set these values, and use unknown as unit?
+ // TODO: ImageOrientation => Orientation
+ NodeList children = dimensionNode.getChildNodes();
+
+ Float aspect = null;
+ Float xRes = null;
+ Float yRes = null;
+
+ for (int i = 0; i < children.getLength(); i++) {
+ Node child = children.item(i);
+ String nodeName = child.getNodeName();
+
+ if ("PixelAspectRatio".equals(nodeName)) {
+ aspect = Float.parseFloat(getAttribute(child, "value"));
+ }
+ else if ("HorizontalPixelSize".equals(nodeName)) {
+ xRes = Float.parseFloat(getAttribute(child, "value"));
+ }
+ else if ("VerticalPixelSize".equals(nodeName)) {
+ yRes = Float.parseFloat(getAttribute(child, "value"));
+ }
+ }
+
+ // If we have one size compute the other
+ if (xRes == null && yRes != null) {
+ xRes = yRes * (aspect != null ? aspect : 1f);
+ }
+ else if (yRes == null && xRes != null) {
+ yRes = xRes / (aspect != null ? aspect : 1f);
+ }
+
+ // If we have resolution
+ if (xRes != null && yRes != null) {
+ // If old unit was DPI, convert values and keep DPI, otherwise use PPCM
+ Entry resUnitEntry = entries.get(TIFF.TAG_RESOLUTION_UNIT);
+ int resUnitValue = resUnitEntry != null && resUnitEntry.getValue() != null
+ && ((Number) resUnitEntry.getValue()).intValue() == TIFFBaseline.RESOLUTION_UNIT_DPI
+ ? TIFFBaseline.RESOLUTION_UNIT_DPI
+ : TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
+
+ // Units from standard format are pixels per mm, convert to cm or inches
+ float scale = resUnitValue == TIFFBaseline.RESOLUTION_UNIT_CENTIMETER ? 10 : 25.4f;
+
+ int x = Math.round(xRes * scale * RATIONAL_SCALE_FACTOR);
+ int y = Math.round(yRes * scale * RATIONAL_SCALE_FACTOR);
+
+ entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(x, RATIONAL_SCALE_FACTOR)));
+ entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(y, RATIONAL_SCALE_FACTOR)));
+ entries.put(TIFF.TAG_RESOLUTION_UNIT,
+ new TIFFImageWriter.TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, resUnitValue));
+ }
+ else if (aspect != null) {
+ if (aspect >= 1) {
+ int v = Math.round(aspect * RATIONAL_SCALE_FACTOR);
+ entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
+ entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(1)));
+ }
+ else {
+ int v = Math.round(RATIONAL_SCALE_FACTOR / aspect);
+ entries.put(TIFF.TAG_X_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_X_RESOLUTION, new Rational(1)));
+ entries.put(TIFF.TAG_Y_RESOLUTION, new TIFFImageWriter.TIFFEntry(TIFF.TAG_Y_RESOLUTION, new Rational(v, RATIONAL_SCALE_FACTOR)));
+ }
+
+ entries.put(TIFF.TAG_RESOLUTION_UNIT,
+ new TIFFImageWriter.TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_NONE));
+ }
+ // Else give up...
+ }
+
+ private void mergeFromStandardDocumentNode(final Node documentNode, final Map entries) {
+ // Document: SubfileType, CreationDate
+ NodeList children = documentNode.getChildNodes();
+
+ for (int i = 0; i < children.getLength(); i++) {
+ Node child = children.item(i);
+ String nodeName = child.getNodeName();
+
+ if ("SubimageInterpretation".equals(nodeName)) {
+ // TODO: SubFileType
+ }
+ else if ("ImageCreationTime".equals(nodeName)) {
+ // TODO: CreationDate
+ }
+ }
+ }
+
+ private void mergeFromStandardTextNode(final Node textNode, final Map entries) throws IIOInvalidTreeException {
+ NodeList textEntries = textNode.getChildNodes();
+
+ for (int i = 0; i < textEntries.getLength(); i++) {
+ Node textEntry = textEntries.item(i);
+
+ if (!"TextEntry".equals(textEntry.getNodeName())) {
+ throw new IIOInvalidTreeException("Text node should only contain TextEntry nodes", textNode);
+ }
+
+ String keyword = getAttribute(textEntry, "keyword");
+ String value = getAttribute(textEntry, "value");
+
+ // DocumentName, ImageDescription, Make, Model, PageName,
+ // Software, Artist, HostComputer, InkNames, Copyright
+ if (value != null && !value.isEmpty() && keyword != null) {
+ // We do all comparisons in lower case, for compatibility
+ keyword = keyword.toLowerCase();
+
+ TIFFImageWriter.TIFFEntry entry;
+
+ if ("documentname".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_DOCUMENT_NAME, TIFF.TYPE_ASCII, value);
+ }
+ else if ("imagedescription".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_IMAGE_DESCRIPTION, TIFF.TYPE_ASCII, value);
+ }
+ else if ("make".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_MAKE, TIFF.TYPE_ASCII, value);
+ }
+ else if ("model".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_MODEL, TIFF.TYPE_ASCII, value);
+ }
+ else if ("pagename".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_PAGE_NAME, TIFF.TYPE_ASCII, value);
+ }
+ else if ("software".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, value);
+ }
+ else if ("artist".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, value);
+ }
+ else if ("hostcomputer".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_HOST_COMPUTER, TIFF.TYPE_ASCII, value);
+ }
+ else if ("inknames".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_INK_NAMES, TIFF.TYPE_ASCII, value);
+ }
+ else if ("copyright".equals(keyword)) {
+ entry = new TIFFImageWriter.TIFFEntry(TIFF.TAG_COPYRIGHT, TIFF.TYPE_ASCII, value);
+ }
+ else {
+ continue;
+ }
+
+ entries.put((Integer) entry.getIdentifier(), entry);
+ }
+ }
+ }
+
+ private void mergeNativeTree(final Node root, final Map entries) throws IIOInvalidTreeException {
+ Directory ifd = toIFD(root.getFirstChild());
+
+ // Merge (overwrite) entries with entries from IFD
+ for (Entry entry : ifd) {
+ entries.put((Integer) entry.getIdentifier(), entry);
+ }
+ }
+
+ private Directory toIFD(final Node ifdNode) throws IIOInvalidTreeException {
+ if (ifdNode == null || !ifdNode.getNodeName().equals("TIFFIFD")) {
+ throw new IIOInvalidTreeException("Expected \"TIFFIFD\" node", ifdNode);
+ }
+
+ List entries = new ArrayList<>();
+ NodeList nodes = ifdNode.getChildNodes();
+
+ for (int i = 0; i < nodes.getLength(); i++) {
+ entries.add(toEntry(nodes.item(i)));
+ }
+
+ return new TIFFIFD(entries);
+ }
+
+ private Entry toEntry(final Node node) throws IIOInvalidTreeException {
+ String name = node.getNodeName();
+
+ if (name.equals("TIFFIFD")) {
+ int tag = Integer.parseInt(getAttribute(node, "parentTagNumber"));
+ Directory subIFD = toIFD(node);
+
+ return new TIFFImageWriter.TIFFEntry(tag, TIFF.TYPE_IFD, subIFD);
+ }
+ else if (name.equals("TIFFField")) {
+ int tag = Integer.parseInt(getAttribute(node, "number"));
+ short type = getTIFFType(node);
+ Object value = getValue(node, type);
+
+ return value != null ? new TIFFImageWriter.TIFFEntry(tag, type, value) : null;
+ }
+ else {
+ throw new IIOInvalidTreeException("Expected \"TIFFIFD\" or \"TIFFField\" node: " + name, node);
+ }
+ }
+
+ private short getTIFFType(final Node node) throws IIOInvalidTreeException {
+ Node containerNode = node.getFirstChild();
+ if (containerNode == null) {
+ throw new IIOInvalidTreeException("Missing value wrapper node", node);
+ }
+
+ String nodeName = containerNode.getNodeName();
+ if (!nodeName.startsWith("TIFF")) {
+ throw new IIOInvalidTreeException("Unexpected value wrapper node, expected type", containerNode);
+ }
+
+ String typeName = nodeName.substring(4);
+
+ if (typeName.equals("Undefined")) {
+ return TIFF.TYPE_UNDEFINED;
+ }
+
+ typeName = typeName.substring(0, typeName.length() - 1).toUpperCase();
+
+ for (int i = 1; i < TIFF.TYPE_NAMES.length; i++) {
+ if (typeName.equals(TIFF.TYPE_NAMES[i])) {
+ return (short) i;
+ }
+ }
+
+ throw new IIOInvalidTreeException("Unknown TIFF type: " + typeName, containerNode);
+ }
+
+ private Object getValue(final Node node, final short type) throws IIOInvalidTreeException {
+ Node child = node.getFirstChild();
+
+ if (child != null) {
+ String typeName = child.getNodeName();
+
+ if (type == TIFF.TYPE_UNDEFINED) {
+ String values = getAttribute(child, "value");
+ String[] vals = values.split(",\\s?");
+
+ byte[] bytes = new byte[vals.length];
+ for (int i = 0; i < vals.length; i++) {
+ bytes[i] = Byte.parseByte(vals[i]);
+ }
+
+ return bytes;
+ }
+ else {
+ NodeList valueNodes = child.getChildNodes();
+
+ // Create array for each type
+ int count = valueNodes.getLength();
+ Object value = createArrayForType(type, count);
+
+ // Parse each value
+ for (int i = 0; i < count; i++) {
+ Node valueNode = valueNodes.item(i);
+
+ if (!typeName.startsWith(valueNode.getNodeName())) {
+ throw new IIOInvalidTreeException("Value node does not match container node", child);
+ }
+
+ String stringValue = getAttribute(valueNode, "value");
+
+ // NOTE: The reason for parsing "wider" type, is to allow for unsigned values
+ switch (type) {
+ case TIFF.TYPE_BYTE:
+ case TIFF.TYPE_SBYTE:
+ ((byte[]) value)[i] = (byte) Short.parseShort(stringValue);
+ break;
+ case TIFF.TYPE_ASCII:
+ ((String[]) value)[i] = stringValue;
+ break;
+ case TIFF.TYPE_SHORT:
+ case TIFF.TYPE_SSHORT:
+ ((short[]) value)[i] = (short) Integer.parseInt(stringValue);
+ break;
+ case TIFF.TYPE_LONG:
+ case TIFF.TYPE_SLONG:
+ ((int[]) value)[i] = (int) Long.parseLong(stringValue);
+ break;
+ case TIFF.TYPE_RATIONAL:
+ case TIFF.TYPE_SRATIONAL:
+ String[] numDenom = stringValue.split("/");
+ ((Rational[]) value)[i] = numDenom.length > 1
+ ? new Rational(Long.parseLong(numDenom[0]), Long.parseLong(numDenom[1]))
+ : new Rational(Long.parseLong(numDenom[0]));
+ break;
+ case TIFF.TYPE_FLOAT:
+ ((float[]) value)[i] = Float.parseFloat(stringValue);
+ break;
+ case TIFF.TYPE_DOUBLE:
+ ((double[]) value)[i] = Double.parseDouble(stringValue);
+ break;
+ default:
+ throw new AssertionError("Unsupported TIFF type: " + type);
+ }
+ }
+
+ // Normalize value
+ if (count == 0) {
+ return null;
+ }
+ if (count == 1) {
+ return Array.get(value, 0);
+ }
+
+ return value;
+ }
+ }
+
+ throw new IIOInvalidTreeException("Empty TIFField node", node);
+ }
+
+ private Object createArrayForType(final short type, final int length) {
+ switch (type) {
+ case TIFF.TYPE_ASCII:
+ return new String[length];
+ case TIFF.TYPE_BYTE:
+ case TIFF.TYPE_SBYTE:
+ case TIFF.TYPE_UNDEFINED: // Not used here, but for completeness
+ return new byte[length];
+ case TIFF.TYPE_SHORT:
+ case TIFF.TYPE_SSHORT:
+ return new short[length];
+ case TIFF.TYPE_LONG:
+ case TIFF.TYPE_SLONG:
+ return new int[length];
+ case TIFF.TYPE_IFD:
+ return new long[length];
+ case TIFF.TYPE_RATIONAL:
+ case TIFF.TYPE_SRATIONAL:
+ return new Rational[length];
+ case TIFF.TYPE_FLOAT:
+ return new float[length];
+ case TIFF.TYPE_DOUBLE:
+ return new double[length];
+ default:
+ throw new AssertionError("Unsupported TIFF type: " + type);
+ }
+ }
+
+ private String getAttribute(final Node node, final String attribute) {
+ return node instanceof Element ? ((Element) node).getAttribute(attribute) : null;
+ }
+
+ @Override
+ public void reset() {
+ super.reset();
+
+ ifd = original;
+ }
+
+ Directory getIFD() {
+ return ifd;
+ }
+
+ /**
+ * Returns an Entry which contains the data of the requested TIFF field.
+ *
+ * @param tagNumber Tag number of the TIFF field.
+ *
+ * @return the TIFF field, or null.
+ */
+ public Entry getTIFFField(final int tagNumber) {
+ return ifd.getEntryById(tagNumber);
+ }
+
+ // TODO: Replace with IFD class when moved to new package and made public!
+ private final static class TIFFIFD extends AbstractDirectory {
+ public TIFFIFD(final Collection entries) {
+ super(entries);
+ }
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
index 685878b6..790b70e5 100755
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java
@@ -28,16 +28,21 @@
package com.twelvemonkeys.imageio.plugins.tiff;
-import com.sun.imageio.plugins.jpeg.JPEGImageReader;
import com.twelvemonkeys.imageio.ImageReaderBase;
+import com.twelvemonkeys.imageio.color.CIELabColorConverter;
+import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
import com.twelvemonkeys.imageio.color.ColorSpaces;
+import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
import com.twelvemonkeys.imageio.metadata.exif.Rational;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
+import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
+import com.twelvemonkeys.imageio.metadata.psd.PSDReader;
+import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
@@ -47,7 +52,6 @@ import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
-import com.twelvemonkeys.xml.XMLSerializer;
import javax.imageio.*;
import javax.imageio.event.IIOReadWarningListener;
@@ -58,11 +62,16 @@ import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
+import java.awt.color.CMMException;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
@@ -80,15 +89,16 @@ import java.util.zip.InflaterInputStream;
* In addition, it supports many common TIFF extensions such as:
*
* Tiling
+ * Class F (Facsimile), CCITT T.4 and T.6 compression (types 3 and 4), 1 bit per sample
* LZW Compression (type 5)
* "Old-style" JPEG Compression (type 6), as a best effort, as the spec is not well-defined
* JPEG Compression (type 7)
* ZLib (aka Adobe-style Deflate) Compression (type 8)
* Deflate Compression (type 32946)
* Horizontal differencing Predictor (type 2) for LZW, ZLib, Deflate and PackBits compression
- * Alpha channel (ExtraSamples type 1/Associated Alpha)
- * CMYK data (PhotometricInterpretation type 5/Separated)
- * YCbCr data (PhotometricInterpretation type 6/YCbCr) for JPEG
+ * Alpha channel (ExtraSamples types 1/Associated Alpha and 2/Unassociated Alpha)
+ * Class S, CMYK data (PhotometricInterpretation type 5/Separated)
+ * Class Y, YCbCr data (PhotometricInterpretation type 6/YCbCr for both JPEG and other compressions
* Planar data (PlanarConfiguration type 2/Planar)
* ICC profiles (ICCProfile)
* BitsPerSample values up to 16 for most PhotometricInterpretations
@@ -106,7 +116,6 @@ import java.util.zip.InflaterInputStream;
public class TIFFImageReader extends ImageReaderBase {
// TODOs ImageIO basic functionality:
// TODO: Thumbnail support
- // TODO: TIFFImageWriter + Spi
// TODOs Full BaseLine support:
// TODO: Support ExtraSamples (an array, if multiple extra samples!)
@@ -116,27 +125,30 @@ public class TIFFImageReader extends ImageReaderBase {
// TODO: Tiling support (readTile, readTileRaster)
// TODO: Implement readAsRenderedImage to allow tiled RenderedImage?
// For some layouts, we could do reads super-fast with a memory mapped buffer.
- // TODO: Implement readAsRaster directly
- // TODO: IIOMetadata (stay close to Sun's TIFF metadata)
+ // TODO: Implement readAsRaster directly (100% correctly)
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
// TODOs Extension support
- // TODO: Support PlanarConfiguration 2, look at PCXImageReader
// TODO: Auto-rotate based on Orientation
- // TODO: Support ICCProfile (fully)
- // TODO: Support Compression 3 & 4 (CCITT T.4 & T.6)
// TODO: Support Compression 34712 (JPEG2000)? Depends on JPEG2000 ImageReader
// TODO: Support Compression 34661 (JBIG)? Depends on JBIG ImageReader
// DONE:
- // Handle SampleFormat (and give up if not == 1)
+ // Handle SampleFormat
// Support Compression 6 ('Old-style' JPEG)
// Support Compression 2 (CCITT Modified Huffman RLE) for bi-level images
// Source region
// Subsampling
+ // IIOMetadata (stay close to Sun's TIFF metadata)
+ // Support ICCProfile
+ // Support PlanarConfiguration 2
+ // Support Compression 3 & 4 (CCITT T.4 & T.6)
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.tiff.debug"));
+ // NOTE: DO NOT MODIFY OR EXPOSE THIS ARRAY OUTSIDE PACKAGE!
+ static final double[] CCIR_601_1_COEFFICIENTS = new double[] {299.0 / 1000.0, 587.0 / 1000.0, 114.0 / 1000.0};
+
private CompoundDirectory IFDs;
private Directory currentIFD;
@@ -165,6 +177,73 @@ public class TIFFImageReader extends ImageReaderBase {
for (int i = 0; i < IFDs.directoryCount(); i++) {
System.err.printf("IFD %d: %s\n", i, IFDs.getDirectory(i));
}
+
+ Entry tiffXMP = IFDs.getEntryById(TIFF.TAG_XMP);
+ if (tiffXMP != null) {
+ byte[] value = (byte[]) tiffXMP.getValue();
+
+ // The XMPReader doesn't like null-termination...
+ int len = value.length;
+ for (int i = len - 1; i > 0; i--) {
+ if (value[i] == 0) {
+ len--;
+ }
+ else {
+ break;
+ }
+ }
+
+ Directory xmp = new XMPReader().read(new ByteArrayImageInputStream(value, 0, len));
+ System.err.println("-----------------------------------------------------------------------------");
+ System.err.println("xmp: " + xmp);
+ }
+
+ Entry tiffIPTC = IFDs.getEntryById(TIFF.TAG_IPTC);
+ if (tiffIPTC != null) {
+ Object value = tiffIPTC.getValue();
+ if (value instanceof short[]) {
+ System.err.println("short[]: " + value);
+ }
+ if (value instanceof long[]) {
+ // As seen in a Magick produced image...
+ System.err.println("long[]: " + value);
+ long[] longs = (long[]) value;
+ value = new byte[longs.length * 8];
+ ByteBuffer.wrap((byte[]) value).asLongBuffer().put(longs);
+ }
+ if (value instanceof float[]) {
+ System.err.println("float[]: " + value);
+ }
+ if (value instanceof double[]) {
+ System.err.println("double[]: " + value);
+ }
+
+ Directory iptc = new IPTCReader().read(new ByteArrayImageInputStream((byte[]) value));
+ System.err.println("-----------------------------------------------------------------------------");
+ System.err.println("iptc: " + iptc);
+ }
+
+ Entry tiffPSD = IFDs.getEntryById(TIFF.TAG_PHOTOSHOP);
+ if (tiffPSD != null) {
+ Directory psd = new PSDReader().read(new ByteArrayImageInputStream((byte[]) tiffPSD.getValue()));
+ System.err.println("-----------------------------------------------------------------------------");
+ System.err.println("psd: " + psd);
+ }
+ Entry tiffPSD2 = IFDs.getEntryById(TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA);
+ if (tiffPSD2 != null) {
+ byte[] value = (byte[]) tiffPSD2.getValue();
+ String foo = "Adobe Photoshop Document Data Block";
+
+ if (Arrays.equals(foo.getBytes(StandardCharsets.US_ASCII), Arrays.copyOf(value, foo.length()))) {
+ System.err.println("foo: " + foo);
+// int offset = foo.length() + 1;
+// ImageInputStream input = new ByteArrayImageInputStream(value, offset, value.length - offset);
+// input.setByteOrder(ByteOrder.LITTLE_ENDIAN); // TODO: WHY???!
+// Directory psd2 = new PSDReader().read(input);
+// System.err.println("-----------------------------------------------------------------------------");
+// System.err.println("psd2: " + psd2);
+ }
+ }
}
}
}
@@ -235,12 +314,29 @@ public class TIFFImageReader extends ImageReaderBase {
readIFD(imageIndex);
int sampleFormat = getSampleFormat();
- int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFExtension.PLANARCONFIG_PLANAR);
- int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
+ int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFBaseline.PLANARCONFIG_CHUNKY);
+ int interpretation = getPhotometricInterpretationWithFallback();
int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1);
int bitsPerSample = getBitsPerSample();
int dataType = getDataType(sampleFormat, bitsPerSample);
+ int opaqueSamplesPerPixel = getOpaqueSamplesPerPixel(interpretation);
+
+ // Spec says ExtraSamples are mandatory of extra samples, however known encoders
+ // (ie. SeaShore) writes ARGB TIFFs without ExtraSamples.
+ long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", false);
+ if (extraSamples == null && samplesPerPixel > opaqueSamplesPerPixel) {
+ // TODO: Log warning!
+ // First extra is alpha, rest is "unspecified"
+ extraSamples = new long[samplesPerPixel - opaqueSamplesPerPixel];
+ extraSamples[0] = TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA;
+ }
+
+ // Determine alpha
+ boolean hasAlpha = extraSamples != null;
+ boolean isAlphaPremultiplied = hasAlpha && extraSamples[0] == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA;
+ int significantSamples = opaqueSamplesPerPixel + (hasAlpha ? 1 : 0);
+
// Read embedded cs
ICC_Profile profile = getICCProfile();
ColorSpace cs;
@@ -250,14 +346,14 @@ public class TIFFImageReader extends ImageReaderBase {
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
// WhiteIsZero
// NOTE: We handle this by inverting the values when reading, as Java has no ColorModel that easily supports this.
- // TODO: Consider returning null?
case TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO:
// BlackIsZero
// Gray scale or B/W
- switch (samplesPerPixel) {
+ switch (significantSamples) {
case 1:
// TIFF 6.0 Spec says: 1, 4 or 8 for baseline (1 for bi-level, 4/8 for gray)
- // ImageTypeSpecifier supports 1, 2, 4, 8 or 16 bits, we'll go with that for now
+ // ImageTypeSpecifier supports 1, 2, 4, 8 or 16 bits per sample, we'll support 32 bits as well.
+ // (Chunky or planar makes no difference for a single channel).
if (profile != null && profile.getColorSpaceType() != ColorSpace.TYPE_GRAY) {
processWarningOccurred(String.format("Embedded ICC color profile (type %s), is incompatible with image data (GRAY/type 6). Ignoring profile.", profile.getColorSpaceType()));
profile = null;
@@ -268,13 +364,51 @@ public class TIFFImageReader extends ImageReaderBase {
if (cs == ColorSpace.getInstance(ColorSpace.CS_GRAY) && (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 || bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32)) {
return ImageTypeSpecifiers.createGrayscale(bitsPerSample, dataType);
}
- else if (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 || bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
+ else if (bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 ) {
+ // Use packed format for 1/2/4 bits
+ return ImageTypeSpecifiers.createPackedGrayscale(cs, bitsPerSample, dataType);
+ }
+ else if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0}, dataType, false, false);
}
- default:
- // TODO: If ExtraSamples is used, PlanarConfiguration must be taken into account also for gray data
- throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for Bi-level/Gray TIFF (expected 1/1, 1/2, 1/4, 1/8 or 1/16): %d/%d", samplesPerPixel, bitsPerSample));
+ throw new IIOException(String.format("Unsupported BitsPerSample for Bi-level/Gray TIFF (expected 1, 2, 4, 8, 16 or 32): %d", bitsPerSample));
+
+ case 2:
+ // Gray + alpha. We'll support:
+ // * 8, 16 or 32 bits per sample
+ // * Associated (pre-multiplied) or unassociated (non-pre-multiplied) alpha
+ // * Chunky (interleaved) or planar (banded) data
+ if (profile != null && profile.getColorSpaceType() != ColorSpace.TYPE_GRAY) {
+ processWarningOccurred(String.format("Embedded ICC color profile (type %s), is incompatible with image data (GRAY/type 6). Ignoring profile.", profile.getColorSpaceType()));
+ profile = null;
+ }
+
+ cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_GRAY) : ColorSpaces.createColorSpace(profile);
+
+ if (cs == ColorSpace.getInstance(ColorSpace.CS_GRAY) && (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32)) {
+ switch (planarConfiguration) {
+ case TIFFBaseline.PLANARCONFIG_CHUNKY:
+ return ImageTypeSpecifiers.createGrayscale(bitsPerSample, dataType, isAlphaPremultiplied);
+ case TIFFExtension.PLANARCONFIG_PLANAR:
+ return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, dataType, true, isAlphaPremultiplied);
+ }
+ }
+ else if (/*bitsPerSample == 1 || bitsPerSample == 2 || bitsPerSample == 4 ||*/ bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
+ // TODO: Should use packed format for 1/2/4 chunky.
+ // TODO: For 1/2/4 bit planar, we might need to fix while reading... Look at IFFImageReader?
+ switch (planarConfiguration) {
+ case TIFFBaseline.PLANARCONFIG_CHUNKY:
+ return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1}, dataType, true, isAlphaPremultiplied);
+ case TIFFExtension.PLANARCONFIG_PLANAR:
+ return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1}, new int[] {0, 0}, dataType, true, isAlphaPremultiplied);
+ }
+ }
+
+ throw new IIOException(String.format("Unsupported BitsPerSample for Gray + Alpha TIFF (expected 8, 16 or 32): %d", bitsPerSample));
+
+ default:
+ throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for Bi-level/Gray TIFF (expected 1/1, 1/2, 1/4, 1/8, 1/16 or 1/32, or 2/8, 2/16 or 2/32): %d/%d", samplesPerPixel, bitsPerSample));
}
case TIFFExtension.PHOTOMETRIC_YCBCR:
@@ -289,15 +423,12 @@ public class TIFFImageReader extends ImageReaderBase {
cs = profile == null ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpaces.createColorSpace(profile);
- switch (samplesPerPixel) {
+ switch (significantSamples) {
case 3:
if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY:
- if (bitsPerSample == 8 && cs.isCS_sRGB()) {
- return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
- }
-
+ // "TYPE_3_BYTE_RGB" if cs.isCS_sRGB()
return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2}, dataType, false, false);
case TIFFExtension.PLANARCONFIG_PLANAR:
@@ -306,22 +437,18 @@ public class TIFFImageReader extends ImageReaderBase {
}
case 4:
if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
- // ExtraSamples 0=unspecified, 1=associated (pre-multiplied), 2=unassociated (TODO: Support unspecified, not alpha)
- long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", true);
-
switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY:
- if (bitsPerSample == 8 && cs.isCS_sRGB()) {
- return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
- }
-
- return ImageTypeSpecifiers.createInterleaved(cs, new int[]{ 0, 1, 2, 3}, dataType, true, extraSamples[0] == 1);
+ // "TYPE_4_BYTE_RGBA" if cs.isCS_sRGB()
+ return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, isAlphaPremultiplied);
case TIFFExtension.PLANARCONFIG_PLANAR:
- return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, extraSamples[0] == 1);
+ return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3}, new int[] {0, 0, 0, 0}, dataType, true, isAlphaPremultiplied);
}
}
- // TODO: More samples might be ok, if multiple alpha or unknown samples
+ else if (bitsPerSample == 4) {
+ return ImageTypeSpecifiers.createPacked(cs, 0xF000, 0xF00, 0xF0, 0xF, DataBuffer.TYPE_USHORT, isAlphaPremultiplied);
+ }
default:
throw new IIOException(String.format("Unsupported SamplesPerPixels/BitsPerSample combination for RGB TIFF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
}
@@ -333,8 +460,8 @@ public class TIFFImageReader extends ImageReaderBase {
else if (bitsPerSample <= 0 || bitsPerSample > 16) {
throw new IIOException("Bad BitsPerSample value for Palette TIFF (expected <= 16): " + bitsPerSample);
}
- // NOTE: If ExtraSamples is used, PlanarConfiguration must be taken into account also for pixel data
+ // NOTE: If ExtraSamples is used, PlanarConfiguration must be taken into account also for pixel data
Entry colorMap = currentIFD.getEntryById(TIFF.TAG_COLOR_MAP);
if (colorMap == null) {
throw new IIOException("Missing ColorMap for Palette TIFF");
@@ -364,7 +491,7 @@ public class TIFFImageReader extends ImageReaderBase {
cs = profile == null ? ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK) : ColorSpaces.createColorSpace(profile);
- switch (samplesPerPixel) {
+ switch (significantSamples) {
case 4:
if (bitsPerSample == 8 || bitsPerSample == 16) {
switch (planarConfiguration) {
@@ -376,46 +503,136 @@ public class TIFFImageReader extends ImageReaderBase {
}
case 5:
if (bitsPerSample == 8 || bitsPerSample == 16) {
- // ExtraSamples 0=unspecified, 1=associated (pre-multiplied), 2=unassociated (TODO: Support unspecified, not alpha)
- long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", true);
-
switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY:
- return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2, 3, 4}, dataType, true, extraSamples[0] == 1);
+ return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2, 3, 4}, dataType, true, isAlphaPremultiplied);
case TIFFExtension.PLANARCONFIG_PLANAR:
- return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, dataType, true, extraSamples[0] == 1);
+ return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2, 3, 4}, new int[] {0, 0, 0, 0, 0}, dataType, true, isAlphaPremultiplied);
}
}
- // TODO: More samples might be ok, if multiple alpha or unknown samples, consult ExtraSamples
-
default:
throw new IIOException(
- String.format("Unsupported TIFF SamplesPerPixels/BitsPerSample combination for Separated TIFF (expected 4/8, 4/16, 5/8 or 5/16): %d/%s", samplesPerPixel, bitsPerSample)
+ String.format("Unsupported SamplesPerPixels/BitsPerSample combination for Separated TIFF (expected 4/8, 4/16, 5/8 or 5/16): %d/%s", samplesPerPixel, bitsPerSample)
+ );
+ }
+ case TIFFExtension.PHOTOMETRIC_CIELAB:
+ case TIFFExtension.PHOTOMETRIC_ICCLAB:
+ case TIFFExtension.PHOTOMETRIC_ITULAB:
+ // TODO: Would probably be more correct to handle using a CIELabColorSpace for RAW type?
+ // L*a*b* color. Handled using conversion to sRGB
+ cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ switch (planarConfiguration) {
+ case TIFFBaseline.PLANARCONFIG_CHUNKY:
+ return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2}, dataType, false, false);
+ case TIFFExtension.PLANARCONFIG_PLANAR:
+ // TODO: Reading works fine, but we can't convert the Lab values properly yet. Need to rewrite normalizeColor
+ //return ImageTypeSpecifiers.createBanded(cs, new int[] {0, 1, 2}, new int[] {0, 0, 0}, dataType, false, false);
+ default:
+ throw new IIOException(
+ String.format("Unsupported PlanarConfiguration for Lab color TIFF (expected 1): %d", planarConfiguration)
);
}
case TIFFBaseline.PHOTOMETRIC_MASK:
// Transparency mask
-
+ // TODO: Treat as grey?
+ case TIFFCustom.PHOTOMETRIC_LOGL:
+ case TIFFCustom.PHOTOMETRIC_LOGLUV:
+ // Log
+ case TIFFCustom.PHOTOMETRIC_CFA:
+ case TIFFCustom.PHOTOMETRIC_LINEAR_RAW:
+ // RAW (DNG)
throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + interpretation);
default:
throw new IIOException("Unknown TIFF PhotometricInterpretation value: " + interpretation);
}
}
+ private int getPhotometricInterpretationWithFallback() throws IIOException {
+ // PhotometricInterpretation is a required TAG, but as it can be guessed this does a fallback that is equal to JAI ImageIO.
+ int interpretation = getValueAsIntWithDefault(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation", -1);
+ if (interpretation == -1) {
+ int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
+ int samplesPerPixel = getValueAsIntWithDefault(TIFF.TAG_SAMPLES_PER_PIXEL, 1);
+ Entry extraSamplesEntry = currentIFD.getEntryById(TIFF.TAG_EXTRA_SAMPLES);
+ int extraSamples = extraSamplesEntry == null ? 0 : extraSamplesEntry.valueCount();
+
+ if (compression == TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE
+ || compression == TIFFExtension.COMPRESSION_CCITT_T4
+ || compression == TIFFExtension.COMPRESSION_CCITT_T6) {
+ interpretation = TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO;
+ }
+ else if (currentIFD.getEntryById(TIFF.TAG_COLOR_MAP) != null) {
+ interpretation = TIFFBaseline.PHOTOMETRIC_PALETTE;
+ }
+ else if ((samplesPerPixel - extraSamples) == 3) {
+ interpretation = TIFFBaseline.PHOTOMETRIC_RGB;
+ }
+ else if ((samplesPerPixel - extraSamples) == 4) {
+ interpretation = TIFFExtension.PHOTOMETRIC_SEPARATED;
+ }
+ else {
+ interpretation = TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO;
+ }
+ processWarningOccurred("Missing PhotometricInterpretation, determining fallback: " + interpretation);
+ }
+ return interpretation;
+ }
+
+ private int getOpaqueSamplesPerPixel(final int photometricInterpretation) throws IIOException {
+ switch (photometricInterpretation) {
+ case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
+ case TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO:
+ case TIFFBaseline.PHOTOMETRIC_PALETTE:
+ case TIFFBaseline.PHOTOMETRIC_MASK:
+ return 1;
+ case TIFFBaseline.PHOTOMETRIC_RGB:
+ case TIFFExtension.PHOTOMETRIC_YCBCR:
+ case TIFFExtension.PHOTOMETRIC_CIELAB:
+ case TIFFExtension.PHOTOMETRIC_ICCLAB:
+ case TIFFExtension.PHOTOMETRIC_ITULAB:
+ return 3;
+ case TIFFExtension.PHOTOMETRIC_SEPARATED:
+ return getValueAsIntWithDefault(TIFF.TAG_NUMBER_OF_INKS, 4);
+
+ case TIFFCustom.PHOTOMETRIC_LOGL:
+ case TIFFCustom.PHOTOMETRIC_LOGLUV:
+ case TIFFCustom.PHOTOMETRIC_CFA:
+ case TIFFCustom.PHOTOMETRIC_LINEAR_RAW:
+ throw new IIOException("Unsupported TIFF PhotometricInterpretation value: " + photometricInterpretation);
+ default:
+ throw new IIOException("Unknown TIFF PhotometricInterpretation value: " + photometricInterpretation);
+ }
+ }
+
private int getDataType(int sampleFormat, int bitsPerSample) throws IIOException {
switch (sampleFormat) {
+ case TIFFExtension.SAMPLEFORMAT_UNDEFINED:
+ // Spec says:
+ // A field value of “undefined” is a statement by the writer that it did not know how
+ // to interpret the data samples; for example, if it were copying an existing image. A
+ // reader would typically treat an image with “undefined” data as if the field were
+ // not present (i.e. as unsigned integer data).
case TIFFBaseline.SAMPLEFORMAT_UINT:
return bitsPerSample <= 8 ? DataBuffer.TYPE_BYTE : bitsPerSample <= 16 ? DataBuffer.TYPE_USHORT : DataBuffer.TYPE_INT;
case TIFFExtension.SAMPLEFORMAT_INT:
- if (bitsPerSample == 16) {
- return DataBuffer.TYPE_SHORT;
+ switch (bitsPerSample) {
+ case 8:
+ return DataBuffer.TYPE_BYTE;
+ case 16:
+ return DataBuffer.TYPE_SHORT;
+ case 32:
+ return DataBuffer.TYPE_INT;
}
- throw new IIOException("Unsupported BitPerSample for SampleFormat 2/Signed Integer (expected 16): " + bitsPerSample);
+
+ throw new IIOException("Unsupported BitsPerSample for SampleFormat 2/Signed Integer (expected 8/16/32): " + bitsPerSample);
+
case TIFFExtension.SAMPLEFORMAT_FP:
- throw new IIOException("Unsupported TIFF SampleFormat: (3/Floating point)");
- case TIFFExtension.SAMPLEFORMAT_UNDEFINED:
- throw new IIOException("Unsupported TIFF SampleFormat (4/Undefined)");
+ if (bitsPerSample == 32) {
+ return DataBuffer.TYPE_FLOAT;
+ }
+
+ throw new IIOException("Unsupported BitsPerSample for SampleFormat 3/Floating Point (expected 32): " + bitsPerSample);
default:
throw new IIOException("Unknown TIFF SampleFormat (expected 1, 2, 3 or 4): " + sampleFormat);
}
@@ -486,9 +703,14 @@ public class TIFFImageReader extends ImageReaderBase {
else {
int bitsPerSample = (int) value[0];
- for (int i = 1; i < value.length; i++) {
- if (value[i] != bitsPerSample) {
- throw new IIOException("Variable BitsPerSample not supported: " + Arrays.toString(value));
+ if (value.length == 3 && (value[0] == 5 && value[1] == 6 && value[2] == 5)) {
+ // Special case for UINT_565. We're good.
+ }
+ else {
+ for (int i = 1; i < value.length; i++) {
+ if (value[i] != bitsPerSample) {
+ throw new IIOException("Variable BitsPerSample not supported: " + Arrays.toString(value));
+ }
}
}
@@ -501,10 +723,9 @@ public class TIFFImageReader extends ImageReaderBase {
readIFD(imageIndex);
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
- Set specs = new LinkedHashSet(5);
+ Set specs = new LinkedHashSet<>(5);
// TODO: Based on raw type, we can probably convert to most RGB types at least, maybe gray etc
- // TODO: Planar to chunky by default
if (rawType.getColorModel().getColorSpace().getType() == ColorSpace.TYPE_RGB) {
if (rawType.getNumBands() == 3 && rawType.getBitsPerBand(0) == 8) {
specs.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
@@ -543,7 +764,7 @@ public class TIFFImageReader extends ImageReaderBase {
WritableRaster destRaster = clipToRect(destination.getRaster(), dstRegion, param != null ? param.getDestinationBands() : null);
- final int interpretation = getValueAsInt(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, "PhotometricInterpretation");
+ final int interpretation = getPhotometricInterpretationWithFallback();
final int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
final int predictor = getValueAsIntWithDefault(TIFF.TAG_PREDICTOR, 1);
final int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFBaseline.PLANARCONFIG_CHUNKY);
@@ -582,7 +803,9 @@ public class TIFFImageReader extends ImageReaderBase {
int tilesAcross = (width + stripTileWidth - 1) / stripTileWidth;
int tilesDown = (height + stripTileHeight - 1) / stripTileHeight;
- WritableRaster rowRaster = rawType.getColorModel().createCompatibleWritableRaster(stripTileWidth, 1);
+ // TODO: If extrasamples, we might need to create a raster with more samples...
+ WritableRaster rowRaster = rawType.createBufferedImage(stripTileWidth, 1).getRaster();
+ Rectangle clip = new Rectangle(srcRegion);
int row = 0;
switch (compression) {
@@ -600,14 +823,14 @@ public class TIFFImageReader extends ImageReaderBase {
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
// CCITT modified Huffman
// Additionally, the specification defines these values as part of the TIFF extensions:
-// case TIFFExtension.COMPRESSION_CCITT_T4:
+ case TIFFExtension.COMPRESSION_CCITT_T4:
// CCITT Group 3 fax encoding
-// case TIFFExtension.COMPRESSION_CCITT_T6:
+ case TIFFExtension.COMPRESSION_CCITT_T6:
// CCITT Group 4 fax encoding
int[] yCbCrSubsampling = null;
int yCbCrPos = 1;
- double[] yCbCrCoefficients = null;
+// double[] yCbCrCoefficients = null;
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
// getRawImageType does the lookup/conversion for these
if (rowRaster.getNumBands() != 3) {
@@ -646,15 +869,15 @@ public class TIFFImageReader extends ImageReaderBase {
yCbCrSubsampling = new int[] {2, 2};
}
- Entry coefficients = currentIFD.getEntryById(TIFF.TAG_YCBCR_COEFFICIENTS);
- if (coefficients != null) {
- Rational[] value = (Rational[]) coefficients.getValue();
- yCbCrCoefficients = new double[] {value[0].doubleValue(), value[1].doubleValue(), value[2].doubleValue()};
- }
- else {
- // Default to y CCIR Recommendation 601-1 values
- yCbCrCoefficients = YCbCrUpsamplerStream.CCIR_601_1_COEFFICIENTS;
- }
+// Entry coefficients = currentIFD.getEntryById(TIFF.TAG_YCBCR_COEFFICIENTS);
+// if (coefficients != null) {
+// Rational[] value = (Rational[]) coefficients.getValue();
+// yCbCrCoefficients = new double[] {value[0].doubleValue(), value[1].doubleValue(), value[2].doubleValue()};
+// }
+// else {
+// // Default to y CCIR Recommendation 601-1 values
+// yCbCrCoefficients = YCbCrUpsamplerStream.CCIR_601_1_COEFFICIENTS;
+// }
}
// Read data
@@ -685,10 +908,10 @@ public class TIFFImageReader extends ImageReaderBase {
adapter = createUnpredictorStream(predictor, stripTileWidth, numBands, getBitsPerSample(), adapter, imageInput.getByteOrder());
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_BYTE) {
- adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients);
+ adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile);
}
else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_USHORT) {
- adapter = new YCbCr16UpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, yCbCrCoefficients, imageInput.getByteOrder());
+ adapter = new YCbCr16UpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, imageInput.getByteOrder());
}
else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
// Handled in getRawImageType
@@ -702,7 +925,6 @@ public class TIFFImageReader extends ImageReaderBase {
}
// Clip the stripTile rowRaster to not exceed the srcRegion
- Rectangle clip = new Rectangle(srcRegion);
clip.width = Math.min((colsInTile + xSub - 1) / xSub, srcRegion.width);
Raster clippedRow = clipRowToRect(rowRaster, clip,
param != null ? param.getSourceBands() : null,
@@ -735,8 +957,7 @@ public class TIFFImageReader extends ImageReaderBase {
// TODO: Refactor all JPEG reading out to separate JPEG support class?
// TODO: Cache the JPEG reader for later use? Remember to reset to avoid resource leaks
- // TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
- ImageReader jpegReader = new JPEGImageReader(getOriginatingProvider());
+ ImageReader jpegReader = createJPEGDelegate();
JPEGImageReadParam jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
@@ -772,19 +993,25 @@ public class TIFFImageReader extends ImageReaderBase {
// Read only tiles that lies within region
if (new Rectangle(col, row, colsInTile, rowsInTile).intersects(srcRegion)) {
imageInput.seek(stripTileOffsets[i]);
- ImageInputStream subStream = new SubImageInputStream(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE);
- try {
+ int length = stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE;
+
+ try (ImageInputStream subStream = new SubImageInputStream(imageInput, length)) {
jpegReader.setInput(subStream);
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
- jpegParam.setDestinationOffset(new Point(col - srcRegion.x, row - srcRegion.y));
- jpegParam.setDestination(destination);
- // TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc...
- // In the latter case we will have to use readAsRaster and do color conversion ourselves
- jpegReader.read(0, jpegParam);
- }
- finally {
- subStream.close();
+
+ if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR || interpretation == TIFFBaseline.PHOTOMETRIC_RGB) {
+ jpegParam.setDestinationOffset(new Point(col - srcRegion.x, row - srcRegion.y));
+ jpegParam.setDestination(destination);
+ jpegReader.read(0, jpegParam);
+ }
+ else {
+ // Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
+ // We'll have to use readAsRaster and later apply color space conversion ourselves
+ Raster raster = jpegReader.readRaster(0, jpegParam);
+ normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData());
+ destination.getRaster().setDataElements(col - srcRegion.x, row - srcRegion.y, raster);
+ }
}
}
@@ -823,25 +1050,20 @@ public class TIFFImageReader extends ImageReaderBase {
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
}
- // May use normal tiling??
-
- // TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
- jpegReader = new JPEGImageReader(getOriginatingProvider());
+ jpegReader = createJPEGDelegate();
jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
- // 513/JPEGInterchangeFormat (may be absent...)
+ // 513/JPEGInterchangeFormat (may be absent or 0)
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
- // 514/JPEGInterchangeFormatLength (may be absent...)
- int jpegLenght = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
+ // 514/JPEGInterchangeFormatLength (may be absent)
+ int jpegLength = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
// TODO: 515/JPEGRestartInterval (may be absent)
// Currently ignored (for lossless only)
// 517/JPEGLosslessPredictors
// 518/JPEGPointTransforms
- ImageInputStream stream;
-
- if (jpegOffset != -1) {
+ if (jpegOffset > 0) {
// Straight forward case: We're good to go! We'll disregard tiling and any tables tags
if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null
|| currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null
@@ -878,23 +1100,49 @@ public class TIFFImageReader extends ImageReaderBase {
}
}
- imageInput.seek(realJPEGOffset);
-
- stream = new SubImageInputStream(imageInput, jpegLenght != -1 ? jpegLenght : Short.MAX_VALUE);
- jpegReader.setInput(stream);
+ // Determine correct JPEG stream length
+ int length;
+ if (jpegLength == -1) {
+ // If have no length, we'll just try to decode, as long as we can
+ length = Integer.MAX_VALUE;
+ processWarningOccurred("Missing JPEGInterchangeFormatLength tag");
+ }
+ else if (stripTileOffsets != null && stripTileOffsets.length == 1 && stripTileOffsets[0] >= jpegOffset + jpegLength) {
+ // NOTE: Some known TIFF encoder writes obviously bogus JPEGInterchangeFormatLength value,
+ // but the real stream length can be determined from the StripByteCounts (may include padding).
+ if (stripTileByteCounts != null && stripTileByteCounts.length == 1 && stripTileByteCounts[0] > jpegLength) {
+ length = (int) (jpegLength + stripTileByteCounts[0]);
+ processWarningOccurred("Incorrect JPEGInterchangeFormatLength tag encountered, using StripByteCounts instead");
+ }
+ else {
+ // No StripByteCounts, we'll just try tro decode as much as we can
+ length = Integer.MAX_VALUE;
+ processWarningOccurred("Incorrect JPEGInterchangeFormatLength tag encountered, ignoring tag value");
+ }
+ }
+ else {
+ // Ok! We'll go with JPEGInterchangeFormatLength
+ length = jpegLength;
+ }
// Read data
processImageStarted(imageIndex); // Better yet, would be to delegate read progress here...
+ imageInput.seek(realJPEGOffset);
- try {
- jpegParam.setSourceRegion(new Rectangle(0, 0, width, height));
- jpegParam.setDestination(destination);
- // TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc...
- // In the latter case we will have to use readAsRaster and do color conversion ourselves
- jpegReader.read(0, jpegParam);
- }
- finally {
- stream.close();
+ try (ImageInputStream stream = new SubImageInputStream(imageInput, length)) {
+ jpegReader.setInput(stream);
+ jpegParam.setSourceRegion(srcRegion);
+
+ if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR || interpretation == TIFFBaseline.PHOTOMETRIC_RGB) {
+ jpegParam.setDestination(destination);
+ jpegReader.read(0, jpegParam);
+ }
+ else {
+ // Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
+ // We'll have to use readAsRaster and later apply color space conversion ourselves
+ Raster raster = jpegReader.readRaster(0, jpegParam);
+ destination.getRaster().setDataElements(0, 0, raster);
+ }
}
processImageProgress(100f);
@@ -969,26 +1217,31 @@ public class TIFFImageReader extends ImageReaderBase {
// Read only tiles that lies within region
if (new Rectangle(col, row, colsInTile, rowsInTile).intersects(srcRegion)) {
imageInput.seek(stripTileOffsets[i]);
- stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
+
+ try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
Arrays.asList(
- createJFIFStream(destRaster, stripTileWidth, stripTileHeight, qTables, dcTables, acTables),
- IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE),
+ createJFIFStream(destRaster.getNumBands(), stripTileWidth, stripTileHeight, qTables, dcTables, acTables),
+ IIOUtil.createStreamAdapter(imageInput, stripTileByteCounts != null
+ ? (int) stripTileByteCounts[i]
+ : Short.MAX_VALUE),
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
)
- )));
-
- jpegReader.setInput(stream);
-
- try {
+ )))) {
+ jpegReader.setInput(stream);
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
jpegParam.setDestinationOffset(new Point(col - srcRegion.x, row - srcRegion.y));
jpegParam.setDestination(destination);
- // TODO: This works only if Gray/YCbCr/RGB, not CMYK/LAB/etc...
- // In the latter case we will have to use readAsRaster and do color conversion ourselves
- jpegReader.read(0, jpegParam);
- }
- finally {
- stream.close();
+
+ if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR || interpretation == TIFFBaseline.PHOTOMETRIC_RGB) {
+ jpegParam.setDestination(destination);
+ jpegReader.read(0, jpegParam);
+ }
+ else {
+ // Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
+ // We'll have to use readAsRaster and later apply color space conversion ourselves
+ Raster raster = jpegReader.readRaster(0, jpegParam);
+ destination.getRaster().setDataElements(0, 0, raster);
+ }
}
}
@@ -1013,10 +1266,6 @@ public class TIFFImageReader extends ImageReaderBase {
break;
// Additionally, the specification defines these values as part of the TIFF extensions:
- case TIFFExtension.COMPRESSION_CCITT_T4:
- // CCITT Group 3 fax encoding
- case TIFFExtension.COMPRESSION_CCITT_T6:
- // CCITT Group 4 fax encoding
// Known, but unsupported compression types
case TIFFCustom.COMPRESSION_NEXT:
@@ -1039,31 +1288,57 @@ public class TIFFImageReader extends ImageReaderBase {
throw new IIOException("Unknown TIFF Compression value: " + compression);
}
+ // TODO: Convert color space from source to destination
+
processImageComplete();
return destination;
}
- private static InputStream createJFIFStream(WritableRaster raster, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
+ private ImageReader createJPEGDelegate() throws IIOException {
+ // TIFF is strictly ISO JPEG, so we should probably stick to the standard reader
+ try {
+ @SuppressWarnings("unchecked")
+ Class readerClass = (Class) Class.forName("com.sun.imageio.plugins.jpeg.JPEGImageReader");
+ Constructor constructor = readerClass.getConstructor(ImageReaderSpi.class);
+ return constructor.newInstance(getOriginatingProvider());
+ }
+ catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException ignore) {
+ if (DEBUG) {
+ ignore.printStackTrace();
+ }
+ // Fall back to default reader below
+ }
+
+ // If we can't get the standard reader, fall back to the default (first) reader
+ Iterator readers = ImageIO.getImageReadersByFormatName("JPEG");
+ if (!readers.hasNext()) {
+ throw new IIOException("Could not instantiate JPEGImageReader");
+ }
+
+ return readers.next();
+ }
+
+ private static InputStream createJFIFStream(int bands, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
- 2 + 2 + 2 + 6 + 3 * raster.getNumBands() +
+ 2 + 2 + 2 + 6 + 3 * bands +
5 * qTables.length + qTables.length * qTables[0].length +
5 * dcTables.length + dcTables.length * dcTables[0].length +
5 * acTables.length + acTables.length * acTables[0].length +
- 8 + 2 * raster.getNumBands()
+ 8 + 2 * bands
);
DataOutputStream out = new DataOutputStream(stream);
out.writeShort(JPEG.SOI);
out.writeShort(JPEG.SOF0);
- out.writeShort(2 + 6 + 3 * raster.getNumBands()); // SOF0 len
- out.writeByte(8); // bits TODO: Consult raster/transfer type or BitsPerSample for 12/16 bits support
+ out.writeShort(2 + 6 + 3 * bands); // SOF0 len
+ out.writeByte(8); // bits TODO: Consult bands/transfer type or BitsPerSample for 12/16 bits support
out.writeShort(stripTileHeight); // height
out.writeShort(stripTileWidth); // width
- out.writeByte(raster.getNumBands()); // Number of components
+ out.writeByte(bands); // Number of components
- for (int comp = 0; comp < raster.getNumBands(); comp++) {
+ for (int comp = 0; comp < bands; comp++) {
out.writeByte(comp); // Component id
out.writeByte(comp == 0 ? 0x22 : 0x11); // h/v subsampling TODO: FixMe, consult YCbCrSubsampling
out.writeByte(comp); // Q table selector TODO: Consider merging if tables are equal
@@ -1097,10 +1372,10 @@ public class TIFFImageReader extends ImageReaderBase {
}
out.writeShort(JPEG.SOS);
- out.writeShort(6 + 2 * raster.getNumBands()); // SOS length
- out.writeByte(raster.getNumBands()); // Num comp
+ out.writeShort(6 + 2 * bands); // SOS length
+ out.writeByte(bands); // Num comp
- for (int component = 0; component < raster.getNumBands(); component++) {
+ for (int component = 0; component < bands; component++) {
out.writeByte(component); // Comp id
out.writeByte(component == 0 ? component : 0x10 + (component & 0xf)); // dc/ac selector
}
@@ -1138,94 +1413,188 @@ public class TIFFImageReader extends ImageReaderBase {
final int colsInTile, final int rowsInTile, final DataInput input)
throws IOException {
+ DataBuffer dataBuffer = tileRowRaster.getDataBuffer();
+ int bands = dataBuffer.getNumBanks();
+ boolean banded = bands > 1;
+
switch (tileRowRaster.getTransferType()) {
case DataBuffer.TYPE_BYTE:
- byte[] rowDataByte = ((DataBufferByte) tileRowRaster.getDataBuffer()).getData();
- for (int row = startRow; row < startRow + rowsInTile; row++) {
- if (row >= srcRegion.y + srcRegion.height) {
- break; // We're done with this tile
- }
+ for (int band = 0; band < bands; band++) {
+ int bank = banded ? ((BandedSampleModel) tileRowRaster.getSampleModel()).getBankIndices()[band] : band;
+ byte[] rowDataByte = ((DataBufferByte) dataBuffer).getData(bank);
+ WritableRaster destChannel = banded
+ ? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
+ : raster;
+ Raster srcChannel = banded
+ ? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
+ : tileRowRaster;
- input.readFully(rowDataByte);
-
- if (row % ySub == 0 && row >= srcRegion.y) {
- normalizeBlack(interpretation, rowDataByte);
-
- // Subsample horizontal
- if (xSub != 1) {
- for (int x = srcRegion.x / xSub * numBands; x < ((srcRegion.x + srcRegion.width) / xSub) * numBands; x += numBands) {
- for (int b = 0; b < numBands; b++) {
- rowDataByte[x + b] = rowDataByte[x * xSub + b];
- }
- }
+ for (int row = startRow; row < startRow + rowsInTile; row++) {
+ if (row >= srcRegion.y + srcRegion.height) {
+ break; // We're done with this tile
}
- raster.setDataElements(startCol, (row - srcRegion.y) / ySub, tileRowRaster);
+ input.readFully(rowDataByte);
+
+ if (row % ySub == 0 && row >= srcRegion.y) {
+ if (!banded) {
+ normalizeColor(interpretation, rowDataByte);
+ }
+
+ // Subsample horizontal
+ if (xSub != 1) {
+ for (int x = srcRegion.x / xSub * numBands; x < ((srcRegion.x + srcRegion.width) / xSub) * numBands; x += numBands) {
+ System.arraycopy(rowDataByte, x * xSub, rowDataByte, x, numBands);
+ }
+ }
+
+ destChannel.setDataElements(startCol, (row - srcRegion.y) / ySub, srcChannel);
+ }
+ // Else skip data
}
- // Else skip data
}
+// if (banded) {
+// // TODO: Normalize colors for tile (need to know tile region and sample model)
+// // Unfortunately, this will disable acceleration...
+// }
+
break;
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
- short[] rowDataShort = tileRowRaster.getTransferType() == DataBuffer.TYPE_USHORT
- ? ((DataBufferUShort) tileRowRaster.getDataBuffer()).getData()
- : ((DataBufferShort) tileRowRaster.getDataBuffer()).getData();
+ for (int band = 0; band < bands; band++) {
+ short[] rowDataShort = dataBuffer.getDataType() == DataBuffer.TYPE_USHORT
+ ? ((DataBufferUShort) dataBuffer).getData(band)
+ : ((DataBufferShort) dataBuffer).getData(band);
- for (int row = startRow; row < startRow + rowsInTile; row++) {
- if (row >= srcRegion.y + srcRegion.height) {
- break; // We're done with this tile
- }
+ WritableRaster destChannel = banded
+ ? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
+ : raster;
+ Raster srcChannel = banded
+ ? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
+ : tileRowRaster;
- readFully(input, rowDataShort);
-
- if (row >= srcRegion.y) {
- normalizeBlack(interpretation, rowDataShort);
-
- // Subsample horizontal
- if (xSub != 1) {
- for (int x = srcRegion.x / xSub * numBands; x < ((srcRegion.x + srcRegion.width) / xSub) * numBands; x += numBands) {
- for (int b = 0; b < numBands; b++) {
- rowDataShort[x + b] = rowDataShort[x * xSub + b];
- }
- }
+ for (int row = startRow; row < startRow + rowsInTile; row++) {
+ if (row >= srcRegion.y + srcRegion.height) {
+ break; // We're done with this tile
}
- raster.setDataElements(startCol, row - srcRegion.y, tileRowRaster);
+ readFully(input, rowDataShort);
+
+ if (row >= srcRegion.y) {
+ normalizeColor(interpretation, rowDataShort);
+
+ // Subsample horizontal
+ if (xSub != 1) {
+ for (int x = srcRegion.x / xSub * numBands; x < ((srcRegion.x + srcRegion.width) / xSub) * numBands; x += numBands) {
+ System.arraycopy(rowDataShort, x * xSub, rowDataShort, x, numBands);
+ }
+ }
+
+ destChannel.setDataElements(startCol, row - srcRegion.y, srcChannel);
+ // TODO: Possible speedup ~30%!:
+// raster.setDataElements(startCol, row - srcRegion.y, colsInTile, 1, rowDataShort);
+ }
+ // Else skip data
}
- // Else skip data
}
break;
case DataBuffer.TYPE_INT:
- int[] rowDataInt = ((DataBufferInt) tileRowRaster.getDataBuffer()).getData();
+ for (int band = 0; band < bands; band++) {
+ int[] rowDataInt = ((DataBufferInt) dataBuffer).getData(band);
- for (int row = startRow; row < startRow + rowsInTile; row++) {
- if (row >= srcRegion.y + srcRegion.height) {
- break; // We're done with this tile
- }
+ WritableRaster destChannel = banded
+ ? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
+ : raster;
+ Raster srcChannel = banded
+ ? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
+ : tileRowRaster;
- readFully(input, rowDataInt);
-
- if (row >= srcRegion.y) {
- normalizeBlack(interpretation, rowDataInt);
-
- // Subsample horizontal
- if (xSub != 1) {
- for (int x = srcRegion.x / xSub * numBands; x < ((srcRegion.x + srcRegion.width) / xSub) * numBands; x += numBands) {
- for (int b = 0; b < numBands; b++) {
- rowDataInt[x + b] = rowDataInt[x * xSub + b];
- }
- }
+ for (int row = startRow; row < startRow + rowsInTile; row++) {
+ if (row >= srcRegion.y + srcRegion.height) {
+ break; // We're done with this tile
}
- raster.setDataElements(startCol, row - srcRegion.y, tileRowRaster);
+ readFully(input, rowDataInt);
+
+ if (row >= srcRegion.y) {
+ normalizeColor(interpretation, rowDataInt);
+
+ // Subsample horizontal
+ if (xSub != 1) {
+ for (int x = srcRegion.x / xSub * numBands; x < ((srcRegion.x + srcRegion.width) / xSub) * numBands; x += numBands) {
+ System.arraycopy(rowDataInt, x * xSub, rowDataInt, x, numBands);
+ }
+ }
+
+ destChannel.setDataElements(startCol, row - srcRegion.y, srcChannel);
+ }
+ // Else skip data
}
- // Else skip data
}
break;
+
+ case DataBuffer.TYPE_FLOAT:
+ for (int band = 0; band < bands; band++) {
+ float[] rowDataFloat = ((DataBufferFloat) tileRowRaster.getDataBuffer()).getData(band);
+
+ WritableRaster destChannel = banded
+ ? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
+ : raster;
+ Raster srcChannel = banded
+ ? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
+ : tileRowRaster;
+
+ for (int row = startRow; row < startRow + rowsInTile; row++) {
+ if (row >= srcRegion.y + srcRegion.height) {
+ break; // We're done with this tile
+ }
+
+ readFully(input, rowDataFloat);
+
+ if (row >= srcRegion.y) {
+ // TODO: Allow param to decide tone mapping strategy, like in the HDRImageReader
+ clamp(rowDataFloat);
+ normalizeColor(interpretation, rowDataFloat);
+
+ // Subsample horizontal
+ if (xSub != 1) {
+ for (int x = srcRegion.x / xSub * numBands; x < ((srcRegion.x + srcRegion.width) / xSub) * numBands; x += numBands) {
+ System.arraycopy(rowDataFloat, x * xSub, rowDataFloat, x, numBands);
+ }
+ }
+
+ destChannel.setDataElements(startCol, row - srcRegion.y, srcChannel);
+ }
+ // Else skip data
+ }
+ }
+
+ break;
+ }
+ }
+
+ private void clamp(float[] rowDataFloat) {
+ for (int i = 0; i < rowDataFloat.length; i++) {
+ if (rowDataFloat[i] > 1) {
+ rowDataFloat[i] = 1;
+ }
+ }
+ }
+
+ // TODO: Candidate util method (with off/len + possibly byte order)
+ private void readFully(final DataInput input, final float[] rowDataFloat) throws IOException {
+ if (input instanceof ImageInputStream) {
+ ImageInputStream imageInputStream = (ImageInputStream) input;
+ imageInputStream.readFully(rowDataFloat, 0, rowDataFloat.length);
+ }
+ else {
+ for (int k = 0; k < rowDataFloat.length; k++) {
+ rowDataFloat[k] = input.readFloat();
+ }
}
}
@@ -1255,49 +1624,223 @@ public class TIFFImageReader extends ImageReaderBase {
}
}
- private void normalizeBlack(int photometricInterpretation, short[] data) {
- if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
- // Inverse values
- for (int i = 0; i < data.length; i++) {
- data[i] = (short) (0xffff - data[i] & 0xffff);
- }
+ private void normalizeColor(int photometricInterpretation, byte[] data) {
+ switch (photometricInterpretation) {
+ case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
+ // Inverse values
+ for (int i = 0; i < data.length; i++) {
+ data[i] ^= -1;
+ }
+
+ break;
+
+ case TIFFExtension.PHOTOMETRIC_CIELAB:
+ case TIFFExtension.PHOTOMETRIC_ICCLAB:
+ case TIFFExtension.PHOTOMETRIC_ITULAB:
+ // TODO: Whitepoint may be encoded in separate tag
+ CIELabColorConverter converter = new CIELabColorConverter(
+ photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB
+ ? Illuminant.D65
+ : Illuminant.D50
+ );
+ float[] temp = new float[3];
+
+ for (int i = 0; i < data.length; i += 3) {
+ // Unsigned scaled form 0...100
+ float LStar = (data[i] & 0xff) * 100f / 255.0f;
+ float aStar;
+ float bStar;
+
+ if (photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB) {
+ // -128...127
+ aStar = data[i + 1];
+ bStar = data[i + 2];
+ }
+ else {
+ // Assumes same data for ICC and ITU (unsigned)
+ // 0...255
+ aStar = (data[i + 1] & 0xff) - 128;
+ bStar = (data[i + 2] & 0xff) - 128;
+ }
+
+ converter.toRGB(LStar, aStar, bStar, temp);
+
+ data[i ] = (byte) temp[0];
+ data[i + 1] = (byte) temp[1];
+ data[i + 2] = (byte) temp[2];
+ }
+
+ break;
+
+ case TIFFExtension.PHOTOMETRIC_YCBCR:
+ Entry coefficients = currentIFD.getEntryById(TIFF.TAG_YCBCR_COEFFICIENTS);
+
+ if (coefficients == null) {
+ for (int i = 0; i < data.length; i += 3) {
+ YCbCrConverter.convertYCbCr2RGB(data, data, i);
+ }
+ }
+ else {
+ Rational[] value = (Rational[]) coefficients.getValue();
+ double[] yCbCrCoefficients = new double[] {value[0].doubleValue(), value[1].doubleValue(), value[2].doubleValue()};
+
+ for (int i = 0; i < data.length; i += 3) {
+ YCbCrConverter.convertYCbCr2RGB(data, data, yCbCrCoefficients, i);
+ }
+ }
+
+ break;
}
}
- private void normalizeBlack(int photometricInterpretation, int[] data) {
- if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
- // Inverse values
- for (int i = 0; i < data.length; i++) {
- data[i] = (0xffffffff - data[i]);
- }
+ private void normalizeColor(int photometricInterpretation, short[] data) {
+ switch (photometricInterpretation) {
+ case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
+ // Inverse values
+ for (int i = 0; i < data.length; i++) {
+ data[i] ^= -1;
+ }
+
+ break;
+
+ case TIFFExtension.PHOTOMETRIC_CIELAB:
+ case TIFFExtension.PHOTOMETRIC_ICCLAB:
+ case TIFFExtension.PHOTOMETRIC_ITULAB:
+ // TODO: Whitepoint may be encoded in separate tag
+ CIELabColorConverter converter = new CIELabColorConverter(
+ photometricInterpretation == TIFFExtension.PHOTOMETRIC_ITULAB
+ ? Illuminant.D65
+ : Illuminant.D50
+ );
+
+ float[] temp = new float[3];
+ float scaleL = photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB ? 65535f : 65280f; // Is for ICC lab, assumes the same for ITU....
+
+ for (int i = 0; i < data.length; i += 3) {
+ // Unsigned scaled form 0...100
+ float LStar = (data[i] & 0xffff) * 100.0f / scaleL;
+ float aStar;
+ float bStar;
+
+ if (photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB) {
+ // -32768...32767
+ aStar = data[i + 1] / 256f;
+ bStar = data[i + 2] / 256f;
+ }
+ else {
+ // Assumes same data for ICC and ITU (unsigned)
+ // 0...65535f
+ aStar = ((data[i + 1] & 0xffff) - 32768) / 256f;
+ bStar = ((data[i + 2] & 0xffff) - 32768) / 256f;
+ }
+
+ converter.toRGB(LStar, aStar, bStar, temp);
+
+ data[i ] = (short) (temp[0] * 257f);
+ data[i + 1] = (short) (temp[1] * 257f);
+ data[i + 2] = (short) (temp[2] * 257f);
+ }
+
+ break;
+
+ case TIFFExtension.PHOTOMETRIC_YCBCR:
+ double[] coefficients;
+
+ Entry coefficientsTag = currentIFD.getEntryById(TIFF.TAG_YCBCR_COEFFICIENTS);
+ if (coefficientsTag != null) {
+ Rational[] value = (Rational[]) coefficientsTag.getValue();
+ coefficients = new double[] {value[0].doubleValue(), value[1].doubleValue(), value[2].doubleValue()};
+ }
+ else {
+ coefficients = CCIR_601_1_COEFFICIENTS;
+ }
+
+ for (int i = 0; i < data.length; i += 3) {
+ convertYCbCr2RGB(data, data, coefficients, i);
+ }
}
}
- private void normalizeBlack(int photometricInterpretation, byte[] data) {
- if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO) {
- // Inverse values
- for (int i = 0; i < data.length; i++) {
- data[i] = (byte) (0xff - data[i] & 0xff);
- }
+ private void normalizeColor(int photometricInterpretation, int[] data) {
+ switch (photometricInterpretation) {
+ case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
+ // Inverse values
+ for (int i = 0; i < data.length; i++) {
+ data[i] ^= -1;
+ }
+
+ break;
+
+ case TIFFExtension.PHOTOMETRIC_CIELAB:
+ case TIFFExtension.PHOTOMETRIC_ICCLAB:
+ case TIFFExtension.PHOTOMETRIC_ITULAB:
+ case TIFFExtension.PHOTOMETRIC_YCBCR:
+ // Not supported
+ break;
}
}
+ private void normalizeColor(int photometricInterpretation, float[] data) {
+ switch (photometricInterpretation) {
+ case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
+ case TIFFExtension.PHOTOMETRIC_CIELAB:
+ case TIFFExtension.PHOTOMETRIC_ICCLAB:
+ case TIFFExtension.PHOTOMETRIC_ITULAB:
+ case TIFFExtension.PHOTOMETRIC_YCBCR:
+ // Not supported
+ break;
+ }
+ }
+
+ private void convertYCbCr2RGB(final short[] yCbCr, final short[] rgb, final double[] coefficients, final int offset) {
+ int y;
+ int cb;
+ int cr;
+
+ y = (yCbCr[offset + 0] & 0xffff);
+ cb = (yCbCr[offset + 1] & 0xffff) - 32768;
+ cr = (yCbCr[offset + 2] & 0xffff) - 32768;
+
+ double lumaRed = coefficients[0];
+ double lumaGreen = coefficients[1];
+ double lumaBlue = coefficients[2];
+
+ int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
+ int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
+ int green = (int) Math.round((y - lumaRed * (red) - lumaBlue * (blue)) / lumaGreen);
+
+ short r = clampShort(red);
+ short g = clampShort(green);
+ short b = clampShort(blue);
+
+ // Short values, depends on byte order!
+ rgb[offset] = r;
+ rgb[offset + 1] = g;
+ rgb[offset + 2] = b;
+ }
+
+ private short clampShort(int val) {
+ return (short) Math.max(0, Math.min(0xffff, val));
+ }
+
private InputStream createDecompressorStream(final int compression, final int width, final int bands, final InputStream stream) throws IOException {
- switch (compression) {
+ switch (compression) {
case TIFFBaseline.COMPRESSION_NONE:
return stream;
case TIFFBaseline.COMPRESSION_PACKBITS:
return new DecoderStream(stream, new PackBitsDecoder(), 1024);
case TIFFExtension.COMPRESSION_LZW:
- return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), width * bands);
+ return new DecoderStream(stream, LZWDecoder.create(LZWDecoder.isOldBitReversedStream(stream)), Math.max(width * bands, 1024));
case TIFFExtension.COMPRESSION_ZLIB:
// TIFFphotoshop.pdf (aka TIFF specification, supplement 2) says ZLIB (8) and DEFLATE (32946) algorithms are identical
case TIFFExtension.COMPRESSION_DEFLATE:
return new InflaterInputStream(stream, new Inflater(), 1024);
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
+ return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1),0L);
case TIFFExtension.COMPRESSION_CCITT_T4:
+ return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1),getValueAsLongWithDefault(TIFF.TAG_GROUP3OPTIONS, 0L));
case TIFFExtension.COMPRESSION_CCITT_T6:
- return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1));
+ return new CCITTFaxDecoderStream(stream, width, compression, getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, 1),getValueAsLongWithDefault(TIFF.TAG_GROUP4OPTIONS, 0L));
default:
throw new IllegalArgumentException("Unsupported TIFF compression: " + compression);
}
@@ -1318,6 +1861,7 @@ public class TIFFImageReader extends ImageReaderBase {
private long[] getValueAsLongArray(final int tag, final String tagName, boolean required) throws IIOException {
Entry entry = currentIFD.getEntryById(tag);
+
if (entry == null) {
if (required) {
throw new IIOException("Missing TIFF tag " + tagName);
@@ -1358,14 +1902,24 @@ public class TIFFImageReader extends ImageReaderBase {
return value;
}
- public ICC_Profile getICCProfile() {
+ private ICC_Profile getICCProfile() throws IOException {
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
- if (entry == null) {
- return null;
+
+ if (entry != null) {
+ byte[] value = (byte[]) entry.getValue();
+
+ try {
+ // WEIRDNESS: Reading profile from InputStream is somehow more compatible
+ // than reading from byte array (chops off extra bytes + validates profile).
+ ICC_Profile profile = ICC_Profile.getInstance(new ByteArrayInputStream(value));
+ return ColorSpaces.validateProfile(profile);
+ }
+ catch (CMMException | IllegalArgumentException ignore) {
+ processWarningOccurred("Ignoring broken/incompatible ICC profile: " + ignore.getMessage());
+ }
}
- byte[] value = (byte[]) entry.getValue();
- return ICC_Profile.getInstance(value);
+ return null;
}
// TODO: Tiling support
@@ -1377,7 +1931,24 @@ public class TIFFImageReader extends ImageReaderBase {
// TODO: Thumbnail support
+ /// Metadata
+
+ @Override
+ public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
+ readIFD(imageIndex);
+
+ return new TIFFImageMetadata(currentIFD);
+ }
+
+ @Override
+ public IIOMetadata getStreamMetadata() throws IOException {
+ // TODO:
+ return super.getStreamMetadata();
+ }
+
public static void main(final String[] args) throws IOException {
+ ImageIO.setUseCache(false);
+
for (final String arg : args) {
File file = new File(arg);
@@ -1447,22 +2018,30 @@ public class TIFFImageReader extends ImageReaderBase {
// param.setSourceSubsampling(sub, sub, 0, 0);
// }
- long start = System.currentTimeMillis();
+ try {
+ long start = System.currentTimeMillis();
// int width = reader.getWidth(imageNo);
// int height = reader.getHeight(imageNo);
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
// param.setSourceRegion(new Rectangle(100, 300, 400, 400));
+// param.setSourceRegion(new Rectangle(3, 3, 9, 9));
// param.setDestinationOffset(new Point(50, 150));
// param.setSourceSubsampling(2, 2, 0, 0);
- BufferedImage image = reader.read(imageNo, param);
- System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
+ BufferedImage image = reader.read(imageNo, param);
+ System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
- IIOMetadata metadata = reader.getImageMetadata(imageNo);
- if (metadata != null) {
- new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(metadata.getNativeMetadataFormatName()), false);
- }
+// IIOMetadata metadata = reader.getImageMetadata(imageNo);
+// if (metadata != null) {
+// if (metadata.getNativeMetadataFormatName() != null) {
+// new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(metadata.getNativeMetadataFormatName()), false);
+// }
+// /*else*/
+// if (metadata.isStandardMetadataFormatSupported()) {
+// new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
+// }
+// }
- System.err.println("image: " + image);
+ System.err.println("image: " + image);
// File tempFile = File.createTempFile("lzw-", ".bin");
// byte[] data = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
@@ -1478,7 +2057,7 @@ public class TIFFImageReader extends ImageReaderBase {
//
// System.err.println("tempFile: " + tempFile.getAbsolutePath());
- // image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
+ // image = new ResampleOp(reader.getWidth(0) / 4, reader.getHeight(0) / 4, ResampleOp.FILTER_LANCZOS).filter(image, null);
//
// int maxW = 800;
// int maxH = 800;
@@ -1495,30 +2074,35 @@ public class TIFFImageReader extends ImageReaderBase {
// // System.err.println("Scale time: " + (System.currentTimeMillis() - start) + " ms");
// }
- if (image.getType() == BufferedImage.TYPE_CUSTOM) {
- start = System.currentTimeMillis();
- image = new ColorConvertOp(null).filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB));
- System.err.println("Conversion time: " + (System.currentTimeMillis() - start) + " ms");
- }
+ if (image.getType() == BufferedImage.TYPE_CUSTOM) {
+ start = System.currentTimeMillis();
+ image = new ColorConvertOp(null).filter(image, new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB));
+ System.err.println("Conversion time: " + (System.currentTimeMillis() - start) + " ms");
+ }
- showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(imageNo), reader.getHeight(imageNo)));
+ showIt(image, String.format("Image: %s [%d x %d]", file.getName(), reader.getWidth(imageNo), reader.getHeight(imageNo)));
- try {
- int numThumbnails = reader.getNumThumbnails(0);
- for (int thumbnailNo = 0; thumbnailNo < numThumbnails; thumbnailNo++) {
- BufferedImage thumbnail = reader.readThumbnail(imageNo, thumbnailNo);
- // System.err.println("thumbnail: " + thumbnail);
- showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
+ try {
+ int numThumbnails = reader.getNumThumbnails(0);
+ for (int thumbnailNo = 0; thumbnailNo < numThumbnails; thumbnailNo++) {
+ BufferedImage thumbnail = reader.readThumbnail(imageNo, thumbnailNo);
+ // System.err.println("thumbnail: " + thumbnail);
+ showIt(thumbnail, String.format("Thumbnail: %s [%d x %d]", file.getName(), thumbnail.getWidth(), thumbnail.getHeight()));
+ }
+ }
+ catch (IIOException e) {
+ System.err.println("Could not read thumbnails: " + e.getMessage());
+ e.printStackTrace();
}
}
- catch (IIOException e) {
- System.err.println("Could not read thumbnails: " + e.getMessage());
- e.printStackTrace();
+ catch (Throwable t) {
+ System.err.println(file + " image " + imageNo + " can't be read:");
+ t.printStackTrace();
}
}
}
catch (Throwable t) {
- System.err.println(file);
+ System.err.println(file + " can't be read:");
t.printStackTrace();
}
finally {
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java
index 69eba0c1..4ed13ee6 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriteParam.java
@@ -39,16 +39,20 @@ import java.util.Locale;
* @version $Id: TIFFImageWriteParam.java,v 1.0 18.09.13 12:47 haraldk Exp$
*/
public final class TIFFImageWriteParam extends ImageWriteParam {
- // TODO: Support no compression (None/1)
- // TODO: Support ZLIB (/Deflate) compression (8)
- // TODO: Support PackBits compression (32773)
- // TODO: Support JPEG compression (7)
- // TODO: Support CCITT Modified Huffman compression (2)
- // TODO: Support LZW compression (5)?
+ // TODO: Support CCITT Modified Huffman compression (2) BASELINE!!
+ // TODO: Support CCITT T.4 (3)
+ // TODO: Support CCITT T.6 (4)
// TODO: Support JBIG compression via ImageIO plugin/delegate?
// TODO: Support JPEG2000 compression via ImageIO plugin/delegate?
// TODO: Support tiling
- // TODO: Support predictor. See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
+ // TODO: Support OPTIONAL predictor. See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
+
+ // DONE:
+ // Support no compression (None/1)
+ // Support ZLIB (/Deflate) compression (8)
+ // Support PackBits compression (32773)
+ // Support LZW compression (5)?
+ // Support JPEG compression (7)
TIFFImageWriteParam() {
this(Locale.getDefault());
@@ -59,7 +63,12 @@ public final class TIFFImageWriteParam extends ImageWriteParam {
// NOTE: We use the same spelling/casing as the JAI equivalent to be as compatible as possible
// See: http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/TIFFImageWriteParam.html
- compressionTypes = new String[] {"None", /* "CCITT RLE", "CCITT T.4", "CCITT T.6", */ "LZW", "JPEG", "ZLib", "PackBits", "Deflate", /* "EXIF JPEG" */ };
+ compressionTypes = new String[] {
+ "None",
+ "CCITT RLE", "CCITT T.4", "CCITT T.6",
+ "LZW", "JPEG", "ZLib", "PackBits", "Deflate",
+ null/* "EXIF JPEG" */ // A well-defined form of "Old-style JPEG", no tables/process, only 513 (offset) and 514 (length)
+ };
compressionType = compressionTypes[0];
canWriteCompressed = true;
}
@@ -102,6 +111,18 @@ public final class TIFFImageWriteParam extends ImageWriteParam {
else if (param.getCompressionType().equals("JPEG")) {
return TIFFExtension.COMPRESSION_JPEG;
}
+ else if (param.getCompressionType().equals("CCITT RLE")) {
+ return TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE;
+ }
+ else if (param.getCompressionType().equals("CCITT T.4")) {
+ return TIFFExtension.COMPRESSION_CCITT_T4;
+ }
+ else if (param.getCompressionType().equals("CCITT T.6")) {
+ return TIFFExtension.COMPRESSION_CCITT_T6;
+ }
+// else if (param.getCompressionType().equals("EXIF JPEG")) {
+// return TIFFExtension.COMPRESSION_OLD_JPEG;
+// }
throw new IllegalArgumentException(String.format("Unsupported compression type: %s", param.getCompressionType()));
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java
index 93eac986..15d934fa 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java
@@ -31,16 +31,21 @@ package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
+import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.exif.EXIFWriter;
import com.twelvemonkeys.imageio.metadata.exif.Rational;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
+import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.io.enc.EncoderStream;
import com.twelvemonkeys.io.enc.PackBitsEncoder;
+import com.twelvemonkeys.lang.Validate;
import javax.imageio.*;
+import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
@@ -49,10 +54,9 @@ import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.image.*;
import java.io.*;
+import java.lang.reflect.Array;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
@@ -65,21 +69,20 @@ import java.util.zip.DeflaterOutputStream;
*/
public final class TIFFImageWriter extends ImageWriterBase {
// Short term
- // TODO: Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
- // TODO: Use sensible defaults for compression based on input? None is sensible... :-)
+ // TODO: Support more of the ImageIO metadata (ie. compression from metadata, etc)
// Long term
// TODO: Support tiling
// TODO: Support thumbnails
- // TODO: Support ImageIO metadata
// TODO: Support CCITT Modified Huffman compression (2)
- // TODO: Full "Baseline TIFF" support
- // TODO: Support LZW compression (5)?
+ // TODO: Full "Baseline TIFF" support (pending CCITT compression 2)
+ // TODO: CCITT compressions T.4 and T.6
+ // TODO: Support JPEG compression of CMYK data (pending JPEGImageWriter CMYK write support)
// ----
// TODO: Support storing multiple images in one stream (multi-page TIFF)
// TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata
// TODO: Support use-case: Transcode multi-page TIFF to multiple single-page TIFFs with metadata
- // TODO: Support use-case: Losslessly transcode JPEG to JPEG in TIFF with (EXIF) metadata (and back)
+ // TODO: Support use-case: Losslessly transcode JPEG to JPEG-in-TIFF with (EXIF) metadata (and back)
// Very long term...
// TODO: Support JBIG compression via ImageIO plugin/delegate? Pending support in Reader
@@ -91,24 +94,111 @@ public final class TIFFImageWriter extends ImageWriterBase {
// Support predictor. See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
// Support PackBits compression (32773) - easy - BASELINE
// Support ZLIB (/Deflate) compression (8) - easy
+ // Support LZW compression (5)
+ // Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
+ // Use sensible defaults for compression based on input? None is sensible... :-)
+ // Support resolution, resolution unit and software tags from ImageIO metadata
public static final Rational STANDARD_DPI = new Rational(72);
+ /**
+ * Flag for active sequence writing
+ */
+ private boolean isWritingSequence = false;
+
+ /**
+ * Metadata writer for sequence writing
+ */
+ private EXIFWriter sequenceExifWriter = null;
+
+ /**
+ * Position of last IFD Pointer on active sequence writing
+ */
+ private long sequenceLastIFDPos = -1;
+
TIFFImageWriter(final ImageWriterSpi provider) {
super(provider);
}
+ @Override
+ public void setOutput(final Object output) {
+ super.setOutput(output);
+
+ // TODO: Allow appending/partly overwrite of existing file...
+ }
+
static final class TIFFEntry extends AbstractEntry {
- TIFFEntry(Object identifier, Object value) {
+ // TODO: Expose a merge of this and the EXIFEntry class...
+ private final short type;
+
+ private static short guessType(final Object val) {
+ // TODO: This code is duplicated in EXIFWriter.getType, needs refactor!
+ Object value = Validate.notNull(val);
+
+ boolean array = value.getClass().isArray();
+ if (array) {
+ value = Array.get(value, 0);
+ }
+
+ // Note: This "narrowing" is to keep data consistent between read/write.
+ // TODO: Check for negative values and use signed types?
+ if (value instanceof Byte) {
+ return TIFF.TYPE_BYTE;
+ }
+ if (value instanceof Short) {
+ if (!array && (Short) value < Byte.MAX_VALUE) {
+ return TIFF.TYPE_BYTE;
+ }
+
+ return TIFF.TYPE_SHORT;
+ }
+ if (value instanceof Integer) {
+ if (!array && (Integer) value < Short.MAX_VALUE) {
+ return TIFF.TYPE_SHORT;
+ }
+
+ return TIFF.TYPE_LONG;
+ }
+ if (value instanceof Long) {
+ if (!array && (Long) value < Integer.MAX_VALUE) {
+ return TIFF.TYPE_LONG;
+ }
+ }
+
+ if (value instanceof Rational) {
+ return TIFF.TYPE_RATIONAL;
+ }
+
+ if (value instanceof String) {
+ return TIFF.TYPE_ASCII;
+ }
+
+ // TODO: More types
+
+ throw new UnsupportedOperationException(String.format("Method guessType not implemented for value of type %s", value.getClass()));
+ }
+
+ TIFFEntry(final int identifier, final Object value) {
+ this(identifier, guessType(value), value);
+ }
+
+ TIFFEntry(int identifier, short type, Object value) {
super(identifier, value);
+ this.type = type;
+ }
+
+ @Override
+ public String getTypeName() {
+ return TIFF.TYPE_NAMES[type];
}
}
@Override
- public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
+ public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
// TODO: Validate input
assertOutput();
+ // TODO: streamMetadata?
// TODO: Consider writing TIFF header, offset to IFD0 (leave blank), write image data with correct
// tiling/compression/etc, then write IFD0, go back and update IFD0 offset?
@@ -116,22 +206,38 @@ public final class TIFFImageWriter extends ImageWriterBase {
// Write minimal TIFF header (required "Baseline" fields)
// Use EXIFWriter to write leading metadata (TODO: consider rename to TTIFFWriter, again...)
// TODO: Make TIFFEntry and possibly TIFFDirectory? public
+ EXIFWriter exifWriter = new EXIFWriter();
+ exifWriter.writeTIFFHeader(imageOutput);
+ long IFD0Pos = imageOutput.getStreamPosition();
+ writePage(image, param, exifWriter, IFD0Pos);
+ imageOutput.writeInt(0); // EOF
+ imageOutput.flush();
+ }
+
+ private long writePage(IIOImage image, ImageWriteParam param, EXIFWriter exifWriter, long lastIFDPointer)
+ throws IOException {
RenderedImage renderedImage = image.getRenderedImage();
ColorModel colorModel = renderedImage.getColorModel();
int numComponents = colorModel.getNumComponents();
+ TIFFImageMetadata metadata;
+ if (image.getMetadata() != null) {
+ metadata = convertImageMetadata(image.getMetadata(), ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
+ }
+ else {
+ metadata = initMeta(null, ImageTypeSpecifier.createFromRenderedImage(renderedImage), param);
+ }
+
SampleModel sampleModel = renderedImage.getSampleModel();
int[] bandOffsets;
int[] bitOffsets;
if (sampleModel instanceof ComponentSampleModel) {
bandOffsets = ((ComponentSampleModel) sampleModel).getBandOffsets();
-// System.err.println("bandOffsets: " + Arrays.toString(bandOffsets));
- bitOffsets = null;
+ bitOffsets = null;
}
else if (sampleModel instanceof SinglePixelPackedSampleModel) {
bitOffsets = ((SinglePixelPackedSampleModel) sampleModel).getBitOffsets();
-// System.err.println("bitOffsets: " + Arrays.toString(bitOffsets));
bandOffsets = null;
}
else if (sampleModel instanceof MultiPixelPackedSampleModel) {
@@ -142,117 +248,207 @@ public final class TIFFImageWriter extends ImageWriterBase {
throw new IllegalArgumentException("Unknown bit/bandOffsets for sample model: " + sampleModel);
}
- List entries = new ArrayList();
- entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
- entries.add(new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
-// entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, 1)); // (optional)
- entries.add(new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, asShortArray(sampleModel.getSampleSize())));
- // If numComponents > 3, write ExtraSamples
- if (numComponents > 3) {
- // TODO: Write per component > 3
+ Map entries = new LinkedHashMap<>();
+ entries.put(TIFF.TAG_IMAGE_WIDTH, new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, renderedImage.getWidth()));
+ entries.put(TIFF.TAG_IMAGE_HEIGHT, new TIFFEntry(TIFF.TAG_IMAGE_HEIGHT, renderedImage.getHeight()));
+ // entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, 1)); // (optional)
+ entries.put(TIFF.TAG_BITS_PER_SAMPLE, new TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, asShortArray(sampleModel.getSampleSize())));
+ // If numComponents > numColorComponents, write ExtraSamples
+ if (numComponents > colorModel.getNumColorComponents()) {
+ // TODO: Write per component > numColorComponents
if (colorModel.hasAlpha()) {
- entries.add(new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, colorModel.isAlphaPremultiplied() ? TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA : TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA));
+ entries.put(TIFF.TAG_EXTRA_SAMPLES, new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, colorModel.isAlphaPremultiplied()
+ ? TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA
+ : TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA));
}
else {
- entries.add(new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, TIFFBaseline.EXTRASAMPLE_UNSPECIFIED));
+ entries.put(TIFF.TAG_EXTRA_SAMPLES, new TIFFEntry(TIFF.TAG_EXTRA_SAMPLES, TIFFBaseline.EXTRASAMPLE_UNSPECIFIED));
}
}
+
// Write compression field from param or metadata
- int compression = TIFFImageWriteParam.getCompressionType(param);
- entries.add(new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
- // TODO: Let param control
+ int compression;
+ if ((param == null || param.getCompressionMode() == TIFFImageWriteParam.MODE_COPY_FROM_METADATA)
+ && image.getMetadata() != null && metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION) != null) {
+ compression = (int) metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION).getValue();
+ }
+ else {
+ compression = TIFFImageWriteParam.getCompressionType(param);
+ }
+ entries.put(TIFF.TAG_COMPRESSION, new TIFFEntry(TIFF.TAG_COMPRESSION, compression));
+
+ // TODO: Let param/metadata control predictor
+ // TODO: Depending on param.getCompressionMode(): DISABLED/EXPLICIT/COPY_FROM_METADATA/DEFAULT
switch (compression) {
case TIFFExtension.COMPRESSION_ZLIB:
case TIFFExtension.COMPRESSION_DEFLATE:
case TIFFExtension.COMPRESSION_LZW:
- entries.add(new TIFFEntry(TIFF.TAG_PREDICTOR, TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING));
+ entries.put(TIFF.TAG_PREDICTOR, new TIFFEntry(TIFF.TAG_PREDICTOR, TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING));
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T4:
+ Entry group3options = metadata.getIFD().getEntryById(TIFF.TAG_GROUP3OPTIONS);
+ if (group3options == null) {
+ group3options = new TIFFEntry(TIFF.TAG_GROUP3OPTIONS, (long) TIFFExtension.GROUP3OPT_2DENCODING);
+ }
+ entries.put(TIFF.TAG_GROUP3OPTIONS, group3options);
+ break;
+ case TIFFExtension.COMPRESSION_CCITT_T6:
+ Entry group4options = metadata.getIFD().getEntryById(TIFF.TAG_GROUP4OPTIONS);
+ if (group4options == null) {
+ group4options = new TIFFEntry(TIFF.TAG_GROUP4OPTIONS, 0L);
+ }
+ entries.put(TIFF.TAG_GROUP4OPTIONS, group4options);
+ break;
default:
}
- int photometric = getPhotometricInterpretation(colorModel);
- entries.add(new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, photometric));
+ // TODO: We might want to support CMYK in JPEG as well... Pending JPEG CMYK write support.
+ int photometric = compression == TIFFExtension.COMPRESSION_JPEG ?
+ TIFFExtension.PHOTOMETRIC_YCBCR :
+ getPhotometricInterpretation(colorModel);
+ entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, photometric));
if (photometric == TIFFBaseline.PHOTOMETRIC_PALETTE && colorModel instanceof IndexColorModel) {
- entries.add(new TIFFEntry(TIFF.TAG_COLOR_MAP, createColorMap((IndexColorModel) colorModel)));
- entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 1));
+ entries.put(TIFF.TAG_COLOR_MAP, new TIFFEntry(TIFF.TAG_COLOR_MAP, createColorMap((IndexColorModel) colorModel)));
+ entries.put(TIFF.TAG_SAMPLES_PER_PIXEL, new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 1));
}
else {
- entries.add(new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, numComponents));
+ if (colorModel.getPixelSize() == 1) {
+ numComponents = 1;
+ }
- // TODO: What is the default TIFF color space?
+ entries.put(TIFF.TAG_SAMPLES_PER_PIXEL, new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, numComponents));
+
+ // Note: Assuming sRGB to be the default RGB interpretation
ColorSpace colorSpace = colorModel.getColorSpace();
- if (colorSpace instanceof ICC_ColorSpace) {
- entries.add(new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
+ if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB()) {
+ entries.put(TIFF.TAG_ICC_PROFILE, new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
}
}
- if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT /* TODO: if (isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
- entries.add(new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
+ // Default sample format SAMPLEFORMAT_UINT need not be written
+ if (sampleModel.getDataType() == DataBuffer.TYPE_SHORT/* TODO: if isSigned(sampleModel.getDataType) or getSampleFormat(sampleModel) != 0 */) {
+ entries.put(TIFF.TAG_SAMPLE_FORMAT, new TIFFEntry(TIFF.TAG_SAMPLE_FORMAT, TIFFExtension.SAMPLEFORMAT_INT));
+ }
+ // TODO: Float values!
+
+ // Get Software from metadata, or use default
+ Entry software = metadata.getIFD().getEntryById(TIFF.TAG_SOFTWARE);
+ entries.put(TIFF.TAG_SOFTWARE, software != null
+ ? software
+ : new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer " + originatingProvider.getVersion()));
+
+ // Copy metadata to output
+ int[] copyTags = {
+ TIFF.TAG_ORIENTATION,
+ TIFF.TAG_DATE_TIME,
+ TIFF.TAG_DOCUMENT_NAME,
+ TIFF.TAG_IMAGE_DESCRIPTION,
+ TIFF.TAG_MAKE,
+ TIFF.TAG_MODEL,
+ TIFF.TAG_PAGE_NAME,
+ TIFF.TAG_PAGE_NUMBER,
+ TIFF.TAG_ARTIST,
+ TIFF.TAG_HOST_COMPUTER,
+ TIFF.TAG_COPYRIGHT
+ };
+ for (int tagID : copyTags) {
+ Entry entry = metadata.getIFD().getEntryById(tagID);
+ if (entry != null) {
+ entries.put(tagID, entry);
+ }
}
- entries.add(new TIFFEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO TIFF writer")); // TODO: Get from metadata (optional) + fill in version number
-
- entries.add(new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
- entries.add(new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
- entries.add(new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
+ // Get X/YResolution and ResolutionUnit from metadata if set, otherwise use defaults
+ // TODO: Add logic here OR in metadata merging, to make sure these 3 values are consistent.
+ Entry xRes = metadata.getIFD().getEntryById(TIFF.TAG_X_RESOLUTION);
+ entries.put(TIFF.TAG_X_RESOLUTION, xRes != null ? xRes : new TIFFEntry(TIFF.TAG_X_RESOLUTION, STANDARD_DPI));
+ Entry yRes = metadata.getIFD().getEntryById(TIFF.TAG_Y_RESOLUTION);
+ entries.put(TIFF.TAG_Y_RESOLUTION, yRes != null ? yRes : new TIFFEntry(TIFF.TAG_Y_RESOLUTION, STANDARD_DPI));
+ Entry resUnit = metadata.getIFD().getEntryById(TIFF.TAG_RESOLUTION_UNIT);
+ entries.put(TIFF.TAG_RESOLUTION_UNIT,
+ resUnit != null ? resUnit : new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFFBaseline.RESOLUTION_UNIT_DPI));
// TODO: RowsPerStrip - can be entire image (or even 2^32 -1), but it's recommended to write "about 8K bytes" per strip
- entries.add(new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
+ entries.put(TIFF.TAG_ROWS_PER_STRIP, new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, Integer.MAX_VALUE)); // TODO: Allowed but not recommended
// - StripByteCounts - for no compression, entire image data... (TODO: How to know the byte counts prior to writing data?)
TIFFEntry dummyStripByteCounts = new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, -1);
- entries.add(dummyStripByteCounts); // Updated later
+ entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, dummyStripByteCounts); // Updated later
// - StripOffsets - can be offset to single strip only (TODO: but how large is the IFD data...???)
TIFFEntry dummyStripOffsets = new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, -1);
- entries.add(dummyStripOffsets); // Updated later
+ entries.put(TIFF.TAG_STRIP_OFFSETS, dummyStripOffsets); // Updated later
- // TODO: If tiled, write tile indexes etc, or always do that?
-
- EXIFWriter exifWriter = new EXIFWriter();
+ // TODO: If tiled, write tile indexes etc
+ // Depending on param.getTilingMode
+ long nextIFDPointer = -1;
+ long stripOffset = -1;
+ long stripByteCount = 0;
if (compression == TIFFBaseline.COMPRESSION_NONE) {
- // This implementation, allows semi-streaming-compatible uncompressed TIFFs
- long streamOffset = exifWriter.computeIFDSize(entries) + 12; // 12 == 4 byte magic, 4 byte IDD 0 pointer, 4 byte EOF
+ long ifdOffset = exifWriter.computeIFDOffsetSize(entries.values());
+ long dataLength = renderedImage.getWidth() * renderedImage.getHeight() * numComponents;
+ long pointerPos = imageOutput.getStreamPosition() + dataLength + 4 + ifdOffset;
+ imageOutput.writeInt((int) pointerPos);
+ }
+ else {
+ imageOutput.writeInt(0); // Update IFD Pointer later
+ }
- entries.remove(dummyStripByteCounts);
- entries.add(new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, renderedImage.getWidth() * renderedImage.getHeight() * numComponents));
+ stripOffset = imageOutput.getStreamPosition();
+ // TODO: Create compressor stream per Tile/Strip
+ if (compression == TIFFExtension.COMPRESSION_JPEG) {
+ Iterator writers = ImageIO.getImageWritersByFormatName("JPEG");
+
+ if (!writers.hasNext()) {
+ // This can only happen if someone deliberately uninstalled it
+ throw new IIOException("No JPEG ImageWriter found!");
+ }
+
+ ImageWriter jpegWriter = writers.next();
+ try {
+ jpegWriter.setOutput(new SubImageOutputStream(imageOutput));
+ jpegWriter.write(renderedImage);
+ }
+ finally {
+ jpegWriter.dispose();
+ }
+ }
+ else {
+ // Write image data
+ writeImageData(createCompressorStream(renderedImage, param, entries), renderedImage, numComponents, bandOffsets,
+ bitOffsets);
+ }
+ stripByteCount = imageOutput.getStreamPosition() - stripOffset;
+
+ // Update IFD0-pointer, and write IFD
+ if (compression != TIFFBaseline.COMPRESSION_NONE) {
entries.remove(dummyStripOffsets);
- entries.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, streamOffset));
+ entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, stripOffset));
+ entries.remove(dummyStripByteCounts);
+ entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, stripByteCount));
- exifWriter.write(entries, imageOutput); // NOTE: Writer takes case of ordering tags
+ long idfOffset = exifWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
+ nextIFDPointer = imageOutput.getStreamPosition();
+ imageOutput.seek(lastIFDPointer);
+ imageOutput.writeInt((int) idfOffset);
+ imageOutput.seek(nextIFDPointer);
imageOutput.flush();
}
else {
- // Unless compression == 1 / COMPRESSION_NONE (and all offsets known), write only TIFF header/magic + leave room for IFD0 offset
- exifWriter.writeTIFFHeader(imageOutput);
- imageOutput.writeInt(-1); // IFD0 pointer, will be updated later
- }
-
- // TODO: Create compressor stream per Tile/Strip
- // Write image data
- writeImageData(createCompressorStream(renderedImage, param), renderedImage, numComponents, bandOffsets, bitOffsets);
-
- // TODO: Update IFD0-pointer, and write IFD
- if (compression != TIFFBaseline.COMPRESSION_NONE) {
- long streamPosition = imageOutput.getStreamPosition();
-
entries.remove(dummyStripOffsets);
- entries.add(new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, 8));
+ entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, stripOffset));
entries.remove(dummyStripByteCounts);
- entries.add(new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, streamPosition - 8));
+ entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, stripByteCount));
- long ifdOffset = exifWriter.writeIFD(entries, imageOutput);
- imageOutput.writeInt(0); // Next IFD (none)
- streamPosition = imageOutput.getStreamPosition();
-
- // Update IFD0 pointer
- imageOutput.seek(4);
- imageOutput.writeInt((int) ifdOffset);
- imageOutput.seek(streamPosition);
+ exifWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
+ nextIFDPointer = imageOutput.getStreamPosition();
imageOutput.flush();
}
+
+ return nextIFDPointer;
}
- private DataOutput createCompressorStream(RenderedImage image, ImageWriteParam param) {
+ private DataOutput createCompressorStream(RenderedImage image, ImageWriteParam param, Map entries) {
/*
36 MB test data:
@@ -304,8 +500,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
output.length: 12600399
*/
- // TODO: Use predictor only by default for -PackBits,- LZW and ZLib/Deflate, unless explicitly disabled (ImageWriteParam)
- int compression = TIFFImageWriteParam.getCompressionType(param);
+ // Use predictor by default for LZW and ZLib/Deflate
+ // TODO: Unless explicitly disabled in TIFFImageWriteParam
+ int compression = (int) entries.get(TIFF.TAG_COMPRESSION).getValue();
OutputStream stream;
switch (compression) {
@@ -321,7 +518,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
case TIFFExtension.COMPRESSION_ZLIB:
case TIFFExtension.COMPRESSION_DEFLATE:
- int deflateSetting = Deflater.BEST_SPEED; // This is consistent with default compression quality being 1.0 and 0 meaning max compression....
+ int deflateSetting = Deflater.BEST_SPEED; // This is consistent with default compression quality being 1.0 and 0 meaning max compression...
if (param.getCompressionMode() == ImageWriteParam.MODE_EXPLICIT) {
// TODO: Determine how to interpret compression quality...
// Docs says:
@@ -348,8 +545,27 @@ public final class TIFFImageWriter extends ImageWriterBase {
case TIFFExtension.COMPRESSION_LZW:
stream = IIOUtil.createStreamAdapter(imageOutput);
- stream = new EncoderStream(stream, new LZWEncoder((image.getTileWidth() * image.getTileHeight() * image.getTile(0, 0).getNumBands() * image.getColorModel().getComponentSize(0) + 7) / 8));
- stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(), image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
+ stream = new EncoderStream(stream, new LZWEncoder((image.getTileWidth() * image.getTileHeight()
+ * image.getTile(0, 0).getNumBands() * image.getColorModel().getComponentSize(0) + 7) / 8));
+ stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(),
+ image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
+
+ return new DataOutputStream(stream);
+ case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
+ case TIFFExtension.COMPRESSION_CCITT_T4:
+ case TIFFExtension.COMPRESSION_CCITT_T6:
+ long option = 0L;
+ if (compression != TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE) {
+ option = (long) entries.get(compression == TIFFExtension.COMPRESSION_CCITT_T4
+ ? TIFF.TAG_GROUP3OPTIONS
+ : TIFF.TAG_GROUP4OPTIONS).getValue();
+ }
+ Entry fillOrderEntry = entries.get(TIFF.TAG_FILL_ORDER);
+ int fillOrder = (int) (fillOrderEntry != null
+ ? fillOrderEntry.getValue()
+ : TIFFBaseline.FILL_LEFT_TO_RIGHT);
+ stream = IIOUtil.createStreamAdapter(imageOutput);
+ stream = new CCITTFaxEncoderStream(stream, image.getTileWidth(), image.getTileHeight(), compression, fillOrder, option);
return new DataOutputStream(stream);
}
@@ -358,12 +574,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
}
private int getPhotometricInterpretation(final ColorModel colorModel) {
- if (colorModel.getNumComponents() == 1 && colorModel.getComponentSize(0) == 1) {
+ if (colorModel.getPixelSize() == 1) {
if (colorModel instanceof IndexColorModel) {
- if (colorModel.getRGB(0) == 0xFFFFFF && colorModel.getRGB(1) == 0x000000) {
+ if (colorModel.getRGB(0) == 0xFFFFFFFF && colorModel.getRGB(1) == 0xFF000000) {
return TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO;
}
- else if (colorModel.getRGB(0) != 0x000000 || colorModel.getRGB(1) != 0xFFFFFF) {
+ else if (colorModel.getRGB(0) != 0xFF000000 || colorModel.getRGB(1) != 0xFFFFFFFF) {
return TIFFBaseline.PHOTOMETRIC_PALETTE;
}
// Else, fall through to default, BLACK_IS_ZERO
@@ -396,9 +612,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
for (int i = 0; i < colorModel.getMapSize(); i++) {
int color = colorModel.getRGB(i);
- colorMap[i ] = (short) upScale((color >> 16) & 0xff);
- colorMap[i + colorMap.length / 3] = (short) upScale((color >> 8) & 0xff);
- colorMap[i + 2 * colorMap.length / 3] = (short) upScale((color ) & 0xff);
+ colorMap[i] = (short) upScale((color >> 16) & 0xff);
+ colorMap[i + colorMap.length / 3] = (short) upScale((color >> 8) & 0xff);
+ colorMap[i + 2 * colorMap.length / 3] = (short) upScale((color) & 0xff);
}
return colorMap;
@@ -438,16 +654,20 @@ public final class TIFFImageWriter extends ImageWriterBase {
// TODO: SampleSize may differ between bands/banks
int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
- final ByteBuffer buffer = ByteBuffer.allocate(tileWidth * renderedImage.getSampleModel().getNumBands() * sampleSize / 8);
-
-// System.err.println("tileWidth: " + tileWidth);
+ final ByteBuffer buffer;
+ if (sampleSize == 1) {
+ buffer = ByteBuffer.allocate((tileWidth + 7) / 8);
+ }
+ else {
+ buffer = ByteBuffer.allocate(tileWidth * renderedImage.getSampleModel().getNumBands() * sampleSize / 8);
+ }
+ // System.err.println("tileWidth: " + tileWidth);
for (int yTile = minTileY; yTile < maxYTiles; yTile++) {
for (int xTile = minTileX; xTile < maxXTiles; xTile++) {
final Raster tile = renderedImage.getTile(xTile, yTile);
final DataBuffer dataBuffer = tile.getDataBuffer();
final int numBands = tile.getNumBands();
-// final SampleModel sampleModel = tile.getSampleModel();
switch (dataBuffer.getDataType()) {
case DataBuffer.TYPE_BYTE:
@@ -455,9 +675,10 @@ public final class TIFFImageWriter extends ImageWriterBase {
// System.err.println("Writing " + numBands + "BYTE -> " + numBands + "BYTE");
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
for (int y = 0; y < tileHeight; y++) {
- final int yOff = y * tileWidth * numBands;
+ int steps = sampleSize == 1 ? (tileWidth + 7) / 8 : tileWidth;
+ final int yOff = y * steps * numBands;
- for (int x = 0; x < tileWidth; x++) {
+ for (int x = 0; x < steps; x++) {
final int xOff = yOff + x * numBands;
for (int s = 0; s < numBands; s++) {
@@ -585,13 +806,75 @@ public final class TIFFImageWriter extends ImageWriterBase {
// Metadata
@Override
- public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
- return null;
+ public TIFFImageMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
+ return initMeta(null, imageType, param);
}
@Override
- public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
- return null;
+ public TIFFImageMetadata convertImageMetadata(final IIOMetadata inData,
+ final ImageTypeSpecifier imageType,
+ final ImageWriteParam param) {
+ Validate.notNull(inData, "inData");
+ Validate.notNull(imageType, "imageType");
+
+ Directory ifd;
+
+ if (inData instanceof TIFFImageMetadata) {
+ ifd = ((TIFFImageMetadata) inData).getIFD();
+ }
+ else {
+ TIFFImageMetadata outData = new TIFFImageMetadata(Collections.emptySet());
+
+ try {
+ if (Arrays.asList(inData.getMetadataFormatNames()).contains(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
+ outData.setFromTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, inData.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME));
+ }
+ else if (inData.isStandardMetadataFormatSupported()) {
+ outData.setFromTree(IIOMetadataFormatImpl.standardMetadataFormatName, inData.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName));
+ }
+ else {
+ // Unknown format, we can't convert it
+ return null;
+ }
+ }
+ catch (IIOInvalidTreeException e) {
+ // TODO: How to issue warning when warning requires imageIndex??? Use -1?
+ }
+
+ ifd = outData.getIFD();
+ }
+
+ // Overwrite in values with values from imageType and param as needed
+ return initMeta(ifd, imageType, param);
+ }
+
+ private TIFFImageMetadata initMeta(final Directory ifd, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
+ Validate.notNull(imageType, "imageType");
+
+ Map entries = new LinkedHashMap<>(ifd != null ? ifd.size() + 10 : 20);
+
+ if (ifd != null) {
+ for (Entry entry : ifd) {
+ entries.put((Integer) entry.getIdentifier(), entry);
+ }
+ }
+
+ // TODO: Set values from imageType
+ entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, getPhotometricInterpretation(imageType.getColorModel())));
+
+ // TODO: Set values from param if != null + combined values...
+
+ return new TIFFImageMetadata(entries.values());
+ }
+
+ @Override
+ public IIOMetadata getDefaultStreamMetadata(final ImageWriteParam param) {
+ return super.getDefaultStreamMetadata(param);
+ }
+
+ @Override
+ public IIOMetadata convertStreamMetadata(final IIOMetadata inData, final ImageWriteParam param) {
+ return super.convertStreamMetadata(inData, param);
}
// Param
@@ -601,13 +884,61 @@ public final class TIFFImageWriter extends ImageWriterBase {
return new TIFFImageWriteParam();
}
+ @Override
+ public boolean canWriteSequence() {
+ return true;
+ }
+
+ @Override
+ public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException {
+ if (isWritingSequence) {
+ throw new IllegalStateException("sequence writing has already been started!");
+ }
+
+ // Ignore streamMetadata. ByteOrder is determined from OutputStream
+ assertOutput();
+ isWritingSequence = true;
+ sequenceExifWriter = new EXIFWriter();
+ sequenceExifWriter.writeTIFFHeader(imageOutput);
+ sequenceLastIFDPos = imageOutput.getStreamPosition();
+ }
+
+ @Override
+ public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException {
+ if (!isWritingSequence) {
+ throw new IllegalStateException("prepareWriteSequence() must be called before writeToSequence()!");
+ }
+ sequenceLastIFDPos = writePage(image, param, sequenceExifWriter, sequenceLastIFDPos);
+ }
+
+ @Override
+ public void endWriteSequence() throws IOException {
+ if (!isWritingSequence) {
+ throw new IllegalStateException("prepareWriteSequence() must be called before endWriteSequence()!");
+ }
+ imageOutput.writeInt(0); // EOF
+ isWritingSequence = false;
+ sequenceExifWriter = null;
+ sequenceLastIFDPos = -1;
+ imageOutput.flush();
+ }
+
+ @Override
+ protected void resetMembers() {
+ super.resetMembers();
+
+ isWritingSequence = false;
+ sequenceExifWriter = null;
+ sequenceLastIFDPos = -1;
+ }
+
// Test
public static void main(String[] args) throws IOException {
int argIdx = 0;
// TODO: Proper argument parsing: -t -c
- int type = args.length > argIdx + 1 ? Integer.parseInt(args[argIdx++]) : -1;
+ int type = args.length > argIdx + 1 ? Integer.parseInt(args[argIdx++]) : -1;
int compression = args.length > argIdx + 1 ? Integer.parseInt(args[argIdx++]) : 0;
if (args.length <= argIdx) {
@@ -663,7 +994,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
// BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_BGR);
// BufferedImage image = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
BufferedImage image;
- if (type < 0 || type == original.getType()) {
+ if (type <= 0 || type == original.getType()) {
image = original;
}
else if (type == BufferedImage.TYPE_BYTE_INDEXED) {
@@ -732,7 +1063,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
// }
// writer.dispose();
-
image = null;
BufferedImage read = ImageIO.read(output);
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFMedataFormat.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFMedataFormat.java
new file mode 100644
index 00000000..54e9e1b9
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFMedataFormat.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2016, 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 javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+
+/**
+ * TIFFMedataFormat.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TIFFMedataFormat.java,v 1.0 17/04/15 harald.kuhr Exp$
+ */
+public final class TIFFMedataFormat extends IIOMetadataFormatImpl {
+ // TODO: Fix typo in class name + rename to TIFFImageMetadataFormat
+ private static final TIFFMedataFormat INSTANCE = new TIFFMedataFormat();
+
+ // We'll reuse the metadata formats defined for JAI
+ public static final String SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME = "com_sun_media_imageio_plugins_tiff_image_1.0";
+
+ public TIFFMedataFormat() {
+ super(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, CHILD_POLICY_SOME);
+
+ // TODO: Implement!
+ }
+
+ @Override
+ public boolean canNodeAppear(String elementName, ImageTypeSpecifier imageType) {
+ return true;
+ }
+
+ public static TIFFMedataFormat getInstance() {
+ return INSTANCE;
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java
index c1e24e58..0d2a9a5d 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfo.java
@@ -1,3 +1,31 @@
+/*
+ * Copyright (c) 2015, 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.ReaderWriterProviderInfo;
@@ -13,17 +41,17 @@ final class TIFFProviderInfo extends ReaderWriterProviderInfo {
protected TIFFProviderInfo() {
super(
TIFFProviderInfo.class,
- new String[] {"tiff", "TIFF"},
+ new String[] {"tiff", "TIFF", "tif", "TIF"},
new String[] {"tif", "tiff"},
new String[] {
"image/tiff", "image/x-tiff"
},
- "com.twelvemkonkeys.imageio.plugins.tiff.TIFFImageReader",
+ "com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader",
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReaderSpi"},
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
- new String[] {"com.twelvemkonkeys.imageio.plugins.tif.TIFFImageWriterSpi"},
- false, null, null, null, null,
- true, null, null, null, null
+ new String[] {"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi"},
+ false, TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadataFormat", null, null,
+ true, TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat", null, null
);
}
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFStreamMetadata.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFStreamMetadata.java
new file mode 100644
index 00000000..af1e38ab
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFStreamMetadata.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2016, 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.lang.Validate;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import java.nio.ByteOrder;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+
+/**
+ * TIFFStreamMetadata.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TIFFStreamMetadata.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public final class TIFFStreamMetadata extends IIOMetadata {
+
+ public static final String SUN_NATIVE_STREAM_METADATA_FORMAT_NAME = "com_sun_media_imageio_plugins_tiff_stream_1.0";
+
+ ByteOrder byteOrder = BIG_ENDIAN;
+
+ public TIFFStreamMetadata() {
+ super(false, SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, null, null, null);
+ }
+
+
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ @Override
+ public Node getAsTree(final String formatName) {
+ Validate.isTrue(nativeMetadataFormatName.equals(formatName), formatName, "Unsupported metadata format: %s");
+
+ IIOMetadataNode root = new IIOMetadataNode(nativeMetadataFormatName);
+
+ IIOMetadataNode byteOrderNode = new IIOMetadataNode("ByteOrder");
+ root.appendChild(byteOrderNode);
+ byteOrderNode.setAttribute("value", byteOrder.toString());
+
+ return root;
+ }
+
+ @Override
+ public void mergeTree(final String formatName, final Node root) throws IIOInvalidTreeException {
+ Validate.isTrue(nativeMetadataFormatName.equals(formatName), formatName, "Unsupported metadata format: %s");
+ Validate.notNull(root, "root");
+
+ if (!nativeMetadataFormatName.equals(root.getNodeName())) {
+ throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, root);
+ }
+
+ Node node = root.getFirstChild();
+ if (node == null || !node.getNodeName().equals("ByteOrder")) {
+ throw new IIOInvalidTreeException("Missing \"ByteOrder\" node", node);
+ }
+
+ NamedNodeMap attributes = node.getAttributes();
+ String value = attributes.getNamedItem("value").getNodeValue();
+
+ if (value == null) {
+ throw new IIOInvalidTreeException("Missing \"value\" attribute in \"ByteOrder\" node", node);
+ }
+
+ ByteOrder order = getByteOrder(value.toUpperCase());
+ if (order == null) {
+ throw new IIOInvalidTreeException("Unknown ByteOrder \"value\" attribute: " + value, node);
+ }
+ else {
+ byteOrder = order;
+ }
+ }
+
+ private ByteOrder getByteOrder(final String value) throws IIOInvalidTreeException {
+ switch (value) {
+ case "BIG_ENDIAN":
+ return ByteOrder.BIG_ENDIAN;
+ case "LITTLE_ENDIAN":
+ return ByteOrder.LITTLE_ENDIAN;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public void reset() {
+ // Big endian is always the default
+ byteOrder = BIG_ENDIAN;
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFStreamMetadataFormat.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFStreamMetadataFormat.java
new file mode 100644
index 00000000..e81d38e4
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFStreamMetadataFormat.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2016, 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 javax.imageio.ImageTypeSpecifier;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * TIFFStreamMetadataFormat.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TIFFStreamMetadataFormat.java,v 1.0 17/04/15 harald.kuhr Exp$
+ */
+public final class TIFFStreamMetadataFormat extends IIOMetadataFormatImpl {
+ private static final TIFFStreamMetadataFormat INSTANCE = new TIFFStreamMetadataFormat();
+
+ // We'll reuse the metadata formats defined for JAI
+ public static final String SUN_NATIVE_STREAM_METADATA_FORMAT_NAME = "com_sun_media_imageio_plugins_tiff_stream_1.0";
+
+ private TIFFStreamMetadataFormat() {
+ super(SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, CHILD_POLICY_ALL);
+
+ addElement("ByteOrder", SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY);
+ addAttribute("ByteOrder", "value", DATATYPE_STRING, true, ByteOrder.BIG_ENDIAN.toString(), Arrays.asList(ByteOrder.BIG_ENDIAN.toString(), ByteOrder.LITTLE_ENDIAN.toString()));
+ }
+
+ @Override
+ public boolean canNodeAppear(final String elementName, final ImageTypeSpecifier imageType) {
+ return true;
+ }
+
+ public static TIFFStreamMetadataFormat getInstance() {
+ return INSTANCE;
+ }
+}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStream.java
index 8f3d591a..8045e632 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStream.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStream.java
@@ -37,21 +37,17 @@ import java.io.InputStream;
import java.nio.ByteOrder;
/**
- * Input stream that provides on-the-fly conversion and upsampling of TIFF subsampled YCbCr 16 bit samples
- * to (raw) RGB 16 bit samples.
+ * Input stream that provides on-the-fly upsampling of TIFF subsampled YCbCr 16 bit samples.
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
*/
final class YCbCr16UpsamplerStream extends FilterInputStream {
- // TODO: As we deal with short/16 bit samples, we need to take byte order into account
private final int horizChromaSub;
private final int vertChromaSub;
private final int yCbCrPos;
private final int columns;
- private final double[] coefficients;
- private final ByteOrder byteOrder;
private final int units;
private final int unitSize;
@@ -65,7 +61,7 @@ final class YCbCr16UpsamplerStream extends FilterInputStream {
int bufferLength;
int bufferPos;
- public YCbCr16UpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final double[] coefficients, final ByteOrder byteOrder) {
+ public YCbCr16UpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final ByteOrder byteOrder) {
super(Validate.notNull(stream, "stream"));
Validate.notNull(chromaSub, "chromaSub");
@@ -76,8 +72,6 @@ final class YCbCr16UpsamplerStream extends FilterInputStream {
this.vertChromaSub = chromaSub[1];
this.yCbCrPos = yCbCrPos;
this.columns = columns;
- this.coefficients = coefficients == null ? YCbCrUpsamplerStream.CCIR_601_1_COEFFICIENTS : coefficients;
- this.byteOrder = byteOrder;
// In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
// For a 4:2 subsampled stream like this:
@@ -150,7 +144,7 @@ final class YCbCr16UpsamplerStream extends FilterInputStream {
decodedRows[pixelOff + 5] = cr2;
// Convert to RGB
- convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
+// convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
}
}
@@ -228,56 +222,4 @@ final class YCbCr16UpsamplerStream extends FilterInputStream {
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
-
- private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
- int y;
- int cb;
- int cr;
-
- // Short values, depends on byte order!
- if (byteOrder == ByteOrder.BIG_ENDIAN) {
- y = ((yCbCr[offset ] & 0xff) << 8) | (yCbCr[offset + 1] & 0xff);
- cb = (((yCbCr[offset + 2] & 0xff) << 8) | (yCbCr[offset + 3] & 0xff)) - 32768;
- cr = (((yCbCr[offset + 4] & 0xff) << 8) | (yCbCr[offset + 5] & 0xff)) - 32768;
- }
- else {
- y = ((yCbCr[offset + 1] & 0xff) << 8) | (yCbCr[offset ] & 0xff);
- cb = (((yCbCr[offset + 3] & 0xff) << 8) | (yCbCr[offset + 2] & 0xff)) - 32768;
- cr = (((yCbCr[offset + 5] & 0xff) << 8) | (yCbCr[offset + 4] & 0xff)) - 32768;
- }
-
- double lumaRed = coefficients[0];
- double lumaGreen = coefficients[1];
- double lumaBlue = coefficients[2];
-
- int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
- int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
- int green = (int) Math.round((y - lumaRed * (red) - lumaBlue * (blue)) / lumaGreen);
-
- short r = clampShort(red);
- short g = clampShort(green);
- short b = clampShort(blue);
-
- // Short values, depends on byte order!
- if (byteOrder == ByteOrder.BIG_ENDIAN) {
- rgb[offset ] = (byte) ((r >>> 8) & 0xff);
- rgb[offset + 1] = (byte) (r & 0xff);
- rgb[offset + 2] = (byte) ((g >>> 8) & 0xff);
- rgb[offset + 3] = (byte) (g & 0xff);
- rgb[offset + 4] = (byte) ((b >>> 8) & 0xff);
- rgb[offset + 5] = (byte) (b & 0xff);
- }
- else {
- rgb[offset ] = (byte) (r & 0xff);
- rgb[offset + 1] = (byte) ((r >>> 8) & 0xff);
- rgb[offset + 2] = (byte) (g & 0xff);
- rgb[offset + 3] = (byte) ((g >>> 8) & 0xff);
- rgb[offset + 4] = (byte) (b & 0xff);
- rgb[offset + 5] = (byte) ((b >>> 8) & 0xff);
- }
- }
-
- private short clampShort(int val) {
- return (short) Math.max(0, Math.min(0xffff, val));
- }
}
diff --git a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java
index 82ab8a17..560845d7 100644
--- a/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java
+++ b/imageio/imageio-tiff/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStream.java
@@ -30,30 +30,24 @@ package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.lang.Validate;
-import java.awt.image.DataBufferByte;
-import java.awt.image.Raster;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Arrays;
/**
- * Input stream that provides on-the-fly conversion and upsampling of TIFF subsampled YCbCr samples to (raw) RGB samples.
+ * Input stream that provides on-the-fly upsampling of TIFF subsampled YCbCr samples.
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
*/
final class YCbCrUpsamplerStream extends FilterInputStream {
- // NOTE: DO NOT MODIFY OR EXPOSE THIS ARRAY OUTSIDE PACKAGE!
- static final double[] CCIR_601_1_COEFFICIENTS = new double[] {299.0 / 1000.0, 587.0 / 1000.0, 114.0 / 1000.0};
private final int horizChromaSub;
private final int vertChromaSub;
private final int yCbCrPos;
private final int columns;
- private final double[] coefficients;
private final int units;
private final int unitSize;
@@ -66,7 +60,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
int bufferLength;
int bufferPos;
- public YCbCrUpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns, final double[] coefficients) {
+ public YCbCrUpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns) {
super(Validate.notNull(stream, "stream"));
Validate.notNull(chromaSub, "chromaSub");
@@ -76,7 +70,6 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
this.vertChromaSub = chromaSub[1];
this.yCbCrPos = yCbCrPos;
this.columns = columns;
- this.coefficients = Arrays.equals(CCIR_601_1_COEFFICIENTS, coefficients) ? null : coefficients;
// In TIFF, subsampled streams are stored in "units" of horiz * vert pixels.
// For a 4:2 subsampled stream like this:
@@ -141,14 +134,6 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
decodedRows[pixelOff] = buffer[bufferPos++];
decodedRows[pixelOff + 1] = cb;
decodedRows[pixelOff + 2] = cr;
-
- // Convert to RGB
- if (coefficients == null) {
- YCbCrConverter.convertYCbCr2RGB(decodedRows, decodedRows, pixelOff);
- }
- else {
- convertYCbCr2RGB(decodedRows, decodedRows, coefficients, pixelOff);
- }
}
}
@@ -227,120 +212,7 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
throw new IOException("mark/reset not supported");
}
- private void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, final int offset) {
- double y = (yCbCr[offset ] & 0xff);
- double cb = (yCbCr[offset + 1] & 0xff) - 128;
- double cr = (yCbCr[offset + 2] & 0xff) - 128;
-
- double lumaRed = coefficients[0];
- double lumaGreen = coefficients[1];
- double lumaBlue = coefficients[2];
-
- int red = (int) Math.round(cr * (2 - 2 * lumaRed) + y);
- int blue = (int) Math.round(cb * (2 - 2 * lumaBlue) + y);
- int green = (int) Math.round((y - lumaRed * red - lumaBlue * blue) / lumaGreen);
-
- rgb[offset ] = clamp(red);
- rgb[offset + 2] = clamp(blue);
- rgb[offset + 1] = clamp(green);
- }
-
private static byte clamp(int val) {
return (byte) Math.max(0, Math.min(255, val));
}
-
- // TODO: This code is copied from JPEG package, make it "more" public: com.tm.imageio.color package?
- /**
- * Static inner class for lazy-loading of conversion tables.
- */
- static final class YCbCrConverter {
- /** Define tables for YCC->RGB color space conversion. */
- private final static int SCALEBITS = 16;
- private final static int MAXJSAMPLE = 255;
- private final static int CENTERJSAMPLE = 128;
- private final static int ONE_HALF = 1 << (SCALEBITS - 1);
-
- private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
- private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
- private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
- private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
-
- /**
- * Initializes tables for YCC->RGB color space conversion.
- */
- private static void buildYCCtoRGBtable() {
- if (TIFFImageReader.DEBUG) {
- System.err.println("Building YCC conversion table");
- }
-
- for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
- // i is the actual input pixel value, in the range 0..MAXJSAMPLE
- // The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
- // Cr=>R value is nearest int to 1.40200 * x
- Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
- // Cb=>B value is nearest int to 1.77200 * x
- Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
- // Cr=>G value is scaled-up -0.71414 * x
- Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
- // Cb=>G value is scaled-up -0.34414 * x
- // We also add in ONE_HALF so that need not do it in inner loop
- Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
- }
- }
-
- static {
- buildYCCtoRGBtable();
- }
-
- static void convertYCbCr2RGB(final Raster raster) {
- final int height = raster.getHeight();
- final int width = raster.getWidth();
- final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
-
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- convertYCbCr2RGB(data, data, (x + y * width) * 3);
- }
- }
- }
-
- static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
- int y = yCbCr[offset ] & 0xff;
- int cr = yCbCr[offset + 2] & 0xff;
- int cb = yCbCr[offset + 1] & 0xff;
-
- rgb[offset ] = clamp(y + Cr_R_LUT[cr]);
- rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
- rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
- }
-
- static void convertYCCK2CMYK(final Raster raster) {
- final int height = raster.getHeight();
- final int width = raster.getWidth();
- final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
-
- for (int y = 0; y < height; y++) {
- for (int x = 0; x < width; x++) {
- convertYCCK2CMYK(data, data, (x + y * width) * 4);
- }
- }
- }
-
- private static void convertYCCK2CMYK(byte[] ycck, byte[] cmyk, int offset) {
- // Inverted
- int y = 255 - ycck[offset ] & 0xff;
- int cb = 255 - ycck[offset + 1] & 0xff;
- int cr = 255 - ycck[offset + 2] & 0xff;
- int k = 255 - ycck[offset + 3] & 0xff;
-
- int cmykC = MAXJSAMPLE - (y + Cr_R_LUT[cr]);
- int cmykM = MAXJSAMPLE - (y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
- int cmykY = MAXJSAMPLE - (y + Cb_B_LUT[cb]);
-
- cmyk[offset ] = clamp(cmykC);
- cmyk[offset + 1] = clamp(cmykM);
- cmyk[offset + 2] = clamp(cmykY);
- cmyk[offset + 3] = (byte) k; // K passes through unchanged
- }
- }
}
diff --git a/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi b/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi
new file mode 100755
index 00000000..208d610d
--- /dev/null
+++ b/imageio/imageio-tiff/src/main/resources/META-INF/services/javax.imageio.spi.ImageWriterSpi
@@ -0,0 +1 @@
+com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java
index 8c735d82..599d26f7 100644
--- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxDecoderStreamTest.java
@@ -33,41 +33,74 @@ import org.junit.Test;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
-import java.io.ByteArrayInputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
+import java.util.Arrays;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
/**
* CCITTFaxDecoderStreamTest
*
* @author Harald Kuhr
* @author last modified by $Author: haraldk$
- * @version $Id: CCITTFaxDecoderStreamTest.java,v 1.0 09.03.13 14:44 haraldk Exp$
+ * @version $Id: CCITTFaxDecoderStreamTest.java,v 1.0 09.03.13 14:44 haraldk
+ * Exp$
*/
public class CCITTFaxDecoderStreamTest {
+ // group3_1d.tif: EOL|3W|1B|2W|EOL|3W|1B|2W|EOL|3W|1B|2W|EOL|2W|2B|2W|5*F
+ static final byte[] DATA_G3_1D = { 0x00, 0x18, 0x4E, 0x00, 0x30, (byte) 0x9C, 0x00, 0x61, 0x38, 0x00, (byte) 0xBE,
+ (byte) 0xE0 };
+
+ // group3_1d_fill.tif
+ static final byte[] DATA_G3_1D_FILL = { 0x00, 0x01, (byte) 0x84, (byte) 0xE0, 0x01, (byte) 0x84, (byte) 0xE0, 0x01,
+ (byte) 0x84, (byte) 0xE0, 0x1, 0x7D, (byte) 0xC0 };
+
+ // group3_2d.tif: EOL|k=1|3W|1B|2W|EOL|k=0|V|V|V|EOL|k=1|3W|1B|2W|EOL|k=0|V-1|V|V|6*F
+ static final byte[] DATA_G3_2D = { 0x00, 0x1C, 0x27, 0x00, 0x17, 0x00, 0x1C, 0x27, 0x00, 0x12, (byte) 0xC0 };
+
+ // group3_2d_fill.tif
+ static final byte[] DATA_G3_2D_FILL = { 0x00, 0x01, (byte) 0xC2, 0x70, 0x01, 0x70, 0x01, (byte) 0xC2, 0x70, 0x01,
+ 0x2C };
+
+ static final byte[] DATA_G3_2D_lsb2msb = { 0x00, 0x38, (byte) 0xE4, 0x00, (byte) 0xE8, 0x00, 0x38, (byte) 0xE4,
+ 0x00, 0x48, 0x03 };
+
+ // group4.tif:
+ // Line 1: V-3, V-2, V0
+ // Line 2: V0 V0 V0
+ // Line 3: V0 V0 V0
+ // Line 4: V-1, V0, V0 EOL EOL
+ static final byte[] DATA_G4 = { 0x04, 0x17, (byte) 0xF5, (byte) 0x80, 0x08, 0x00, (byte) 0x80 };
+
// TODO: Better tests (full A4 width scan lines?)
// From http://www.mikekohn.net/file_formats/tiff.php
- static final byte[] DATA_TYPE_2 = {
- (byte) 0x84, (byte) 0xe0, // 10000100 11100000
+ static final byte[] DATA_TYPE_2 = { (byte) 0x84, (byte) 0xe0, // 10000100
+ // 11100000
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
(byte) 0x84, (byte) 0xe0, // 10000100 11100000
(byte) 0x7d, (byte) 0xc0, // 01111101 11000000
};
- static final byte[] DATA_TYPE_3 = {
- 0x00, 0x01, (byte) 0xc2, 0x70,
- 0x00, 0x01, 0x70,
- 0x01,
+ static final byte[] DATA_TYPE_3 = { 0x00, 0x01, (byte) 0xc2, 0x70, // 00000000
+ // 00000001
+ // 11000010
+ // 01110000
+ 0x00, 0x01, 0x78, // 00000000 00000001 01111000
+ 0x00, 0x01, 0x78, // 00000000 00000001 01110000
+ 0x00, 0x01, 0x56, // 00000000 00000001 01010110
+ // 0x01, // 00000001
};
- static final byte[] DATA_TYPE_4 = {
- 0x26, (byte) 0xb0, 95, (byte) 0xfa, (byte) 0xc0
+ // 001 00110101 10 000010 1 1 1 1 1 1 1 1 1 1 010 11 (000000 padding)
+ static final byte[] DATA_TYPE_4 = { 0x26, // 001 00110
+ (byte) 0xb0, // 101 10 000
+ 0x5f, // 010 1 1 1 1 1
+ (byte) 0xfa, // 1 1 1 1 1 010
+ (byte) 0xc0 // 11 (000000 padding)
};
// Image should be (6 x 4):
@@ -75,92 +108,139 @@ public class CCITTFaxDecoderStreamTest {
// 1 1 1 0 1 1 x x
// 1 1 1 0 1 1 x x
// 1 1 0 0 1 1 x x
- BufferedImage image;
+ final BufferedImage image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);;
@Before
public void init() {
- image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);
+
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 6; x++) {
- image.setRGB(x, y, x == 3 ? 0xff000000 : 0xffffffff);
+ image.setRGB(x, y, x != 3 ? 0xff000000 : 0xffffffff);
}
}
- image.setRGB(2, 3, 0xff000000);
- }
-
- @Test
- public void testReadCountType2() throws IOException {
- InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1);
-
- int count = 0;
- int read;
- while ((read = stream.read()) >= 0) {
- count++;
- }
-
- // Just make sure we'll have 4 bytes
- assertEquals(4, count);
-
- // Verify that we don't return arbitrary values
- assertEquals(-1, read);
+ image.setRGB(2, 3, 0xffffffff);
}
@Test
public void testDecodeType2() throws IOException {
- InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6, TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1);
+ InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_2), 6,
+ TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
new DataInputStream(stream).readFully(bytes);
-
-// JPanel panel = new JPanel();
-// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
-// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
-// JOptionPane.showConfirmDialog(null, panel);
-
assertArrayEquals(imageData, bytes);
}
- @Test(expected = IllegalArgumentException.class)
- public void testDecodeType3() throws IOException {
- InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_3), 6, TIFFExtension.COMPRESSION_CCITT_T4, 1);
+ @Test
+ public void testDecodeType3_1D() throws IOException {
+ InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D), 6,
+ TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
- DataInputStream dataInput = new DataInputStream(stream);
-
- for (int y = 0; y < image.getHeight(); y++) {
- System.err.println("y: " + y);
- dataInput.readFully(bytes, y * image.getWidth(), image.getWidth());
- }
-
-// JPanel panel = new JPanel();
-// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
-// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
-// JOptionPane.showConfirmDialog(null, panel);
-
+ new DataInputStream(stream).readFully(bytes);
assertArrayEquals(imageData, bytes);
}
- @Test(expected = IllegalArgumentException.class)
+ @Test
+ public void testDecodeType3_1D_FILL() throws IOException {
+ InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_1D_FILL), 6,
+ TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS);
+
+ byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
+ byte[] bytes = new byte[imageData.length];
+ new DataInputStream(stream).readFully(bytes);
+ assertArrayEquals(imageData, bytes);
+ }
+
+ @Test
+ public void testDecodeType3_2D() throws IOException {
+ InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D), 6,
+ TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING);
+
+ byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
+ byte[] bytes = new byte[imageData.length];
+ new DataInputStream(stream).readFully(bytes);
+ assertArrayEquals(imageData, bytes);
+ }
+
+ @Test
+ public void testDecodeType3_2D_FILL() throws IOException {
+ InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_FILL), 6,
+ TIFFExtension.COMPRESSION_CCITT_T4, 1,
+ TIFFExtension.GROUP3OPT_2DENCODING | TIFFExtension.GROUP3OPT_FILLBITS);
+
+ byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
+ byte[] bytes = new byte[imageData.length];
+ new DataInputStream(stream).readFully(bytes);
+ assertArrayEquals(imageData, bytes);
+ }
+
+ @Test
+ public void testDecodeType3_2D_REVERSED() throws IOException {
+ InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G3_2D_lsb2msb), 6,
+ TIFFExtension.COMPRESSION_CCITT_T4, 2, TIFFExtension.GROUP3OPT_2DENCODING);
+
+ byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
+ byte[] bytes = new byte[imageData.length];
+ new DataInputStream(stream).readFully(bytes);
+ assertArrayEquals(imageData, bytes);
+ }
+
+ @Test
public void testDecodeType4() throws IOException {
- InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_TYPE_4), 6, TIFFExtension.COMPRESSION_CCITT_T6, 1);
+ InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(DATA_G4), 6,
+ TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] bytes = new byte[imageData.length];
- DataInputStream dataInput = new DataInputStream(stream);
+ new DataInputStream(stream).readFully(bytes);
+ assertArrayEquals(imageData, bytes);
+ }
- for (int y = 0; y < image.getHeight(); y++) {
- System.err.println("y: " + y);
- dataInput.readFully(bytes, y * image.getWidth(), image.getWidth());
+ @Test
+ public void testDecodeMissingRows() throws IOException {
+ // See https://github.com/haraldk/TwelveMonkeys/pull/225 and https://github.com/haraldk/TwelveMonkeys/issues/232
+ InputStream inputStream = getClass().getResourceAsStream("/tiff/ccitt_tolessrows.tif");
+
+ // Skip until StripOffsets: 8
+ for (int i = 0; i < 8; i++) {
+ inputStream.read();
}
-// JPanel panel = new JPanel();
-// panel.add(new JLabel("Expected", new BufferedImageIcon(image, 300, 300, true), JLabel.CENTER));
-// panel.add(new JLabel("Actual", new BufferedImageIcon(new BufferedImage(image.getColorModel(), Raster.createPackedRaster(new DataBufferByte(bytes, bytes.length), 6, 4, 1, null), false, null), 300, 300, true), JLabel.CENTER));
-// JOptionPane.showConfirmDialog(null, panel);
+ // Read until StripByteCounts: 7
+ byte[] data = new byte[7];
+ new DataInputStream(inputStream).readFully(data);
+ InputStream stream = new CCITTFaxDecoderStream(new ByteArrayInputStream(data),
+ 6, TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
+
+ byte[] bytes = new byte[6]; // 6 x 6 pixel, 1 bpp => 6 bytes
+ new DataInputStream(stream).readFully(bytes);
+
+ // Pad image data with 0s
+ byte[] imageData = Arrays.copyOf(((DataBufferByte) image.getData().getDataBuffer()).getData(), 6);
assertArrayEquals(imageData, bytes);
+
+ // Ideally, we should have no more data now, but the stream don't know that...
+ // assertEquals("Should contain no more data", -1, stream.read());
+ }
+
+ @Test
+ public void testMoreChangesThanColumns() throws IOException {
+ // Produces an CCITT Stream with 9 changes on 8 columns.
+ byte[] data = new byte[] {(byte) 0b10101010};
+ ByteArrayOutputStream imageOutput = new ByteArrayOutputStream();
+ OutputStream outputSteam = new CCITTFaxEncoderStream(imageOutput, 8, 1, TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
+ outputSteam.write(data);
+ outputSteam.close();
+
+ byte[] encoded = imageOutput.toByteArray();
+ InputStream inputStream = new CCITTFaxDecoderStream(new ByteArrayInputStream(encoded), 8,
+ TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
+ byte decoded = (byte) inputStream.read();
+ assertEquals(data[0], decoded);
}
}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStreamTest.java
new file mode 100644
index 00000000..a9eb0599
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/CCITTFaxEncoderStreamTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2013, 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.plugins.tiff.CCITTFaxEncoderStream.Code;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.imageio.IIOImage;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.ImageWriter;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferByte;
+import java.io.*;
+import java.net.URL;
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+/**
+ * CCITTFaxEncoderStreamTest
+ *
+ * @author Oliver Schmidtmer
+ * @author last modified by $Author$
+ * @version $Id$
+ */
+public class CCITTFaxEncoderStreamTest {
+
+ // Image should be (6 x 4):
+ // 1 1 1 0 1 1 x x
+ // 1 1 1 0 1 1 x x
+ // 1 1 1 0 1 1 x x
+ // 1 1 0 0 1 1 x x
+ BufferedImage image;
+
+ @Before
+ public void init() {
+ image = new BufferedImage(6, 4, BufferedImage.TYPE_BYTE_BINARY);
+ for (int y = 0; y < 4; y++) {
+ for (int x = 0; x < 6; x++) {
+ image.setRGB(x, y, x != 3 ? 0xff000000 : 0xffffffff);
+ }
+ }
+
+ image.setRGB(2, 3, 0xffffffff);
+ }
+
+ @Test
+ public void testBuildCodes() throws IOException {
+ assertTrue(CCITTFaxEncoderStream.WHITE_TERMINATING_CODES.length == 64);
+ for (Code code : CCITTFaxEncoderStream.WHITE_TERMINATING_CODES) {
+ assertNotNull(code);
+ }
+ assertTrue(CCITTFaxEncoderStream.WHITE_NONTERMINATING_CODES.length == 40);
+ for (Code code : CCITTFaxEncoderStream.WHITE_NONTERMINATING_CODES) {
+ assertNotNull(code);
+ }
+ assertTrue(CCITTFaxEncoderStream.BLACK_TERMINATING_CODES.length == 64);
+ for (Code code : CCITTFaxEncoderStream.BLACK_TERMINATING_CODES) {
+ assertNotNull(code);
+ }
+ assertTrue(CCITTFaxEncoderStream.BLACK_NONTERMINATING_CODES.length == 40);
+ for (Code code : CCITTFaxEncoderStream.BLACK_NONTERMINATING_CODES) {
+ assertNotNull(code);
+ }
+ }
+
+ @Test
+ public void testType2() throws IOException {
+ testStreamEncodeDecode(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L);
+ }
+
+ @Test
+ public void testType4() throws IOException {
+ testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L);
+ testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS);
+ testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING);
+ testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1,
+ TIFFExtension.GROUP3OPT_FILLBITS | TIFFExtension.GROUP3OPT_2DENCODING);
+ }
+
+ @Test
+ public void testType6() throws IOException {
+ testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
+ }
+
+ @Test
+ public void testReversedFillOrder() throws IOException {
+ testStreamEncodeDecode(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 2, 0L);
+ testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T6, 2, 0L);
+ }
+
+ @Test
+ public void testReencodeImages() throws IOException {
+ try (ImageInputStream iis = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/fivepages-scan-causingerrors.tif").openStream())) {
+ ImageReader reader = ImageIO.getImageReaders(iis).next();
+ reader.setInput(iis, true);
+
+ ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
+ ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next();
+ BufferedImage originalImage;
+
+ try (ImageOutputStream output = ImageIO.createImageOutputStream(outputBuffer)) {
+ writer.setOutput(output);
+ originalImage = reader.read(0);
+
+ IIOImage outputImage = new IIOImage(originalImage, null, reader.getImageMetadata(0));
+ writer.write(outputImage);
+ }
+
+ byte[] originalData = ((DataBufferByte) originalImage.getData().getDataBuffer()).getData();
+
+ BufferedImage reencodedImage = ImageIO.read(new ByteArrayInputStream(outputBuffer.toByteArray()));
+ byte[] reencodedData = ((DataBufferByte) reencodedImage.getData().getDataBuffer()).getData();
+
+ assertArrayEquals(originalData, reencodedData);
+ }
+ }
+
+ @Test
+ public void testRunlengthIssue() throws IOException {
+ // Test for "Fixed an issue with long runlengths in CCITTFax writing #188"
+ byte[] data = new byte[400];
+ Arrays.fill(data, (byte) 0xFF);
+ data[0] = 0;
+ data[399] = 0;
+
+ ByteArrayOutputStream imageOutput = new ByteArrayOutputStream();
+ OutputStream outputSteam = new CCITTFaxEncoderStream(imageOutput, 3200, 1, TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
+ outputSteam.write(data);
+ outputSteam.close();
+ byte[] encodedData = imageOutput.toByteArray();
+
+ byte[] decodedData = new byte[data.length];
+ CCITTFaxDecoderStream inputStream = new CCITTFaxDecoderStream(new ByteArrayInputStream(encodedData), 3200, TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
+ new DataInputStream(inputStream).readFully(decodedData);
+ inputStream.close();
+
+ assertArrayEquals(data, decodedData);
+ }
+
+ protected URL getClassLoaderResource(final String pName) {
+ return getClass().getResource(pName);
+ }
+
+ private void testStreamEncodeDecode(int type, int fillOrder, long options) throws IOException {
+ byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
+ byte[] redecodedData = new byte[imageData.length];
+
+ ByteArrayOutputStream imageOutput = new ByteArrayOutputStream();
+ OutputStream outputSteam = new CCITTFaxEncoderStream(imageOutput, 6, 4, type, fillOrder, options);
+ outputSteam.write(imageData);
+ outputSteam.close();
+ byte[] encodedData = imageOutput.toByteArray();
+
+ try (CCITTFaxDecoderStream inputStream =
+ new CCITTFaxDecoderStream(new ByteArrayInputStream(encodedData), 6, type, fillOrder, options)) {
+ new DataInputStream(inputStream).readFully(redecodedData);
+ }
+
+ assertArrayEquals(imageData, redecodedData);
+ }
+
+}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadataTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadataTest.java
new file mode 100644
index 00000000..242044d0
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageMetadataTest.java
@@ -0,0 +1,659 @@
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.imageio.metadata.Directory;
+import com.twelvemonkeys.imageio.metadata.Entry;
+import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
+import com.twelvemonkeys.imageio.metadata.exif.Rational;
+import com.twelvemonkeys.imageio.metadata.exif.TIFF;
+import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
+import com.twelvemonkeys.lang.StringUtil;
+import org.junit.Test;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.imageio.ImageIO;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataFormatImpl;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.spi.IIORegistry;
+import javax.imageio.stream.ImageInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+/**
+ * TIFFImageMetadataTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TIFFImageMetadataTest.java,v 1.0 30/07/15 harald.kuhr Exp$
+ */
+public class TIFFImageMetadataTest {
+
+ static {
+ IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
+ ImageIO.setUseCache(false);
+ }
+
+ // TODO: Candidate super method
+ private URL getClassLoaderResource(final String resource) {
+ return getClass().getResource(resource);
+ }
+
+ // TODO: Candidate abstract super method
+ private IIOMetadata createMetadata(final String resource) throws IOException {
+ try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource(resource))) {
+ Directory ifd = new EXIFReader().read(input);
+// System.err.println("ifd: " + ifd);
+ return new TIFFImageMetadata(ifd);
+ }
+ }
+
+ @Test
+ public void testMetadataStandardFormat() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/smallliz.tif");
+ Node root = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
+
+ // Root: "javax_imageio_1.0"
+ assertNotNull(root);
+ assertEquals(IIOMetadataFormatImpl.standardMetadataFormatName, root.getNodeName());
+ assertEquals(6, root.getChildNodes().getLength());
+
+ // "Chroma"
+ Node chroma = root.getFirstChild();
+ assertEquals("Chroma", chroma.getNodeName());
+
+ assertEquals(3, chroma.getChildNodes().getLength());
+
+ Node colorSpaceType = chroma.getFirstChild();
+ assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
+ assertEquals("YCbCr", ((Element) colorSpaceType).getAttribute("value"));
+
+ Node numChannels = colorSpaceType.getNextSibling();
+ assertEquals("NumChannels", numChannels.getNodeName());
+ assertEquals("3", ((Element) numChannels).getAttribute("value"));
+
+ Node blackIsZero = numChannels.getNextSibling();
+ assertEquals("BlackIsZero", blackIsZero.getNodeName());
+ assertEquals(0, blackIsZero.getAttributes().getLength());
+
+ // "Compression"
+ Node compression = chroma.getNextSibling();
+ assertEquals("Compression", compression.getNodeName());
+ assertEquals(2, compression.getChildNodes().getLength());
+
+ Node compressionTypeName = compression.getFirstChild();
+ assertEquals("CompressionTypeName", compressionTypeName.getNodeName());
+ assertEquals("Old JPEG", ((Element) compressionTypeName).getAttribute("value"));
+
+ Node lossless = compressionTypeName.getNextSibling();
+ assertEquals("Lossless", lossless.getNodeName());
+ assertEquals("FALSE", ((Element) lossless).getAttribute("value"));
+
+ // "Data"
+ Node data = compression.getNextSibling();
+ assertEquals("Data", data.getNodeName());
+ assertEquals(4, data.getChildNodes().getLength());
+
+ Node planarConfiguration = data.getFirstChild();
+ assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
+ assertEquals("PixelInterleaved", ((Element) planarConfiguration).getAttribute("value"));
+
+ Node sampleFormat = planarConfiguration.getNextSibling();
+ assertEquals("SampleFormat", sampleFormat.getNodeName());
+ assertEquals("UnsignedIntegral", ((Element) sampleFormat).getAttribute("value"));
+
+ Node bitsPerSample = sampleFormat.getNextSibling();
+ assertEquals("BitsPerSample", bitsPerSample.getNodeName());
+ assertEquals("8 8 8", ((Element) bitsPerSample).getAttribute("value"));
+
+ Node sampleMSB = bitsPerSample.getNextSibling();
+ assertEquals("SampleMSB", sampleMSB.getNodeName());
+ assertEquals("0 0 0", ((Element) sampleMSB).getAttribute("value"));
+
+ // "Dimension"
+ Node dimension = data.getNextSibling();
+ assertEquals("Dimension", dimension.getNodeName());
+ assertEquals(3, dimension.getChildNodes().getLength());
+
+ Node pixelAspectRatio = dimension.getFirstChild();
+ assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
+ assertEquals("1.0", ((Element) pixelAspectRatio).getAttribute("value"));
+
+ Node horizontalPixelSize = pixelAspectRatio.getNextSibling();
+ assertEquals("HorizontalPixelSize", horizontalPixelSize.getNodeName());
+ assertEquals("0.254", ((Element) horizontalPixelSize).getAttribute("value"));
+
+ Node verticalPixelSize = horizontalPixelSize.getNextSibling();
+ assertEquals("VerticalPixelSize", verticalPixelSize.getNodeName());
+ assertEquals("0.254", ((Element) verticalPixelSize).getAttribute("value"));
+
+ // "Document"
+ Node document = dimension.getNextSibling();
+ assertEquals("Document", document.getNodeName());
+ assertEquals(1, document.getChildNodes().getLength());
+
+ Node formatVersion = document.getFirstChild();
+ assertEquals("FormatVersion", formatVersion.getNodeName());
+ assertEquals("6.0", ((Element) formatVersion).getAttribute("value"));
+
+ // "Text"
+ Node text = document.getNextSibling();
+ assertEquals("Text", text.getNodeName());
+ assertEquals(1, text.getChildNodes().getLength());
+
+ // NOTE: Could be multiple "TextEntry" elements, with different "keyword" attributes
+ Node textEntry = text.getFirstChild();
+ assertEquals("TextEntry", textEntry.getNodeName());
+ assertEquals("Software", ((Element) textEntry).getAttribute("keyword"));
+ assertEquals("HP IL v1.1", ((Element) textEntry).getAttribute("value"));
+ }
+
+ @Test
+ public void testMetadataNativeFormat() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/quad-lzw.tif");
+ Node root = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
+
+ // Root: "com_sun_media_imageio_plugins_tiff_image_1.0"
+ assertNotNull(root);
+ assertEquals(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, root.getNodeName());
+ assertEquals(1, root.getChildNodes().getLength());
+
+ // IFD: "TIFFIFD"
+ Node ifd = root.getFirstChild();
+ assertEquals("TIFFIFD", ifd.getNodeName());
+
+ NodeList entries = ifd.getChildNodes();
+ assertEquals(13, entries.getLength());
+
+ String[] stripOffsets = {
+ "8", "150", "292", "434", "576", "718", "860", "1002", "1144", "1286",
+ "1793", "3823", "7580", "12225", "17737", "23978", "30534", "36863", "42975", "49180",
+ "55361", "61470", "67022", "71646", "74255", "75241", "75411", "75553", "75695", "75837",
+ "75979", "76316", "77899", "80466", "84068", "88471", "93623", "99105", "104483", "109663",
+ "114969", "120472", "126083", "131289", "135545", "138810", "140808", "141840", "141982", "142124",
+ "142266", "142408", "142615", "144074", "146327", "149721", "154066", "158927", "164022", "169217",
+ "174409", "179657", "185166", "190684", "196236", "201560", "206064", "209497", "211612", "212419",
+ "212561", "212703", "212845", "212987", "213129", "213271", "213413"
+ };
+
+ String[] stripByteCounts = {
+ "142", "142", "142", "142", "142", "142", "142", "142", "142", "507",
+ "2030", "3757", "4645", "5512", "6241", "6556", "6329", "6112", "6205", "6181",
+ "6109", "5552", "4624", "2609", "986", "170", "142", "142", "142", "142",
+ "337", "1583", "2567", "3602", "4403", "5152", "5482", "5378", "5180", "5306",
+ "5503", "5611", "5206", "4256", "3265", "1998", "1032", "142", "142", "142",
+ "142", "207", "1459", "2253", "3394", "4345", "4861", "5095", "5195", "5192",
+ "5248", "5509", "5518", "5552", "5324", "4504", "3433", "2115", "807", "142",
+ "142", "142", "142", "142", "142", "142", "128"
+ };
+
+ // The 13 entries
+ assertSingleNodeWithValue(entries, TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, "512");
+ assertSingleNodeWithValue(entries, TIFF.TAG_IMAGE_HEIGHT, TIFF.TYPE_SHORT, "384");
+ assertSingleNodeWithValue(entries, TIFF.TAG_BITS_PER_SAMPLE, TIFF.TYPE_SHORT, "8", "8", "8");
+ assertSingleNodeWithValue(entries, TIFF.TAG_COMPRESSION, TIFF.TYPE_SHORT, "5");
+ assertSingleNodeWithValue(entries, TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, "2");
+ assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffsets);
+ assertSingleNodeWithValue(entries, TIFF.TAG_SAMPLES_PER_PIXEL, TIFF.TYPE_SHORT, "3");
+ assertSingleNodeWithValue(entries, TIFF.TAG_ROWS_PER_STRIP, TIFF.TYPE_LONG, "5");
+ assertSingleNodeWithValue(entries, TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCounts);
+ assertSingleNodeWithValue(entries, TIFF.TAG_PLANAR_CONFIGURATION, TIFF.TYPE_SHORT, "1");
+ assertSingleNodeWithValue(entries, TIFF.TAG_X_POSITION, TIFF.TYPE_RATIONAL, "0");
+ assertSingleNodeWithValue(entries, TIFF.TAG_Y_POSITION, TIFF.TYPE_RATIONAL, "0");
+ assertSingleNodeWithValue(entries, 32995, TIFF.TYPE_SHORT, "0"); // Matteing tag, obsoleted by ExtraSamples tag in TIFF 6.0
+ }
+
+ @Test
+ public void testTreeDetached() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
+
+ Node nativeTree = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
+ assertNotNull(nativeTree);
+
+ Node nativeTree2 = metadata.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
+ assertNotNull(nativeTree2);
+
+ assertNotSame(nativeTree, nativeTree2);
+ assertNodeEquals("Unmodified trees differs", nativeTree, nativeTree2); // Both not modified
+
+ // Modify one of the trees
+ Node ifdNode = nativeTree2.getFirstChild();
+ ifdNode.removeChild(ifdNode.getFirstChild());
+ IIOMetadataNode tiffField = new IIOMetadataNode("TIFFField");
+ ifdNode.appendChild(tiffField);
+
+ assertNodeNotEquals("Modified tree does not differ", nativeTree, nativeTree2);
+ }
+
+ @Test
+ public void testMergeTree() throws IOException {
+ TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+
+ Node nativeTree = metadata.getAsTree(nativeFormat);
+ assertNotNull(nativeTree);
+
+ IIOMetadataNode newTree = new IIOMetadataNode("com_sun_media_imageio_plugins_tiff_image_1.0");
+ IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
+ newTree.appendChild(ifdNode);
+
+ createTIFFFieldNode(ifdNode, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_DPI);
+ createTIFFFieldNode(ifdNode, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(300));
+ createTIFFFieldNode(ifdNode, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, new Rational(30001, 100));
+
+ metadata.mergeTree(nativeFormat, newTree);
+
+ Directory ifd = metadata.getIFD();
+
+ assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
+ assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
+ assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
+ assertEquals(new Rational(30001, 100), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
+ assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
+ assertEquals(TIFFBaseline.RESOLUTION_UNIT_DPI, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
+
+ Node mergedTree = metadata.getAsTree(nativeFormat);
+ NodeList fields = mergedTree.getFirstChild().getChildNodes();
+
+ // Validate there's one and only one resolution unit, x res and y res
+ // Validate resolution unit == 1, x res & y res
+ assertSingleNodeWithValue(fields, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, String.valueOf(TIFFBaseline.RESOLUTION_UNIT_DPI));
+ assertSingleNodeWithValue(fields, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, "300");
+ assertSingleNodeWithValue(fields, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, "30001/100");
+ }
+
+ @Test
+ public void testMergeTreeStandardFormat() throws IOException {
+ TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/zackthecat.tif");
+
+ String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
+
+ Node standardTree = metadata.getAsTree(standardFormat);
+ assertNotNull(standardTree);
+
+ IIOMetadataNode newTree = new IIOMetadataNode(standardFormat);
+ IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
+ newTree.appendChild(dimensionNode);
+
+ IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
+ dimensionNode.appendChild(horizontalPixelSize);
+ horizontalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
+
+ IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
+ dimensionNode.appendChild(verticalPixelSize);
+ verticalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
+
+ metadata.mergeTree(standardFormat, newTree);
+
+ Directory ifd = metadata.getIFD();
+
+ assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
+ assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
+ assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
+ assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
+
+ // Should keep DPI as unit
+ assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
+ assertEquals(TIFFBaseline.RESOLUTION_UNIT_DPI, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
+ }
+
+ @Test
+ public void testMergeTreeStandardFormatAspectOnly() throws IOException {
+ TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
+
+ String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
+
+ Node standardTree = metadata.getAsTree(standardFormat);
+ assertNotNull(standardTree);
+
+ IIOMetadataNode newTree = new IIOMetadataNode(standardFormat);
+ IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
+ newTree.appendChild(dimensionNode);
+
+ IIOMetadataNode aspectRatio = new IIOMetadataNode("PixelAspectRatio");
+ dimensionNode.appendChild(aspectRatio);
+ aspectRatio.setAttribute("value", String.valueOf(3f / 2f));
+
+ metadata.mergeTree(standardFormat, newTree);
+
+ Directory ifd = metadata.getIFD();
+
+ assertNotNull(ifd.getEntryById(TIFF.TAG_X_RESOLUTION));
+ assertEquals(new Rational(3, 2), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
+ assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
+ assertEquals(new Rational(1), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
+ assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
+ assertEquals(TIFFBaseline.RESOLUTION_UNIT_NONE, ((Number) ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT).getValue()).intValue());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testMergeTreeUnsupportedFormat() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
+
+ String nativeFormat = "com_foo_bar_tiff_42";
+ metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat));
+ }
+
+ @Test(expected = IIOInvalidTreeException.class)
+ public void testMergeTreeFormatMisMatch() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+ metadata.mergeTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
+ }
+
+ @Test(expected = IIOInvalidTreeException.class)
+ public void testMergeTreeInvalid() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+ metadata.mergeTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
+ }
+
+ // TODO: Test that failed merge leaves metadata unchanged
+
+ @Test
+ public void testSetFromTreeEmpty() throws IOException {
+ // Read from file, set empty to see that all is cleared
+ TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/sm_colors_tile.tif");
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+ IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
+ root.appendChild(new IIOMetadataNode("TIFFIFD"));
+
+ metadata.setFromTree(nativeFormat, root);
+
+ Directory ifd = metadata.getIFD();
+ assertNotNull(ifd);
+ assertEquals(0, ifd.size());
+
+ Node tree = metadata.getAsTree(nativeFormat);
+
+ assertNotNull(tree);
+ assertNotNull(tree.getFirstChild());
+ assertEquals(1, tree.getChildNodes().getLength());
+ }
+
+ @Test
+ public void testSetFromTree() throws IOException {
+ String softwareString = "12M UberTIFF 1.0";
+
+ TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.emptySet());
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+ IIOMetadataNode root = new IIOMetadataNode(nativeFormat);
+
+ IIOMetadataNode ifdNode = new IIOMetadataNode("TIFFIFD");
+ root.appendChild(ifdNode);
+
+ createTIFFFieldNode(ifdNode, TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, softwareString);
+
+ metadata.setFromTree(nativeFormat, root);
+
+ Directory ifd = metadata.getIFD();
+ assertNotNull(ifd);
+ assertEquals(1, ifd.size());
+
+ assertNotNull(ifd.getEntryById(TIFF.TAG_SOFTWARE));
+ assertEquals(softwareString, ifd.getEntryById(TIFF.TAG_SOFTWARE).getValue());
+
+ Node tree = metadata.getAsTree(nativeFormat);
+
+ assertNotNull(tree);
+ assertNotNull(tree.getFirstChild());
+ assertEquals(1, tree.getChildNodes().getLength());
+ }
+
+ @Test
+ public void testSetFromTreeStandardFormat() throws IOException {
+ String softwareString = "12M UberTIFF 1.0";
+ String copyrightString = "Copyright (C) TwelveMonkeys, 2015";
+
+ TIFFImageMetadata metadata = new TIFFImageMetadata(Collections.emptySet());
+
+ String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
+ IIOMetadataNode root = new IIOMetadataNode(standardFormat);
+
+ IIOMetadataNode textNode = new IIOMetadataNode("Text");
+ root.appendChild(textNode);
+
+ IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
+ textNode.appendChild(textEntry);
+
+ textEntry.setAttribute("keyword", "SOFTWARE"); // Spelling should not matter
+ textEntry.setAttribute("value", softwareString);
+
+ textEntry = new IIOMetadataNode("TextEntry");
+ textNode.appendChild(textEntry);
+
+ textEntry.setAttribute("keyword", "copyright"); // Spelling should not matter
+ textEntry.setAttribute("value", copyrightString);
+
+ metadata.setFromTree(standardFormat, root);
+
+ Directory ifd = metadata.getIFD();
+ assertNotNull(ifd);
+ assertEquals(2, ifd.size());
+
+ assertNotNull(ifd.getEntryById(TIFF.TAG_SOFTWARE));
+ assertEquals(softwareString, ifd.getEntryById(TIFF.TAG_SOFTWARE).getValue());
+
+ assertNotNull(ifd.getEntryById(TIFF.TAG_COPYRIGHT));
+ assertEquals(copyrightString, ifd.getEntryById(TIFF.TAG_COPYRIGHT).getValue());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetFromTreeUnsupportedFormat() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
+
+ String nativeFormat = "com_foo_bar_tiff_42";
+ metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat));
+ }
+
+ @Test(expected = IIOInvalidTreeException.class)
+ public void testSetFromTreeFormatMisMatch() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+ metadata.setFromTree(nativeFormat, new IIOMetadataNode("com_foo_bar_tiff_42"));
+ }
+
+ @Test(expected = IIOInvalidTreeException.class)
+ public void testSetFromTreeInvalid() throws IOException {
+ IIOMetadata metadata = createMetadata("/tiff/sm_colors_tile.tif");
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+ metadata.setFromTree(nativeFormat, new IIOMetadataNode(nativeFormat)); // Requires at least one child node
+ }
+
+ @Test
+ public void testStandardChromaSamplesPerPixel() {
+ Set entries = new HashSet<>();
+ entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
+ entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 4));
+ entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8})); // This is incorrect, just making sure the correct value is selected
+
+ IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
+ assertNotNull(chromaNode);
+
+ IIOMetadataNode numChannels = (IIOMetadataNode) chromaNode.getElementsByTagName("NumChannels").item(0);
+ assertEquals("4", numChannels.getAttribute("value"));
+ }
+
+ @Test
+ public void testStandardChromaSamplesPerPixelFallbackBitsPerSample() {
+ Set entries = new HashSet<>();
+ entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
+ entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_BITS_PER_SAMPLE, new int[] {8, 8, 8}));
+
+ IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
+ assertNotNull(chromaNode);
+
+ IIOMetadataNode numChannels = (IIOMetadataNode) chromaNode.getElementsByTagName("NumChannels").item(0);
+ assertEquals("3", numChannels.getAttribute("value"));
+ }
+
+ @Test
+ public void testStandardChromaSamplesPerPixelFallbackDefault() {
+ Set entries = new HashSet<>();
+ entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO));
+
+ IIOMetadataNode chromaNode = new TIFFImageMetadata(entries).getStandardChromaNode();
+ assertNotNull(chromaNode);
+ IIOMetadataNode numChannels = (IIOMetadataNode) chromaNode.getElementsByTagName("NumChannels").item(0);
+ assertEquals("1", numChannels.getAttribute("value"));
+ }
+
+ @Test
+ public void testStandardDataBitsPerSampleFallbackDefault() {
+ Set entries = new HashSet<>();
+ entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO));
+
+ IIOMetadataNode dataNode = new TIFFImageMetadata(entries).getStandardDataNode();
+ assertNotNull(dataNode);
+ IIOMetadataNode numChannels = (IIOMetadataNode) dataNode.getElementsByTagName("BitsPerSample").item(0);
+ assertEquals("1", numChannels.getAttribute("value"));
+ }
+
+ @Test
+ public void testStandardNodeSamplesPerPixelFallbackDefault() {
+ Set entries = new HashSet<>();
+ entries.add(new TIFFImageWriter.TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFFBaseline.PHOTOMETRIC_RGB));
+
+ // Just to make sure we haven't accidentally missed something
+ IIOMetadataNode standardTree = (IIOMetadataNode) new TIFFImageMetadata(entries).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
+ assertNotNull(standardTree);
+ }
+
+ // TODO: Test that failed set leaves metadata unchanged
+
+ private void assertSingleNodeWithValue(final NodeList fields, final int tag, int type, final String... expectedValue) {
+ String tagNumber = String.valueOf(tag);
+ String typeName = StringUtil.capitalize(TIFF.TYPE_NAMES[type].toLowerCase());
+
+ boolean foundTag = false;
+
+ for (int i = 0; i < fields.getLength(); i++) {
+ Element field = (Element) fields.item(i);
+
+ if (tagNumber.equals(field.getAttribute("number"))) {
+ assertFalse("Duplicate tag " + tagNumber + " found", foundTag);
+
+ assertEquals(1, field.getChildNodes().getLength());
+ Node containerNode = field.getFirstChild();
+ assertEquals("TIFF" + typeName + "s", containerNode.getNodeName());
+
+ NodeList valueNodes = containerNode.getChildNodes();
+ assertEquals("Unexpected number of values for tag " + tagNumber, expectedValue.length, valueNodes.getLength());
+
+ for (int j = 0; j < expectedValue.length; j++) {
+ Element valueNode = (Element) valueNodes.item(j);
+ assertEquals("TIFF" + typeName, valueNode.getNodeName());
+ assertEquals("Unexpected tag " + tagNumber + " value", expectedValue[j], valueNode.getAttribute("value"));
+ }
+
+ foundTag = true;
+ }
+ }
+
+ assertTrue("No tag " + tagNumber + " found", foundTag);
+ }
+
+ static void createTIFFFieldNode(final IIOMetadataNode parentIFDNode, int tag, short type, Object value) {
+ IIOMetadataNode fieldNode = new IIOMetadataNode("TIFFField");
+ parentIFDNode.appendChild(fieldNode);
+
+ fieldNode.setAttribute("number", String.valueOf(tag));
+
+ switch (type) {
+ case TIFF.TYPE_ASCII:
+ createTIFFFieldContainerNode(fieldNode, "Ascii", value);
+ break;
+ case TIFF.TYPE_BYTE:
+ createTIFFFieldContainerNode(fieldNode, "Byte", value);
+ break;
+ case TIFF.TYPE_SHORT:
+ createTIFFFieldContainerNode(fieldNode, "Short", value);
+ break;
+ case TIFF.TYPE_RATIONAL:
+ createTIFFFieldContainerNode(fieldNode, "Rational", value);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported type: " + type);
+ }
+ }
+
+ static void createTIFFFieldContainerNode(final IIOMetadataNode fieldNode, final String type, final Object value) {
+ IIOMetadataNode containerNode = new IIOMetadataNode("TIFF" + type + "s");
+ fieldNode.appendChild(containerNode);
+
+ IIOMetadataNode valueNode = new IIOMetadataNode("TIFF" + type);
+ valueNode.setAttribute("value", String.valueOf(value));
+ containerNode.appendChild(valueNode);
+ }
+
+ private void assertNodeNotEquals(final String message, final Node expected, final Node actual) {
+ // Lame, lazy implementation...
+ try {
+ assertNodeEquals(message, expected, actual);
+ }
+ catch (AssertionError ignore) {
+ return;
+ }
+
+ fail(message);
+ }
+
+ private void assertNodeEquals(final String message, final Node expected, final Node actual) {
+ assertEquals(message + " class differs", expected.getClass(), actual.getClass());
+ assertEquals(message, expected.getNodeValue(), actual.getNodeValue());
+
+ if (expected instanceof IIOMetadataNode) {
+ IIOMetadataNode expectedIIO = (IIOMetadataNode) expected;
+ IIOMetadataNode actualIIO = (IIOMetadataNode) actual;
+
+ assertEquals(message, expectedIIO.getUserObject(), actualIIO.getUserObject());
+ }
+
+ NodeList expectedChildNodes = expected.getChildNodes();
+ NodeList actualChildNodes = actual.getChildNodes();
+
+ assertEquals(message + " child length differs: " + toString(expectedChildNodes) + " != " + toString(actualChildNodes),
+ expectedChildNodes.getLength(), actualChildNodes.getLength());
+
+ for (int i = 0; i < expectedChildNodes.getLength(); i++) {
+ Node expectedChild = expectedChildNodes.item(i);
+ Node actualChild = actualChildNodes.item(i);
+
+ assertEquals(message + " node name differs", expectedChild.getLocalName(), actualChild.getLocalName());
+ assertNodeEquals(message + "/" + expectedChild.getLocalName(), expectedChild, actualChild);
+ }
+ }
+
+ private String toString(final NodeList list) {
+ if (list.getLength() == 0) {
+ return "[]";
+ }
+
+ StringBuilder builder = new StringBuilder("[");
+ for (int i = 0; i < list.getLength(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+
+ Node node = list.item(i);
+ builder.append(node.getLocalName());
+ }
+ builder.append("]");
+
+ return builder.toString();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java
index 69b56e2a..bd81a43e 100644
--- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReaderTest.java
@@ -26,10 +26,12 @@ package com.twelvemonkeys.imageio.plugins.tiff;/*
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
+import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
+import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
@@ -37,10 +39,11 @@ import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.*;
import static org.mockito.Matchers.contains;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
@@ -52,7 +55,7 @@ import static org.mockito.Mockito.*;
* @author last modified by $Author: haraldk$
* @version $Id: TIFFImageReaderTest.java,v 1.0 08.05.12 15:25 haraldk Exp$
*/
-public class TIFFImageReaderTest extends ImageReaderAbstractTestCase {
+public class TIFFImageReaderTest extends ImageReaderAbstractTest {
private static final TIFFImageReaderSpi SPI = new TIFFImageReaderSpi();
@@ -77,7 +80,54 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCase getMIMETypes() {
- return Arrays.asList("image/tiff");
+ return Collections.singletonList("image/tiff");
}
@@ -138,9 +188,8 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTestCase getTestData() {
- BufferedImage image = new BufferedImage(300, 200, BufferedImage.TYPE_INT_ARGB);
- Graphics2D graphics = image.createGraphics();
- try {
- graphics.setColor(Color.RED);
- graphics.fillRect(0, 0, 100, 200);
- graphics.setColor(Color.BLUE);
- graphics.fillRect(100, 0, 100, 200);
- graphics.clearRect(200, 0, 100, 200);
+ return Arrays.asList(
+ new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
+ new BufferedImage(300, 200, BufferedImage.TYPE_INT_ARGB),
+ new BufferedImage(300, 200, BufferedImage.TYPE_3BYTE_BGR),
+ new BufferedImage(300, 200, BufferedImage.TYPE_4BYTE_ABGR),
+ new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_GRAY),
+ new BufferedImage(300, 200, BufferedImage.TYPE_USHORT_GRAY),
+// new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_BINARY), // TODO!
+ new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_INDEXED)
+ );
+ }
+
+ // TODO: Test write bilevel stays bilevel
+ // TODO: Test write indexed stays indexed
+
+ @Test
+ public void testWriteWithCustomResolutionNative() throws IOException {
+ // Issue 139 Writing TIFF files with custom resolution value
+ Rational resolutionValue = new Rational(1200);
+ int resolutionUnitValue = TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
+
+ RenderedImage image = getTestData(0);
+
+ ImageWriter writer = createImageWriter();
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+ IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
+
+ IIOMetadataNode customMeta = new IIOMetadataNode(nativeFormat);
+
+ IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD");
+ customMeta.appendChild(ifd);
+
+ createTIFFFieldNode(ifd, TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, resolutionUnitValue);
+ createTIFFFieldNode(ifd, TIFF.TAG_X_RESOLUTION, TIFF.TYPE_RATIONAL, resolutionValue);
+ createTIFFFieldNode(ifd, TIFF.TAG_Y_RESOLUTION, TIFF.TYPE_RATIONAL, resolutionValue);
+
+ metadata.mergeTree(nativeFormat, customMeta);
+
+ writer.write(null, new IIOImage(image, null, metadata), null);
}
- finally {
- graphics.dispose();
+ catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
}
- return Arrays.asList(image);
+ assertTrue("No image data written", buffer.size() > 0);
+
+ Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
+
+ Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
+ assertNotNull(resolutionUnit);
+ assertEquals(resolutionUnitValue, ((Number) resolutionUnit.getValue()).intValue());
+
+ Entry xResolution = ifds.getEntryById(TIFF.TAG_X_RESOLUTION);
+ assertNotNull(xResolution);
+ assertEquals(resolutionValue, xResolution.getValue());
+
+ Entry yResolution = ifds.getEntryById(TIFF.TAG_Y_RESOLUTION);
+ assertNotNull(yResolution);
+ assertEquals(resolutionValue, yResolution.getValue());
+ }
+
+ @Test
+ public void testWriteWithCustomSoftwareNative() throws IOException {
+ String softwareString = "12M TIFF Test 1.0 (build $foo$)";
+
+ RenderedImage image = getTestData(0);
+
+ ImageWriter writer = createImageWriter();
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
+
+ String nativeFormat = TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
+ IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
+
+ IIOMetadataNode customMeta = new IIOMetadataNode(nativeFormat);
+
+ IIOMetadataNode ifd = new IIOMetadataNode("TIFFIFD");
+ customMeta.appendChild(ifd);
+
+ createTIFFFieldNode(ifd, TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, softwareString);
+
+ metadata.mergeTree(nativeFormat, customMeta);
+
+ writer.write(null, new IIOImage(image, null, metadata), null);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+
+ assertTrue("No image data written", buffer.size() > 0);
+
+ Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
+ Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
+ assertNotNull(software);
+ assertEquals(softwareString, software.getValueAsString());
+ }
+
+ @Test
+ public void testWriteWithCustomResolutionStandard() throws IOException {
+ // Issue 139 Writing TIFF files with custom resolution value
+ double resolutionValue = 300 / 25.4; // 300 dpi, 1 inch = 2.54 cm or 25.4 mm
+ int resolutionUnitValue = TIFFBaseline.RESOLUTION_UNIT_CENTIMETER;
+ Rational expectedResolutionValue = new Rational(Math.round(resolutionValue * 10 * TIFFImageMetadata.RATIONAL_SCALE_FACTOR), TIFFImageMetadata.RATIONAL_SCALE_FACTOR);
+
+ RenderedImage image = getTestData(0);
+
+ ImageWriter writer = createImageWriter();
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
+
+ String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
+ IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
+
+ IIOMetadataNode customMeta = new IIOMetadataNode(standardFormat);
+
+ IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
+ customMeta.appendChild(dimension);
+
+ IIOMetadataNode xSize = new IIOMetadataNode("HorizontalPixelSize");
+ dimension.appendChild(xSize);
+ xSize.setAttribute("value", String.valueOf(resolutionValue));
+
+ IIOMetadataNode ySize = new IIOMetadataNode("VerticalPixelSize");
+ dimension.appendChild(ySize);
+ ySize.setAttribute("value", String.valueOf(resolutionValue));
+
+ metadata.mergeTree(standardFormat, customMeta);
+
+ writer.write(null, new IIOImage(image, null, metadata), null);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+
+ assertTrue("No image data written", buffer.size() > 0);
+
+ Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
+
+ Entry resolutionUnit = ifds.getEntryById(TIFF.TAG_RESOLUTION_UNIT);
+ assertNotNull(resolutionUnit);
+ assertEquals(resolutionUnitValue, ((Number) resolutionUnit.getValue()).intValue());
+
+ Entry xResolution = ifds.getEntryById(TIFF.TAG_X_RESOLUTION);
+ assertNotNull(xResolution);
+ assertEquals(expectedResolutionValue, xResolution.getValue());
+
+ Entry yResolution = ifds.getEntryById(TIFF.TAG_Y_RESOLUTION);
+ assertNotNull(yResolution);
+ assertEquals(expectedResolutionValue, yResolution.getValue());
+ }
+
+ @Test
+ public void testWriteWithCustomSoftwareStandard() throws IOException {
+ String softwareString = "12M TIFF Test 1.0 (build $foo$)";
+
+ RenderedImage image = getTestData(0);
+
+ ImageWriter writer = createImageWriter();
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
+ writer.setOutput(stream);
+
+ String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
+ IIOMetadata metadata = writer.getDefaultImageMetadata(ImageTypeSpecifier.createFromRenderedImage(image), null);
+
+ IIOMetadataNode customMeta = new IIOMetadataNode(standardFormat);
+
+ IIOMetadataNode dimension = new IIOMetadataNode("Text");
+ customMeta.appendChild(dimension);
+
+ IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
+ dimension.appendChild(textEntry);
+ textEntry.setAttribute("keyword", "Software");
+ textEntry.setAttribute("value", softwareString);
+
+ metadata.mergeTree(standardFormat, customMeta);
+
+ writer.write(null, new IIOImage(image, null, metadata), null);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+
+ assertTrue("No image data written", buffer.size() > 0);
+
+ Directory ifds = new EXIFReader().read(new ByteArrayImageInputStream(buffer.toByteArray()));
+ Entry software = ifds.getEntryById(TIFF.TAG_SOFTWARE);
+ assertNotNull(software);
+ assertEquals(softwareString, software.getValueAsString());
+ }
+
+ @Test
+ public void testSequenceWriter() throws IOException {
+ ImageWriter writer = createImageWriter();
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ ImageOutputStream stream = ImageIO.createImageOutputStream(buffer);
+ writer.setOutput(stream);
+
+ Graphics2D g2d = null;
+ BufferedImage image[] = new BufferedImage[] {
+ new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB),
+ new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB),
+ new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB)
+ };
+ g2d = image[0].createGraphics();
+ g2d.setColor(Color.red);
+ g2d.fillRect(0,0,100,100);
+ g2d.dispose();
+ g2d = image[1].createGraphics();
+ g2d.setColor(Color.green);
+ g2d.fillRect(0,0,100,100);
+ g2d.dispose();
+ g2d = image[2].createGraphics();
+ g2d.setColor(Color.blue);
+ g2d.fillRect(0,0,100,100);
+ g2d.dispose();
+
+
+ ImageWriteParam params = writer.getDefaultWriteParam();
+ params.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+
+ assertTrue("", writer.canWriteSequence());
+
+ try {
+ writer.prepareWriteSequence(null);
+
+ params.setCompressionType("JPEG");
+ writer.writeToSequence(new IIOImage(image[0], null, null), params);
+ params.setCompressionType("None");
+ writer.writeToSequence(new IIOImage(image[1], null, null), params);
+ params.setCompressionType("JPEG");
+ writer.writeToSequence(new IIOImage(image[2], null, null), params);
+ g2d.dispose();
+ writer.endWriteSequence();
+ }
+ catch (IOException e) {
+ fail(e.getMessage());
+ }
+ finally {
+ stream.close(); // Force data to be written
+ }
+
+ ImageInputStream input = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray()));
+ ImageReader reader = ImageIO.getImageReaders(input).next();
+ reader.setInput(input);
+ assertEquals("wrong image count", 3, reader.getNumImages(true));
+ for(int i = 0; i < reader.getNumImages(true); i++){
+ reader.read(i);
+ }
}
}
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfoTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfoTest.java
new file mode 100644
index 00000000..bdb8df3b
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFProviderInfoTest.java
@@ -0,0 +1,19 @@
+package com.twelvemonkeys.imageio.plugins.tiff;
+
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
+import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
+
+/**
+ * TIFFProviderInfoTest.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: harald.kuhr$
+ * @version $Id: TIFFProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$
+ */
+public class TIFFProviderInfoTest extends ReaderWriterProviderInfoTest {
+
+ @Override
+ protected ReaderWriterProviderInfo createProviderInfo() {
+ return new TIFFProviderInfo();
+ }
+}
\ No newline at end of file
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStreamTest.java
index 04b65723..f998ae29 100644
--- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStreamTest.java
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCr16UpsamplerStreamTest.java
@@ -38,7 +38,8 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
/**
* YCbCr16UpsamplerStreamTest
@@ -50,22 +51,22 @@ import static org.junit.Assert.*;
public class YCbCr16UpsamplerStreamTest {
@Test(expected = IllegalArgumentException.class)
public void testCreateNullStream() {
- new YCbCr16UpsamplerStream(null, new int[2], 7, 5, null, ByteOrder.LITTLE_ENDIAN);
+ new YCbCr16UpsamplerStream(null, new int[2], 7, 5, ByteOrder.LITTLE_ENDIAN);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateNullChroma() {
- new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[3], 7, 5, null, ByteOrder.LITTLE_ENDIAN);
+ new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[3], 7, 5, ByteOrder.LITTLE_ENDIAN);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateShortChroma() {
- new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[1], 7, 5, null, ByteOrder.LITTLE_ENDIAN);
+ new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[1], 7, 5, ByteOrder.LITTLE_ENDIAN);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateNoByteOrder() {
- new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[] {2, 2}, 7, 5, null, null);
+ new YCbCr16UpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[] {2, 2}, 7, 5, null);
}
// TODO: The expected values seems bogus...
@@ -81,11 +82,11 @@ public class YCbCr16UpsamplerStreamTest {
byte[] bytes = new byte[shorts.length * 2];
ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shorts);
- InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 8, null, ByteOrder.LITTLE_ENDIAN);
+ InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 8, ByteOrder.LITTLE_ENDIAN);
short[] expected = new short[] {
- 0, -30864, 0, 0, -30863, 0, 0, -30966, 0, 0, -30965, 0, 0, -30870, 0, 0, -30869, 0, 0, -30815, 0, 0, -30761, 0,
- 0, -30862, 0, 0, -30861, 0, 0, -30931, 0, 0, -30877, 0, 0, -30868, 0, 0, -30867, 0, 0, -30858, 0, 0, -30858, 0
+ 1, 5, 6, 2, 5, 6, 7, 108, 109, 8, 108, 109, 110, 114, 115, 111, 114, 115, 43, 0, 0, 97, 0, 0,
+ 3, 5, 6, 4, 5, 6, 42, 108, 109, 96, 108, 109, 112, 114, 115, 113, 114, 115, 0, 0, 0, 0, 0, 0
};
short[] upsampled = new short[expected.length];
@@ -107,10 +108,10 @@ public class YCbCr16UpsamplerStreamTest {
byte[] bytes = new byte[shorts.length * 2];
ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asShortBuffer().put(shorts);
- InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 1}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null, ByteOrder.BIG_ENDIAN);
+ InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 1}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, ByteOrder.BIG_ENDIAN);
short[] expected = new short[] {
- 0, -30861, 0, 0, -30860, 0, 0, -30923, 0, 0, -30869, 0, 0, -30816, 0, 0, -30815, 0, 0, -30868, 0, 0, -30922, 0
+ 1, 3, 4, 2, 3, 4, 42, 77, 112, 96, 77, 112, 113, 115, 43, 114, 115, 43, 97, 77, 112, 43, 77, 112
};
short[] upsampled = new short[expected.length];
@@ -132,10 +133,10 @@ public class YCbCr16UpsamplerStreamTest {
byte[] bytes = new byte[shorts.length * 2];
ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).asShortBuffer().put(shorts);
- InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {1, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null, ByteOrder.BIG_ENDIAN);
+ InputStream stream = new YCbCr16UpsamplerStream(new ByteArrayInputStream(bytes), new int[] {1, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, ByteOrder.BIG_ENDIAN);
short[] expected = new short[] {
- 0, -30861, 0, 0, -30923, 0, 0, -30816, 0, 0, -30761, 0, 0, -30860, 0, 0, -30869, 0, 0, -30815, 0, 0, -30815, 0
+ 1, 3, 4, 42, 77, 112, 113, 115, 43, 97, 0, 0, 2, 3, 4, 96, 77, 112, 114, 115, 43, 43, 0, 0
};
short[] upsampled = new short[expected.length];
diff --git a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java
index a0faaced..3b0b6c73 100644
--- a/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java
+++ b/imageio/imageio-tiff/src/test/java/com/twelvemonkeys/imageio/plugins/tiff/YCbCrUpsamplerStreamTest.java
@@ -35,7 +35,8 @@ import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
/**
* YCbCrUpsamplerStreamTest
@@ -47,17 +48,17 @@ import static org.junit.Assert.*;
public class YCbCrUpsamplerStreamTest {
@Test(expected = IllegalArgumentException.class)
public void testCreateNullStream() {
- new YCbCrUpsamplerStream(null, new int[2], 7, 5, null);
+ new YCbCrUpsamplerStream(null, new int[2], 7, 5);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateNullChroma() {
- new YCbCrUpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[3], 7, 5, null);
+ new YCbCrUpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[3], 7, 5);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateShortChroma() {
- new YCbCrUpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[1], 7, 5, null);
+ new YCbCrUpsamplerStream(new ByteArrayInputStream(new byte[0]), new int[1], 7, 5);
}
@Test
@@ -66,16 +67,17 @@ public class YCbCrUpsamplerStreamTest {
1, 2, 3, 4, 5, 6, 7, 8, 42, 96,
108, 109, 110, 111, 112, 113, 114, 115, 43, 97
};
- InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 8, null);
+ InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 8);
byte[] expected = new byte[] {
- 0, -126, 0, 0, -125, 0, 0, 27, 0, 0, 28, 0, 92, 124, 85, 93, 125, 86, 0, -78, 0, 0, -24, 0,
- 0, -124, 0, 0, -123, 0, 15, 62, 7, 69, 116, 61, 94, 126, 87, 95, 127, 88, 0, -121, 0, 0, -121, 0
+ 1, 5, 6, 2, 5, 6, 7, 108, 109, 8, 108, 109, 110, 114, 115, 111, 114, 115, 43, 0, 0, 97, 0, 0,
+ 3, 5, 6, 4, 5, 6, 42, 108, 109, 96, 108, 109, 112, 114, 115, 113, 114, 115, 0, 0, 0, 0, 0, 0
};
byte[] upsampled = new byte[expected.length];
new DataInputStream(stream).readFully(upsampled);
+
assertArrayEquals(expected, upsampled);
assertEquals(-1, stream.read());
}
@@ -86,10 +88,10 @@ public class YCbCrUpsamplerStreamTest {
1, 2, 3, 4, 42, 96, 77,
112, 113, 114, 115, 43, 97, 43
};
- InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 1}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null);
+ InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {2, 1}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4);
byte[] expected = new byte[] {
- 0, -123, 0, 0, -122, 0, 20, 71, 0, 74, 125, 6, 0, -78, 90, 0, -77, 91, 75, 126, 7, 21, 72, 0
+ 1, 3, 4, 2, 3, 4, 42, 77, 112, 96, 77, 112, 113, 115, 43, 114, 115, 43, 97, 77, 112, 43, 77, 112
};
byte[] upsampled = new byte[expected.length];
@@ -105,10 +107,10 @@ public class YCbCrUpsamplerStreamTest {
1, 2, 3, 4, 42, 96, 77,
112, 113, 114, 115, 43, 97, 43
};
- InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {1, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4, null);
+ InputStream stream = new YCbCrUpsamplerStream(new ByteArrayInputStream(bytes), new int[] {1, 2}, TIFFExtension.YCBCR_POSITIONING_CENTERED, 4);
byte[] expected = new byte[] {
- 0, -123, 0, 20, 71, 0, 0, -78, 90, 0, -24, 0, 0, -122, 0, 74, 125, 6, 0, -77, 91, 0, -78, 0
+ 1, 3, 4, 42, 77, 112, 113, 115, 43, 97, 0, 0, 2, 3, 4, 96, 77, 112, 114, 115, 43, 43, 0, 0
};
byte[] upsampled = new byte[expected.length];
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/CCITTgetNextChangingElement.tif b/imageio/imageio-tiff/src/test/resources/tiff/CCITTgetNextChangingElement.tif
new file mode 100644
index 00000000..2422087a
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/CCITTgetNextChangingElement.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ColorCheckerCalculator.tif b/imageio/imageio-tiff/src/test/resources/tiff/ColorCheckerCalculator.tif
new file mode 100755
index 00000000..125b9310
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ColorCheckerCalculator.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif
new file mode 100644
index 00000000..5a87ee3d
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d_fill.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d_fill.tif
new file mode 100644
index 00000000..e1ce7e06
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_1d_fill.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif
new file mode 100644
index 00000000..9d0a4ff9
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_fill.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_fill.tif
new file mode 100644
index 00000000..70518f64
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_fill.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_lsb2msb.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_lsb2msb.tif
new file mode 100644
index 00000000..ff1dd6dc
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group3_2d_lsb2msb.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif
new file mode 100644
index 00000000..aa06b432
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt/group4.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/ccitt_tolessrows.tif b/imageio/imageio-tiff/src/test/resources/tiff/ccitt_tolessrows.tif
new file mode 100644
index 00000000..e8c46c33
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/ccitt_tolessrows.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/cmyk_jpeg.tif b/imageio/imageio-tiff/src/test/resources/tiff/cmyk_jpeg.tif
new file mode 100644
index 00000000..85d0bc92
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/cmyk_jpeg.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/cmyk_jpeg_no_profile.tif b/imageio/imageio-tiff/src/test/resources/tiff/cmyk_jpeg_no_profile.tif
new file mode 100644
index 00000000..fa6d491e
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/cmyk_jpeg_no_profile.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/README.txt b/imageio/imageio-tiff/src/test/resources/tiff/depth/README.txt
new file mode 100644
index 00000000..dd74611f
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/resources/tiff/depth/README.txt
@@ -0,0 +1,9 @@
+These sample TIFF image files are prepared by Bob Friesenhahn
+ using a development version of
+GraphicsMagick 1.2.
+
+See the file summary.txt for a description of the images.
+
+These files are hereby placed in the public domain.
+
+
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-02.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-02.tif
new file mode 100644
index 00000000..2278aff1
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-02.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-04.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-04.tif
new file mode 100644
index 00000000..2583c42f
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-04.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-06.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-06.tif
new file mode 100644
index 00000000..a9c0c3dd
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-06.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-08.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-08.tif
new file mode 100644
index 00000000..f7d83407
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-08.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-10.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-10.tif
new file mode 100644
index 00000000..d93960cb
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-10.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-12.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-12.tif
new file mode 100644
index 00000000..1673be84
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-12.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-14.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-14.tif
new file mode 100644
index 00000000..1d53867f
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-14.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-16.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-16.tif
new file mode 100644
index 00000000..3c2a6dd7
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-16.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-24.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-24.tif
new file mode 100644
index 00000000..5035df45
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-24.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-32.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-32.tif
new file mode 100644
index 00000000..36d48d4d
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-minisblack-32.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-02.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-02.tif
new file mode 100644
index 00000000..6bd445de
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-02.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-04.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-04.tif
new file mode 100644
index 00000000..b50ac8cc
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-04.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-08.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-08.tif
new file mode 100644
index 00000000..b778037e
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-08.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-16.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-16.tif
new file mode 100644
index 00000000..cf3de609
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-palette-16.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-02.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-02.tif
new file mode 100644
index 00000000..3813313d
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-02.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-04.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-04.tif
new file mode 100644
index 00000000..b469f946
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-04.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-08.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-08.tif
new file mode 100644
index 00000000..2ea09440
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-08.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-10.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-10.tif
new file mode 100644
index 00000000..b6a22402
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-10.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-12.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-12.tif
new file mode 100644
index 00000000..20485733
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-12.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-14.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-14.tif
new file mode 100644
index 00000000..c9e27c02
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-14.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-16.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-16.tif
new file mode 100644
index 00000000..869fe05a
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-16.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-24.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-24.tif
new file mode 100644
index 00000000..6037f52f
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-24.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-32.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-32.tif
new file mode 100644
index 00000000..eb13d6c2
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-contig-32.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-02.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-02.tif
new file mode 100644
index 00000000..afd364c7
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-02.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-04.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-04.tif
new file mode 100644
index 00000000..ec97dc3b
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-04.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-08.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-08.tif
new file mode 100644
index 00000000..bbe287fb
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-08.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-10.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-10.tif
new file mode 100644
index 00000000..9d21bbe9
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-10.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-12.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-12.tif
new file mode 100644
index 00000000..34a39449
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-12.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-14.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-14.tif
new file mode 100644
index 00000000..2c690024
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-14.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-16.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-16.tif
new file mode 100644
index 00000000..93566664
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-16.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-24.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-24.tif
new file mode 100644
index 00000000..e9365d18
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-24.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-32.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-32.tif
new file mode 100644
index 00000000..000391cf
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-rgb-planar-32.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-contig-08.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-contig-08.tif
new file mode 100644
index 00000000..7eefd539
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-contig-08.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-contig-16.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-contig-16.tif
new file mode 100644
index 00000000..5df4246f
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-contig-16.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-planar-08.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-planar-08.tif
new file mode 100644
index 00000000..6994a129
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-planar-08.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-planar-16.tif b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-planar-16.tif
new file mode 100644
index 00000000..380b9b4f
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/depth/flower-separated-planar-16.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/depth/summary.txt b/imageio/imageio-tiff/src/test/resources/tiff/depth/summary.txt
new file mode 100644
index 00000000..19f3aabd
--- /dev/null
+++ b/imageio/imageio-tiff/src/test/resources/tiff/depth/summary.txt
@@ -0,0 +1,37 @@
+
+flower-minisblack-02.tif 73x43 2-bit minisblack gray image
+flower-minisblack-04.tif 73x43 4-bit minisblack gray image
+flower-minisblack-06.tif 73x43 6-bit minisblack gray image
+flower-minisblack-08.tif 73x43 8-bit minisblack gray image
+flower-minisblack-10.tif 73x43 10-bit minisblack gray image
+flower-minisblack-12.tif 73x43 12-bit minisblack gray image
+flower-minisblack-14.tif 73x43 14-bit minisblack gray image
+flower-minisblack-16.tif 73x43 16-bit minisblack gray image
+flower-minisblack-24.tif 73x43 24-bit minisblack gray image
+flower-minisblack-32.tif 73x43 32-bit minisblack gray image
+flower-palette-02.tif 73x43 4-entry colormapped image
+flower-palette-04.tif 73x43 16-entry colormapped image
+flower-palette-08.tif 73x43 256-entry colormapped image
+flower-palette-16.tif 73x43 65536-entry colormapped image
+flower-rgb-contig-02.tif 73x43 2-bit contiguous RGB image
+flower-rgb-contig-04.tif 73x43 4-bit contiguous RGB image
+flower-rgb-contig-08.tif 73x43 8-bit contiguous RGB image
+flower-rgb-contig-10.tif 73x43 10-bit contiguous RGB image
+flower-rgb-contig-12.tif 73x43 12-bit contiguous RGB image
+flower-rgb-contig-14.tif 73x43 14-bit contiguous RGB image
+flower-rgb-contig-16.tif 73x43 16-bit contiguous RGB image
+flower-rgb-contig-24.tif 73x43 24-bit contiguous RGB image
+flower-rgb-contig-32.tif 73x43 32-bit contiguous RGB image
+flower-rgb-planar-02.tif 73x43 2-bit seperated RGB image
+flower-rgb-planar-04.tif 73x43 4-bit seperated RGB image
+flower-rgb-planar-08.tif 73x43 8-bit seperated RGB image
+flower-rgb-planar-10.tif 73x43 10-bit seperated RGB image
+flower-rgb-planar-12.tif 73x43 12-bit seperated RGB image
+flower-rgb-planar-14.tif 73x43 14-bit seperated RGB image
+flower-rgb-planar-16.tif 73x43 16-bit seperated RGB image
+flower-rgb-planar-24.tif 73x43 24-bit seperated RGB image
+flower-rgb-planar-32.tif 73x43 32-bit seperated RGB image
+flower-separated-contig-08.tif 73x43 8-bit contiguous CMYK image
+flower-separated-contig-16.tif 73x43 16-bit contiguous CMYK image
+flower-separated-planar-08.tif 73x43 8-bit separated CMYK image
+flower-separated-planar-16.tif 73x43 16-bit separated CMYK image
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/fivepages-scan-causingerrors.tif b/imageio/imageio-tiff/src/test/resources/tiff/fivepages-scan-causingerrors.tif
new file mode 100644
index 00000000..1e3dc912
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/fivepages-scan-causingerrors.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/floatingpoint-32bit.tif b/imageio/imageio-tiff/src/test/resources/tiff/floatingpoint-32bit.tif
new file mode 100644
index 00000000..a1e24a81
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/floatingpoint-32bit.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/general-cmm-error.tif b/imageio/imageio-tiff/src/test/resources/tiff/general-cmm-error.tif
new file mode 100644
index 00000000..feb25c6a
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/general-cmm-error.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/grayscale-alpha.tiff b/imageio/imageio-tiff/src/test/resources/tiff/grayscale-alpha.tiff
new file mode 100644
index 00000000..6b951e77
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/grayscale-alpha.tiff differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/flower-rgb-contig-08.tif b/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/flower-rgb-contig-08.tif
new file mode 100644
index 00000000..62a1b19d
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/flower-rgb-contig-08.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/flower-separated-planar-08.tif b/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/flower-separated-planar-08.tif
new file mode 100644
index 00000000..ad0bfea8
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/flower-separated-planar-08.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/group4.tif b/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/group4.tif
new file mode 100644
index 00000000..0dee7750
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/guessPhotometric/group4.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/lzw-buffer-overflow.tif b/imageio/imageio-tiff/src/test/resources/tiff/lzw-buffer-overflow.tif
new file mode 100644
index 00000000..d9b08dc3
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/lzw-buffer-overflow.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/lzw-rgba-4444.tif b/imageio/imageio-tiff/src/test/resources/tiff/lzw-rgba-4444.tif
new file mode 100644
index 00000000..4c1e60a8
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/lzw-rgba-4444.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/lzw-rgba-padded-icc.tif b/imageio/imageio-tiff/src/test/resources/tiff/lzw-rgba-padded-icc.tif
new file mode 100644
index 00000000..c8801c07
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/lzw-rgba-padded-icc.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/old-style-jpeg-bogus-jpeginterchangeformatlength.tif b/imageio/imageio-tiff/src/test/resources/tiff/old-style-jpeg-bogus-jpeginterchangeformatlength.tif
new file mode 100755
index 00000000..4930b9bd
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/old-style-jpeg-bogus-jpeginterchangeformatlength.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/scan-mono-iccgray.tif b/imageio/imageio-tiff/src/test/resources/tiff/scan-mono-iccgray.tif
new file mode 100755
index 00000000..197b6d30
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/scan-mono-iccgray.tif differ
diff --git a/imageio/imageio-tiff/src/test/resources/tiff/signed-integral-8bit.tif b/imageio/imageio-tiff/src/test/resources/tiff/signed-integral-8bit.tif
new file mode 100644
index 00000000..fa80da15
Binary files /dev/null and b/imageio/imageio-tiff/src/test/resources/tiff/signed-integral-8bit.tif differ
diff --git a/imageio/pom.xml b/imageio/pom.xml
index c25c074a..1c30e3b5 100644
--- a/imageio/pom.xml
+++ b/imageio/pom.xml
@@ -3,7 +3,7 @@
com.twelvemonkeys
twelvemonkeys
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
4.0.0
com.twelvemonkeys.imageio
@@ -26,10 +26,12 @@
imageio-core
imageio-metadata
+
imageio-clippath
imageio-bmp
+ imageio-hdr
imageio-icns
imageio-iff
imageio-jpeg
@@ -75,14 +77,14 @@
com.twelvemonkeys.common
common-lang
${project.version}
- tests
+ test-jar
test
com.twelvemonkeys.common
common-io
${project.version}
- tests
+ test-jar
test
@@ -137,7 +139,7 @@
${project.groupId}
imageio-core
${project.version}
- tests
+ test-jar
test
diff --git a/pom.xml b/pom.xml
index afcfe4f9..872c37e2 100755
--- a/pom.xml
+++ b/pom.xml
@@ -8,16 +8,26 @@
com.twelvemonkeys
twelvemonkeys
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
pom
Twelvemonkeys
+
+
+ The BSD License
+ https://github.com/haraldk/TwelveMonkeys#license
+ repo
+
+
+
common
servlet
imageio
+ contrib
+ bom
@@ -41,8 +51,27 @@
maven-guru
+
+ Oliver Schmidtmer
+ mail@trek7891.de
+
+ contributor
+
+
+
+ Jason Palmer
+ jpalmer@itemmaster.com
+
+ contributor
+
+
+
+ GitHub
+ https://github.com/haraldk/TwelveMonkeys/issues
+
+
scm:git:https://github.com/haraldk/TwelveMonkeys
scm:git:https://github.com/haraldk/TwelveMonkeys
@@ -94,7 +123,7 @@
org.apache.maven.plugins
maven-resources-plugin
- 2.5
+ 2.7
UTF-8
@@ -102,7 +131,7 @@
org.apache.maven.plugins
maven-jar-plugin
- 2.4
+ 2.6
true
@@ -117,23 +146,23 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 2.9.1
+ 2.10.3
org.apache.maven.plugins
maven-source-plugin
- 2.2.1
+ 2.4
org.apache.maven.plugins
maven-compiler-plugin
- 2.3.2
+ 3.3
true
1.7
1.7
false
- -g:lines
+ source,lines
iso-8859-1
@@ -143,7 +172,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 2.10
+ 2.18.1
@@ -154,22 +183,10 @@
-
- org.apache.maven.plugins
- maven-idea-plugin
- true
- 2.2
-
- 1.7
- 1.7
- true
-
-
-
org.apache.maven.plugins
maven-release-plugin
- 2.4.2
+ 2.5.2
org.apache.maven.scm
@@ -189,22 +206,22 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 2.9.1
+ 2.10.3
org.apache.maven.plugins
maven-surefire-report-plugin
- 2.16
+ 2.18.1
org.codehaus.mojo
cobertura-maven-plugin
- 2.6
+ 2.7
org.apache.maven.plugins
maven-pmd-plugin
- 3.0.1
+ 3.4
1.7
@@ -212,7 +229,7 @@
org.apache.maven.plugins
maven-checkstyle-plugin
- 2.10
+ 2.15
diff --git a/sandbox/pom.xml b/sandbox/pom.xml
index a8f332cd..cdcb591f 100644
--- a/sandbox/pom.xml
+++ b/sandbox/pom.xml
@@ -91,14 +91,14 @@
common-io
${project.version}
test
- tests
+ test-jar
com.twelvemonkeys.common
common-lang
${project.version}
test
- tests
+ test-jar
diff --git a/sandbox/sandbox-common/pom.xml b/sandbox/sandbox-common/pom.xml
index 596e8a41..31f6b315 100644
--- a/sandbox/sandbox-common/pom.xml
+++ b/sandbox/sandbox-common/pom.xml
@@ -44,14 +44,14 @@
com.twelvemonkeys.common
common-io
test
- tests
+ test-jar
com.twelvemonkeys.common
common-lang
test
- tests
+ test-jar
diff --git a/sandbox/sandbox-imageio/pom.xml b/sandbox/sandbox-imageio/pom.xml
index a7b23040..0d831a2f 100644
--- a/sandbox/sandbox-imageio/pom.xml
+++ b/sandbox/sandbox-imageio/pom.xml
@@ -72,14 +72,14 @@
com.twelvemonkeys.common
common-io
test
- tests
+ test-jar
com.twelvemonkeys.common
common-lang
test
- tests
+ test-jar
diff --git a/servlet/pom.xml b/servlet/pom.xml
index c44ea86e..2f2fc663 100644
--- a/servlet/pom.xml
+++ b/servlet/pom.xml
@@ -3,7 +3,7 @@
com.twelvemonkeys
twelvemonkeys
- 3.1-SNAPSHOT
+ 3.3-SNAPSHOT
4.0.0
@@ -32,14 +32,14 @@
com.twelvemonkeys.common
common-lang
${project.version}
- tests
+ test-jar
test
com.twelvemonkeys.common
common-io
${project.version}
- tests
+ test-jar
test