mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 03:55:28 -04:00
PICT metadata + PNTG support
This commit is contained in:
parent
bb650e5280
commit
967f8e6984
@ -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 |
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<BufferedImage> images = new ArrayList<>();
|
||||
private final List<BufferedImage> 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<ImageTypeSpecifier> getImageTypes(int pIndex) throws IOException {
|
||||
public Iterator<ImageTypeSpecifier> 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) {
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,92 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pict;
|
||||
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
/**
|
||||
* PICTMetadata.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<ImageReader> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -56,7 +56,7 @@ final class QuickTime {
|
||||
private static final List<QTDecompressor> 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)
|
||||
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<ImageTypeSpecifier> 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<ImageTypeSpecifier> 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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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";
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pntg;
|
||||
|
||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
/**
|
||||
* PNTGMetadata.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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();
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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
|
||||
);
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi
|
||||
com.twelvemonkeys.imageio.plugins.pntg.PNTGImageReaderSpi
|
||||
com.twelvemonkeys.imageio.plugins.pict.PICTImageReaderSpi
|
||||
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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));
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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)));
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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)));
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PNTGImageReaderTest.java,v 1.0 23/03/2021 haraldk Exp$
|
||||
*/
|
||||
public class PNTGImageReaderTest extends ImageReaderAbstractTest<PNTGImageReader> {
|
||||
|
||||
@Override
|
||||
protected ImageReaderSpi createProvider() {
|
||||
return new PNTGImageReaderSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TestData> 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<String> getFormatNames() {
|
||||
return Arrays.asList("PNTG", "pntg");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> getSuffixes() {
|
||||
return Arrays.asList("mac", "pntg");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<String> 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> 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pntg;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* PNTGMetadataTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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();
|
||||
}
|
||||
}
|
BIN
imageio/imageio-pict/src/test/resources/mac/MARBLES.MAC
Normal file
BIN
imageio/imageio-pict/src/test/resources/mac/MARBLES.MAC
Normal file
Binary file not shown.
BIN
imageio/imageio-pict/src/test/resources/mac/porsches.mac
Normal file
BIN
imageio/imageio-pict/src/test/resources/mac/porsches.mac
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user