Major ImageMetadata refactor for more consistent standard metadata support.

Fixes a few related bugs as a bonus.
This commit is contained in:
Harald Kuhr
2022-10-08 13:43:26 +02:00
parent 9375bfda9a
commit 6458fcdcbd
37 changed files with 1648 additions and 1952 deletions

View File

@@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
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<Map.Entry<String, String>> 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<Map.Entry<String, String>> 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<String, String> entries) {
return withTextEntries(notNull(entries, "entries").entrySet());
}
public Builder withTextEntries(Collection<Map.Entry<String, String>> 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<String, String> 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;
}
}

View File

@@ -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<String> 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<String> 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<String> 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<String> 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()));
}
}
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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));
}
}

View File

@@ -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 <a href="http://en.wikipedia.org/wiki/Apple_Icon_Image_format">Apple Icon Image format (Wikipedia)</a>
*/
public final class ICNSImageReader extends ImageReaderBase {
// TODO: Support ToC resource for faster parsing/faster determine number of icons?
// TODO: Subsampled reading for completeness, even if never used?
private List<IconResource> icons = new ArrayList<IconResource>();
private List<IconResource> masks = new ArrayList<IconResource>();
private final List<IconResource> icons = new ArrayList<>();
private final List<IconResource> 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<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
List<ImageTypeSpecifier> 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;

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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<GenericChunk> 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");
}
// 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()));
return "Unknown";
}
}
// 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()));
// }
private static int[] bitsPerSample(Form header) {
int bitplanes = header.bitplanes();
return chroma;
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
if (header.compressionType() == BMHDChunk.COMPRESSION_NONE) {
return null; // All defaults
}
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) {
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;
private static List<Map.Entry<String, String>> textEntries(Form header) {
if (header.meta.isEmpty()) {
return emptyList();
}
@Override
protected IIOMetadataNode getStandardTextNode() {
if (meta.isEmpty()) {
return null;
}
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<Map.Entry<String, String>> 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;
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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");
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 PlanarConfiguration planarConfiguration(PCXHeader header) {
System.out.println("header = " + header);
return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null;
}
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);
}
private static String compressionName(PCXHeader header) {
switch (header.getCompression()) {
case PCX.COMPRESSION_NONE:
return "None";
case PCX.COMPRESSION_RLE:
return "RLE";
}
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";
}
}

View File

@@ -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<ImageTypeSpecifier> getImageTypes(int pIndex) {
public Iterator<ImageTypeSpecifier> 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) {

View File

@@ -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
}
}

View File

@@ -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<ImageTypeSpecifier> 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 {

View File

@@ -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;

View File

@@ -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"));
}
}

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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 {

View File

@@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
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(' ');
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
}
}
buffer.append(values[i % values.length]);
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;
}
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;
}
// No document node
@Override
protected IIOMetadataNode getStandardTextNode() {
// TODO: Could avoid this override, by changing the StandardImageMetadataSupport to
// use List<Entry<String, String>> instead of Map<String, String> (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;
}
}

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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(' ');
private static String compressionName(SGIHeader header) {
switch (header.getCompression()) {
case SGI.COMPRESSION_NONE:
return "None";
case SGI.COMPRESSION_RLE:
return "RLE";
}
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", "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";
}
}

View File

@@ -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?

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;
private static double pixelAspectRatio(TGAExtensions extensions) {
return extensions != null ? extensions.getPixelAspectRatio() : 1f;
}
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 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;
private static Calendar documentCreationTime(TGAExtensions extensions) {
return extensions != null ? extensions.creationDate : null;
}
@Override
protected IIOMetadataNode getStandardDocumentNode() {
IIOMetadataNode document = new IIOMetadataNode("Document");
private static Map<String, String> textEntries(TGAHeader header, TGAExtensions extensions) {
LinkedHashMap<String, String> textEntries = new LinkedHashMap<>();
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;
}
@Override
protected IIOMetadataNode getStandardTextNode() {
IIOMetadataNode text = new IIOMetadataNode("Text");
// 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<String, String> 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;
}
}

View File

@@ -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)));
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
@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");
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
// 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)
}

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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 :-)
);
}
}

View File

@@ -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