diff --git a/README.md b/README.md index 6b8e80ea..c885241c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ The goal is to create a set of efficient and robust ImageIO plug-ins, that can b | | JPEG Lossless | | ✔ | - | Native & Standard | | [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | ✔ | - | Standard | | | DCX | Multi-page PCX fax document | ✔ | - | Standard | -| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple Mac Paint Picture Format | ✔ | ✔ | - | +| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple QuickTime Picture Format | ✔ | ✔ | Standard | +| | PNTG | Apple MacPaint Picture Format | ✔ | | Standard | | [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | ✔ | ✔ | Standard | | | PBM | NetPBM Portable Bit Map | ✔ | - | Standard | | | PGM | NetPBM Portable Grey Map | ✔ | - | Standard | diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java index 5055bc64..9ae42c28 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java @@ -265,8 +265,9 @@ public abstract class ImageReaderBase extends ImageReader { // - transferType is ok // - bands are ok // TODO: Test if color model is ok? - if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType() && - specifier.getNumBands() <= dest.getSampleModel().getNumBands()) { + if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType() + && Arrays.equals(specifier.getSampleModel().getSampleSize(), dest.getSampleModel().getSampleSize()) + && specifier.getNumBands() <= dest.getSampleModel().getNumBands()) { found = true; break; } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java index b1d2b681..e2dda333 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java @@ -68,6 +68,7 @@ import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; import javax.imageio.*; +import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; @@ -76,11 +77,8 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.image.*; import java.io.*; -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. @@ -123,10 +121,11 @@ public final class PICTImageReader extends ImageReaderBase { private double screenImageYRatio; // List of images created during image import - private List images = new ArrayList<>(); + private final List images = new ArrayList<>(); private long imageStartStreamPos; protected int picSize; + @Deprecated public PICTImageReader() { this(null); } @@ -168,14 +167,14 @@ public final class PICTImageReader extends ImageReaderBase { * @throws IOException if an I/O error occurs while reading the image. */ private void readPICTHeader(final ImageInputStream pStream) throws IOException { - pStream.seek(0l); + pStream.seek(0L); try { readPICTHeader0(pStream); } catch (IIOException e) { // Rest and try again - pStream.seek(0l); + pStream.seek(0L); // Skip first 512 bytes PICTImageReaderSpi.skipNullHeader(pStream); @@ -207,7 +206,7 @@ public final class PICTImageReader extends ImageReaderBase { System.out.println("frame: " + frame); } - // Set default display ratios. 72 dpi is the standard Macintosh resolution. + // Set default display ratios. 72 dpi is the standard Mac resolution. screenImageXRatio = 1.0; screenImageYRatio = 1.0; @@ -215,7 +214,7 @@ public final class PICTImageReader extends ImageReaderBase { boolean isExtendedV2 = false; int version = pStream.readShort(); if (DEBUG) { - System.out.println(String.format("PICT version: 0x%04x", version)); + System.out.printf("PICT version: 0x%04x%n", version); } if (version == (PICT.OP_VERSION << 8) + 0x01) { @@ -231,24 +230,20 @@ public final class PICTImageReader extends ImageReaderBase { int headerVersion = pStream.readInt(); if (DEBUG) { - System.out.println(String.format("headerVersion: 0x%04x", headerVersion)); + System.out.printf("headerVersion: 0x%04x%n", headerVersion); } // TODO: This (headerVersion) should be picture size (bytes) for non-V2-EXT...? // - but.. We should take care to make sure we don't mis-interpret non-PICT data... - //if (headerVersion == PICT.HEADER_V2) { if ((headerVersion & 0xffff0000) != PICT.HEADER_V2_EXT) { // TODO: Test this.. Looks dodgy to me.. // Get the image resolution and calculate the ratio between // the default Mac screen resolution and the image resolution - // int y (fixed point) + // int y, x, w(?), h (fixed point) double y2 = PICTUtil.readFixedPoint(pStream); - // int x (fixed point) double x2 = PICTUtil.readFixedPoint(pStream); - // int w (fixed point) double w2 = PICTUtil.readFixedPoint(pStream); // ?! - // int h (fixed point) double h2 = PICTUtil.readFixedPoint(pStream); screenImageXRatio = (w - x) / (w2 - x2); @@ -264,7 +259,7 @@ public final class PICTImageReader extends ImageReaderBase { // int reserved pStream.skipBytes(4); } - else /*if ((headerVersion & 0xffff0000) == PICT.HEADER_V2_EXT)*/ { + else { isExtendedV2 = true; // Get the image resolution // Not sure if they are useful for anything... @@ -281,13 +276,10 @@ public final class PICTImageReader extends ImageReaderBase { // Get the image resolution and calculate the ratio between // the default Mac screen resolution and the image resolution - // short y + // short y, x, h, w short y2 = pStream.readShort(); - // short x short x2 = pStream.readShort(); - // short h short h2 = pStream.readShort(); - // short w short w2 = pStream.readShort(); screenImageXRatio = (w - x) / (double) (w2 - x2); @@ -400,7 +392,7 @@ public final class PICTImageReader extends ImageReaderBase { } break; - case PICT.OP_CLIP_RGN:// OK for RECTS, not for regions yet + case PICT.OP_CLIP_RGN:// OK for RECTs, not for regions yet // Read the region if ((region = readRegion(pStream, bounds)) == null) { throw new IIOException("Could not read region"); @@ -735,12 +727,13 @@ public final class PICTImageReader extends ImageReaderBase { case 0x25: case 0x26: case 0x27: + case 0x2F: // Apple reserved dataLength = pStream.readUnsignedShort(); pStream.readFully(new byte[dataLength], 0, dataLength); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; @@ -829,14 +822,6 @@ public final class PICTImageReader extends ImageReaderBase { } break; - case 0x2F: - dataLength = pStream.readUnsignedShort(); - pStream.readFully(new byte[dataLength], 0, dataLength); - if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); - } - break; - //-------------------------------------------------------------------------------- // Rect treatments //-------------------------------------------------------------------------------- @@ -920,7 +905,7 @@ public final class PICTImageReader extends ImageReaderBase { case 0x003e: case 0x003f: if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; @@ -1092,7 +1077,7 @@ public final class PICTImageReader extends ImageReaderBase { case 0x57: pStream.readFully(new byte[8], 0, 8); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; @@ -1187,7 +1172,7 @@ public final class PICTImageReader extends ImageReaderBase { case 0x67: pStream.readFully(new byte[12], 0, 12); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; case 0x6d: @@ -1195,7 +1180,7 @@ public final class PICTImageReader extends ImageReaderBase { case 0x6f: pStream.readFully(new byte[4], 0, 4); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; @@ -1283,7 +1268,7 @@ public final class PICTImageReader extends ImageReaderBase { case 0x7e: case 0x7f: if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; @@ -1293,7 +1278,7 @@ public final class PICTImageReader extends ImageReaderBase { // Read the polygon polygon = readPoly(pStream, bounds); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; @@ -1384,7 +1369,7 @@ public final class PICTImageReader extends ImageReaderBase { // Read the region region = readRegion(pStream, bounds); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; @@ -1414,7 +1399,7 @@ public final class PICTImageReader extends ImageReaderBase { dataLength = pStream.readUnsignedShort(); pStream.readFully(new byte[dataLength], 0, dataLength); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x - length: %d", PICT.APPLE_USE_RESERVED_FIELD, opCode, dataLength)); + System.out.printf("%s: 0x%04x - length: %d%n", PICT.APPLE_USE_RESERVED_FIELD, opCode, dataLength); } break; @@ -1442,7 +1427,7 @@ public final class PICTImageReader extends ImageReaderBase { dataLength = pStream.readUnsignedShort(); pStream.readFully(new byte[dataLength], 0, dataLength); if (DEBUG) { - System.out.println(String.format("%s: 0x%04x", PICT.APPLE_USE_RESERVED_FIELD, opCode)); + System.out.printf("%s: 0x%04x%n", PICT.APPLE_USE_RESERVED_FIELD, opCode); } break; @@ -1478,7 +1463,7 @@ public final class PICTImageReader extends ImageReaderBase { // TODO: Read this as well, need test data dataLength = pStream.readInt(); if (DEBUG) { - System.out.println(String.format("unCompressedQuickTime, length %d", dataLength)); + System.out.printf("unCompressedQuickTime, length %d%n", dataLength); } pStream.readFully(new byte[dataLength], 0, dataLength); break; @@ -1515,7 +1500,7 @@ public final class PICTImageReader extends ImageReaderBase { } if (DEBUG) { - System.out.println(String.format("%s: 0x%04x - length: %s", PICT.APPLE_USE_RESERVED_FIELD, opCode, dataLength)); + System.out.printf("%s: 0x%04x - length: %s%n", PICT.APPLE_USE_RESERVED_FIELD, opCode, dataLength); } if (dataLength != 0) { @@ -1577,7 +1562,7 @@ public final class PICTImageReader extends ImageReaderBase { matrix[i] = pStream.readInt(); } if (DEBUG) { - System.out.println(String.format("matrix: %s", Arrays.toString(matrix))); + System.out.printf("matrix: %s%n", Arrays.toString(matrix)); } // Matte @@ -1833,7 +1818,7 @@ public final class PICTImageReader extends ImageReaderBase { //////////////////////////////////////////////////// // TODO: This works for single image PICTs only... // However, this is the most common case. Ok for now - processImageProgress(scanline * 100 / srcRect.height); + processImageProgress(scanline * 100 / (float) srcRect.height); if (abortRequested()) { processReadAborted(); @@ -2134,7 +2119,7 @@ public final class PICTImageReader extends ImageReaderBase { //////////////////////////////////////////////////// // TODO: This works for single image PICTs only... // However, this is the most common case. Ok for now - processImageProgress(scanline * 100 / srcRect.height); + processImageProgress(scanline * 100 / (float) srcRect.height); if (abortRequested()) { processReadAborted(); @@ -2626,7 +2611,7 @@ public final class PICTImageReader extends ImageReaderBase { return getYPtCoord(getPICTFrame().height); } - public Iterator getImageTypes(int pIndex) throws IOException { + public Iterator getImageTypes(int pIndex) { // TODO: The images look slightly different in Preview.. Could indicate the color space is wrong... return Collections.singletonList( ImageTypeSpecifiers.createPacked( @@ -2636,11 +2621,19 @@ public final class PICTImageReader extends ImageReaderBase { ).iterator(); } + @Override + public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { + checkBounds(imageIndex); + getPICTFrame(); // TODO: Would probably be better to use readPictHeader here, but it isn't cached + + return new PICTMetadata(version, screenImageXRatio, screenImageYRatio); + } + protected static void showIt(final BufferedImage pImage, final String pTitle) { ImageReaderBase.showIt(pImage, pTitle); } - public static void main(final String[] pArgs) throws IOException { + public static void main(final String[] pArgs) { ImageReader reader = new PICTImageReader(new PICTImageReaderSpi()); for (String arg : pArgs) { diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java index 27399d00..66d232db 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReaderSpi.java @@ -71,7 +71,7 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase { try { if (isPICT(stream)) { - // If PICT Clipping format, return true immediately + // If PICT clipboard format, return true immediately return true; } else { @@ -154,8 +154,8 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase { pStream.skipBytes(PICT.PICT_NULL_HEADER_SIZE); } + // NOTE: As the PICT format has a very weak identifier, a true return value is not necessarily a PICT... private boolean isPICT(final ImageInputStream pStream) throws IOException { - // TODO: Need to validate better... // Size may be 0, so we can't use this for validation... pStream.readUnsignedShort(); @@ -169,8 +169,8 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase { return false; } + // Validate magic int magic = pStream.readInt(); - return (magic & 0xffff0000) == PICT.MAGIC_V1 || magic == PICT.MAGIC_V2; } @@ -179,6 +179,6 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase { } public String getDescription(final Locale pLocale) { - return "Apple Mac Paint Picture (PICT) image reader"; + return "Apple MacPaint/QuickDraw Picture (PICT) image reader"; } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTMetadata.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTMetadata.java new file mode 100644 index 00000000..8a882a48 --- /dev/null +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTMetadata.java @@ -0,0 +1,92 @@ +package com.twelvemonkeys.imageio.plugins.pict; + +import com.twelvemonkeys.imageio.AbstractMetadata; + +import javax.imageio.metadata.IIOMetadataNode; + +/** + * PICTMetadata. + * + * @author Harald Kuhr + * @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; + } +} diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java index cb80be0b..1c574d3d 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTUtil.java @@ -37,6 +37,7 @@ import java.awt.image.IndexColorModel; import java.io.DataInput; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; /** @@ -76,7 +77,8 @@ final class PICTUtil { static String readIdString(final DataInput pStream) throws IOException { byte[] bytes = new byte[4]; pStream.readFully(bytes); - return new String(bytes, "ASCII"); + + return new String(bytes, StandardCharsets.US_ASCII); } /** diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressor.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressor.java index 7d39b2ea..3ddd7bf2 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressor.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressor.java @@ -30,12 +30,16 @@ package com.twelvemonkeys.imageio.plugins.pict; +import com.twelvemonkeys.imageio.plugins.pict.QuickTime.ImageDesc; import com.twelvemonkeys.io.FastByteArrayOutputStream; import com.twelvemonkeys.io.LittleEndianDataOutputStream; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.SequenceInputStream; +import java.nio.charset.StandardCharsets; /** * QTBMPDecompressor @@ -45,28 +49,24 @@ import java.io.*; * @version $Id: QTBMPDecompressor.java,v 1.0 Feb 16, 2009 9:18:28 PM haraldk Exp$ */ final class QTBMPDecompressor extends QTDecompressor { - public boolean canDecompress(final QuickTime.ImageDesc pDescription) { - return QuickTime.VENDOR_APPLE.equals(pDescription.compressorVendor) && "WRLE".equals(pDescription.compressorIdentifer) - && "bmp ".equals(idString(pDescription.extraDesc, 4)); + public boolean canDecompress(final ImageDesc description) { + return QuickTime.VENDOR_APPLE.equals(description.compressorVendor) + && "WRLE".equals(description.compressorIdentifer) + && "bmp ".equals(idString(description.extraDesc, 4)); } - private static String idString(final byte[] pData, final int pOffset) { - try { - return new String(pData, pOffset, 4, "ASCII"); - } - catch (UnsupportedEncodingException e) { - throw new Error("ASCII charset must always be supported", e); - } + private static String idString(final byte[] data, final int offset) { + return new String(data, offset, 4, StandardCharsets.US_ASCII); } - public BufferedImage decompress(final QuickTime.ImageDesc pDescription, final InputStream pStream) throws IOException { - return ImageIO.read(new SequenceInputStream(fakeBMPHeader(pDescription), pStream)); + public BufferedImage decompress(final ImageDesc description, final InputStream stream) throws IOException { + return ImageIO.read(new SequenceInputStream(fakeBMPHeader(description), stream)); } - private InputStream fakeBMPHeader(final QuickTime.ImageDesc pDescription) throws IOException { + private InputStream fakeBMPHeader(final ImageDesc description) throws IOException { int bmpHeaderSize = 14; int dibHeaderSize = 12; // 12: OS/2 V1 - ByteArrayOutputStream out = new FastByteArrayOutputStream(bmpHeaderSize + dibHeaderSize); + FastByteArrayOutputStream out = new FastByteArrayOutputStream(bmpHeaderSize + dibHeaderSize); LittleEndianDataOutputStream stream = new LittleEndianDataOutputStream(out); @@ -74,7 +74,7 @@ final class QTBMPDecompressor extends QTDecompressor { stream.writeByte('B'); stream.writeByte('M'); - stream.writeInt(pDescription.dataSize + bmpHeaderSize + dibHeaderSize); // Data size + BMP header + DIB header + stream.writeInt(description.dataSize + bmpHeaderSize + dibHeaderSize); // Data size + BMP header + DIB header stream.writeShort(0x0); // Reserved stream.writeShort(0x0); // Reserved @@ -84,12 +84,12 @@ final class QTBMPDecompressor extends QTDecompressor { // DIB header stream.writeInt(dibHeaderSize); // DIB header size - stream.writeShort(pDescription.width); - stream.writeShort(pDescription.height); + stream.writeShort(description.width); + stream.writeShort(description.height); stream.writeShort(1); // Planes, only legal value: 1 - stream.writeShort(pDescription.depth); // Bit depth + stream.writeShort(description.depth); // Bit depth - return new ByteArrayInputStream(out.toByteArray()); + return out.createInputStream(); } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTDecompressor.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTDecompressor.java index 28b58aca..adf42a51 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTDecompressor.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTDecompressor.java @@ -30,6 +30,8 @@ package com.twelvemonkeys.imageio.plugins.pict; +import com.twelvemonkeys.imageio.plugins.pict.QuickTime.ImageDesc; + import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; @@ -46,20 +48,20 @@ abstract class QTDecompressor { * Returns whether this decompressor is capable of decompressing the image * data described by the given image description. * - * @param pDescription the image description ({@code 'idsc'} Atom). + * @param description the image description ({@code 'idsc'} Atom). * @return {@code true} if this decompressor is capable of decompressing * he data in the given image description, otherwise {@code false}. */ - public abstract boolean canDecompress(QuickTime.ImageDesc pDescription); + public abstract boolean canDecompress(ImageDesc description); /** * Decompresses an image. * - * @param pDescription the image description ({@code 'idsc'} Atom). - * @param pStream the image data stream + * @param description the image description ({@code 'idsc'} Atom). + * @param stream the image data stream * @return the decompressed image * * @throws java.io.IOException if an I/O exception occurs during reading. */ - public abstract BufferedImage decompress(QuickTime.ImageDesc pDescription, InputStream pStream) throws IOException; + public abstract BufferedImage decompress(ImageDesc description, InputStream stream) throws IOException; } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressor.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressor.java index 4e58c91c..9a9b3ee8 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressor.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressor.java @@ -31,9 +31,14 @@ package com.twelvemonkeys.imageio.plugins.pict; import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.stream.ImageInputStream; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; +import java.util.Iterator; + +import static com.twelvemonkeys.imageio.plugins.pict.QuickTime.ImageDesc; /** * QTGenericDecompressor @@ -43,11 +48,36 @@ import java.io.InputStream; * @version $Id: QTGenericDecompressor.java,v 1.0 Feb 16, 2009 9:26:13 PM haraldk Exp$ */ final class QTGenericDecompressor extends QTDecompressor { - public boolean canDecompress(final QuickTime.ImageDesc pDescription) { + public boolean canDecompress(final ImageDesc description) { + // Instead of testing, we just allow everything, and might eventually fail on decompress later... return true; } - public BufferedImage decompress(final QuickTime.ImageDesc pDescription, final InputStream pStream) throws IOException { - return ImageIO.read(pStream); + public BufferedImage decompress(final ImageDesc description, final InputStream stream) throws IOException { + BufferedImage image = ImageIO.read(stream); + + if (image == null) { + return readUsingFormatName(description.compressorIdentifer.trim(), stream); + } + + return image; + } + + private BufferedImage readUsingFormatName(final String formatName, final InputStream stream) throws IOException { + Iterator readers = ImageIO.getImageReadersByFormatName(formatName); + + if (readers.hasNext()) { + ImageReader reader = readers.next(); + + try (ImageInputStream input = ImageIO.createImageInputStream(stream)) { + reader.setInput(input); + return reader.read(0); + } + finally { + reader.dispose(); + } + } + + return null; } } diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressor.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressor.java index c585e12d..53f00058 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressor.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressor.java @@ -38,6 +38,9 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import static com.twelvemonkeys.imageio.plugins.pict.QuickTime.ImageDesc; +import static com.twelvemonkeys.imageio.plugins.pict.QuickTime.VENDOR_APPLE; + /** * QTRAWDecompressor * @@ -51,21 +54,17 @@ final class QTRAWDecompressor extends QTDecompressor { // - Have a look at com.sun.media.imageio.stream.RawImageInputStream... // TODO: Support different bit depths - public boolean canDecompress(final QuickTime.ImageDesc pDescription) { - return QuickTime.VENDOR_APPLE.equals(pDescription.compressorVendor) - && "raw ".equals(pDescription.compressorIdentifer) - && (pDescription.depth == 24 || pDescription.depth == 32); + public boolean canDecompress(final ImageDesc description) { + return VENDOR_APPLE.equals(description.compressorVendor) + && "raw ".equals(description.compressorIdentifer) + && (description.depth == 24 || description.depth == 32 || description.depth == 40); } - public BufferedImage decompress(final QuickTime.ImageDesc pDescription, final InputStream pStream) throws IOException { - byte[] data = new byte[pDescription.dataSize]; + public BufferedImage decompress(final ImageDesc description, final InputStream stream) throws IOException { + byte[] data = new byte[description.dataSize]; - DataInputStream stream = new DataInputStream(pStream); - try { - stream.readFully(data, 0, pDescription.dataSize); - } - finally { - stream.close(); + try (DataInputStream dataStream = new DataInputStream(stream)) { + dataStream.readFully(data, 0, description.dataSize); } DataBuffer buffer = new DataBufferByte(data, data.length); @@ -73,12 +72,12 @@ final class QTRAWDecompressor extends QTDecompressor { WritableRaster raster; // TODO: Depth parameter can be 1-32 (color) or 33-40 (gray scale) - switch (pDescription.depth) { + switch (description.depth) { case 40: // 8 bit gray (untested) raster = Raster.createInterleavedRaster( buffer, - pDescription.width, pDescription.height, - pDescription.width, 1, + description.width, description.height, + description.width, 1, new int[] {0}, null ); @@ -86,8 +85,8 @@ final class QTRAWDecompressor extends QTDecompressor { case 24: // 24 bit RGB raster = Raster.createInterleavedRaster( buffer, - pDescription.width, pDescription.height, - pDescription.width * 3, 3, + description.width, description.height, + description.width * 3, 3, new int[] {0, 1, 2}, null ); @@ -96,9 +95,9 @@ final class QTRAWDecompressor extends QTDecompressor { // WORKAROUND: There is a bug in the way Java 2D interprets the band offsets in // Raster.createInterleavedRaster (see below) before Java 6. So, instead of // passing a correct offset array below, we swap channel 1 & 3 to make it ABGR... - for (int y = 0; y < pDescription.height; y++) { - for (int x = 0; x < pDescription.width; x++) { - int offset = 4 * y * pDescription.width + x * 4; + for (int y = 0; y < description.height; y++) { + for (int x = 0; x < description.width; x++) { + int offset = 4 * y * description.width + x * 4; byte temp = data[offset + 1]; data[offset + 1] = data[offset + 3]; data[offset + 3] = temp; @@ -107,21 +106,21 @@ final class QTRAWDecompressor extends QTDecompressor { raster = Raster.createInterleavedRaster( buffer, - pDescription.width, pDescription.height, - pDescription.width * 4, 4, + description.width, description.height, + description.width * 4, 4, new int[] {3, 2, 1, 0}, // B & R mixed up. {1, 2, 3, 0} is correct null ); break; default: - throw new IIOException("Unsupported RAW depth: " + pDescription.depth); + throw new IIOException("Unsupported QuickTime RAW depth: " + description.depth); } ColorModel cm = new ComponentColorModel( - pDescription.depth <= 32 ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpace.getInstance(ColorSpace.CS_GRAY), - pDescription.depth == 32, + description.depth <= 32 ? ColorSpace.getInstance(ColorSpace.CS_sRGB) : ColorSpace.getInstance(ColorSpace.CS_GRAY), + description.depth == 32, false, - pDescription.depth == 32 ? Transparency.TRANSLUCENT : Transparency.OPAQUE, + description.depth == 32 ? Transparency.TRANSLUCENT : Transparency.OPAQUE, DataBuffer.TYPE_BYTE ); diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickTime.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickTime.java index 826f1ccb..50fca198 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickTime.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/QuickTime.java @@ -56,7 +56,7 @@ final class QuickTime { private static final List sDecompressors = Arrays.asList( new QTBMPDecompressor(), new QTRAWDecompressor(), - // The GenericDecompressor must be the last in the list + // The GenericDecompressor MUST be the last in the list, as it claims to read everything... new QTGenericDecompressor() ); @@ -87,7 +87,7 @@ final class QuickTime { kH263CodecType ='h263' kIndeo4CodecType ='IV41' kJPEGCodecType ='jpeg' -> JPEG, SUPPORTED - kMacPaintCodecType ='PNTG' -> Isn't this the PICT format itself? Does that make sense?! ;-) + kMacPaintCodecType ='PNTG' -> PNTG, should work, but lacks test data kMicrosoftVideo1CodecType ='msvc' kMotionJPEGACodecType ='mjpa' kMotionJPEGBCodecType ='mjpb' @@ -99,12 +99,12 @@ final class QuickTime { kQuickDrawCodecType ='qdrw' -> QD? kQuickDrawGXCodecType ='qdgx' -> QD? kRawCodecType ='raw ' -> Raw (A)RGB pixel data - kSGICodecType ='.SGI' + kSGICodecType ='.SGI' -> SGI, should work, but lacks test data k16GrayCodecType ='b16g' -> Raw 16 bit gray data? k64ARGBCodecType ='b64a' -> Raw 64 bit (16 bpp) color data? kSorensonCodecType ='SVQ1' kSorensonYUV9CodecType ='syv9' - kTargaCodecType ='tga ' -> TGA, maybe create a plugin for that + kTargaCodecType ='tga ' -> TGA, should work, but lacks test data k32AlphaGrayCodecType ='b32a' -> 16 bit gray + 16 bit alpha raw data? kTIFFCodecType ='tiff' -> TIFF, SUPPORTED kVectorCodecType ='path' @@ -117,13 +117,13 @@ final class QuickTime { /** * Gets a decompressor that can decompress the described data. * - * @param pDescription the image description ({@code 'idsc'} Atom). + * @param description the image description ({@code 'idsc'} Atom). * @return a decompressor that can decompress data decribed by the given {@link ImageDesc description}, * or {@code null} if no decompressor is found */ - private static QTDecompressor getDecompressor(final ImageDesc pDescription) { + private static QTDecompressor getDecompressor(final ImageDesc description) { for (QTDecompressor decompressor : sDecompressors) { - if (decompressor.canDecompress(pDescription)) { + if (decompressor.canDecompress(description)) { return decompressor; } } @@ -134,13 +134,13 @@ final class QuickTime { /** * Decompresses the QuickTime image data from the given stream. * - * @param pStream the image input stream + * @param stream the image input stream * @return a {@link BufferedImage} containing the image data, or {@code null} if no decompressor is capable of * decompressing the image. * @throws IOException if an I/O exception occurs during read */ - public static BufferedImage decompress(final ImageInputStream pStream) throws IOException { - ImageDesc description = ImageDesc.read(pStream); + public static BufferedImage decompress(final ImageInputStream stream) throws IOException { + ImageDesc description = ImageDesc.read(stream); if (PICTImageReader.DEBUG) { System.out.println(description); @@ -152,12 +152,8 @@ final class QuickTime { return null; } - InputStream streamAdapter = IIOUtil.createStreamAdapter(pStream, description.dataSize); - try { - return decompressor.decompress(description, streamAdapter); - } - finally { - streamAdapter.close(); + try (InputStream streamAdapter = IIOUtil.createStreamAdapter(stream, description.dataSize)) { + return decompressor.decompress(description, streamAdapter); } } @@ -195,7 +191,7 @@ final class QuickTime { byte[] extraDesc; - private ImageDesc() {} + ImageDesc() {} public static ImageDesc read(final DataInput pStream) throws IOException { // The following looks like the 'idsc' Atom (as described in the QuickTime File Format) diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReader.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReader.java new file mode 100644 index 00000000..1c697054 --- /dev/null +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReader.java @@ -0,0 +1,146 @@ +package com.twelvemonkeys.imageio.plugins.pntg; + +import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.util.IIOUtil; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; +import com.twelvemonkeys.io.enc.DecoderStream; +import com.twelvemonkeys.io.enc.PackBitsDecoder; + +import javax.imageio.IIOException; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import java.awt.*; +import java.awt.image.*; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +import static com.twelvemonkeys.imageio.plugins.pntg.PNTGImageReaderSpi.isMacBinaryPNTG; +import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow; + +/** + * PNTGImageReader. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PNTGImageReader.java,v 1.0 23/03/2021 haraldk Exp$ + */ +public final class PNTGImageReader extends ImageReaderBase { + + private static final Set IMAGE_TYPES = + Collections.singleton(ImageTypeSpecifiers.createIndexed(new int[] {-1, 0}, false, -1, 1, DataBuffer.TYPE_BYTE)); + + protected PNTGImageReader(final ImageReaderSpi provider) { + super(provider); + } + + @Override + protected void resetMembers() { + } + + @Override + public int getWidth(final int imageIndex) throws IOException { + checkBounds(imageIndex); + + return 576; + } + + @Override + public int getHeight(final int imageIndex) throws IOException { + checkBounds(imageIndex); + + return 720; + } + + @Override + public Iterator getImageTypes(final int imageIndex) throws IOException { + checkBounds(imageIndex); + + return IMAGE_TYPES.iterator(); + } + + @Override + public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException { + checkBounds(imageIndex); + readHeader(); + + int width = getWidth(imageIndex); + int height = getHeight(imageIndex); + + BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height); + int[] destBands = param != null ? param.getDestinationBands() : null; + + Rectangle srcRegion = new Rectangle(); + Rectangle destRegion = new Rectangle(); + computeRegions(param, width, height, destination, srcRegion, destRegion); + + int xSub = param != null ? param.getSourceXSubsampling() : 1; + int ySub = param != null ? param.getSourceYSubsampling() : 1; + + WritableRaster destRaster = destination.getRaster() + .createWritableChild(destRegion.x, destRegion.y, destRegion.width, destRegion.height, 0, 0, destBands); + + Raster rowRaster = Raster.createPackedRaster(DataBuffer.TYPE_BYTE, width, 1, 1, 1, null) + .createChild(srcRegion.x, 0, destRegion.width, 1, 0, 0, destBands); + + processImageStarted(imageIndex); + + readData(srcRegion, destRegion, xSub, ySub, destRaster, rowRaster); + + processImageComplete(); + + return destination; + } + + private void readData(Rectangle srcRegion, Rectangle destRegion, int xSub, int ySub, WritableRaster destRaster, Raster rowRaster) throws IOException { + byte[] rowData = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); + + try (DataInputStream decoderStream = new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(imageInput), new PackBitsDecoder()))) { + int srcMaxY = srcRegion.y + srcRegion.height; + for (int y = 0; y < srcMaxY; y++) { + decoderStream.readFully(rowData); + + if (y >= srcRegion.y && y % ySub == 0) { + subsampleRow(rowData, srcRegion.x, srcRegion.width, rowData, destRegion.x, 1, 1, xSub); + + int destY = (y - srcRegion.y) / ySub; + destRaster.setDataElements(0, destY, rowRaster); + + processImageProgress(y / (float) srcMaxY); + } + + if (abortRequested()) { + processReadAborted(); + break; + } + } + } + } + + @Override + public IIOMetadata getImageMetadata(final int imageIndex) throws IOException { + checkBounds(imageIndex); + + return new PNTGMetadata(); + } + + private void readHeader() throws IOException { + if (isMacBinaryPNTG(imageInput)) { + // Seek to end of MacBinary header + // TODO: Could actually get the file name, creation date etc metadata from this data + imageInput.seek(128); + } + else { + imageInput.seek(0); + } + + // Skip pattern data section (usually all 0s) + if (imageInput.skipBytes(512) != 512) { + throw new IIOException("Could not skip pattern data"); + } + } +} diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderSpi.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderSpi.java new file mode 100644 index 00000000..27f852ec --- /dev/null +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderSpi.java @@ -0,0 +1,71 @@ +package com.twelvemonkeys.imageio.plugins.pntg; + +import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; + +import javax.imageio.stream.ImageInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.util.Locale; + +/** + * PNTGImageReaderSpi. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PNTGImageReaderSpi.java,v 1.0 23/03/2021 haraldk Exp$ + */ +public final class PNTGImageReaderSpi extends ImageReaderSpiBase { + public PNTGImageReaderSpi() { + super(new PNTGProviderInfo()); + } + + @Override + public boolean canDecodeInput(final Object source) throws IOException { + if (!(source instanceof ImageInputStream)) { + return false; + } + + ImageInputStream stream = (ImageInputStream) source; + stream.mark(); + + try { + // TODO: Figure out how to read the files without the MacBinary header... + // Probably not possible, as it's just 512 bytes of nulls OR pattern information + return isMacBinaryPNTG(stream); + } + catch (EOFException ignore) { + return false; + } + finally { + stream.reset(); + } + } + + static boolean isMacBinaryPNTG(final ImageInputStream stream) throws IOException { + stream.seek(0); + + if (stream.readByte() != 0) { + return false; + } + + byte nameLen = stream.readByte(); + if (nameLen < 0 || nameLen > 63) { + return false; + } + + stream.skipBytes(63); + + // Validate that type is PNTG and that next 4 bytes are all within the ASCII range, typically 'MPNT' + return stream.readInt() == ('P' << 24 | 'N' << 16 | 'T' << 8 | 'G') && (stream.readInt() & 0x80808080) == 0; + } + + @Override + public PNTGImageReader createReaderInstance(final Object extension) { + return new PNTGImageReader(this); + } + + @Override + public String getDescription(final Locale locale) { + return "Apple MacPaint Painting (PNTG) image reader"; + } +} diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadata.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadata.java new file mode 100644 index 00000000..90f22b95 --- /dev/null +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadata.java @@ -0,0 +1,86 @@ +package com.twelvemonkeys.imageio.plugins.pntg; + +import com.twelvemonkeys.imageio.AbstractMetadata; + +import javax.imageio.metadata.IIOMetadataNode; + +/** + * PNTGMetadata. + * + * @author Harald Kuhr + * @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(); + } +} diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGProviderInfo.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGProviderInfo.java new file mode 100644 index 00000000..a30edcae --- /dev/null +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGProviderInfo.java @@ -0,0 +1,56 @@ +/* + * 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.pntg; + +import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; + +/** + * PNTGProviderInfo. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: PNTGProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$ + */ +final class PNTGProviderInfo extends ReaderWriterProviderInfo { + protected PNTGProviderInfo() { + super( + PNTGProviderInfo.class, + new String[] {"pntg", "PNTG"}, + new String[] {"mac", "pic", "pntg"}, + new String[] {"image/x-pntg"}, + "com.twelvemonkeys.imageio.plugins.mac.MACImageReader", + new String[] {"com.twelvemonkeys.imageio.plugins.mac.MACImageReaderSpi"}, + null, null, + false, null, null, null, null, + true, null, null, null, null + ); + } +} diff --git a/imageio/imageio-pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi b/imageio/imageio-pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi index 1f4a92e9..d004807f 100755 --- a/imageio/imageio-pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi +++ b/imageio/imageio-pict/src/main/resources/META-INF/services/javax.imageio.spi.ImageReaderSpi @@ -1 +1,2 @@ -com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi \ No newline at end of file +com.twelvemonkeys.imageio.plugins.pntg.PNTGImageReaderSpi +com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressorTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressorTest.java new file mode 100644 index 00000000..860a0f35 --- /dev/null +++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTBMPDecompressorTest.java @@ -0,0 +1,30 @@ +package com.twelvemonkeys.imageio.plugins.pict; + +import com.twelvemonkeys.imageio.plugins.pict.QuickTime.ImageDesc; + +import org.junit.Test; + +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertTrue; + +/** + * QTBMPDecompressorTest. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: QTBMPDecompressorTest.java,v 1.0 24/03/2021 haraldk Exp$ + */ +public class QTBMPDecompressorTest { + @Test + public void canDecompress() { + QTDecompressor decompressor = new QTBMPDecompressor(); + + ImageDesc description = new ImageDesc(); + description.compressorVendor = QuickTime.VENDOR_APPLE; + description.compressorIdentifer = "WRLE"; + description.extraDesc = "....bmp ...something...".getBytes(StandardCharsets.UTF_8); + + assertTrue(decompressor.canDecompress(description)); + } +} \ No newline at end of file diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressorTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressorTest.java new file mode 100644 index 00000000..cbb0707b --- /dev/null +++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTGenericDecompressorTest.java @@ -0,0 +1,52 @@ +package com.twelvemonkeys.imageio.plugins.pict; + +import com.twelvemonkeys.imageio.plugins.pict.QuickTime.ImageDesc; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * QTBMPDecompressorTest. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: QTBMPDecompressorTest.java,v 1.0 24/03/2021 haraldk Exp$ + */ +public class QTGenericDecompressorTest { + private ImageDesc createDescription(final String identifer, final String name, final int depth) { + ImageDesc description = new ImageDesc(); + description.compressorVendor = QuickTime.VENDOR_APPLE; + description.compressorIdentifer = identifer; + description.compressorName = name; + description.depth = (short) depth; + + return description; + } + + @Test + public void canDecompressJPEG() { + QTDecompressor decompressor = new QTGenericDecompressor(); + + assertTrue(decompressor.canDecompress(createDescription("jpeg", "Photo - JPEG", 8))); + assertTrue(decompressor.canDecompress(createDescription("jpeg", "Photo - JPEG", 24))); + } + + @Test + public void canDecompressPNG() { + QTDecompressor decompressor = new QTGenericDecompressor(); + + assertTrue(decompressor.canDecompress(createDescription("png ", "PNG", 8))); + assertTrue(decompressor.canDecompress(createDescription("png ", "PNG", 24))); + assertTrue(decompressor.canDecompress(createDescription("png ", "PNG", 32))); + } + + @Test + public void canDecompressTIFF() { + QTDecompressor decompressor = new QTGenericDecompressor(); + + assertTrue(decompressor.canDecompress(createDescription("tiff", "TIFF", 8))); + assertTrue(decompressor.canDecompress(createDescription("tiff", "TIFF", 24))); + assertTrue(decompressor.canDecompress(createDescription("tiff", "TIFF", 32))); + } +} \ No newline at end of file diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressorTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressorTest.java new file mode 100644 index 00000000..a8188432 --- /dev/null +++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pict/QTRAWDecompressorTest.java @@ -0,0 +1,46 @@ +package com.twelvemonkeys.imageio.plugins.pict; + +import com.twelvemonkeys.imageio.plugins.pict.QuickTime.ImageDesc; + +import org.junit.Test; + +import static org.junit.Assert.assertTrue; + +/** + * QTBMPDecompressorTest. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: QTBMPDecompressorTest.java,v 1.0 24/03/2021 haraldk Exp$ + */ +public class QTRAWDecompressorTest { + private ImageDesc createDescription(int bitDepth) { + ImageDesc description = new ImageDesc(); + description.compressorVendor = QuickTime.VENDOR_APPLE; + description.compressorIdentifer = "raw "; + description.depth = (short) bitDepth; + + return description; + } + + @Test + public void canDecompressRGB() { + QTDecompressor decompressor = new QTRAWDecompressor(); + + assertTrue(decompressor.canDecompress(createDescription(24))); + } + + @Test + public void canDecompressRGBA() { + QTDecompressor decompressor = new QTRAWDecompressor(); + + assertTrue(decompressor.canDecompress(createDescription(32))); + } + + @Test + public void canDecompressGray() { + QTDecompressor decompressor = new QTRAWDecompressor(); + + assertTrue(decompressor.canDecompress(createDescription(40))); + } +} \ No newline at end of file diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderTest.java new file mode 100644 index 00000000..8b75cce9 --- /dev/null +++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGImageReaderTest.java @@ -0,0 +1,65 @@ +package com.twelvemonkeys.imageio.plugins.pntg; + +import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; + +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import java.awt.*; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * PNTGImageReaderTest. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PNTGImageReaderTest.java,v 1.0 23/03/2021 haraldk Exp$ + */ +public class PNTGImageReaderTest extends ImageReaderAbstractTest { + + @Override + protected ImageReaderSpi createProvider() { + return new PNTGImageReaderSpi(); + } + + @Override + protected List getTestData() { + return Arrays.asList(new TestData(getClassLoaderResource("/mac/porsches.mac"), new Dimension(576, 720)), + new TestData(getClassLoaderResource("/mac/MARBLES.MAC"), new Dimension(576, 720))); + } + + @Override + protected List getFormatNames() { + return Arrays.asList("PNTG", "pntg"); + } + + @Override + protected List getSuffixes() { + return Arrays.asList("mac", "pntg"); + } + + @Override + protected List getMIMETypes() { + return Collections.singletonList("image/x-pntg"); + } + + @Override + public void testProviderCanRead() throws IOException { + // TODO: This a kind of hack... + // Currently, the provider don't claim to read the MARBLES.MAC image, + // as it lacks the MacBinary header and thus no way to identify format. + // We can still read it, so we'll include it in the other tests. + List testData = getTestData().subList(0, 1); + + for (TestData data : testData) { + ImageInputStream stream = data.getInputStream(); + assertNotNull(stream); + assertTrue("Provider is expected to be able to decode data: " + data, provider.canDecodeInput(stream)); + } + } +} \ No newline at end of file diff --git a/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadataTest.java b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadataTest.java new file mode 100644 index 00000000..27cf8f9a --- /dev/null +++ b/imageio/imageio-pict/src/test/java/com/twelvemonkeys/imageio/plugins/pntg/PNTGMetadataTest.java @@ -0,0 +1,17 @@ +package com.twelvemonkeys.imageio.plugins.pntg; + +import org.junit.Test; + +/** + * PNTGMetadataTest. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PNTGMetadataTest.java,v 1.0 23/03/2021 haraldk Exp$ + */ +public class PNTGMetadataTest { + @Test + public void testCreate() { + new PNTGMetadata(); + } +} \ No newline at end of file diff --git a/imageio/imageio-pict/src/test/resources/mac/MARBLES.MAC b/imageio/imageio-pict/src/test/resources/mac/MARBLES.MAC new file mode 100644 index 00000000..668d0683 Binary files /dev/null and b/imageio/imageio-pict/src/test/resources/mac/MARBLES.MAC differ diff --git a/imageio/imageio-pict/src/test/resources/mac/porsches.mac b/imageio/imageio-pict/src/test/resources/mac/porsches.mac new file mode 100644 index 00000000..e50096ac Binary files /dev/null and b/imageio/imageio-pict/src/test/resources/mac/porsches.mac differ