From 6458fcdcbd9961e787a78edc46f2ba8b41bd7d49 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sat, 8 Oct 2022 13:43:26 +0200 Subject: [PATCH] Major ImageMetadata refactor for more consistent standard metadata support. Fixes a few related bugs as a bonus. --- .../imageio/StandardImageMetadataSupport.java | 586 ++++++++++++++++++ .../StandardImageMetadataSupportTest.java | 140 +++++ .../imageio/plugins/hdr/HDRImageReader.java | 12 +- .../imageio/plugins/hdr/HDRMetadata.java | 116 +--- .../plugins/icns/ICNSImageMetadata.java | 41 ++ .../imageio/plugins/icns/ICNSImageReader.java | 44 +- .../imageio/plugins/icns/ICNSImageWriter.java | 9 +- .../imageio/plugins/icns/SipsJP2Reader.java | 24 +- .../imageio/plugins/iff/Form.java | 42 +- .../imageio/plugins/iff/IFFImageMetadata.java | 317 +++------- .../imageio/plugins/iff/IFFImageReader.java | 13 +- .../plugins/iff/IFFImageMetadataTest.java | 225 ++++--- .../imageio/plugins/pcx/PCXImageReader.java | 12 +- .../imageio/plugins/pcx/PCXMetadata.java | 244 +------- .../imageio/plugins/pict/PICTImageReader.java | 30 +- .../imageio/plugins/pict/PICTMetadata.java | 119 ++-- .../imageio/plugins/pntg/PNTGImageReader.java | 36 +- .../plugins/pntg/PNTGImageReaderSpi.java | 30 + .../imageio/plugins/pntg/PNTGMetadata.java | 111 ++-- .../plugins/pntg/PNTGProviderInfo.java | 2 +- .../plugins/pntg/PNTGMetadataTest.java | 6 +- .../imageio/plugins/pnm/PNMImageReader.java | 7 +- .../imageio/plugins/pnm/PNMMetadata.java | 154 ++--- .../imageio/plugins/sgi/SGIHeader.java | 4 +- .../imageio/plugins/sgi/SGIImageReader.java | 11 +- .../imageio/plugins/sgi/SGIMetadata.java | 212 +------ .../imageio/plugins/tga/TGAHeader.java | 22 +- .../imageio/plugins/tga/TGAImageReader.java | 8 +- .../imageio/plugins/tga/TGAImageWriter.java | 22 +- .../imageio/plugins/tga/TGAMetadata.java | 343 ++-------- .../plugins/tga/TGAImageWriteParamTest.java | 28 +- .../imageio/plugins/tga/TGAMetadataTest.java | 163 +++-- .../plugins/webp/WebPImageMetadata.java | 187 +----- .../imageio/plugins/webp/WebPImageReader.java | 5 +- .../plugins/webp/WebPImageMetadataTest.java | 127 ++-- .../imageio/plugins/xwd/XWDImageMetadata.java | 141 +---- .../imageio/plugins/xwd/XWDImageReader.java | 7 +- 37 files changed, 1648 insertions(+), 1952 deletions(-) create mode 100644 imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/StandardImageMetadataSupport.java create mode 100644 imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/StandardImageMetadataSupportTest.java create mode 100644 imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageMetadata.java diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/StandardImageMetadataSupport.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/StandardImageMetadataSupport.java new file mode 100644 index 00000000..29c3c05b --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/StandardImageMetadataSupport.java @@ -0,0 +1,586 @@ +package com.twelvemonkeys.imageio; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import java.awt.*; +import java.awt.color.*; +import java.awt.image.*; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Map; + +import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType.*; +import static com.twelvemonkeys.lang.Validate.isTrue; +import static com.twelvemonkeys.lang.Validate.notNull; + +/** + * Base class for easy read-only implementation of the standard image metadata format. + * Chroma, Data and Transparency nodes values are based on the required + * {@link ImageTypeSpecifier}. + * Other values or overrides may be specified using the builder. + * + * @author Harald Kuhr + */ +public class StandardImageMetadataSupport extends AbstractMetadata { + + // The only required field, most standard metadata can be extracted from the type + private final ImageTypeSpecifier type; + protected final ColorSpaceType colorSpaceType; + protected final boolean blackIsZero; + private final IndexColorModel palette; + protected final String compressionName; + protected final boolean compressionLossless; + protected final PlanarConfiguration planarConfiguration; + private final int[] bitsPerSample; + private final int[] significantBits; + private final int[] sampleMSB; + protected final Double pixelAspectRatio; + protected final ImageOrientation orientation; + protected final String formatVersion; + protected final SubimageInterpretation subimageInterpretation; + private final Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type, Consider a long timestamp + TimeZone to avoid messing up the API... + private final Collection> textEntries; + + protected StandardImageMetadataSupport(Builder builder) { + notNull(builder, "builder"); + + // Baseline + type = builder.type; + + // Chroma + colorSpaceType = builder.colorSpaceType; + blackIsZero = builder.blackIsZero; + palette = builder.palette; + + // Compression + compressionName = builder.compressionName; + compressionLossless = builder.compressionLossless; + + // Data + planarConfiguration = builder.planarConfiguration; + bitsPerSample = builder.bitsPerSample; + significantBits = builder.significantBits; + sampleMSB = builder.sampleMSB; + + // Dimension + orientation = builder.orientation; + pixelAspectRatio = builder.pixelAspectRatio; + + // Document + formatVersion = builder.formatVersion; + documentCreationTime = builder.documentCreationTime; + subimageInterpretation = builder.subimageInterpretation; + + // Text + textEntries = builder.textEntries; + } + + public static Builder builder(ImageTypeSpecifier type) { + return new Builder(type); + } + + public static class Builder { + private final ImageTypeSpecifier type; + private ColorSpaceType colorSpaceType; + private boolean blackIsZero = true; + private IndexColorModel palette; + private String compressionName; + private boolean compressionLossless = true; + private PlanarConfiguration planarConfiguration; + public int[] bitsPerSample; + private int[] significantBits; + private int[] sampleMSB; + private Double pixelAspectRatio; + private ImageOrientation orientation = ImageOrientation.Normal; + private String formatVersion; + private SubimageInterpretation subimageInterpretation; + private Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type + private final Collection> textEntries = new ArrayList<>(); + + protected Builder(ImageTypeSpecifier type) { + this.type = notNull(type, "type"); + } + + public Builder withColorSpaceType(ColorSpaceType colorSpaceType) { + this.colorSpaceType = colorSpaceType; + + return this; + } + + public Builder withBlackIsZero(boolean blackIsZero) { + this.blackIsZero = blackIsZero; + + return this; + } + + public Builder withPalette(IndexColorModel palette) { + this.palette = palette; + + return this; + } + + public Builder withCompressionName(String compressionName) { + this.compressionName = notNull(compressionName, "compressionName").equalsIgnoreCase("none") ? null : compressionName; + + return this; + } + + public Builder withCompressionLossless(boolean lossless) { + if (!lossless && compressionName == null) { + throw new IllegalStateException("Lossy compression requires compression name"); + } + + this.compressionLossless = lossless; + + return this; + } + + public Builder withPlanarConfiguration(PlanarConfiguration planarConfiguration) { + this.planarConfiguration = planarConfiguration; + + return this; + } + + public Builder withBitsPerSample(int... bitsPerSample) { + this.bitsPerSample = bitsPerSample; + + return this; + } + + public Builder withSignificantBitsPerSample(int... significantBits) { + this.significantBits = isTrue(significantBits.length == 1 || significantBits.length == type.getNumBands(), + significantBits, + String.format("single value or %d values expected", type.getNumBands())); + + return this; + } + + public Builder withSampleMSB(int... sampleMSB) { + this.sampleMSB = isTrue(sampleMSB.length == 1 || sampleMSB.length == type.getNumBands(), + sampleMSB, + String.format("single value or %d values expected", type.getNumBands())); + + return this; + } + + public Builder withPixelAspectRatio(Double pixelAspectRatio) { + this.pixelAspectRatio = pixelAspectRatio; + + return this; + } + + public Builder withOrientation(ImageOrientation orientation) { + this.orientation = notNull(orientation, "orientation"); + + return this; + } + + public Builder withFormatVersion(String formatVersion) { + this.formatVersion = notNull(formatVersion, "formatVersion"); + + return this; + } + + public Builder withSubimageInterpretation(SubimageInterpretation interpretation) { + this.subimageInterpretation = interpretation; + + return this; + } + + public Builder withDocumentCreationTime(Calendar creationTime) { + this.documentCreationTime = creationTime; + + return this; + } + + public Builder withTextEntries(Map entries) { + return withTextEntries(notNull(entries, "entries").entrySet()); + } + + public Builder withTextEntries(Collection> entries) { + this.textEntries.addAll(notNull(entries, "entries")); + + return this; + } + + public Builder withTextEntry(String keyword, String value) { + if (value != null && !value.isEmpty()) { + this.textEntries.add(new SimpleImmutableEntry<>(notNull(keyword, "keyword"), value)); + } + + return this; + } + + public IIOMetadata build() { + return new StandardImageMetadataSupport(this); + } + } + + protected enum ColorSpaceType { + XYZ(3), + Lab(3), + Luv(3), + YCbCr(3), + Yxy(3), + YCCK(4), + PhotoYCC(3), + RGB(3), + GRAY(1), + HSV(3), + HLS(3), + CMYK(3), + CMY(3), + + // Generic types (so much extra work, because Java names can't start with a number, phew...) + GENERIC_2CLR(2, "2CLR"), + GENERIC_3CLR(3, "3CLR"), + GENERIC_4CLR(4, "4CLR"), + GENERIC_5CLR(5, "5CLR"), + GENERIC_6CLR(6, "6CLR"), + GENERIC_7CLR(7, "7CLR"), + GENERIC_8CLR(8, "8CLR"), + GENERIC_9CLR(9, "9CLR"), + GENERIC_ACLR(0xA, "ACLR"), + GENERIC_BCLR(0xB, "BCLR"), + GENERIC_CCLR(0xC, "CCLR"), + GENERIC_DCLR(0xD, "DCLR"), + GENERIC_ECLR(0xE, "ECLR"), + GENERIC_FCLR(0xF, "FCLR"); + + final int numChannels; + private final String nameOverride; + + ColorSpaceType(int numChannels) { + this(numChannels, null); + } + ColorSpaceType(int numChannels, String nameOverride) { + this.numChannels = numChannels; + this.nameOverride = nameOverride; + } + + @Override + public String toString() { + return nameOverride != null ? nameOverride : super.toString(); + } + } + + protected enum PlanarConfiguration { + PixelInterleaved, + PlaneInterleaved, + LineInterleaved, + TileInterleaved + } + + protected enum ImageOrientation { + Normal, + Rotate90, + Rotate180, + Rotate270, + FlipH, + FlipV, + FlipHRotate90, + FlipVRotate90 + } + + protected enum SubimageInterpretation { + Standalone, + SinglePage, + FullResolution, + ReducedResolution, + PyramidLayer, + Preview, + VolumeSlice, + ObjectView, + Panorama, + AnimationFrame, + TransparencyMask, + CompositingLayer, + SpectralSlice, + Unknown + } + + @Override + protected IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chromaNode = new IIOMetadataNode("Chroma"); + + ColorModel colorModel = colorSpaceType != null ? null : type.getColorModel(); + ColorSpaceType csType = colorSpaceType != null ? colorSpaceType : colorSpaceType(colorModel.getColorSpace()); + int numComponents = colorSpaceType != null ? colorSpaceType.numChannels : colorModel.getNumComponents(); + + IIOMetadataNode colorSpaceTypeNode = new IIOMetadataNode("ColorSpaceType"); + chromaNode.appendChild(colorSpaceTypeNode); + colorSpaceTypeNode.setAttribute("name", csType.toString()); + + IIOMetadataNode numChannelsNode = new IIOMetadataNode("NumChannels"); + numChannelsNode.setAttribute("value", String.valueOf(numComponents)); + chromaNode.appendChild(numChannelsNode); + + IIOMetadataNode blackIsZeroNode = new IIOMetadataNode("BlackIsZero"); + blackIsZeroNode.setAttribute("value", booleanString(blackIsZero)); + chromaNode.appendChild(blackIsZeroNode); + + if (colorModel instanceof IndexColorModel || palette != null) { + IndexColorModel colorMap = palette != null ? palette : (IndexColorModel) colorModel; + + IIOMetadataNode paletteNode = new IIOMetadataNode("Palette"); + chromaNode.appendChild(paletteNode); + + for (int i = 0; i < colorMap.getMapSize(); i++) { + IIOMetadataNode paletteEntryNode = new IIOMetadataNode("PaletteEntry"); + paletteNode.appendChild(paletteEntryNode); + + paletteEntryNode.setAttribute("index", Integer.toString(i)); + paletteEntryNode.setAttribute("red", Integer.toString(colorMap.getRed(i))); + paletteEntryNode.setAttribute("green", Integer.toString(colorMap.getGreen(i))); + paletteEntryNode.setAttribute("blue", Integer.toString(colorMap.getBlue(i))); + + // Assumption: BITMASK transparency will use single transparent pixel + if (colorMap.getTransparency() == Transparency.TRANSLUCENT) { + paletteEntryNode.setAttribute("alpha", Integer.toString(colorMap.getAlpha(i))); + } + } + + if (colorMap.getTransparentPixel() != -1) { + IIOMetadataNode backgroundIndexNode = new IIOMetadataNode("BackgroundIndex"); + chromaNode.appendChild(backgroundIndexNode); + backgroundIndexNode.setAttribute("value", Integer.toString(colorMap.getTransparentPixel())); + } + } + + // TODO: BackgroundColor? + + return chromaNode; + } + + private static ColorSpaceType colorSpaceType(ColorSpace colorSpace) { + switch (colorSpace.getType()) { + case ColorSpace.TYPE_XYZ: + return XYZ; + case ColorSpace.TYPE_Lab: + return Lab; + case ColorSpace.TYPE_Luv: + return Luv; + case ColorSpace.TYPE_YCbCr: + return YCbCr; + case ColorSpace.TYPE_Yxy: + return Yxy; + // Note: Can't map to YCCK or PhotoYCC, as there's no corresponding constant in java.awt.ColorSpace + case ColorSpace.TYPE_RGB: + return RGB; + case ColorSpace.TYPE_GRAY: + return GRAY; + case ColorSpace.TYPE_HSV: + return HSV; + case ColorSpace.TYPE_HLS: + return HLS; + case ColorSpace.TYPE_CMYK: + return CMYK; + case ColorSpace.TYPE_CMY: + return CMY; + default: + int numComponents = colorSpace.getNumComponents(); + if (numComponents == 1) { + return GRAY; + } + else if (numComponents < 16) { + return ColorSpaceType.valueOf("GENERIC_" + Integer.toHexString(numComponents) + "CLR"); + } + } + + throw new IllegalArgumentException("Unknown ColorSpace type: " + colorSpace); + } + + @Override + protected IIOMetadataNode getStandardCompressionNode() { + if (compressionName == null) { + return null; + } + + IIOMetadataNode node = new IIOMetadataNode("Compression"); + + IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); + compressionTypeName.setAttribute("value", compressionName); + node.appendChild(compressionTypeName); + + IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); + lossless.setAttribute("value", booleanString(compressionLossless)); + node.appendChild(lossless); + + return node; + } + + protected static String booleanString(boolean booleanValue) { + return booleanValue ? "TRUE" : "FALSE"; + } + + @Override + protected IIOMetadataNode getStandardDataNode() { + IIOMetadataNode dataNode = new IIOMetadataNode("Data"); + + IIOMetadataNode planarConfigurationNode = new IIOMetadataNode("PlanarConfiguration"); + dataNode.appendChild(planarConfigurationNode); + planarConfigurationNode.setAttribute("value", planarConfiguration != null ? planarConfiguration.toString() : + (type.getSampleModel() instanceof BandedSampleModel ? "PlaneInterleaved" : "PixelInterleaved")); + + String sampleFormatValue = colorSpaceType == null && type.getColorModel() instanceof IndexColorModel + ? "Index" + : sampleFormat(type.getSampleModel()); + + if (sampleFormatValue != null) { + IIOMetadataNode sampleFormatNode = new IIOMetadataNode("SampleFormat"); + sampleFormatNode.setAttribute("value", sampleFormatValue); + dataNode.appendChild(sampleFormatNode); + } + + int[] bitsPerSample = this.bitsPerSample != null ? this.bitsPerSample : type.getSampleModel().getSampleSize(); + IIOMetadataNode bitsPerSampleNode = new IIOMetadataNode("BitsPerSample"); + bitsPerSampleNode.setAttribute("value", createListValue(bitsPerSample.length, bitsPerSample)); + dataNode.appendChild(bitsPerSampleNode); + + if (significantBits != null) { + String significantBitsValue = createListValue(type.getNumBands(), significantBits); + if (!significantBitsValue.equals(bitsPerSampleNode.getAttribute("value"))) { + IIOMetadataNode significantBitsPerSampleNode = new IIOMetadataNode("SignificantBitsPerSample"); + significantBitsPerSampleNode.setAttribute("value", significantBitsValue); + dataNode.appendChild(significantBitsPerSampleNode); + } + } + + if (sampleMSB != null) { + // TODO: Only if different from default! + IIOMetadataNode sampleMSBNode = new IIOMetadataNode("SampleMSB"); + sampleMSBNode.setAttribute("value", createListValue(type.getNumBands(), sampleMSB)); + dataNode.appendChild(sampleMSBNode); + } + + return dataNode; + } + + private static String createListValue(final int itemCount, final int... 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(); + } + + private static String sampleFormat(SampleModel sampleModel) { + switch (sampleModel.getDataType()) { + case DataBuffer.TYPE_SHORT: + case DataBuffer.TYPE_INT: + if (sampleModel instanceof ComponentSampleModel) { + return "SignedIntegral"; + } + // Otherwise fall-through, most likely a *PixelPackedSampleModel + case DataBuffer.TYPE_BYTE: + case DataBuffer.TYPE_USHORT: + return "UnsignedIntegral"; + case DataBuffer.TYPE_FLOAT: + case DataBuffer.TYPE_DOUBLE: + return "Real"; + default: + return null; + } + } + + @Override + protected IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension"); + + if (pixelAspectRatio != null) { + IIOMetadataNode pixelAspectRatioNode = new IIOMetadataNode("PixelAspectRatio"); + pixelAspectRatioNode.setAttribute("value", String.valueOf(pixelAspectRatio)); + dimensionNode.appendChild(pixelAspectRatioNode); + } + + IIOMetadataNode imageOrientationNode = new IIOMetadataNode("ImageOrientation"); + imageOrientationNode.setAttribute("value", orientation.toString()); + dimensionNode.appendChild(imageOrientationNode); + + return dimensionNode.hasChildNodes() ? dimensionNode : null; + } + + @Override + protected IIOMetadataNode getStandardDocumentNode() { + IIOMetadataNode documentNode = new IIOMetadataNode("Document"); + + if (formatVersion != null) { + IIOMetadataNode formatVersionNode = new IIOMetadataNode("FormatVersion"); + documentNode.appendChild(formatVersionNode); + formatVersionNode.setAttribute("value", formatVersion); + } + + if (subimageInterpretation != null) { + IIOMetadataNode subImageInterpretationNode = new IIOMetadataNode("SubimageInterpretation"); + documentNode.appendChild(subImageInterpretationNode); + subImageInterpretationNode.setAttribute("value", subimageInterpretation.toString()); + } + + if (documentCreationTime != null) { + IIOMetadataNode imageCreationTimeNode = new IIOMetadataNode("ImageCreationTime"); + documentNode.appendChild(imageCreationTimeNode); + + imageCreationTimeNode.setAttribute("year", String.valueOf(documentCreationTime.get(Calendar.YEAR))); + imageCreationTimeNode.setAttribute("month", String.valueOf(documentCreationTime.get(Calendar.MONTH) + 1)); + imageCreationTimeNode.setAttribute("day", String.valueOf(documentCreationTime.get(Calendar.DAY_OF_MONTH))); + imageCreationTimeNode.setAttribute("hour", String.valueOf(documentCreationTime.get(Calendar.HOUR_OF_DAY))); + imageCreationTimeNode.setAttribute("minute", String.valueOf(documentCreationTime.get(Calendar.MINUTE))); + imageCreationTimeNode.setAttribute("second", String.valueOf(documentCreationTime.get(Calendar.SECOND))); + } + + return documentNode.hasChildNodes() ? documentNode : null; + } + + @Override + protected IIOMetadataNode getStandardTextNode() { + if (textEntries.isEmpty()) { + return null; + } + + IIOMetadataNode textNode = new IIOMetadataNode("Text"); + + // DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright: + // /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value. + + for (Map.Entry entry : textEntries) { + IIOMetadataNode textEntryNode = new IIOMetadataNode("TextEntry"); + textNode.appendChild(textEntryNode); + textEntryNode.setAttribute("keyword", entry.getKey()); + textEntryNode.setAttribute("value", entry.getValue()); + } + + return textNode; + + } + + @Override + protected IIOMetadataNode getStandardTransparencyNode() { + IIOMetadataNode transparencyNode = new IIOMetadataNode("Transparency"); + + ColorModel colorModel = type.getColorModel(); + + IIOMetadataNode alphaNode = new IIOMetadataNode("Alpha"); + transparencyNode.appendChild(alphaNode); + alphaNode.setAttribute("value", colorModel.hasAlpha() ? (colorModel.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied") : "none"); + + if (colorModel instanceof IndexColorModel) { + IndexColorModel icm = (IndexColorModel) colorModel; + if (icm.getTransparentPixel() != -1) { + IIOMetadataNode transparentIndexNode = new IIOMetadataNode("TransparentIndex"); + transparencyNode.appendChild(transparentIndexNode); + transparentIndexNode.setAttribute("value", Integer.toString(icm.getTransparentPixel())); + } + } + + return transparencyNode; + } +} diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/StandardImageMetadataSupportTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/StandardImageMetadataSupportTest.java new file mode 100644 index 00000000..94d8c90f --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/StandardImageMetadataSupportTest.java @@ -0,0 +1,140 @@ +package com.twelvemonkeys.imageio; + +import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ImageOrientation; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport.PlanarConfiguration; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport.SubimageInterpretation; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; + +import org.junit.Test; + +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataNode; +import java.awt.image.*; +import java.util.Arrays; +import java.util.Collection; + +import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.builder; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class StandardImageMetadataSupportTest { + @Test(expected = IllegalArgumentException.class) + public void createNullBuilder() { + new StandardImageMetadataSupport(null); + } + + @Test(expected = IllegalArgumentException.class) + public void createNullType() { + new StandardImageMetadataSupport(builder(null)); + } + + @Test(expected = IllegalArgumentException.class) + public void builderNullType() { + builder(null).build(); + } + + @Test + public void createValid() { + IIOMetadata metadata = new StandardImageMetadataSupport(builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB))); + assertNotNull(metadata); + } + + @Test + public void builderValid() { + IIOMetadata metadata = builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)) + .build(); + assertNotNull(metadata); + } + + + @Test + public void withPlanarColorspaceType() { + // See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html + Collection allowedValues = Arrays.asList( + "XYZ", "Lab", "Luv", "YCbCr", "Yxy", "YCCK", "PhotoYCC", + "RGB", "GRAY", "HSV", "HLS", "CMYK", "CMY", + "2CLR", "3CLR", "4CLR", "5CLR", "6CLR", "7CLR", "8CLR", + "9CLR", "ACLR", "BCLR", "CCLR", "DCLR", "ECLR", "FCLR" + ); + + for (ColorSpaceType value : ColorSpaceType.values()) { + StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY)) + .withColorSpaceType(value) + .build(); + assertNotNull(metadata); + + IIOMetadataNode documentNode = metadata.getStandardChromaNode(); + assertNotNull(documentNode); + + IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ColorSpaceType").item(0); + assertEquals(value.toString(), subImageInterpretation.getAttribute("name")); // Format oddity: Why is this not "value"? + assertTrue(allowedValues.contains(value.toString())); + } + } + + @Test + public void withPlanarConfiguration() { + // See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html + Collection allowedValues = Arrays.asList("PixelInterleaved", "PlaneInterleaved", "LineInterleaved", "TileInterleaved"); + + for (PlanarConfiguration value : PlanarConfiguration.values()) { + StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)) + .withPlanarConfiguration(value) + .build(); + assertNotNull(metadata); + + IIOMetadataNode documentNode = metadata.getStandardDataNode(); + assertNotNull(documentNode); + + IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("PlanarConfiguration").item(0); + assertEquals(value.toString(), subImageInterpretation.getAttribute("value")); + assertTrue(allowedValues.contains(value.toString())); + } + } + + @Test + public void withImageOrientation() { + // See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html + Collection allowedValues = Arrays.asList("Normal", "Rotate90", "Rotate180", "Rotate270", "FlipH", "FlipV", "FlipHRotate90", "FlipVRotate90"); + + for (ImageOrientation value : ImageOrientation.values()) { + StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY)) + .withOrientation(value) + .build(); + assertNotNull(metadata); + + IIOMetadataNode documentNode = metadata.getStandardDimensionNode(); + assertNotNull(documentNode); + + IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ImageOrientation").item(0); + assertEquals(value.toString(), subImageInterpretation.getAttribute("value")); + assertTrue(allowedValues.contains(value.toString())); + } + } + + @Test + public void withSubimageInterpretation() { + // See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html + Collection allowedValues = Arrays.asList( + "Standalone", "SinglePage", "FullResolution", "ReducedResolution", "PyramidLayer", + "Preview", "VolumeSlice", "ObjectView", "Panorama", "AnimationFrame", + "TransparencyMask", "CompositingLayer", "SpectralSlice", "Unknown" + ); + + for (SubimageInterpretation value : SubimageInterpretation.values()) { + StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB)) + .withSubimageInterpretation(value) + .build(); + assertNotNull(metadata); + + IIOMetadataNode documentNode = metadata.getStandardDocumentNode(); + assertNotNull(documentNode); + + IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("SubimageInterpretation").item(0); + assertEquals(value.toString(), subImageInterpretation.getAttribute("value")); + assertTrue(allowedValues.contains(value.toString())); + } + } +} \ No newline at end of file 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 index b1af2080..f7e33590 100644 --- 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 @@ -40,11 +40,8 @@ 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.awt.color.*; +import java.awt.image.*; import java.io.File; import java.io.IOException; import java.util.Collections; @@ -244,10 +241,7 @@ public final class HDRImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(int imageIndex) throws IOException { - checkBounds(imageIndex); - readHeader(); - - return new HDRMetadata(header); + return new HDRMetadata(getRawImageType(imageIndex), header); } public static void main(final String[] args) throws IOException { 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 index b783c68f..ec35918f 100755 --- 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 @@ -1,83 +1,19 @@ -/* - * 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 of the copyright holder 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 HOLDER 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 com.twelvemonkeys.imageio.StandardImageMetadataSupport; +import javax.imageio.ImageTypeSpecifier; 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; +public class HDRMetadata extends StandardImageMetadataSupport { + public HDRMetadata(ImageTypeSpecifier type, HDRHeader header) { + super(builder(type) + .withCompressionName("RLE") + .withTextEntry("Software", header.getSoftware())); } + // For HDR, the stored sample data is UnsignedIntegral and data is 4 channels (RGB+Exp), + // but decoded to Real (float) 3 chanel RGB @Override protected IIOMetadataNode getStandardDataNode() { IIOMetadataNode node = new IIOMetadataNode("Data"); @@ -92,38 +28,4 @@ final class HDRMetadata extends AbstractMetadata { 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-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageMetadata.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageMetadata.java new file mode 100644 index 00000000..20cf3783 --- /dev/null +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageMetadata.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.StandardImageMetadataSupport; + +import javax.imageio.ImageTypeSpecifier; + +final class ICNSImageMetadata extends StandardImageMetadataSupport { + ICNSImageMetadata(ImageTypeSpecifier type, String compressionName) { + super(builder(type).withCompressionName(compressionName)); + } +} diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java index 82962c98..8ceb1faf 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageReader.java @@ -35,11 +35,16 @@ import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; -import javax.imageio.*; +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.color.ColorSpace; +import java.awt.color.*; import java.awt.image.*; import java.io.DataInputStream; import java.io.File; @@ -61,10 +66,9 @@ import java.util.List; * @see Apple Icon Image format (Wikipedia) */ public final class ICNSImageReader extends ImageReaderBase { - // TODO: Support ToC resource for faster parsing/faster determine number of icons? // TODO: Subsampled reading for completeness, even if never used? - private List icons = new ArrayList(); - private List masks = new ArrayList(); + private final List icons = new ArrayList<>(); + private final List masks = new ArrayList<>(); private IconResource lastResourceRead; private int length; @@ -136,7 +140,7 @@ public final class ICNSImageReader extends ImageReaderBase { ImageTypeSpecifier rawType = getRawImageType(imageIndex); IconResource resource = readIconResource(imageIndex); - List specifiers = new ArrayList(); + List specifiers = new ArrayList<>(); switch (resource.depth()) { case 1: @@ -230,14 +234,9 @@ public final class ICNSImageReader extends ImageReaderBase { packedSize -= 4; } - InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize); - - try { + try (InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize)) { ICNSUtil.decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data } - finally { - input.close(); - } } else { data = new byte[resource.length - ICNS.RESOURCE_HEADER_SIZE]; @@ -491,7 +490,7 @@ public final class ICNSImageReader extends ImageReaderBase { String format; - if (Arrays.equals(ICNS.PNG_MAGIC, magic)) { + if (Arrays.equals(ICNS.PNG_MAGIC, Arrays.copyOfRange(magic, 0, ICNS.PNG_MAGIC.length))) { format = "PNG"; } else if (Arrays.equals(ICNS.JPEG_2000_MAGIC, magic)) { @@ -527,7 +526,6 @@ public final class ICNSImageReader extends ImageReaderBase { IconResource resource = IconResource.read(imageInput); if (resource.isTOC()) { - // TODO: IconResource.readTOC()? int resourceCount = (resource.length - ICNS.RESOURCE_HEADER_SIZE) / ICNS.RESOURCE_HEADER_SIZE; long pos = resource.start + resource.length; @@ -570,6 +568,23 @@ public final class ICNSImageReader extends ImageReaderBase { } } + @Override + public IIOMetadata getImageMetadata(int imageIndex) throws IOException { + IconResource resource = readIconResource(imageIndex); + + String compressionName; + if (resource.isForeignFormat()) { + // Special handling of PNG/JPEG 2000 icons + imageInput.seek(resource.start + ICNS.RESOURCE_HEADER_SIZE); + compressionName = getForeignFormat(imageInput); + } + else { + compressionName = resource.isCompressed() ? "RLE" : "None"; + } + + return new ICNSImageMetadata(getRawImageType(imageIndex), compressionName); + } + private static final class ICNSBitMaskColorModel extends IndexColorModel { static final IndexColorModel INSTANCE = new ICNSBitMaskColorModel(); @@ -578,7 +593,6 @@ public final class ICNSImageReader extends ImageReaderBase { } } - @SuppressWarnings({"UnusedAssignment"}) public static void main(String[] args) throws IOException { int argIndex = 0; diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java index 56641a40..be78fb08 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/ICNSImageWriter.java @@ -34,7 +34,13 @@ import com.twelvemonkeys.imageio.ImageWriterBase; import com.twelvemonkeys.imageio.stream.SubImageOutputStream; import com.twelvemonkeys.imageio.util.ProgressListenerBase; -import javax.imageio.*; +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; import javax.imageio.event.IIOWriteWarningListener; import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageWriterSpi; @@ -104,6 +110,7 @@ public final class ICNSImageWriter extends ImageWriterBase { sequenceIndex = 0; } + @SuppressWarnings("RedundantThrows") @Override public void endWriteSequence() throws IOException { assertOutput(); diff --git a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java index 9b1fb949..ef129ddf 100644 --- a/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java +++ b/imageio/imageio-icns/src/main/java/com/twelvemonkeys/imageio/plugins/icns/SipsJP2Reader.java @@ -38,8 +38,13 @@ import javax.imageio.ImageIO; import javax.imageio.ImageReadParam; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; -import java.awt.image.BufferedImage; -import java.io.*; +import java.awt.image.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.Iterator; /** @@ -140,17 +145,12 @@ final class SipsJP2Reader { } private static String checkErrorMessage(final Process process) throws IOException { - InputStream stream = process.getErrorStream(); - - try { + try (InputStream stream = process.getErrorStream()) { BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); String message = reader.readLine(); return message != null && message.startsWith("Error: ") ? message.substring(7) : null; } - finally { - stream.close(); - } } private static String[] buildCommand(final File sipsCommand, final File tempFile) { @@ -159,19 +159,13 @@ final class SipsJP2Reader { }; } - private static File dumpToFile(final ImageInputStream stream) throws IOException { File tempFile = File.createTempFile("imageio-icns-", ".png"); tempFile.deleteOnExit(); - FileOutputStream out = new FileOutputStream(tempFile); - - try { + try (FileOutputStream out = new FileOutputStream(tempFile)) { FileUtil.copy(IIOUtil.createStreamAdapter(stream), out); } - finally { - out.close(); - } return tempFile; } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java index 1462d237..c43b821b 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/Form.java @@ -1,13 +1,13 @@ package com.twelvemonkeys.imageio.plugins.iff; import javax.imageio.IIOException; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; +import java.awt.image.*; import java.util.ArrayList; import java.util.List; +import static com.twelvemonkeys.imageio.plugins.iff.IFF.*; import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr; +import static com.twelvemonkeys.lang.Validate.isTrue; /** * Form. @@ -27,7 +27,7 @@ abstract class Form { abstract int width(); abstract int height(); - abstract float aspect(); + abstract double aspect(); abstract int bitplanes(); abstract int compressionType(); @@ -118,7 +118,7 @@ abstract class Form { } private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final XS24Chunk thumbnail, final BODYChunk body) { - super(formType); + super(isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s")); this.bitmapHeader = bitmapHeader; this.viewMode = viewMode; this.colorMap = colorMap; @@ -127,6 +127,19 @@ abstract class Form { this.body = body; } + private static boolean validFormType(int formType) { + switch (formType) { + case TYPE_ACBM: + case TYPE_ILBM: + case TYPE_PBM: + case TYPE_RGB8: + case TYPE_RGBN: + return true; + default: + return false; + } + } + @Override int width() { return bitmapHeader.width; @@ -148,8 +161,8 @@ abstract class Form { } @Override - float aspect() { - return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (float) bitmapHeader.yAspect); + double aspect() { + return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (double) bitmapHeader.yAspect); } @Override @@ -297,7 +310,7 @@ abstract class Form { } private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final XS24Chunk thumbnail, final BODYChunk body) { - super(formType); + super(isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s")); this.deepGlobal = deepGlobal; this.deepLocation = deepLocation; this.deepPixel = deepPixel; @@ -305,6 +318,15 @@ abstract class Form { this.body = body; } + private static boolean validFormType(int formType) { + switch (formType) { + case TYPE_DEEP: + case TYPE_TVPP: + return true; + default: + return false; + } + } @Override int width() { @@ -337,8 +359,8 @@ abstract class Form { } @Override - float aspect() { - return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (float) deepGlobal.yAspect; + double aspect() { + return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (double) deepGlobal.yAspect; } @Override diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java index 239c5215..8ab39545 100644 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadata.java @@ -1,188 +1,85 @@ +/* + * Copyright (c) 2020, 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 of the copyright holder 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 HOLDER 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.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; -import javax.imageio.metadata.IIOMetadataNode; -import java.awt.*; -import java.awt.image.IndexColorModel; +import javax.imageio.ImageTypeSpecifier; +import java.awt.image.*; import java.nio.charset.StandardCharsets; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import static com.twelvemonkeys.imageio.plugins.iff.IFF.*; -import static com.twelvemonkeys.lang.Validate.isTrue; +import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr; import static com.twelvemonkeys.lang.Validate.notNull; +import static java.util.Collections.emptyList; -final class IFFImageMetadata extends AbstractMetadata { - private final Form header; - private final IndexColorModel colorMap; - private final List meta; - - IFFImageMetadata(Form header, IndexColorModel colorMap) { - this.header = notNull(header, "header"); - isTrue(validFormType(header.formType), header.formType, "Unknown IFF Form type: %s"); - this.colorMap = colorMap; - this.meta = header.meta; +final class IFFImageMetadata extends StandardImageMetadataSupport { + IFFImageMetadata(ImageTypeSpecifier type, Form header, IndexColorModel palette) { + this(builder(type), notNull(header, "header"), palette); } - private boolean validFormType(int formType) { - switch (formType) { - default: - return false; - case TYPE_ACBM: - case TYPE_DEEP: - case TYPE_ILBM: - case TYPE_PBM: - case TYPE_RGB8: - case TYPE_RGBN: - case TYPE_TVPP: - return true; - } + private IFFImageMetadata(Builder builder, Form header, IndexColorModel palette) { + super(builder.withPalette(palette) + .withCompressionName(compressionName(header)) + .withBitsPerSample(bitsPerSample(header)) + .withPlanarConfiguration(planarConfiguration(header)) + .withPixelAspectRatio(header.aspect() != 0 ? header.aspect() : null) + .withFormatVersion("1.0") + .withTextEntries(textEntries(header))); } - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); - - IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); - chroma.appendChild(csType); - - switch (header.bitplanes()) { - case 8: - if (colorMap == null) { - csType.setAttribute("name", "GRAY"); - break; - } - case 1: - case 2: - case 3: + private static String compressionName(Form header) { + switch (header.compressionType()) { + case BMHDChunk.COMPRESSION_NONE: + return "None"; + case BMHDChunk.COMPRESSION_BYTE_RUN: + return "RLE"; case 4: - case 5: - case 6: - case 7: - case 24: - case 25: - case 32: - csType.setAttribute("name", "RGB"); - break; + // Compression type 4 means different things for different FORM types, we support + // Impulse RGB8 RLE compression: 24 bit RGB + 1 bit mask + 7 bit run count + if (header.formType == TYPE_RGB8) { + return "RGB8"; + } default: - csType.setAttribute("name", "Unknown"); + return "Unknown"; } - - // 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); - if (colorMap == null && header.bitplanes() == 8) { - numChannels.setAttribute("value", Integer.toString(1)); - } - else if (header.bitplanes() == 25 || header.bitplanes() == 32) { - numChannels.setAttribute("value", Integer.toString(4)); - } - else { - numChannels.setAttribute("value", Integer.toString(3)); - } - - IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - 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, - // as it might be unexpected... Then again... - if (colorMap != null) { - IIOMetadataNode palette = new IIOMetadataNode("Palette"); - chroma.appendChild(palette); - - 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))); - } - - if (colorMap.getTransparentPixel() != -1) { - IIOMetadataNode backgroundIndex = new IIOMetadataNode("BackgroundIndex"); - chroma.appendChild(backgroundIndex); - backgroundIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel())); - } - } - - // TODO: TVPP TVPaint Project files have a MIXR chunk with a background color - // and also a BGP1 (background pen 1?) and BGP2 chunks -// 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() { - if (header.compressionType() == BMHDChunk.COMPRESSION_NONE) { - return null; // All defaults - } + private static int[] bitsPerSample(Form header) { + int bitplanes = header.bitplanes(); - 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 data = new IIOMetadataNode("Data"); - - // PlanarConfiguration - IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); - switch (header.formType) { - case TYPE_DEEP: - case TYPE_TVPP: - case TYPE_RGB8: - case TYPE_PBM: - planarConfiguration.setAttribute("value", "PixelInterleaved"); - break; - case TYPE_ILBM: - planarConfiguration.setAttribute("value", "PlaneInterleaved"); - break; - default: - planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(header.formType)); - break; - } - data.appendChild(planarConfiguration); - - IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); - sampleFormat.setAttribute("value", colorMap != null ? "Index" : "UnsignedIntegral"); - data.appendChild(sampleFormat); - - // BitsPerSample - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - String value = bitsPerSampleValue(header.bitplanes()); - bitsPerSample.setAttribute("value", value); - data.appendChild(bitsPerSample); - - // SignificantBitsPerSample not in format - // SampleMSB not in format - - return data; - } - - private String bitsPerSampleValue(int bitplanes) { switch (bitplanes) { case 1: case 2: @@ -192,91 +89,47 @@ final class IFFImageMetadata extends AbstractMetadata { case 6: case 7: case 8: - return Integer.toString(bitplanes); + return new int[] {bitplanes}; case 24: - return "8 8 8"; + return new int[] {8, 8, 8}; case 25: if (header.formType != TYPE_RGB8) { throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(header.formType))); } - return "8 8 8 1"; + return new int[] {8, 8, 8, 1}; case 32: - return "8 8 8 8"; + return new int[] {8, 8, 8, 8}; default: throw new IllegalArgumentException("Unknown bit count: " + bitplanes); } } - @Override - protected IIOMetadataNode getStandardDimensionNode() { - if (header.aspect() == 0) { - return null; + private static PlanarConfiguration planarConfiguration(Form header) { + switch (header.formType) { + case TYPE_DEEP: + case TYPE_TVPP: + case TYPE_RGB8: + case TYPE_PBM: + return PlanarConfiguration.PixelInterleaved; + case TYPE_ILBM: + return PlanarConfiguration.PlaneInterleaved; + default: + return null; } - - IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); - - // PixelAspectRatio - IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); - pixelAspectRatio.setAttribute("value", String.valueOf(header.aspect())); - dimension.appendChild(pixelAspectRatio); - - // TODO: HorizontalScreenSize? - // TODO: VerticalScreenSize? - - return dimension; } - @Override - protected IIOMetadataNode getStandardDocumentNode() { - IIOMetadataNode document = new IIOMetadataNode("Document"); - - IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); - document.appendChild(formatVersion); - formatVersion.setAttribute("value", "1.0"); - - return document; - } - - @Override - protected IIOMetadataNode getStandardTextNode() { - if (meta.isEmpty()) { - return null; + private static List> textEntries(Form header) { + if (header.meta.isEmpty()) { + return emptyList(); } - IIOMetadataNode text = new IIOMetadataNode("Text"); - - // /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value. - for (GenericChunk chunk : meta) { - IIOMetadataNode node = new IIOMetadataNode("TextEntry"); - node.setAttribute("keyword", IFFUtil.toChunkStr(chunk.chunkId)); - node.setAttribute("value", new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII)); - text.appendChild(node); + List> text = new ArrayList<>(); + for (GenericChunk chunk : header.meta) { + text.add(new SimpleImmutableEntry<>(toChunkStr(chunk.chunkId), + new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII))); } return text; } - - @Override - protected IIOMetadataNode getStandardTransparencyNode() { - if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes() != 32 && header.bitplanes() != 25) { - return null; - } - - IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); - - if (header.bitplanes() == 25 || header.bitplanes() == 32) { - IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); - alpha.setAttribute("value", header.premultiplied() ? "premultiplied" : "nonpremultiplied"); - transparency.appendChild(alpha); - } - - if (colorMap != null && colorMap.getTransparency() == Transparency.BITMASK) { - IIOMetadataNode transparentIndex = new IIOMetadataNode("TransparentIndex"); - transparentIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel())); - transparency.appendChild(transparentIndex); - } - - return transparency; - } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java index 6705de12..6ccc1f68 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java @@ -38,14 +38,19 @@ import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; -import javax.imageio.*; +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.color.ColorSpace; +import java.awt.color.*; import java.awt.image.*; import java.io.DataInputStream; +import java.io.EOFException; import java.io.File; import java.io.IOException; import java.util.Arrays; @@ -350,9 +355,7 @@ public final class IFFImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(int imageIndex) throws IOException { - init(imageIndex); - - return new IFFImageMetadata(header, header.colorMap()); + return new IFFImageMetadata(getRawImageType(imageIndex), header, header.colorMap()); } @Override diff --git a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java index 151857e1..6d378ff2 100644 --- a/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java +++ b/imageio/imageio-iff/src/test/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageMetadataTest.java @@ -1,24 +1,37 @@ package com.twelvemonkeys.imageio.plugins.iff; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; + import org.junit.Test; import org.junit.function.ThrowingRunnable; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import javax.imageio.IIOException; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; -import java.awt.image.IndexColorModel; +import java.awt.image.*; import java.nio.charset.StandardCharsets; +import static java.awt.image.BufferedImage.*; import static org.junit.Assert.*; public class IFFImageMetadataTest { + + private static final ImageTypeSpecifier TYPE_8_BIT_GRAY = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_BYTE_GRAY); + private static final ImageTypeSpecifier TYPE_8_BIT_PALETTE = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_BYTE_INDEXED); + private static final ImageTypeSpecifier TYPE_24_BIT_RGB = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_3BYTE_BGR); + private static final ImageTypeSpecifier TYPE_32_BIT_ARGB = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); + private static final ImageTypeSpecifier TYPE_32_BIT_ARGB_DEEP = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE); + @Test public void testStandardFeatures() throws IIOException { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - final IFFImageMetadata metadata = new IFFImageMetadata(header, null); + final IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); // Standard metadata format assertTrue(metadata.isStandardMetadataFormatSupported()); @@ -51,9 +64,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap()); - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(3, chroma.getLength()); @@ -78,9 +91,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(3, chroma.getLength()); @@ -106,9 +119,10 @@ public class IFFImageMetadataTest { .with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1)); byte[] bw = {0, (byte) 0xff}; - IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex())); + ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex())); + IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap()); - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(5, chroma.getLength()); @@ -119,7 +133,7 @@ public class IFFImageMetadataTest { IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); assertEquals("NumChannels", numChannels.getNodeName()); - assertEquals("3", numChannels.getAttribute("value")); + assertEquals("4", numChannels.getAttribute("value")); IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); assertEquals("BlackIsZero", blackIsZero.getNodeName()); @@ -153,9 +167,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); - IIOMetadataNode compression = metadata.getStandardCompressionNode(); + IIOMetadataNode compression = getStandardNode(metadata, "Compression"); assertNotNull(compression); assertEquals("Compression", compression.getNodeName()); assertEquals(2, compression.getLength()); @@ -176,9 +190,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); - assertNull(metadata.getStandardCompressionNode()); // No compression, all default... + assertNull(getStandardNode(metadata, "Compression")); // No compression, all default... } @Test @@ -186,9 +200,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap()); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -213,9 +227,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -240,9 +254,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap()); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -269,9 +283,10 @@ public class IFFImageMetadataTest { .with(new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here - IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0)); + ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0)); + IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap()); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -297,9 +312,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_PBM) .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap()); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -324,9 +339,9 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_PBM) .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -356,10 +371,20 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(bitmapHeader); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); - IIOMetadataNode dimension = metadata.getStandardDimensionNode(); - assertNull(dimension); + IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); + + if (dimension != null) { + assertEquals("Dimension", dimension.getNodeName()); + assertEquals(1, dimension.getLength()); + + IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("Normal", imageOrientation.getAttribute("value")); + + assertNull(imageOrientation.getNextSibling()); // No more children + } } @Test @@ -368,20 +393,24 @@ public class IFFImageMetadataTest { .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) .with(new CAMGChunk(4)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); - IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); // No Dimension node is okay, or one with an aspect ratio of 1.0 if (dimension != null) { assertEquals("Dimension", dimension.getNodeName()); - assertEquals(1, dimension.getLength()); + assertEquals(2, dimension.getLength()); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("1.0", pixelAspectRatio.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("Normal", imageOrientation.getAttribute("value")); + + assertNull(imageOrientation.getNextSibling()); // No more children } } @@ -398,18 +427,22 @@ public class IFFImageMetadataTest { .with(bitmapHeader) .with(viewPort); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); - IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); assertNotNull(dimension); assertEquals("Dimension", dimension.getNodeName()); - assertEquals(1, dimension.getLength()); + assertEquals(2, dimension.getLength()); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("2.0", pixelAspectRatio.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("Normal", imageOrientation.getAttribute("value")); + + assertNull(imageOrientation.getNextSibling()); // No more children } @Test @@ -425,18 +458,22 @@ public class IFFImageMetadataTest { .with(bitmapHeader) .with(viewPort); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); - IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); assertNotNull(dimension); assertEquals("Dimension", dimension.getNodeName()); - assertEquals(1, dimension.getLength()); + assertEquals(2, dimension.getLength()); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("0.5", pixelAspectRatio.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("Normal", imageOrientation.getAttribute("value")); + + assertNull(imageOrientation.getNextSibling()); // No more children } @Test @@ -447,18 +484,22 @@ public class IFFImageMetadataTest { .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) .with(viewPort); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); - IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); assertNotNull(dimension); assertEquals("Dimension", dimension.getNodeName()); - assertEquals(1, dimension.getLength()); + assertEquals(2, dimension.getLength()); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("1.0", pixelAspectRatio.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("Normal", imageOrientation.getAttribute("value")); + + assertNull(imageOrientation.getNextSibling()); // No more children } @Test @@ -466,32 +507,33 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); - IIOMetadataNode document = metadata.getStandardDocumentNode(); + IIOMetadataNode document = getStandardNode(metadata, "Document"); assertNotNull(document); assertEquals("Document", document.getNodeName()); assertEquals(1, document.getLength()); - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) document.getFirstChild(); - assertEquals("FormatVersion", pixelAspectRatio.getNodeName()); - assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + IIOMetadataNode formatVersion = (IIOMetadataNode) document.getFirstChild(); + assertEquals("FormatVersion", formatVersion.getNodeName()); + assertEquals("1.0", formatVersion.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + assertNull(formatVersion.getNextSibling()); // No more children } @Test public void testStandardText() throws IIOException { - int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_UTF8}; - String[] texts = {"annotation", "äñnótâtïøñ"}; + int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_ANNO, IFF.CHUNK_UTF8}; + String[] texts = {"annotation", "dupe", "äñnótâtïøñ"}; Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) .with(new GenericChunk(chunks[0], texts[0].getBytes(StandardCharsets.US_ASCII))) - .with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.UTF_8))); + .with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.US_ASCII))) + .with(new GenericChunk(chunks[2], texts[2].getBytes(StandardCharsets.UTF_8))); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); - IIOMetadataNode text = metadata.getStandardTextNode(); + IIOMetadataNode text = getStandardNode(metadata, "Text"); assertNotNull(text); assertEquals("Text", text.getNodeName()); assertEquals(texts.length, text.getLength()); @@ -509,10 +551,21 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); - assertNull(transparency); // No transparency, just defaults + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); + + if (transparency != null) { + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", alpha.getNodeName()); + assertEquals("none", alpha.getAttribute("value")); + + assertNull(alpha.getNextSibling()); // No more children + } + // Otherwise, no transparency, just defaults } @Test @@ -520,18 +573,18 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_ILBM) .with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap()); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); assertNotNull(transparency); assertEquals("Transparency", transparency.getNodeName()); assertEquals(1, transparency.getLength()); - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild(); - assertEquals("Alpha", pixelAspectRatio.getNodeName()); - assertEquals("nonpremultiplied", pixelAspectRatio.getAttribute("value")); + IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", alpha.getNodeName()); + assertEquals("nonpremultiplied", alpha.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + assertNull(alpha.getNextSibling()); // No more children } @Test @@ -540,28 +593,33 @@ public class IFFImageMetadataTest { .with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1)); byte[] bw = {0, (byte) 0xff}; - IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex())); + ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex())); + IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap()); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); assertNotNull(transparency); assertEquals("Transparency", transparency.getNodeName()); - assertEquals(1, transparency.getLength()); + assertEquals(2, transparency.getLength()); - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild(); - assertEquals("TransparentIndex", pixelAspectRatio.getNodeName()); - assertEquals("1", pixelAspectRatio.getAttribute("value")); + IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", alpha.getNodeName()); + assertEquals("nonpremultiplied", alpha.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode transparentIndex = (IIOMetadataNode) alpha.getNextSibling(); + assertEquals("TransparentIndex", transparentIndex.getNodeName()); + assertEquals("1", transparentIndex.getAttribute("value")); + + assertNull(transparentIndex.getNextSibling()); // No more children } @Test public void testStandardRGB8() throws IIOException { Form header = Form.ofType(IFF.TYPE_RGB8) .with(new BMHDChunk(300, 200, 25, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap()); // Chroma - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(3, chroma.getLength()); @@ -581,7 +639,7 @@ public class IFFImageMetadataTest { assertNull(blackIsZero.getNextSibling()); // No more children // Data - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -601,7 +659,7 @@ public class IFFImageMetadataTest { assertNull(bitsPerSample.getNextSibling()); // No more children // Transparency - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); assertNotNull(transparency); assertEquals("Transparency", transparency.getNodeName()); assertEquals(1, transparency.getLength()); @@ -624,10 +682,10 @@ public class IFFImageMetadataTest { Form header = Form.ofType(IFF.TYPE_DEEP) .with(new DGBLChunk(8)) .with(dpel); - IFFImageMetadata metadata = new IFFImageMetadata(header, null); + IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB_DEEP, header, header.colorMap()); // Chroma - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(3, chroma.getLength()); @@ -649,7 +707,7 @@ public class IFFImageMetadataTest { assertNull(blackIsZero.getNextSibling()); // No more children // Data - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -669,7 +727,7 @@ public class IFFImageMetadataTest { assertNull(bitsPerSample.getNextSibling()); // No more children // Transparency - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); assertNotNull(transparency); assertEquals("Transparency", transparency.getNodeName()); assertEquals(1, transparency.getLength()); @@ -680,4 +738,13 @@ public class IFFImageMetadataTest { assertNull(alpha.getNextSibling()); // No more children } + + // TODO: Test RGB8 + ColorMap + + private IIOMetadataNode getStandardNode(IIOMetadata metadata, String nodeName) { + IIOMetadataNode asTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + NodeList nodes = asTree.getElementsByTagName(nodeName); + + return nodes.getLength() > 0 ? (IIOMetadataNode) nodes.item(0) : null; + } } \ No newline at end of file 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 c6e2e2d3..45bb0427 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 @@ -45,7 +45,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.color.ColorSpace; +import java.awt.color.*; import java.awt.image.*; import java.io.DataInput; import java.io.DataInputStream; @@ -377,10 +377,12 @@ public final class PCXImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { - checkBounds(imageIndex); - readHeader(); - - return new PCXMetadata(header, getVGAPalette()); +// checkBounds(imageIndex); +// readHeader(); +// +// return new PCXMetadata(header, getVGAPalette()); + ImageTypeSpecifier rawType = getRawImageType(imageIndex); + return new PCXMetadata(rawType, header); } private IndexColorModel getVGAPalette() throws IOException { 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 cd5f6e20..68352b41 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 @@ -1,236 +1,30 @@ -/* - * 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 of the copyright holder 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 HOLDER 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.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; -import javax.imageio.metadata.IIOMetadataNode; -import java.awt.image.IndexColorModel; +import javax.imageio.ImageTypeSpecifier; -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; +final class PCXMetadata extends StandardImageMetadataSupport { + public PCXMetadata(ImageTypeSpecifier type, PCXHeader header) { + super(builder(type) + .withPlanarConfiguration(planarConfiguration(header)) + .withCompressionName(compressionName(header)) + .withFormatVersion(String.valueOf(header.getVersion()))); } - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); + private static PlanarConfiguration planarConfiguration(PCXHeader header) { + System.out.println("header = " + header); + return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null; + } - IndexColorModel palette = null; - boolean gray = false; - - IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); - switch (header.getBitsPerPixel()) { - case 1: - case 2: - case 4: - palette = header.getEGAPalette(); - csType.setAttribute("name", "RGB"); - break; - case 8: - // We may have IndexColorModel here for 1 channel images - if (header.getChannels() == 1 && vgaPalette != null) { - palette = vgaPalette; - csType.setAttribute("name", "RGB"); - break; - } - if (header.getChannels() == 1) { - csType.setAttribute("name", "GRAY"); - gray = true; - break; - } - csType.setAttribute("name", "RGB"); - break; - - case 24: - // Some sources says this is possible... Untested. - csType.setAttribute("name", "RGB"); - break; - - default: - csType.setAttribute("name", "Unknown"); + private static String compressionName(PCXHeader header) { + switch (header.getCompression()) { + case PCX.COMPRESSION_NONE: + return "None"; + case PCX.COMPRESSION_RLE: + return "RLE"; } - chroma.appendChild(csType); - - // NOTE: Channels in chroma node reflects channels in color model, not data! (see data node) - IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); - numChannels.setAttribute("value", gray ? "1" : "3"); - chroma.appendChild(numChannels); - - IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - blackIsZero.setAttribute("value", "TRUE"); - chroma.appendChild(blackIsZero); - - if (palette != null) { - IIOMetadataNode paletteNode = new IIOMetadataNode("Palette"); - chroma.appendChild(paletteNode); - - for (int i = 0; i < palette.getMapSize(); i++) { - IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry"); - paletteEntry.setAttribute("index", Integer.toString(i)); - - paletteEntry.setAttribute("red", Integer.toString(palette.getRed(i))); - paletteEntry.setAttribute("green", Integer.toString(palette.getGreen(i))); - paletteEntry.setAttribute("blue", Integer.toString(palette.getBlue(i))); - - paletteNode.appendChild(paletteEntry); - } - } - - return chroma; - } - - // No compression - - @Override - protected IIOMetadataNode getStandardCompressionNode() { - if (header.getCompression() != PCX.COMPRESSION_NONE) { - IIOMetadataNode node = new IIOMetadataNode("Compression"); - - IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); - compressionTypeName.setAttribute("value", header.getCompression() == PCX.COMPRESSION_RLE ? "RLE" : "Uknown"); - node.appendChild(compressionTypeName); - - IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); - lossless.setAttribute("value", "TRUE"); - node.appendChild(lossless); - - return node; - } - - return null; - } - - @Override - protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode node = new IIOMetadataNode("Data"); - - // Planar configuration only makes sense for multi-channel images - if (header.getChannels() > 1) { - IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); - planarConfiguration.setAttribute("value", "LineInterleaved"); - node.appendChild(planarConfiguration); - } - - IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); - - switch (header.getBitsPerPixel()) { - case 1: - case 2: - case 4: - sampleFormat.setAttribute("value", "Index"); - break; - case 8: - if (header.getChannels() == 1 && vgaPalette != null) { - sampleFormat.setAttribute("value", "Index"); - break; - } - // Else fall through for GRAY - default: - sampleFormat.setAttribute("value", "UnsignedIntegral"); - break; - } - - node.appendChild(sampleFormat); - - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - bitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBitsPerPixel()))); - node.appendChild(bitsPerSample); - - IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample"); - significantBitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBitsPerPixel()))); - node.appendChild(significantBitsPerSample); - - IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB"); - sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0")); - - return node; - } - - 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"); - - IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); - imageOrientation.setAttribute("value", "Normal"); - dimension.appendChild(imageOrientation); - - return dimension; - } - - @Override - protected IIOMetadataNode getStandardDocumentNode() { - IIOMetadataNode dimension = new IIOMetadataNode("Document"); - - IIOMetadataNode imageOrientation = new IIOMetadataNode("FormatVersion"); - imageOrientation.setAttribute("value", String.valueOf(header.getVersion())); - dimension.appendChild(imageOrientation); - - return dimension; - } - - // No text node - - // No tiling - - @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"); - transparency.appendChild(alpha); - - return transparency; + return "Uknown"; } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java index e2dda333..647a4f10 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java @@ -67,18 +67,28 @@ import com.twelvemonkeys.io.enc.Decoder; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; -import javax.imageio.*; +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageReader; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.color.ColorSpace; -import java.awt.geom.AffineTransform; -import java.awt.geom.Area; +import java.awt.color.*; +import java.awt.geom.*; import java.awt.image.*; -import java.io.*; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; import java.util.List; -import java.util.*; /** * Reader for Apple Mac Paint Picture (PICT) format. @@ -2611,7 +2621,9 @@ public final class PICTImageReader extends ImageReaderBase { return getYPtCoord(getPICTFrame().height); } - public Iterator getImageTypes(int pIndex) { + public Iterator getImageTypes(int imageIndex) throws IOException { + checkBounds(imageIndex); + // TODO: The images look slightly different in Preview.. Could indicate the color space is wrong... return Collections.singletonList( ImageTypeSpecifiers.createPacked( @@ -2623,10 +2635,10 @@ public final class PICTImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { - checkBounds(imageIndex); + ImageTypeSpecifier rawType = getRawImageType(imageIndex); getPICTFrame(); // TODO: Would probably be better to use readPictHeader here, but it isn't cached - return new PICTMetadata(version, screenImageXRatio, screenImageYRatio); + return new PICTMetadata(rawType, version, screenImageXRatio, screenImageYRatio); } protected static void showIt(final BufferedImage pImage, final String pTitle) { diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTMetadata.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTMetadata.java index 8a882a48..6ffbc481 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTMetadata.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTMetadata.java @@ -1,8 +1,38 @@ +/* + * Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; -import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.ImageTypeSpecifier; /** * PICTMetadata. @@ -11,82 +41,13 @@ import javax.imageio.metadata.IIOMetadataNode; * @author last modified by $Author: haraldk$ * @version $Id: PICTMetadata.java,v 1.0 23/03/2021 haraldk Exp$ */ -public class PICTMetadata extends AbstractMetadata { - - private final int version; - private final double screenImageXRatio; - private final double screenImageYRatio; - - PICTMetadata(final int version, final double screenImageXRatio, final double screenImageYRatio) { - this.version = version; - this.screenImageXRatio = screenImageXRatio; - this.screenImageYRatio = screenImageYRatio; - } - - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); - - IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); - chroma.appendChild(csType); - csType.setAttribute("name", "RGB"); - - // 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); - numChannels.setAttribute("value", "3"); - - IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - chroma.appendChild(blackIsZero); - blackIsZero.setAttribute("value", "TRUE"); - - return chroma; - } - - @Override - protected IIOMetadataNode getStandardDimensionNode() { - if (screenImageXRatio > 0.0d && screenImageYRatio > 0.0d) { - IIOMetadataNode node = new IIOMetadataNode("Dimension"); - double ratio = screenImageXRatio / screenImageYRatio; - IIOMetadataNode subNode = new IIOMetadataNode("PixelAspectRatio"); - subNode.setAttribute("value", "" + ratio); - node.appendChild(subNode); - - return node; - } - return null; - } - - @Override - protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode data = new IIOMetadataNode("Data"); - - // As this is a vector-ish format, with possibly multiple regions of pixel data, this makes no sense... :-P - // This is, however, consistent with the getRawImageTyp/getImageTypes - - IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); - planarConfiguration.setAttribute("value", "PixelInterleaved"); - data.appendChild(planarConfiguration); - - IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); - sampleFormat.setAttribute("value", "UnsignedIntegral"); - data.appendChild(sampleFormat); - - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - bitsPerSample.setAttribute("value", "32"); - data.appendChild(bitsPerSample); - - return data; - } - - @Override - protected IIOMetadataNode getStandardDocumentNode() { - IIOMetadataNode document = new IIOMetadataNode("Document"); - - IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); - document.appendChild(formatVersion); - formatVersion.setAttribute("value", Integer.toString(version)); - - return document; +final class PICTMetadata extends StandardImageMetadataSupport { + PICTMetadata(final ImageTypeSpecifier type, final int version, final double screenImageXRatio, final double screenImageYRatio) { + super(builder(type) + .withPixelAspectRatio(screenImageXRatio > 0.0d && screenImageYRatio > 0.0d ? screenImageXRatio / screenImageYRatio : 1) + .withFormatVersion(Integer.toString(version)) + ); + // As this is a vector-ish format, some of the data makes no sense... :-P + // It is, however, consistent with the getRawImageTyp/getImageTypes } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReader.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReader.java index 1c697054..c16e42dd 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReader.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReader.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.pntg; import com.twelvemonkeys.imageio.ImageReaderBase; @@ -34,7 +64,7 @@ public final class PNTGImageReader extends ImageReaderBase { private static final Set IMAGE_TYPES = Collections.singleton(ImageTypeSpecifiers.createIndexed(new int[] {-1, 0}, false, -1, 1, DataBuffer.TYPE_BYTE)); - protected PNTGImageReader(final ImageReaderSpi provider) { + PNTGImageReader(final ImageReaderSpi provider) { super(provider); } @@ -123,9 +153,7 @@ public final class PNTGImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { - checkBounds(imageIndex); - - return new PNTGMetadata(); + return new PNTGMetadata(getRawImageType(imageIndex)); } private void readHeader() throws IOException { diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderSpi.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderSpi.java index 27f852ec..3c7b1955 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderSpi.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderSpi.java @@ -1,3 +1,33 @@ +/* + * Copyright (c) 2021, 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 of the copyright holder 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 HOLDER 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.pntg; import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadata.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadata.java index 90f22b95..70bdccdb 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadata.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadata.java @@ -1,8 +1,38 @@ +/* + * Copyright (c) 2022, 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 of the copyright holder 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 HOLDER 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.pntg; -import com.twelvemonkeys.imageio.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; -import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.ImageTypeSpecifier; /** * PNTGMetadata. @@ -11,76 +41,11 @@ import javax.imageio.metadata.IIOMetadataNode; * @author last modified by $Author: haraldk$ * @version $Id: PNTGMetadata.java,v 1.0 23/03/2021 haraldk Exp$ */ -public class PNTGMetadata extends AbstractMetadata { - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); - - IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); - chroma.appendChild(csType); - csType.setAttribute("name", "GRAY"); - - // 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); - numChannels.setAttribute("value", "1"); - - IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - chroma.appendChild(blackIsZero); - blackIsZero.setAttribute("value", "FALSE"); - - return chroma; - } - - @Override - protected IIOMetadataNode getStandardCompressionNode() { - IIOMetadataNode compressionNode = new IIOMetadataNode("Compression"); - - IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); - compressionTypeName.setAttribute("value", "PackBits"); // RLE? - compressionNode.appendChild(compressionTypeName); - compressionNode.appendChild(new IIOMetadataNode("Lossless")); - // "value" defaults to TRUE - - return compressionNode; - } - - @Override - protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode data = new IIOMetadataNode("Data"); - - // PlanarConfiguration - IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); - planarConfiguration.setAttribute("value", "PixelInterleaved"); - data.appendChild(planarConfiguration); - - IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); - sampleFormat.setAttribute("value", "UnsignedIntegral"); - data.appendChild(sampleFormat); - - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - bitsPerSample.setAttribute("value", "1"); - data.appendChild(bitsPerSample); - - return data; - } - - @Override - protected IIOMetadataNode getStandardDocumentNode() { - IIOMetadataNode document = new IIOMetadataNode("Document"); - - IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); - document.appendChild(formatVersion); - formatVersion.setAttribute("value", "1.0"); - - // TODO: We could get the file creation time from MacBinary header here... - - return document; - } - - @Override - protected IIOMetadataNode getStandardTextNode() { - // TODO: We could get the file name from MacBinary header here... - return super.getStandardTextNode(); +final class PNTGMetadata extends StandardImageMetadataSupport { + public PNTGMetadata(ImageTypeSpecifier type) { + super(builder(type) + .withBlackIsZero(false) + .withCompressionName("PackBits") + .withFormatVersion("1.0")); } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGProviderInfo.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGProviderInfo.java index a30edcae..aad07ae7 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGProviderInfo.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGProviderInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Harald Kuhr + * Copyright (c) 2021, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadataTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadataTest.java index 27cf8f9a..ba757b99 100644 --- a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadataTest.java +++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadataTest.java @@ -1,7 +1,11 @@ package com.twelvemonkeys.imageio.plugins.pntg; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; + import org.junit.Test; +import java.awt.image.*; + /** * PNTGMetadataTest. * @@ -12,6 +16,6 @@ import org.junit.Test; public class PNTGMetadataTest { @Test public void testCreate() { - new PNTGMetadata(); + new PNTGMetadata(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY)); } } \ No newline at end of file diff --git a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReader.java b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReader.java index f432f7c9..3cf4f51b 100755 --- a/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReader.java +++ b/imageio/imageio-pnm/src/main/java/com/twelvemonkeys/imageio/plugins/pnm/PNMImageReader.java @@ -43,7 +43,7 @@ import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.color.ColorSpace; +import java.awt.color.*; import java.awt.image.*; import java.io.DataInput; import java.io.DataInputStream; @@ -468,10 +468,7 @@ public final class PNMImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { - checkBounds(imageIndex); - readHeader(); - - return new PNMMetadata(header); + return new PNMMetadata(getRawImageType(imageIndex), header); } public static void main(String[] args) throws IOException { 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 cc11bc03..96dfb17b 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Harald Kuhr + * Copyright (c) 2022, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,92 +30,35 @@ package com.twelvemonkeys.imageio.plugins.pnm; -import com.twelvemonkeys.imageio.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadataNode; -import java.awt.*; -import java.awt.image.DataBuffer; +import java.awt.image.*; import java.nio.ByteOrder; -final class PNMMetadata extends AbstractMetadata { +/** + * PNMMetadata. + * + * @author Harald Kuhr + */ +final class PNMMetadata extends StandardImageMetadataSupport { private final PNMHeader header; - PNMMetadata(final PNMHeader header) { + PNMMetadata(ImageTypeSpecifier type, PNMHeader header) { + super(builder(type) + .withColorSpaceType(colorSpace(header)) + // TODO: Might make sense to set gamma? + .withBlackIsZero(header.getTupleType() != TupleType.BLACKANDWHITE_WHITE_IS_ZERO) + .withSignificantBitsPerSample(significantBits(header)) + .withSampleMSB(header.getByteOrder() == ByteOrder.BIG_ENDIAN ? 0 : header.getBitsPerSample() - 1) + .withOrientation(orientation(header)) + ); + this.header = header; } - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); - - IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); - switch (header.getTupleType()) { - case BLACKANDWHITE: - case BLACKANDWHITE_ALPHA: - case BLACKANDWHITE_WHITE_IS_ZERO: - case GRAYSCALE: - case GRAYSCALE_ALPHA: - csType.setAttribute("name", "GRAY"); - break; - case RGB: - case RGB_ALPHA: - csType.setAttribute("name", "RGB"); - break; - case CMYK: - case CMYK_ALPHA: - csType.setAttribute("name", "CMYK"); - break; - } - - if (csType.getAttribute("name") != null) { - chroma.appendChild(csType); - } - - IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); - numChannels.setAttribute("value", Integer.toString(header.getSamplesPerPixel())); - chroma.appendChild(numChannels); - - // TODO: Might make sense to set gamma? - - IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO - ? "FALSE" - : "TRUE"); - chroma.appendChild(blackIsZero); - - return chroma; - } - - // No compression - - @Override - protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode node = new IIOMetadataNode("Data"); - - IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); - sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT - ? "Real" - : "UnsignedIntegral"); - node.appendChild(sampleFormat); - - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - bitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(header.getBitsPerSample()))); - node.appendChild(bitsPerSample); - - IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample"); - 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); - IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB"); - sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb)); - - return node; - } - - private int computeSignificantBits() { + private static int significantBits(PNMHeader header) { if (header.getTransferType() == DataBuffer.TYPE_FLOAT) { return header.getBitsPerSample(); } @@ -132,38 +75,30 @@ final class PNMMetadata extends AbstractMetadata { return significantBits; } - 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]); + private static ColorSpaceType colorSpace(PNMHeader header) { + switch (header.getTupleType()) { + case BLACKANDWHITE: + case BLACKANDWHITE_ALPHA: + case BLACKANDWHITE_WHITE_IS_ZERO: + case GRAYSCALE: + case GRAYSCALE_ALPHA: + return ColorSpaceType.GRAY; + default: + return null; // Fall back to color model's type } - - return buffer.toString(); } - @Override - protected IIOMetadataNode getStandardDimensionNode() { - IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); - - IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); - imageOrientation.setAttribute("value", - header.getFileType() == PNM.PFM_GRAY || header.getFileType() == PNM.PFM_RGB - ? "FlipH" - : "Normal"); - dimension.appendChild(imageOrientation); - - return dimension; + private static ImageOrientation orientation(PNMHeader header) { + // For some reason, the float values are stored bottom-up + return header.getFileType() == PNM.PFM_GRAY || header.getFileType() == PNM.PFM_RGB + ? ImageOrientation.FlipH + : ImageOrientation.Normal; } - // No document node - @Override protected IIOMetadataNode getStandardTextNode() { + // TODO: Could avoid this override, by changing the StandardImageMetadataSupport to + // use List> instead of Map (we use duplicate "comment"s). if (!header.getComments().isEmpty()) { IIOMetadataNode text = new IIOMetadataNode("Text"); @@ -179,17 +114,4 @@ final class PNMMetadata extends AbstractMetadata { return null; } - - // No tiling - - @Override - protected IIOMetadataNode getStandardTransparencyNode() { - IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); - - IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); - alpha.setAttribute("value", header.getTransparency() == Transparency.OPAQUE ? "none" : "nonpremultiplied"); - transparency.appendChild(alpha); - - return transparency; - } } diff --git a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIHeader.java b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIHeader.java index 8b893c06..d61fc44b 100755 --- a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIHeader.java +++ b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIHeader.java @@ -33,7 +33,7 @@ package com.twelvemonkeys.imageio.plugins.sgi; import javax.imageio.IIOException; import javax.imageio.stream.ImageInputStream; import java.io.IOException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; final class SGIHeader { private int compression; @@ -157,6 +157,6 @@ final class SGIHeader { } } - return new String(bytes, 0, len, Charset.forName("ASCII")); + return new String(bytes, 0, len, StandardCharsets.US_ASCII); } } diff --git a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReader.java b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReader.java index b173e910..67a1e5ca 100755 --- a/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReader.java +++ b/imageio/imageio-sgi/src/main/java/com/twelvemonkeys/imageio/plugins/sgi/SGIImageReader.java @@ -45,7 +45,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.color.ColorSpace; +import java.awt.color.*; import java.awt.image.*; import java.io.DataInput; import java.io.DataInputStream; @@ -378,11 +378,10 @@ public final class SGIImageReader extends ImageReaderBase { imageInput.seek(imageInput.getFlushedPosition()); } - @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { - checkBounds(imageIndex); - readHeader(); - - return new SGIMetadata(header); + @Override + public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { + ImageTypeSpecifier rawType = getRawImageType(imageIndex); + return new SGIMetadata(rawType, header); } public static void main(String[] args) throws IOException { 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 42cb005a..8f613d46 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 @@ -1,209 +1,39 @@ -/* - * 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 of the copyright holder 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 HOLDER 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.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; -import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.ImageTypeSpecifier; -final class SGIMetadata extends AbstractMetadata { - private final SGIHeader header; - - SGIMetadata(final SGIHeader header) { - this.header = header; +final class SGIMetadata extends StandardImageMetadataSupport { + public SGIMetadata(ImageTypeSpecifier type, SGIHeader header) { + super(builder(type) + .withSignificantBitsPerSample(computeSignificantBits(header)) + .withCompressionName(compressionName(header)) + .withOrientation(ImageOrientation.FlipV) + .withTextEntry("DocumentName", header.getName()) + ); } - @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 - // 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...) - IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); - switch (header.getColorMode()) { - case SGI.COLORMODE_NORMAL: - switch (header.getChannels()) { - case 1: - case 2: - csType.setAttribute("name", "GRAY"); - break; - case 3: - case 4: - csType.setAttribute("name", "RGB"); - break; - default: - csType.setAttribute("name", Integer.toHexString(header.getChannels()).toUpperCase() + "CLR"); - break; - } - break; - - // SGIIMAGE.TXT describes these as RGB - case SGI.COLORMODE_DITHERED: - case SGI.COLORMODE_SCREEN: - case SGI.COLORMODE_COLORMAP: - csType.setAttribute("name", "RGB"); - break; - } - - if (csType.getAttribute("name") != null) { - chroma.appendChild(csType); - } - - IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); - numChannels.setAttribute("value", Integer.toString(header.getChannels())); - chroma.appendChild(numChannels); - - IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - blackIsZero.setAttribute("value", "TRUE"); - chroma.appendChild(blackIsZero); - - return chroma; - } - - // No compression - - @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"); - node.appendChild(compressionTypeName); - - IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); - lossless.setAttribute("value", "TRUE"); - node.appendChild(lossless); - - return node; - } - - return null; - } - - @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", createListValue(header.getChannels(), Integer.toString(header.getBytesPerPixel() * 8))); - node.appendChild(bitsPerSample); - - IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample"); - significantBitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(computeSignificantBits()))); - node.appendChild(significantBitsPerSample); - - IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB"); - sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0")); - - return node; - } - - private int computeSignificantBits() { - int significantBits = 0; - + private static int computeSignificantBits(SGIHeader header) { int maxSample = header.getMaxValue(); - while (maxSample > 0) { - maxSample >>>= 1; + int significantBits = 1; + + while ((maxSample >>>= 1) != 0) { significantBits++; } return significantBits; } - 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]); + private static String compressionName(SGIHeader header) { + switch (header.getCompression()) { + case SGI.COMPRESSION_NONE: + return "None"; + case SGI.COMPRESSION_RLE: + return "RLE"; } - return buffer.toString(); - } - - @Override - protected IIOMetadataNode getStandardDimensionNode() { - IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); - - IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); - imageOrientation.setAttribute("value", "FlipV"); - dimension.appendChild(imageOrientation); - - return dimension; - } - - // No document node - - @Override - protected IIOMetadataNode getStandardTextNode() { - if (!header.getName().isEmpty()) { - IIOMetadataNode text = new IIOMetadataNode("Text"); - - IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry"); - textEntry.setAttribute("keyword", "name"); - textEntry.setAttribute("value", header.getName()); - text.appendChild(textEntry); - - return text; - } - - return null; - } - - // No tiling - - @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"); - transparency.appendChild(alpha); - - return transparency; + return "Uknown"; } } diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAHeader.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAHeader.java index ec0fbf4f..5fcb674e 100755 --- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAHeader.java +++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAHeader.java @@ -31,18 +31,16 @@ package com.twelvemonkeys.imageio.plugins.tga; import javax.imageio.IIOException; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.stream.ImageInputStream; -import java.awt.image.ColorModel; -import java.awt.image.IndexColorModel; -import java.awt.image.RenderedImage; +import java.awt.image.*; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.nio.charset.StandardCharsets; import static com.twelvemonkeys.lang.Validate.notNull; -import static java.awt.color.ColorSpace.TYPE_GRAY; -import static java.awt.color.ColorSpace.TYPE_RGB; +import static java.awt.color.ColorSpace.*; final class TGAHeader { @@ -118,10 +116,14 @@ final class TGAHeader { '}'; } - static TGAHeader from(final RenderedImage image, final boolean compressed) { - notNull(image, "image"); + static TGAHeader from(final ImageTypeSpecifier type, final boolean compressed) { + return from(type, 0, 0, compressed); + } - ColorModel colorModel = image.getColorModel(); + static TGAHeader from(final ImageTypeSpecifier type, int width, int height, final boolean compressed) { + notNull(type, "type"); + + ColorModel colorModel = type.getColorModel(); IndexColorModel colorMap = colorModel instanceof IndexColorModel ? (IndexColorModel) colorModel : null; TGAHeader header = new TGAHeader(); @@ -135,8 +137,8 @@ final class TGAHeader { header.x = 0; header.y = 0; - header.width = image.getWidth(); // TODO: Param source region/subsampling might affect this - header.height = image.getHeight(); // // TODO: Param source region/subsampling might affect this + header.width = width; + header.height = height; header.pixelDepth = colorModel.getPixelSize() == 15 ? 16 : colorModel.getPixelSize(); header.origin = TGA.ORIGIN_UPPER_LEFT; // TODO: Allow parameter to control this? 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 e819ece7..79bb5e7b 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 @@ -47,7 +47,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; -import java.awt.color.ColorSpace; +import java.awt.color.*; import java.awt.image.*; import java.io.DataInput; import java.io.File; @@ -538,10 +538,8 @@ final class TGAImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { - checkBounds(imageIndex); - readHeader(); - - return new TGAMetadata(header, extensions); + ImageTypeSpecifier rawType = getRawImageType(imageIndex); + return new TGAMetadata(rawType, header, extensions); } public static void main(String[] args) throws IOException { diff --git a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageWriter.java b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageWriter.java index 9dbedc87..d1c4763c 100644 --- a/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageWriter.java +++ b/imageio/imageio-tga/src/main/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageWriter.java @@ -37,12 +37,16 @@ import com.twelvemonkeys.io.LittleEndianDataOutputStream; import com.twelvemonkeys.io.enc.EncoderStream; import com.twelvemonkeys.lang.Validate; -import javax.imageio.*; +import javax.imageio.IIOException; +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.ImageWriteParam; import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageWriterSpi; import javax.imageio.stream.ImageOutputStream; import java.awt.*; -import java.awt.color.ColorSpace; +import java.awt.color.*; import java.awt.image.*; import java.io.DataOutput; import java.io.File; @@ -65,8 +69,7 @@ final class TGAImageWriter extends ImageWriterBase { public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) { Validate.notNull(imageType, "imageType"); - TGAHeader header = TGAHeader.from(imageType.createBufferedImage(1, 1), isRLE(param, null)); - return new TGAMetadata(header, null); + return new TGAMetadata(imageType, TGAHeader.from(imageType, isRLE(param, null)), null); } @Override @@ -107,7 +110,8 @@ final class TGAImageWriter extends ImageWriterBase { final boolean compressed = isRLE(param, image.getMetadata()); RenderedImage renderedImage = image.getRenderedImage(); - TGAHeader header = TGAHeader.from(renderedImage, compressed); + ImageTypeSpecifier type = ImageTypeSpecifiers.createFromRenderedImage(renderedImage); + TGAHeader header = TGAHeader.from(type, renderedImage.getWidth(), renderedImage.getHeight(), compressed); header.write(imageOutput); @@ -117,7 +121,7 @@ final class TGAImageWriter extends ImageWriterBase { ? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {2, 1, 0, 3}, DataBuffer.TYPE_BYTE, true, false).createBufferedImage(renderedImage.getWidth(), 1).getRaster() : renderedImage.getSampleModel().getTransferType() == DataBuffer.TYPE_INT ? ImageTypeSpecifiers.createInterleaved(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false).createBufferedImage(renderedImage.getWidth(), 1).getRaster() - : ImageTypeSpecifier.createFromRenderedImage(renderedImage).createBufferedImage(renderedImage.getWidth(), 1).getRaster(); + : type.createBufferedImage(renderedImage.getWidth(), 1).getRaster(); final DataBuffer buffer = rowRaster.getDataBuffer(); @@ -135,7 +139,7 @@ final class TGAImageWriter extends ImageWriterBase { break; } - DataOutput imageOutput = compressed ? createRLEStream(header, this.imageOutput) : this.imageOutput; + DataOutput imageOutput = compressed ? createRLEStream(this.imageOutput, header.getPixelDepth()) : this.imageOutput; switch (buffer.getDataType()) { case DataBuffer.TYPE_BYTE: @@ -174,8 +178,8 @@ final class TGAImageWriter extends ImageWriterBase { processImageComplete(); } - private static LittleEndianDataOutputStream createRLEStream(final TGAHeader header, final ImageOutputStream stream) { - return new LittleEndianDataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(stream), new RLEEncoder(header.getPixelDepth()))); + private static LittleEndianDataOutputStream createRLEStream(final ImageOutputStream stream, int pixelDepth) { + return new LittleEndianDataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(stream), new RLEEncoder(pixelDepth))); } // TODO: Refactor to common util 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 98a0a74f..08654426 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 @@ -1,340 +1,83 @@ -/* - * 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 of the copyright holder 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 HOLDER 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.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; -import javax.imageio.metadata.IIOMetadataNode; -import java.awt.*; -import java.awt.image.IndexColorModel; +import javax.imageio.ImageTypeSpecifier; import java.util.Calendar; +import java.util.LinkedHashMap; +import java.util.Map; -import static com.twelvemonkeys.lang.Validate.notNull; - -final class TGAMetadata extends AbstractMetadata { - private final TGAHeader header; - private final TGAExtensions extensions; - - TGAMetadata(final TGAHeader header, final TGAExtensions extensions) { - this.header = notNull(header, "header"); - this.extensions = extensions; +final class TGAMetadata extends StandardImageMetadataSupport { + TGAMetadata(ImageTypeSpecifier type, TGAHeader header, TGAExtensions extensions) { + super(builder(type) + .withCompressionName(compressionName(header)) + .withPixelAspectRatio(pixelAspectRatio(extensions)) + .withOrientation(orientation(header)) + .withFormatVersion(extensions == null ? "1.0" : "2.0") + .withDocumentCreationTime(documentCreationTime(extensions)) + .withTextEntries(textEntries(header, extensions)) + ); } - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); - - IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); - chroma.appendChild(csType); - + private static String compressionName(TGAHeader header) { switch (header.getImageType()) { - case TGA.IMAGETYPE_MONOCHROME: - case TGA.IMAGETYPE_MONOCHROME_RLE: - csType.setAttribute("name", "GRAY"); - break; - + case TGA.IMAGETYPE_NONE: + case TGA.IMAGETYPE_COLORMAPPED: case TGA.IMAGETYPE_TRUECOLOR: - case TGA.IMAGETYPE_TRUECOLOR_RLE: - case TGA.IMAGETYPE_COLORMAPPED: - case TGA.IMAGETYPE_COLORMAPPED_RLE: - case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN: - case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE: - csType.setAttribute("name", "RGB"); - break; - default: - csType.setAttribute("name", "Unknown"); - } - - // 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: - if (header.getImageType() == TGA.IMAGETYPE_MONOCHROME || header.getImageType() == TGA.IMAGETYPE_MONOCHROME_RLE) { - numChannels.setAttribute("value", Integer.toString(1)); - } - else { - numChannels.setAttribute("value", Integer.toString(3)); - } - 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; - case 32: - numChannels.setAttribute("value", Integer.toString(4)); - break; - } - - IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - 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, - // as it might be unexpected... Then again... - IndexColorModel colorMap = header.getColorMap(); - if (colorMap != null) { - IIOMetadataNode palette = new IIOMetadataNode("Palette"); - chroma.appendChild(palette); - - 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))); - } - } - - 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() { - switch (header.getImageType()) { + case TGA.IMAGETYPE_MONOCHROME: + return "None"; case TGA.IMAGETYPE_COLORMAPPED_RLE: case TGA.IMAGETYPE_TRUECOLOR_RLE: case TGA.IMAGETYPE_MONOCHROME_RLE: + return "RLE"; case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN: case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE: - IIOMetadataNode node = new IIOMetadataNode("Compression"); - - IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); - node.appendChild(compressionTypeName); - String value = header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN || header.getImageType() == TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE - ? "Unknown" : "RLE"; - compressionTypeName.setAttribute("value", value); - - IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); - node.appendChild(lossless); - lossless.setAttribute("value", "TRUE"); - - return node; default: - // No compression - return null; + return "Unknown"; } } - @Override - protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode node = new IIOMetadataNode("Data"); - - IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); - 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: - case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN: - case TGA.IMAGETYPE_COLORMAPPED_HUFFMAN_QUADTREE: - sampleFormat.setAttribute("value", "Index"); - break; - default: - sampleFormat.setAttribute("value", "UnsignedIntegral"); - break; - } - - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - node.appendChild(bitsPerSample); - - switch (header.getPixelDepth()) { - case 8: - bitsPerSample.setAttribute("value", createListValue(1, "8")); - break; - case 16: - if (header.getImageType() == TGA.IMAGETYPE_MONOCHROME || header.getImageType() == TGA.IMAGETYPE_MONOCHROME_RLE) { - bitsPerSample.setAttribute("value", "16"); - } - else 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, "8")); - break; - case 32: - bitsPerSample.setAttribute("value", createListValue(4, "8")); - break; - } - - return node; + private static double pixelAspectRatio(TGAExtensions extensions) { + return extensions != null ? extensions.getPixelAspectRatio() : 1f; } - 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"); - - IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); - dimension.appendChild(imageOrientation); - - switch (header.getOrigin()) { + private static ImageOrientation orientation(TGAHeader header) { + switch (header.origin) { case TGA.ORIGIN_LOWER_LEFT: - imageOrientation.setAttribute("value", "FlipH"); - break; + return ImageOrientation.FlipH; case TGA.ORIGIN_LOWER_RIGHT: - imageOrientation.setAttribute("value", "Rotate180"); - break; + return ImageOrientation.Rotate180; case TGA.ORIGIN_UPPER_LEFT: - imageOrientation.setAttribute("value", "Normal"); - break; + return ImageOrientation.Normal; case TGA.ORIGIN_UPPER_RIGHT: - imageOrientation.setAttribute("value", "FlipV"); - break; + return ImageOrientation.FlipV; + default: + throw new IllegalArgumentException("Unknown orientation: " + header.origin); } - - IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); - dimension.appendChild(pixelAspectRatio); - pixelAspectRatio.setAttribute("value", extensions != null ? String.valueOf(extensions.getPixelAspectRatio()) : "1.0"); - - return dimension; } - @Override - protected IIOMetadataNode getStandardDocumentNode() { - IIOMetadataNode document = new IIOMetadataNode("Document"); - - IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); - document.appendChild(formatVersion); - formatVersion.setAttribute("value", extensions == null ? "1.0" : "2.0"); - - // ImageCreationTime from extensions date - if (extensions != null && extensions.getCreationDate() != null) { - IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime"); - document.appendChild(imageCreationTime); - - 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 document; + private static Calendar documentCreationTime(TGAExtensions extensions) { + return extensions != null ? extensions.creationDate : null; } - @Override - protected IIOMetadataNode getStandardTextNode() { - IIOMetadataNode text = new IIOMetadataNode("Text"); + private static Map textEntries(TGAHeader header, TGAExtensions extensions) { + LinkedHashMap textEntries = new LinkedHashMap<>(); - // NOTE: Names corresponds to equivalent fields in TIFF - appendTextEntry(text, "DocumentName", header.getIdentification()); + // NOTE: Keywords follow TIFF standard naming + putIfValue(textEntries, "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()); + putIfValue(textEntries, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : (extensions.getSoftware() + " " + extensions.getSoftwareVersion())); + putIfValue(textEntries, "Artist", extensions.getAuthorName()); + putIfValue(textEntries, "UserComment", extensions.getAuthorComments()); } - return text.hasChildNodes() ? text : null; + return textEntries; } - private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) { + private static void putIfValue(final Map textEntries, final String keyword, final String value) { if (value != null && !value.isEmpty()) { - IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry"); - parent.appendChild(textEntry); - textEntry.setAttribute("keyword", keyword); - textEntry.setAttribute("value", value); + textEntries.put(keyword, value); } } - - // No tiling - - @Override - protected IIOMetadataNode getStandardTransparencyNode() { - IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); - - IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); - 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/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageWriteParamTest.java b/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageWriteParamTest.java index d8ff3059..59cf7cbc 100644 --- a/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageWriteParamTest.java +++ b/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAImageWriteParamTest.java @@ -30,12 +30,17 @@ package com.twelvemonkeys.imageio.plugins.tga; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; + import org.junit.Test; +import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriteParam; -import java.awt.image.BufferedImage; +import java.awt.image.*; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; /** @@ -46,6 +51,9 @@ import static org.junit.Assume.assumeFalse; * @version $Id: TGAImageWriteParamTest.java,v 1.0 08/04/2021 haraldk Exp$ */ public class TGAImageWriteParamTest { + + private static final ImageTypeSpecifier TYPE_3BYTE_BGR = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); + @Test public void testDefaultCopyFromMetadata() { TGAImageWriteParam param = new TGAImageWriteParam(); @@ -107,8 +115,8 @@ public class TGAImageWriteParamTest { ImageWriteParam param = new ImageWriteParam(null); assumeFalse(param.canWriteCompressed()); - assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false), null))); - assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null))); + assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, false), null))); + assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null))); } @Test @@ -116,7 +124,7 @@ public class TGAImageWriteParamTest { ImageWriteParam param = new TGAImageWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_COPY_FROM_METADATA); - assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null))); + assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null))); } @Test @@ -124,7 +132,7 @@ public class TGAImageWriteParamTest { ImageWriteParam param = new TGAImageWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_COPY_FROM_METADATA); - assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null))); + assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null))); } @Test @@ -132,12 +140,12 @@ public class TGAImageWriteParamTest { TGAImageWriteParam param = new TGAImageWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false), null))); - assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null))); + assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, false), null))); + assertFalse(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null))); param.setCompressionType("RLE"); - assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false), null))); - assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true), null))); + assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, false), null))); + assertTrue(TGAImageWriteParam.isRLE(param, new TGAMetadata(TYPE_3BYTE_BGR, TGAHeader.from(TYPE_3BYTE_BGR, true), null))); } } \ No newline at end of file diff --git a/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadataTest.java b/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadataTest.java index c1bd18e1..4d87e57a 100644 --- a/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadataTest.java +++ b/imageio/imageio-tga/src/test/java/com/twelvemonkeys/imageio/plugins/tga/TGAMetadataTest.java @@ -30,14 +30,18 @@ package com.twelvemonkeys.imageio.plugins.tga; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; + import org.junit.Test; import org.junit.function.ThrowingRunnable; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; -import java.awt.image.BufferedImage; -import java.awt.image.IndexColorModel; +import java.awt.image.*; import java.util.Calendar; import static org.junit.Assert.*; @@ -50,10 +54,15 @@ import static org.junit.Assert.*; * @version $Id: TGAMetadataTest.java,v 1.0 08/04/2021 haraldk Exp$ */ public class TGAMetadataTest { + + private static final ImageTypeSpecifier TYPE_BYTE_GRAY = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY); + + private static final ImageTypeSpecifier TYPE_3BYTE_BGR = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); + @Test public void testStandardFeatures() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false); - final TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, false); + final TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null); // Standard metadata format assertTrue(metadata.isStandardMetadataFormatSupported()); @@ -83,10 +92,10 @@ public class TGAMetadataTest { @Test public void testStandardChromaGray() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), false); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, false); + TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null); - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(3, chroma.getLength()); @@ -108,10 +117,10 @@ public class TGAMetadataTest { @Test public void testStandardChromaRGB() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, false); + TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null); - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(3, chroma.getLength()); @@ -135,10 +144,11 @@ public class TGAMetadataTest { public void testStandardChromaPalette() { byte[] bw = {0, (byte) 0xff}; IndexColorModel indexColorModel = new IndexColorModel(8, bw.length, bw, bw, bw, -1); - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, indexColorModel), false); - TGAMetadata metadata = new TGAMetadata(header, null); + ImageTypeSpecifier type = ImageTypeSpecifiers.createFromIndexColorModel(indexColorModel); + TGAHeader header = TGAHeader.from(type, false); + TGAMetadata metadata = new TGAMetadata(type, header, null); - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(4, chroma.getLength()); @@ -174,10 +184,10 @@ public class TGAMetadataTest { @Test public void testStandardCompressionRLE() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, true); + TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null); - IIOMetadataNode compression = metadata.getStandardCompressionNode(); + IIOMetadataNode compression = getStandardNode(metadata, "Compression"); assertNotNull(compression); assertEquals("Compression", compression.getNodeName()); assertEquals(2, compression.getLength()); @@ -195,18 +205,18 @@ public class TGAMetadataTest { @Test public void testStandardCompressionNone() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), false); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, false); + TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null); - assertNull(metadata.getStandardCompressionNode()); // No compression, all default... + assertNull(getStandardNode(metadata, "Compression")); // No compression, all default... } @Test public void testStandardDataGray() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true); + TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -228,10 +238,10 @@ public class TGAMetadataTest { @Test public void testStandardDataRGB() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, true); + TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -253,10 +263,11 @@ public class TGAMetadataTest { @Test public void testStandardDataRGBA() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB), true); - TGAMetadata metadata = new TGAMetadata(header, null); + ImageTypeSpecifier type = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB); + TGAHeader header = TGAHeader.from(type, true); + TGAMetadata metadata = new TGAMetadata(type, header, null); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -280,10 +291,11 @@ public class TGAMetadataTest { public void testStandardDataPalette() { byte[] rgb = new byte[1 << 8]; // Colors doesn't really matter here IndexColorModel indexColorModel = new IndexColorModel(8, rgb.length, rgb, rgb, rgb, 0); - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, indexColorModel), true); - TGAMetadata metadata = new TGAMetadata(header, null); + ImageTypeSpecifier type = ImageTypeSpecifiers.createFromIndexColorModel(indexColorModel); + TGAHeader header = TGAHeader.from(type, true); + TGAMetadata metadata = new TGAMetadata(type, header, null); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -305,53 +317,56 @@ public class TGAMetadataTest { @Test public void testStandardDimensionNormal() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true); + TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null); - IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); assertNotNull(dimension); assertEquals("Dimension", dimension.getNodeName()); assertEquals(2, dimension.getLength()); - IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild(); - assertEquals("ImageOrientation", imageOrientation.getNodeName()); - assertEquals("Normal", imageOrientation.getAttribute("value")); - - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) imageOrientation.getNextSibling(); + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("1.0", pixelAspectRatio.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("Normal", imageOrientation.getAttribute("value")); + + + assertNull(imageOrientation.getNextSibling()); // No more children } @Test public void testStandardDimensionFlipH() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true); + TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true); header.origin = TGA.ORIGIN_LOWER_LEFT; - TGAMetadata metadata = new TGAMetadata(header, null); + TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null); - IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); assertNotNull(dimension); assertEquals("Dimension", dimension.getNodeName()); assertEquals(2, dimension.getLength()); - IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild(); - assertEquals("ImageOrientation", imageOrientation.getNodeName()); - assertEquals("FlipH", imageOrientation.getAttribute("value")); - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) imageOrientation.getNextSibling(); + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("1.0", pixelAspectRatio.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("FlipH", imageOrientation.getAttribute("value")); + + + assertNull(imageOrientation.getNextSibling()); // No more children } @Test public void testStandardDocument() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true); + TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, null); - IIOMetadataNode document = metadata.getStandardDocumentNode(); + IIOMetadataNode document = getStandardNode(metadata, "Document"); assertNotNull(document); assertEquals("Document", document.getNodeName()); assertEquals(1, document.getLength()); @@ -365,13 +380,13 @@ public class TGAMetadataTest { @Test public void testStandardDocumentExtensions() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true); + TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true); TGAExtensions extensions = new TGAExtensions(); extensions.creationDate = Calendar.getInstance(); extensions.creationDate.set(2021, Calendar.APRIL, 8, 18, 55, 0); - TGAMetadata metadata = new TGAMetadata(header, extensions); + TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, extensions); - IIOMetadataNode document = metadata.getStandardDocumentNode(); + IIOMetadataNode document = getStandardNode(metadata, "Document"); assertNotNull(document); assertEquals("Document", document.getNodeName()); assertEquals(2, document.getLength()); @@ -394,7 +409,7 @@ public class TGAMetadataTest { @Test public void testStandardText() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY), true); + TGAHeader header = TGAHeader.from(TYPE_BYTE_GRAY, true); header.identification = "MY_FILE.TGA"; TGAExtensions extensions = new TGAExtensions(); @@ -402,9 +417,9 @@ public class TGAMetadataTest { extensions.authorName = "Harald K"; extensions.authorComments = "Comments, comments... "; - TGAMetadata metadata = new TGAMetadata(header, extensions); + TGAMetadata metadata = new TGAMetadata(TYPE_BYTE_GRAY, header, extensions); - IIOMetadataNode text = metadata.getStandardTextNode(); + IIOMetadataNode text = getStandardNode(metadata, "Text"); assertNotNull(text); assertEquals("Text", text.getNodeName()); assertEquals(4, text.getLength()); @@ -432,10 +447,10 @@ public class TGAMetadataTest { @Test public void testStandardTransparencyRGB() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR), true); - TGAMetadata metadata = new TGAMetadata(header, null); + TGAHeader header = TGAHeader.from(TYPE_3BYTE_BGR, true); + TGAMetadata metadata = new TGAMetadata(TYPE_3BYTE_BGR, header, null); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); assertNotNull(transparency); assertEquals("Transparency", transparency.getNodeName()); assertEquals(1, transparency.getLength()); @@ -449,10 +464,11 @@ public class TGAMetadataTest { @Test public void testStandardTransparencyRGBA() { - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR), true); - TGAMetadata metadata = new TGAMetadata(header, null); + ImageTypeSpecifier type = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); + TGAHeader header = TGAHeader.from(type, true); + TGAMetadata metadata = new TGAMetadata(type, header, null); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); assertNotNull(transparency); assertEquals("Transparency", transparency.getNodeName()); assertEquals(1, transparency.getLength()); @@ -468,19 +484,30 @@ public class TGAMetadataTest { public void testStandardTransparencyPalette() { byte[] bw = {0, (byte) 0xff}; IndexColorModel indexColorModel = new IndexColorModel(8, bw.length, bw, bw, bw, 1); - TGAHeader header = TGAHeader.from(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, indexColorModel), true); - TGAMetadata metadata = new TGAMetadata(header, null); + ImageTypeSpecifier type = ImageTypeSpecifiers.createFromIndexColorModel(indexColorModel); + TGAHeader header = TGAHeader.from(type, true); + TGAMetadata metadata = new TGAMetadata(type, header, null); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); assertNotNull(transparency); assertEquals("Transparency", transparency.getNodeName()); - assertEquals(1, transparency.getLength()); + assertEquals(2, transparency.getLength()); IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); assertEquals("Alpha", alpha.getNodeName()); assertEquals("nonpremultiplied", alpha.getAttribute("value")); - assertNull(alpha.getNextSibling()); // No more children + IIOMetadataNode transparentIndex = (IIOMetadataNode) alpha.getNextSibling(); + assertEquals("TransparentIndex", transparentIndex.getNodeName()); + assertEquals("1", transparentIndex.getAttribute("value")); + + assertNull(transparentIndex.getNextSibling()); // No more children } + private IIOMetadataNode getStandardNode(IIOMetadata metadata, String nodeName) { + IIOMetadataNode asTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + NodeList nodes = asTree.getElementsByTagName(nodeName); + + return nodes.getLength() > 0 ? (IIOMetadataNode) nodes.item(0) : null; + } } \ No newline at end of file diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadata.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadata.java index 21e47bea..39842c93 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadata.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadata.java @@ -1,184 +1,19 @@ -/* - * Copyright (c) 2017, 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 of the copyright holder 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 HOLDER 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.webp; -import com.twelvemonkeys.imageio.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; -import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.ImageTypeSpecifier; import static com.twelvemonkeys.lang.Validate.notNull; -/** - * WebPMetadata - */ -final class WebPImageMetadata extends AbstractMetadata { - private final VP8xChunk header; - - WebPImageMetadata(final VP8xChunk header) { - this.header = notNull(header, "header"); +final class WebPImageMetadata extends StandardImageMetadataSupport { + WebPImageMetadata(ImageTypeSpecifier type, VP8xChunk header) { + super(builder(type) + .withCompressionName(notNull(header, "header").isLossless ? "VP8L" : "VP8") + .withCompressionLossless(header.isLossless) + .withPixelAspectRatio(1.0) + .withFormatVersion("1.0") + // TODO: Get useful text nodes from EXIF or XMP + ); } - - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); - - IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType"); - chroma.appendChild(csType); - csType.setAttribute("name", "RGB"); - - // 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); - numChannels.setAttribute("value", Integer.toString(header.containsALPH ? 4 : 3)); - - IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero"); - chroma.appendChild(blackIsZero); - blackIsZero.setAttribute("value", "TRUE"); - - return chroma; - } - - @Override - protected IIOMetadataNode getStandardCompressionNode() { - IIOMetadataNode node = new IIOMetadataNode("Compression"); - - IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); - node.appendChild(compressionTypeName); - - String value = header.isLossless ? "VP8L" : "VP8"; // TODO: Naming: VP8L and VP8 or WebP and WebP Lossless? - compressionTypeName.setAttribute("value", value); - - // TODO: VP8 + lossless alpha! - IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); - node.appendChild(lossless); - lossless.setAttribute("value", header.isLossless ? "TRUE" : "FALSE"); - - return node; - } - - @Override - protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode node = new IIOMetadataNode("Data"); - - // TODO: WebP seems to support planar as well? - IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); - node.appendChild(planarConfiguration); - planarConfiguration.setAttribute("value", "PixelInterleaved"); - - IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); - node.appendChild(sampleFormat); - sampleFormat.setAttribute("value", "UnsignedIntegral"); - - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - node.appendChild(bitsPerSample); - - bitsPerSample.setAttribute("value", createListValue(header.containsALPH ? 4 : 3, Integer.toString(8))); - - return node; - } - - 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"); - - IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); - dimension.appendChild(imageOrientation); - imageOrientation.setAttribute("value", "Normal"); - - IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio"); - dimension.appendChild(pixelAspectRatio); - pixelAspectRatio.setAttribute("value", "1.0"); - - return dimension; - } - - @Override - protected IIOMetadataNode getStandardDocumentNode() { - IIOMetadataNode document = new IIOMetadataNode("Document"); - - IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); - document.appendChild(formatVersion); - formatVersion.setAttribute("value", "1.0"); - - return document; - } - - @Override - protected IIOMetadataNode getStandardTextNode() { - IIOMetadataNode text = new IIOMetadataNode("Text"); - - // TODO: Get useful text nodes from EXIF or XMP - // NOTE: Names corresponds to equivalent fields in TIFF - - 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() { - if (header.containsALPH) { - IIOMetadataNode transparency = new IIOMetadataNode("Transparency"); - IIOMetadataNode alpha = new IIOMetadataNode("Alpha"); - transparency.appendChild(alpha); - alpha.setAttribute("value", "nonpremultiplied"); - return transparency; - } - - return null; - } - - // TODO: Define native WebP metadata format (probably use RIFF structure) } diff --git a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java index 5f35eb27..dca166b5 100644 --- a/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java +++ b/imageio/imageio-webp/src/main/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageReader.java @@ -661,10 +661,7 @@ final class WebPImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(int imageIndex) throws IOException { - readHeader(imageIndex); - readMeta(); - - return new WebPImageMetadata(header); + return new WebPImageMetadata(getRawImageType(imageIndex), header); } private void readMeta() throws IOException { diff --git a/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadataTest.java b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadataTest.java index 41b83899..17aee33d 100644 --- a/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadataTest.java +++ b/imageio/imageio-webp/src/test/java/com/twelvemonkeys/imageio/plugins/webp/WebPImageMetadataTest.java @@ -1,11 +1,17 @@ package com.twelvemonkeys.imageio.plugins.webp; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; + import org.junit.Test; import org.junit.function.ThrowingRunnable; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; +import java.awt.image.*; import static org.junit.Assert.*; @@ -17,10 +23,14 @@ import static org.junit.Assert.*; * @version $Id: WebPImageMetadataTest.java,v 1.0 21/11/2020 haraldk Exp$ */ public class WebPImageMetadataTest { + + private static final ImageTypeSpecifier TYPE_3BYTE_BGR = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR); + private static final ImageTypeSpecifier TYPE_4BYTE_ABGR = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR); + @Test public void testStandardFeatures() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8_, 27, 33); - final WebPImageMetadata metadata = new WebPImageMetadata(header); + final WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); // Standard metadata format assertTrue(metadata.isStandardMetadataFormatSupported()); @@ -51,9 +61,9 @@ public class WebPImageMetadataTest { @Test public void testStandardChromaRGB() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8_, 27, 33); - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(3, chroma.getLength()); @@ -77,9 +87,9 @@ public class WebPImageMetadataTest { public void testStandardChromaRGBA() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); header.containsALPH = true; - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_4BYTE_ABGR, header); - IIOMetadataNode chroma = metadata.getStandardChromaNode(); + IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); assertNotNull(chroma); assertEquals("Chroma", chroma.getNodeName()); assertEquals(3, chroma.getLength()); @@ -103,9 +113,9 @@ public class WebPImageMetadataTest { @Test public void testStandardCompressionVP8() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8_, 27, 33); - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode compression = metadata.getStandardCompressionNode(); + IIOMetadataNode compression = getStandardNode(metadata, "Compression"); assertNotNull(compression); assertEquals("Compression", compression.getNodeName()); assertEquals(2, compression.getLength()); @@ -125,9 +135,9 @@ public class WebPImageMetadataTest { public void testStandardCompressionVP8L() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8L, 27, 33); header.isLossless = true; - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode compression = metadata.getStandardCompressionNode(); + IIOMetadataNode compression = getStandardNode(metadata, "Compression"); assertNotNull(compression); assertEquals("Compression", compression.getNodeName()); assertEquals(2, compression.getLength()); @@ -146,9 +156,9 @@ public class WebPImageMetadataTest { @Test public void testStandardCompressionVP8X() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode compression = metadata.getStandardCompressionNode(); + IIOMetadataNode compression = getStandardNode(metadata, "Compression"); assertNotNull(compression); assertEquals("Compression", compression.getNodeName()); assertEquals(2, compression.getLength()); @@ -168,9 +178,9 @@ public class WebPImageMetadataTest { public void testStandardCompressionVP8XLossless() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); header.isLossless = true; - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode compression = metadata.getStandardCompressionNode(); + IIOMetadataNode compression = getStandardNode(metadata, "Compression"); assertNotNull(compression); assertEquals("Compression", compression.getNodeName()); assertEquals(2, compression.getLength()); @@ -189,9 +199,9 @@ public class WebPImageMetadataTest { @Test public void testStandardDataRGB() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8_, 27, 33); - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -215,9 +225,9 @@ public class WebPImageMetadataTest { public void testStandardDataRGBA() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); header.containsALPH = true; - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_4BYTE_ABGR, header); - IIOMetadataNode data = metadata.getStandardDataNode(); + IIOMetadataNode data = getStandardNode(metadata, "Data"); assertNotNull(data); assertEquals("Data", data.getNodeName()); assertEquals(3, data.getLength()); @@ -240,78 +250,109 @@ public class WebPImageMetadataTest { @Test public void testStandardDimensionNormal() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode dimension = metadata.getStandardDimensionNode(); + IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); assertNotNull(dimension); assertEquals("Dimension", dimension.getNodeName()); assertEquals(2, dimension.getLength()); - IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild(); - assertEquals("ImageOrientation", imageOrientation.getNodeName()); - assertEquals("Normal", imageOrientation.getAttribute("value")); - - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) imageOrientation.getNextSibling(); + IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("1.0", pixelAspectRatio.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); + assertEquals("ImageOrientation", imageOrientation.getNodeName()); + assertEquals("Normal", imageOrientation.getAttribute("value")); + + assertNull(imageOrientation.getNextSibling()); // No more children } @Test public void testStandardDocument() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode document = metadata.getStandardDocumentNode(); + IIOMetadataNode document = getStandardNode(metadata, "Document"); assertNotNull(document); assertEquals("Document", document.getNodeName()); assertEquals(1, document.getLength()); - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) document.getFirstChild(); - assertEquals("FormatVersion", pixelAspectRatio.getNodeName()); - assertEquals("1.0", pixelAspectRatio.getAttribute("value")); + IIOMetadataNode formatVersion = (IIOMetadataNode) document.getFirstChild(); + assertEquals("FormatVersion", formatVersion.getNodeName()); + assertEquals("1.0", formatVersion.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + assertNull(formatVersion.getNextSibling()); // No more children } @Test public void testStandardText() { + // No text node yet... } @Test public void testStandardTransparencyVP8() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); - assertNull(transparency); // No transparency, just defaults + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); + + if (transparency != null) { + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", alpha.getNodeName()); + assertEquals("none", alpha.getAttribute("value")); + + assertNull(alpha.getNextSibling()); // No more children + } + // Else no transparency, just defaults } @Test public void testStandardTransparencyVP8L() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_3BYTE_BGR, header); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); - assertNull(transparency); // No transparency, just defaults + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); + if (transparency != null) { + assertNotNull(transparency); + assertEquals("Transparency", transparency.getNodeName()); + assertEquals(1, transparency.getLength()); + + IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", alpha.getNodeName()); + assertEquals("none", alpha.getAttribute("value")); + + assertNull(alpha.getNextSibling()); // No more children + } + // Else no transparency, just defaults } @Test public void testStandardTransparencyVP8X() { VP8xChunk header = new VP8xChunk(WebP.CHUNK_VP8X, 27, 33); header.containsALPH = true; - WebPImageMetadata metadata = new WebPImageMetadata(header); + WebPImageMetadata metadata = new WebPImageMetadata(TYPE_4BYTE_ABGR, header); - IIOMetadataNode transparency = metadata.getStandardTransparencyNode(); + IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); assertNotNull(transparency); assertEquals("Transparency", transparency.getNodeName()); assertEquals(1, transparency.getLength()); - IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild(); - assertEquals("Alpha", pixelAspectRatio.getNodeName()); - assertEquals("nonpremultiplied", pixelAspectRatio.getAttribute("value")); + IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); + assertEquals("Alpha", alpha.getNodeName()); + assertEquals("nonpremultiplied", alpha.getAttribute("value")); - assertNull(pixelAspectRatio.getNextSibling()); // No more children + assertNull(alpha.getNextSibling()); // No more children + } + + private IIOMetadataNode getStandardNode(IIOMetadata metadata, String nodeName) { + IIOMetadataNode asTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + NodeList nodes = asTree.getElementsByTagName(nodeName); + + return nodes.getLength() > 0 ? (IIOMetadataNode) nodes.item(0) : null; } } \ No newline at end of file diff --git a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageMetadata.java b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageMetadata.java index 020ad941..986b4eed 100644 --- a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageMetadata.java +++ b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageMetadata.java @@ -1,139 +1,16 @@ package com.twelvemonkeys.imageio.plugins.xwd; -import com.twelvemonkeys.imageio.AbstractMetadata; +import com.twelvemonkeys.imageio.StandardImageMetadataSupport; -import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.ImageTypeSpecifier; import java.nio.ByteOrder; -import static com.twelvemonkeys.lang.Validate.notNull; - -final class XWDImageMetadata extends AbstractMetadata { - private final XWDX11Header header; - - XWDImageMetadata(XWDX11Header header) { - super(true, null, null, null, null); - this.header = notNull(header, "header"); - } - - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); - IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType"); - - switch (header.visualClass) { - case X11.VISUAL_CLASS_STATIC_GRAY: - case X11.VISUAL_CLASS_GRAY_SCALE: - colorSpaceType.setAttribute("name", "GRAY"); - break; - default: - colorSpaceType.setAttribute("name", "RGB"); - } - - chroma.appendChild(colorSpaceType); - - // TODO: Depending on visual class OR the presence of color mop!? - switch (header.visualClass) { - case X11.VISUAL_CLASS_STATIC_COLOR: - case X11.VISUAL_CLASS_PSEUDO_COLOR: - IIOMetadataNode palette = new IIOMetadataNode("Palette"); - chroma.appendChild(palette); - - for (int i = 0; i < header.colorMap.getMapSize(); i++) { - IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry"); - paletteEntry.setAttribute("index", Integer.toString(i)); - - paletteEntry.setAttribute("red", Integer.toString(header.colorMap.getRed(i))); - paletteEntry.setAttribute("green", Integer.toString(header.colorMap.getGreen(i))); - paletteEntry.setAttribute("blue", Integer.toString(header.colorMap.getBlue(i))); - - palette.appendChild(paletteEntry); - } - break; - - default: - // No palette - } - - - return chroma; - } - - @Override - protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode node = new IIOMetadataNode("Data"); - - IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration"); - planarConfiguration.setAttribute("value", "PixelInterleaved"); - node.appendChild(planarConfiguration); - - IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat"); - node.appendChild(sampleFormat); - - switch (header.visualClass) { - case X11.VISUAL_CLASS_STATIC_COLOR: - case X11.VISUAL_CLASS_PSEUDO_COLOR: - sampleFormat.setAttribute("value", "Index"); - break; - default: - sampleFormat.setAttribute("value", "UnsignedIntegral"); - break; - } - - IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample"); - node.appendChild(bitsPerSample); - - int numComponents = header.numComponents(); - bitsPerSample.setAttribute("value", createListValue(numComponents, Integer.toString(header.bitsPerPixel / numComponents))); - - // SampleMSB - if (header.bitsPerRGB < 8 && header.bitFillOrder == ByteOrder.LITTLE_ENDIAN) { - IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB"); - node.appendChild(sampleMSB); - sampleMSB.setAttribute("value", createListValue(header.numComponents(), "0")); - } - - return node; - } - - - @Override - protected IIOMetadataNode getStandardDocumentNode() { - IIOMetadataNode document = new IIOMetadataNode("Document"); - - // The only format we support is the X11 format, and it's version is 7. - IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion"); - document.appendChild(formatVersion); - formatVersion.setAttribute("value", "7"); - - return document; - } - - @Override - protected IIOMetadataNode getStandardTextNode() { - IIOMetadataNode text = new IIOMetadataNode("Text"); - - if (header.windowName != null) { - IIOMetadataNode node = new IIOMetadataNode("TextEntry"); - text.appendChild(node); - node.setAttribute("keyword", "DocumentName"); // For TIFF interop. :-) - node.setAttribute("value", header.windowName); - } - - return text.hasChildNodes() ? text : null; - } - - // 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(); +final class XWDImageMetadata extends StandardImageMetadataSupport { + XWDImageMetadata(ImageTypeSpecifier type, XWDX11Header header) { + super(builder(type) + .withSampleMSB(header.bitsPerRGB < 8 && header.bitFillOrder == ByteOrder.LITTLE_ENDIAN ? 0 : 7) // TODO: This is unlikely to be correct... + .withFormatVersion("7.0") // The only format we support is the X11 format, and it's version is 7 + .withTextEntry("DocumentName", header.windowName) // For TIFF interop :-) + ); } } diff --git a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReader.java b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReader.java index ebc62f01..b8b0feae 100644 --- a/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReader.java +++ b/imageio/imageio-xwd/src/main/java/com/twelvemonkeys/imageio/plugins/xwd/XWDImageReader.java @@ -9,7 +9,7 @@ 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.color.*; import java.awt.image.*; import java.io.IOException; import java.nio.ByteOrder; @@ -48,10 +48,7 @@ final class XWDImageReader extends ImageReaderBase { @Override public IIOMetadata getImageMetadata(int imageIndex) throws IOException { - checkBounds(imageIndex); - readHeader(); - - return new XWDImageMetadata(header); + return new XWDImageMetadata(getRawImageType(imageIndex), header); } @Override