mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 03:25:28 -04:00
TMI-PNM: Initial commit.
This commit is contained in:
parent
c6558d7433
commit
eca8f84f6e
25
imageio/imageio-pnm/license.txt
Executable file
25
imageio/imageio-pnm/license.txt
Executable file
@ -0,0 +1,25 @@
|
||||
Copyright (c) 2014, Harald Kuhr
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name "TwelveMonkeys" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
imageio/imageio-pnm/pom.xml
Executable file
29
imageio/imageio-pnm/pom.xml
Executable file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pnm</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
|
||||
<description>
|
||||
ImageIO plugin for NetPBM Portable Any Map (PNM)
|
||||
</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
@ -0,0 +1,43 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
abstract class HeaderParser {
|
||||
protected final ImageInputStream input;
|
||||
|
||||
protected HeaderParser(final ImageInputStream input) {
|
||||
this.input = notNull(input);
|
||||
}
|
||||
|
||||
public abstract PNMHeader parse() throws IOException;
|
||||
|
||||
public static PNMHeader parse(ImageInputStream input) throws IOException {
|
||||
short type = input.readShort();
|
||||
|
||||
return createParser(input, type).parse();
|
||||
}
|
||||
|
||||
private static HeaderParser createParser(final ImageInputStream input, final short type) throws IOException {
|
||||
switch (type) {
|
||||
case PNM.PBM_PLAIN:
|
||||
case PNM.PBM:
|
||||
case PNM.PGM_PLAIN:
|
||||
case PNM.PGM:
|
||||
case PNM.PPM_PLAIN:
|
||||
case PNM.PPM:
|
||||
return new PNMHeaderParser(input, type);
|
||||
case PNM.PAM:
|
||||
return new PAMHeaderParser(input);
|
||||
case PNM.PFM_GRAY:
|
||||
case PNM.PFM_RGB:
|
||||
return new PFMHeaderParser(input, type);
|
||||
default:
|
||||
throw new IIOException("Unexpected type for PBM, PGM or PPM format: " + type);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
abstract class HeaderWriter {
|
||||
protected static final Charset UTF8 = Charset.forName("UTF8");
|
||||
protected final ImageOutputStream imageOutput;
|
||||
|
||||
protected HeaderWriter(final ImageOutputStream imageOutput) {
|
||||
this.imageOutput = imageOutput;
|
||||
}
|
||||
|
||||
public static void write(final IIOImage image, final ImageWriterSpi provider, final ImageOutputStream imageOutput) throws IOException {
|
||||
// TODO: This is somewhat sketchy...
|
||||
if (provider.getFormatNames()[0].equals("pam")) {
|
||||
new PAMHeaderWriter(imageOutput).writeHeader(image, provider);
|
||||
}
|
||||
else if (provider.getFormatNames()[0].equals("pnm")) {
|
||||
new PNMHeaderWriter(imageOutput).writeHeader(image, provider);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError("Unsupported provider: " + provider);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void writeHeader(IIOImage image, final ImageWriterSpi provider) throws IOException;
|
||||
|
||||
protected final int getWidth(final IIOImage image) {
|
||||
return image.hasRaster() ? image.getRaster().getWidth() : image.getRenderedImage().getWidth();
|
||||
}
|
||||
|
||||
protected final int getHeight(final IIOImage image) {
|
||||
return image.hasRaster() ? image.getRaster().getHeight() : image.getRenderedImage().getHeight();
|
||||
}
|
||||
|
||||
protected final int getNumBands(final IIOImage image) {
|
||||
return image.hasRaster() ? image.getRaster().getNumBands() : image.getRenderedImage().getSampleModel().getNumBands();
|
||||
}
|
||||
|
||||
protected int getMaxVal(final IIOImage image) {
|
||||
int transferType = getTransferType(image);
|
||||
|
||||
if (transferType == DataBuffer.TYPE_BYTE) {
|
||||
return PNM.MAX_VAL_8BIT;
|
||||
}
|
||||
else if (transferType == DataBuffer.TYPE_USHORT) {
|
||||
return PNM.MAX_VAL_16BIT;
|
||||
}
|
||||
// else if (transferType == DataBuffer.TYPE_INT) {
|
||||
// TODO: Support TYPE_INT through conversion, if number of channels is 3 or 4 (TYPE_INT_RGB, TYPE_INT_ARGB)
|
||||
// }
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported data type: " + transferType);
|
||||
}
|
||||
}
|
||||
|
||||
protected final int getTransferType(final IIOImage image) {
|
||||
return image.hasRaster() ? image.getRaster().getTransferType() : image.getRenderedImage().getSampleModel().getTransferType();
|
||||
}
|
||||
|
||||
protected final void writeComments(final IIOMetadata metadata, final ImageWriterSpi provider) throws IOException {
|
||||
// TODO: Only write creator if not already present
|
||||
imageOutput.write(String.format("# CREATOR: %s %s\n", provider.getVendorName(), provider.getDescription(Locale.getDefault())).getBytes(UTF8));
|
||||
|
||||
// Comments from metadata
|
||||
if (metadata != null && metadata.isStandardMetadataFormatSupported()) {
|
||||
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||
NodeList textEntries = root.getElementsByTagName("TextEntry");
|
||||
|
||||
for (int i = 0; i < textEntries.getLength(); i++) {
|
||||
// TODO: Write on the format "# KEYWORD: value" (if keyword != comment)?
|
||||
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
|
||||
imageOutput.write(String.format("# %s", textEntry.getAttribute("value")).getBytes(UTF8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
final class PAMHeaderParser extends HeaderParser {
|
||||
|
||||
static final String ENDHDR = "ENDHDR";
|
||||
static final String WIDTH = "WIDTH";
|
||||
static final String HEIGHT = "HEIGHT";
|
||||
static final String MAXVAL = "MAXVAL";
|
||||
static final String DEPTH = "DEPTH";
|
||||
static final String TUPLTYPE = "TUPLTYPE";
|
||||
|
||||
public PAMHeaderParser(final ImageInputStream input) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
@Override public PNMHeader parse() throws IOException {
|
||||
/* Note: Comments are allowed
|
||||
P7
|
||||
WIDTH 227
|
||||
HEIGHT 149
|
||||
DEPTH 3
|
||||
MAXVAL 255
|
||||
TUPLTYPE RGB
|
||||
ENDHDR
|
||||
*/
|
||||
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
int depth = -1;
|
||||
int maxVal = -1;
|
||||
TupleType tupleType = null;
|
||||
List<String> comments = new ArrayList<String>();
|
||||
|
||||
String line;
|
||||
while ((line = input.readLine()) != null && !line.startsWith(ENDHDR)) {
|
||||
line = line.trim();
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith(WIDTH)) {
|
||||
width = Integer.parseInt(line.substring(WIDTH.length() + 1));
|
||||
}
|
||||
else if (line.startsWith(HEIGHT)) {
|
||||
height = Integer.parseInt(line.substring(HEIGHT.length() + 1));
|
||||
}
|
||||
else if (line.startsWith(DEPTH)) {
|
||||
depth = Integer.parseInt(line.substring(DEPTH.length() + 1));
|
||||
}
|
||||
else if (line.startsWith(MAXVAL)) {
|
||||
maxVal = Integer.parseInt(line.substring(MAXVAL.length() + 1));
|
||||
}
|
||||
else if (line.startsWith(TUPLTYPE)) {
|
||||
tupleType = TupleType.valueOf(line.substring(TUPLTYPE.length() + 1));
|
||||
}
|
||||
else if (line.startsWith("#")) {
|
||||
comments.add(line.substring(1).trim());
|
||||
}
|
||||
else {
|
||||
throw new IIOException("Unknown PAM header token: '" + line + "'");
|
||||
}
|
||||
}
|
||||
|
||||
if (tupleType == null) {
|
||||
// TODO: Assume a type, based on depth + maxVal, or at least, allow reading as raster
|
||||
}
|
||||
|
||||
return new PNMHeader(PNM.PAM, tupleType, width, height, depth, maxVal, comments);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
final class PAMHeaderWriter extends HeaderWriter {
|
||||
public PAMHeaderWriter(final ImageOutputStream imageOutput) {
|
||||
super(imageOutput);
|
||||
}
|
||||
|
||||
@Override public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException {
|
||||
// Write PAM magic
|
||||
imageOutput.writeShort(PNM.PAM);
|
||||
imageOutput.write('\n');
|
||||
// Comments
|
||||
writeComments(image.getMetadata(), provider);
|
||||
// Write width/height and number of channels
|
||||
imageOutput.write(String.format("WIDTH %s\nHEIGHT %s\n", getWidth(image), getHeight(image)).getBytes(UTF8));
|
||||
imageOutput.write(String.format("DEPTH %s\n", getNumBands(image)).getBytes(UTF8));
|
||||
|
||||
// TODO: maxSample (8 or16 bit)
|
||||
imageOutput.write(String.format("MAXVAL %s\n", getMaxVal(image)).getBytes(UTF8));
|
||||
|
||||
// TODO: Determine tuple type based on input color model and image data
|
||||
TupleType tupleType = getNumBands(image) > 3 ? TupleType.RGB_ALPHA : TupleType.RGB;
|
||||
imageOutput.write(String.format("TUPLTYPE %s\nENDHDR\n", tupleType).getBytes(UTF8));
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
public final class PAMImageWriterSpi extends ImageWriterSpi {
|
||||
|
||||
/**
|
||||
* Creates a {@code PAMImageWriterSpi}.
|
||||
*/
|
||||
public PAMImageWriterSpi() {
|
||||
this(IIOUtil.getProviderInfo(PAMImageWriterSpi.class));
|
||||
}
|
||||
|
||||
private PAMImageWriterSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"pam", "PAM"},
|
||||
new String[]{"pam"},
|
||||
new String[]{
|
||||
// No official IANA record exists, these are conventional
|
||||
"image/x-portable-arbitrarymap" // PAM
|
||||
},
|
||||
"com.twelvemonkeys.imageio.plugins.pnm.PNMImageWriter",
|
||||
new Class[] {ImageOutputStream.class},
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.pnm.PNMImageReaderSpi"},
|
||||
true, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
);
|
||||
}
|
||||
|
||||
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
|
||||
// TODO: FixMe
|
||||
return true;
|
||||
}
|
||||
|
||||
public ImageWriter createWriterInstance(final Object pExtension) {
|
||||
return new PNMImageWriter(this);
|
||||
}
|
||||
|
||||
@Override public String getDescription(final Locale locale) {
|
||||
return "NetPBM Portable Arbitrary Map (PAM) image writer";
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
final class PFMHeaderParser extends HeaderParser {
|
||||
private final short fileType;
|
||||
private final TupleType tupleType;
|
||||
|
||||
public PFMHeaderParser(final ImageInputStream input, final short type) {
|
||||
super(input);
|
||||
this.fileType = type;
|
||||
this.tupleType = asTupleType(type);
|
||||
}
|
||||
|
||||
static TupleType asTupleType(int fileType) {
|
||||
switch (fileType) {
|
||||
case PNM.PFM_GRAY:
|
||||
return TupleType.GRAYSCALE;
|
||||
case PNM.PFM_RGB:
|
||||
return TupleType.RGB;
|
||||
default:
|
||||
throw new AssertionError("Illegal PNM type :" + fileType);
|
||||
}
|
||||
}
|
||||
|
||||
// http://netpbm.sourceforge.net/doc/pfm.html
|
||||
// http://www.pauldebevec.com/Research/HDR/PFM/ (note that this is just one of *several* *incompatible* specs)
|
||||
// The text header of a .pfm file takes the following form:
|
||||
// [type]
|
||||
// [xres] [yres]
|
||||
// [scale/byte_order] where positive means big-endian, negative means little-endian, maxVal is abs(scale)
|
||||
// Samples are 1 or 3 samples/pixels, interleaved, IEEE 32 bit floating point values
|
||||
@Override public PNMHeader parse() throws IOException {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
float maxSample = tupleType == TupleType.BLACKANDWHITE_WHITE_IS_ZERO ? 1 : 0; // PBM has no maxSample line
|
||||
|
||||
List<String> comments = new ArrayList<String>();
|
||||
|
||||
while (width == 0 || height == 0 || maxSample == 0) {
|
||||
String line = input.readLine();
|
||||
|
||||
if (line == null) {
|
||||
throw new IIOException("Unexpeced end of stream");
|
||||
}
|
||||
|
||||
int commentStart = line.indexOf('#');
|
||||
if (commentStart >= 0) {
|
||||
String comment = line.substring(commentStart + 1).trim();
|
||||
if (!comment.isEmpty()) {
|
||||
comments.add(comment);
|
||||
}
|
||||
|
||||
line = line.substring(0, commentStart);
|
||||
}
|
||||
|
||||
line = line.trim();
|
||||
|
||||
if (!line.isEmpty()) {
|
||||
// We have tokens...
|
||||
String[] tokens = line.split("\\s");
|
||||
for (String token : tokens) {
|
||||
if (width == 0) {
|
||||
width = Integer.parseInt(token);
|
||||
} else if (height == 0) {
|
||||
height = Integer.parseInt(token);
|
||||
} else if (maxSample == 0) {
|
||||
maxSample = Float.parseFloat(token);
|
||||
} else {
|
||||
throw new IIOException("Unknown PNM token: " + token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PNMHeader(fileType, tupleType, width, height, tupleType.getSamplesPerPixel(), byteOrder(maxSample), comments);
|
||||
}
|
||||
|
||||
private ByteOrder byteOrder(final float maxSample) {
|
||||
return maxSample > 0 ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
/**
|
||||
* @see <a href="http://netpbm.sourceforge.net/doc/index.html#formats">The Netpbm Formats</a>.
|
||||
*/
|
||||
interface PNM {
|
||||
/** 1 bit per sample, ASCII format, white is zero. */
|
||||
short PBM_PLAIN = 'P' << 8 | '1';
|
||||
/** Grayscale up to 16 bits per sample, ASCII format. */
|
||||
short PGM_PLAIN = 'P' << 8 | '2';
|
||||
/** Color (RGB) up to 16 bits per sample, ASCII format. */
|
||||
short PPM_PLAIN = 'P' << 8 | '3';
|
||||
/** 1 bit per sample, RAW format, white is zero. */
|
||||
short PBM = 'P' << 8 | '4';
|
||||
/** Grayscale up to 16 bits per sample, RAW format. */
|
||||
short PGM = 'P' << 8 | '5';
|
||||
/** Color (RGB) up to 16 bits per sample, RAW format. */
|
||||
short PPM = 'P' << 8 | '6';
|
||||
|
||||
/**
|
||||
* PAM format, may contain data in same formats as the above, has extended header.
|
||||
* Always 1-16 bits per sample, RAW format.
|
||||
* @see <a href="http://netpbm.sourceforge.net/doc/pam.html">PAM format</a>
|
||||
*/
|
||||
short PAM = 'P' << 8 | '7';
|
||||
|
||||
// Consider these for a future PFM (floating point) format
|
||||
short PFM_RGB = 'P' << 8 | 'F'; // PPM_FLOAT? PFM?
|
||||
short PFM_GRAY = 'P' << 8 | 'f'; // PGM_FLOAT? PfM?
|
||||
|
||||
/** Max value for 1 bit rasters (1). */
|
||||
int MAX_VAL_1BIT = 1;
|
||||
/** Max value for 8 bit rasters (255). */
|
||||
int MAX_VAL_8BIT = 255;
|
||||
/** Max value for 16 bit rasters (65535). */
|
||||
int MAX_VAL_16BIT = 65535;
|
||||
/** Max value for 32 bit rasters (4294967295). Experimental, not supported by the "spec". */
|
||||
long MAX_VAL_32BIT = 4294967295L;
|
||||
|
||||
/** In order to not confuse PAM ("P7") with xv thumbnails. */
|
||||
int XV_THUMBNAIL_MAGIC = (' ' << 24 | '3' << 16 | '3' << 8 | '2');;
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.*;
|
||||
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
final class PNMHeader {
|
||||
private final short fileType;
|
||||
private final TupleType tupleType;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final int maxSample;
|
||||
|
||||
private final List<String> comments;
|
||||
private final ByteOrder byteOrder;
|
||||
|
||||
public PNMHeader(final short fileType, final TupleType tupleType, final int width, final int height, final int depth, final int maxSample, final Collection<String> comments) {
|
||||
this.fileType = isTrue(isValidFileType(fileType), fileType, String.format("Illegal type: %s", PNMImageReader.asASCII(fileType)));
|
||||
this.tupleType = notNull(tupleType, "tuple type may not be null");
|
||||
this.width = isTrue(width > 0, width, "width must be greater than 0: %d");
|
||||
this.height = isTrue(height > 0, height, "height must be greater than: %d");
|
||||
isTrue(depth == tupleType.getSamplesPerPixel(), depth, String.format("incorrect depth for %s, expected %d: %d", tupleType, tupleType.getSamplesPerPixel(), depth));
|
||||
this.maxSample = isTrue(tupleType.isValidMaxSample(maxSample), maxSample, "maxSample out of range: %d");
|
||||
|
||||
this.comments = Collections.unmodifiableList(new ArrayList<String>(comments));
|
||||
|
||||
byteOrder = ByteOrder.BIG_ENDIAN;
|
||||
}
|
||||
|
||||
public PNMHeader(final short fileType, final TupleType tupleType, final int width, final int height, final int depth, final ByteOrder byteOrder, final Collection<String> comments) {
|
||||
this.fileType = isTrue(isValidFileType(fileType), fileType, String.format("Illegal type: %s", PNMImageReader.asASCII(fileType)));
|
||||
this.tupleType = notNull(tupleType, "tuple type may not be null");
|
||||
this.width = isTrue(width > 0, width, "width must be greater than 0: %d");
|
||||
this.height = isTrue(height > 0, height, "height must be greater than: %d");
|
||||
isTrue(depth == tupleType.getSamplesPerPixel(), depth, String.format("incorrect depth for %s, expected %d: %d", tupleType, tupleType.getSamplesPerPixel(), depth));
|
||||
|
||||
this.maxSample = -1;
|
||||
this.byteOrder = byteOrder;
|
||||
|
||||
this.comments = Collections.unmodifiableList(new ArrayList<String>(comments));
|
||||
}
|
||||
|
||||
private boolean isValidFileType(final short fileType) {
|
||||
return (fileType >= PNM.PBM_PLAIN && fileType <= PNM.PAM || fileType == PNM.PFM_GRAY || fileType == PNM.PFM_RGB);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public TupleType getTupleType() {
|
||||
return tupleType;
|
||||
}
|
||||
|
||||
public int getMaxSample() {
|
||||
return maxSample;
|
||||
}
|
||||
|
||||
public int getTransparency() {
|
||||
return tupleType.getTransparency();
|
||||
}
|
||||
|
||||
public int getSamplesPerPixel() {
|
||||
return tupleType.getSamplesPerPixel();
|
||||
}
|
||||
|
||||
public int getBitsPerSample() {
|
||||
if (fileType == PNM.PFM_GRAY || fileType == PNM.PFM_RGB) {
|
||||
return 32;
|
||||
}
|
||||
if (tupleType == TupleType.BLACKANDWHITE_WHITE_IS_ZERO) {
|
||||
// Special case for PBM, PAM B/W uses 8 bits per sample for some reason
|
||||
return 1;
|
||||
}
|
||||
if (maxSample <= PNM.MAX_VAL_8BIT) {
|
||||
return 8;
|
||||
}
|
||||
if (maxSample <= PNM.MAX_VAL_16BIT) {
|
||||
return 16;
|
||||
}
|
||||
if ((maxSample & 0xffffffffL) <= PNM.MAX_VAL_32BIT) {
|
||||
return 32;
|
||||
}
|
||||
|
||||
throw new AssertionError("maxSample exceeds 32 bit");
|
||||
}
|
||||
|
||||
public int getTransferType() {
|
||||
if (fileType == PNM.PFM_GRAY || fileType == PNM.PFM_RGB) {
|
||||
return DataBuffer.TYPE_FLOAT;
|
||||
}
|
||||
if (maxSample <= PNM.MAX_VAL_8BIT) {
|
||||
return DataBuffer.TYPE_BYTE;
|
||||
}
|
||||
if (maxSample <= PNM.MAX_VAL_16BIT) {
|
||||
return DataBuffer.TYPE_USHORT;
|
||||
}
|
||||
if ((maxSample & 0xffffffffL) <= PNM.MAX_VAL_32BIT) {
|
||||
return DataBuffer.TYPE_INT;
|
||||
}
|
||||
|
||||
throw new AssertionError("maxSample exceeds 32 bit");
|
||||
}
|
||||
|
||||
public List<String> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
public short getFileType() {
|
||||
return fileType;
|
||||
}
|
||||
|
||||
public ByteOrder getByteOrder() {
|
||||
return byteOrder;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "PNMHeader{" +
|
||||
"fileType=" + PNMImageReader.asASCII(fileType) +
|
||||
", tupleType=" + tupleType +
|
||||
", width=" + width +
|
||||
", height=" + height +
|
||||
(getTransferType() == DataBuffer.TYPE_FLOAT ? ", byteOrder=" + byteOrder : ", maxSample=" + maxSample) +
|
||||
", comments=" + comments +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
final class PNMHeaderParser extends HeaderParser {
|
||||
private final short fileType;
|
||||
private final TupleType tupleType;
|
||||
|
||||
public PNMHeaderParser(final ImageInputStream input, final short type) {
|
||||
super(input);
|
||||
this.fileType = type;
|
||||
this.tupleType = asTupleType(type);
|
||||
}
|
||||
|
||||
static TupleType asTupleType(int fileType) {
|
||||
switch (fileType) {
|
||||
case PNM.PBM:
|
||||
case PNM.PBM_PLAIN:
|
||||
return TupleType.BLACKANDWHITE_WHITE_IS_ZERO;
|
||||
case PNM.PGM:
|
||||
case PNM.PGM_PLAIN:
|
||||
return TupleType.GRAYSCALE;
|
||||
case PNM.PPM:
|
||||
case PNM.PPM_PLAIN:
|
||||
return TupleType.RGB;
|
||||
default:
|
||||
throw new AssertionError("Illegal PNM type :" + fileType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public PNMHeader parse() throws IOException {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int maxSample = tupleType == TupleType.BLACKANDWHITE_WHITE_IS_ZERO ? 1 : 0; // PBM has no maxSample line
|
||||
|
||||
List<String> comments = new ArrayList<String>();
|
||||
|
||||
while (width == 0 || height == 0 || maxSample == 0) {
|
||||
String line = input.readLine();
|
||||
|
||||
if (line == null) {
|
||||
throw new IIOException("Unexpeced end of stream");
|
||||
}
|
||||
|
||||
int commentStart = line.indexOf('#');
|
||||
if (commentStart >= 0) {
|
||||
String comment = line.substring(commentStart + 1).trim();
|
||||
if (!comment.isEmpty()) {
|
||||
comments.add(comment);
|
||||
}
|
||||
|
||||
line = line.substring(0, commentStart);
|
||||
}
|
||||
|
||||
line = line.trim();
|
||||
|
||||
if (!line.isEmpty()) {
|
||||
// We have tokens...
|
||||
String[] tokens = line.split("\\s");
|
||||
for (String token : tokens) {
|
||||
if (width == 0) {
|
||||
width = Integer.parseInt(token);
|
||||
} else if (height == 0) {
|
||||
height = Integer.parseInt(token);
|
||||
} else if (maxSample == 0) {
|
||||
maxSample = Integer.parseInt(token);
|
||||
} else {
|
||||
throw new IIOException("Unknown PNM token: " + token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new PNMHeader(fileType, tupleType, width, height, tupleType.getSamplesPerPixel(), maxSample, comments);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
final class PNMHeaderWriter extends HeaderWriter {
|
||||
public PNMHeaderWriter(final ImageOutputStream imageOutput) {
|
||||
super(imageOutput);
|
||||
}
|
||||
|
||||
@Override public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException {
|
||||
// Write P4/P5/P6 magic (Support only RAW formats for now; if we are to support PLAIN formats, pass parameter)
|
||||
// TODO: Determine PBM, PBM or PPM based on input color model and image data?
|
||||
short type = PNM.PPM;
|
||||
imageOutput.writeShort(type);
|
||||
imageOutput.write('\n');
|
||||
|
||||
// Comments
|
||||
writeComments(image.getMetadata(), provider);
|
||||
|
||||
// Dimensions (width/height)
|
||||
imageOutput.write(String.format("%s %s\n", getWidth(image), getHeight(image)).getBytes(HeaderWriter.UTF8));
|
||||
|
||||
// MaxSample
|
||||
if (type != PNM.PBM) {
|
||||
imageOutput.write(String.format("%s\n", getMaxVal(image)).getBytes(HeaderWriter.UTF8));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,502 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorConvertOp;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.DataBufferByte;
|
||||
import java.awt.image.DataBufferFloat;
|
||||
import java.awt.image.DataBufferUShort;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
public final class PNMImageReader extends ImageReaderBase {
|
||||
// TODO: Allow reading unknown tuple types as Raster!
|
||||
// TODO: readAsRenderedImage?
|
||||
|
||||
private PNMHeader header;
|
||||
|
||||
PNMImageReader(final ImageReaderSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@Override protected void resetMembers() {
|
||||
header = null;
|
||||
}
|
||||
|
||||
private void readHeader() throws IOException {
|
||||
if (header == null) {
|
||||
header = HeaderParser.parse(imageInput);
|
||||
|
||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||
imageInput.setByteOrder(header.getByteOrder()); // For PFM support
|
||||
} else {
|
||||
imageInput.seek(imageInput.getFlushedPosition());
|
||||
}
|
||||
}
|
||||
|
||||
static String asASCII(final short type) {
|
||||
byte[] asciiBytes = {(byte) ((type >> 8) & 0xff), (byte) (type & 0xff)};
|
||||
return new String(asciiBytes, Charset.forName("ASCII"));
|
||||
}
|
||||
|
||||
@Override public int getWidth(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return header.getWidth();
|
||||
}
|
||||
|
||||
@Override public int getHeight(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return header.getHeight();
|
||||
}
|
||||
|
||||
@Override public ImageTypeSpecifier getRawImageType(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
int bitsPerSample = header.getBitsPerSample();
|
||||
int transferType = header.getTransferType();
|
||||
int samplesPerPixel = header.getSamplesPerPixel();
|
||||
boolean hasAlpha = header.getTransparency() != Transparency.OPAQUE;
|
||||
|
||||
switch (header.getTupleType()) {
|
||||
case BLACKANDWHITE_WHITE_IS_ZERO:
|
||||
// PBM: As TIFF WhiteIsZero
|
||||
// NOTE: We handle this by inverting the values when reading, as Java has no ColorModel that easily supports this.
|
||||
case BLACKANDWHITE_ALPHA:
|
||||
case GRAYSCALE_ALPHA:
|
||||
case BLACKANDWHITE:
|
||||
case GRAYSCALE:
|
||||
// PGM: Linear or non-linear gray?
|
||||
ColorSpace gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||
|
||||
if (header.getTransferType() == DataBuffer.TYPE_FLOAT) {
|
||||
return ImageTypeSpecifier.createInterleaved(gray, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
|
||||
}
|
||||
if (header.getMaxSample() <= PNM.MAX_VAL_16BIT) {
|
||||
return hasAlpha ? ImageTypeSpecifier.createGrayscale(bitsPerSample, transferType, false, false)
|
||||
: ImageTypeSpecifier.createGrayscale(bitsPerSample, transferType, false);
|
||||
}
|
||||
|
||||
return ImageTypeSpecifier.createInterleaved(gray, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
|
||||
|
||||
case RGB:
|
||||
case RGB_ALPHA:
|
||||
// Using sRGB seems sufficient for PPM, as it is very close to ITU-R Recommendation BT.709 (same gamut and white point CIE D65)
|
||||
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
if (header.getTransferType() == DataBuffer.TYPE_FLOAT) {
|
||||
return ImageTypeSpecifier.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
|
||||
}
|
||||
|
||||
return ImageTypeSpecifier.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
|
||||
|
||||
case CMYK:
|
||||
case CMYK_ALPHA:
|
||||
ColorSpace cmyk = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
|
||||
return ImageTypeSpecifier.createInterleaved(cmyk, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
|
||||
|
||||
default:
|
||||
// TODO: Allow reading unknown tuple types as Raster!
|
||||
|
||||
throw new AssertionError("Unknown PNM tuple type: " + header.getTupleType());
|
||||
}
|
||||
}
|
||||
|
||||
private int[] createBandOffsets(int numBands) {
|
||||
int[] offsets = new int[numBands];
|
||||
|
||||
for (int i = 0; i < numBands; i++) {
|
||||
offsets[i] = i;
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
@Override public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
|
||||
|
||||
switch (header.getTupleType()) {
|
||||
case RGB:
|
||||
if (header.getTransferType() == DataBuffer.TYPE_BYTE) {
|
||||
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
||||
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case RGB_ALPHA:
|
||||
if (header.getTransferType() == DataBuffer.TYPE_BYTE) {
|
||||
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
|
||||
// TODO: Why does ColorConvertOp choke on these (Ok, because it misinterprets the alpha channel for a color component, but how do we make it work)?
|
||||
// specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB));
|
||||
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE));
|
||||
// specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (rawType != null) {
|
||||
specifiers.add(rawType);
|
||||
}
|
||||
|
||||
return specifiers.iterator();
|
||||
}
|
||||
|
||||
@Override public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||
Iterator<ImageTypeSpecifier> imageTypes = getImageTypes(imageIndex);
|
||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||
|
||||
int width = getWidth(imageIndex);
|
||||
int height = getHeight(imageIndex);
|
||||
|
||||
BufferedImage destination = getDestination(param, imageTypes, width, height);
|
||||
|
||||
Rectangle srcRegion = new Rectangle();
|
||||
Rectangle destRegion = new Rectangle();
|
||||
computeRegions(param, width, height, destination, srcRegion, destRegion);
|
||||
|
||||
WritableRaster destRaster = clipToRect(destination.getRaster(), destRegion, param != null ? param.getDestinationBands() : null);
|
||||
checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands());
|
||||
|
||||
WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
|
||||
// Clip to source region
|
||||
Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
|
||||
param != null ? param.getSourceBands() : null,
|
||||
param != null ? param.getSourceXSubsampling() : 1);
|
||||
|
||||
int transferType = rowRaster.getTransferType();
|
||||
int samplesPerPixel = header.getSamplesPerPixel();
|
||||
|
||||
byte[] rowDataByte = null;
|
||||
short[] rowDataUShort = null;
|
||||
float[] rowDataFloat = null;
|
||||
|
||||
switch (transferType) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
rowDataUShort = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
break;
|
||||
case DataBuffer.TYPE_FLOAT:
|
||||
rowDataFloat = ((DataBufferFloat) rowRaster.getDataBuffer()).getData();
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported transfer type: " + transferType);
|
||||
}
|
||||
|
||||
ColorConvertOp colorConvert = null;
|
||||
if (!destination.getColorModel().isCompatibleRaster(rowRaster)) {
|
||||
colorConvert = new ColorConvertOp(rawType.getColorModel().getColorSpace(), destination.getColorModel().getColorSpace(), null);
|
||||
}
|
||||
|
||||
int xSub = param == null ? 1 : param.getSourceXSubsampling();
|
||||
int ySub = param == null ? 1 : param.getSourceYSubsampling();
|
||||
|
||||
DataInput input = wrapInput();
|
||||
|
||||
processImageStarted(imageIndex);
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
switch (transferType) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
readRowByte(destRaster, clippedRow, colorConvert, rowDataByte, samplesPerPixel, input, y, srcRegion, xSub, ySub);
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
readRowUShort(destRaster, clippedRow, rowDataUShort, samplesPerPixel, input, y, srcRegion, xSub, ySub);
|
||||
break;
|
||||
case DataBuffer.TYPE_FLOAT:
|
||||
readRowFloat(destRaster, clippedRow, rowDataFloat, samplesPerPixel, input, y, srcRegion, xSub, ySub);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unsupported transfer type: " + transferType);
|
||||
}
|
||||
|
||||
processImageProgress(100f * y / height);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
break;
|
||||
}
|
||||
|
||||
if (y >= srcRegion.y + srcRegion.height) {
|
||||
// We're done
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private DataInput wrapInput() throws IIOException {
|
||||
switch (header.getFileType()) {
|
||||
case PNM.PBM_PLAIN:
|
||||
return new DataInputStream(new Plain1BitDecoder(IIOUtil.createStreamAdapter(imageInput), header.getWidth() * header.getSamplesPerPixel()));
|
||||
case PNM.PGM_PLAIN:
|
||||
case PNM.PPM_PLAIN:
|
||||
if (header.getBitsPerSample() <= 8) {
|
||||
return new DataInputStream(new Plain8BitDecoder(IIOUtil.createStreamAdapter(imageInput)));
|
||||
}
|
||||
if (header.getBitsPerSample() <= 16) {
|
||||
return new DataInputStream(new Plain16BitDecoder(IIOUtil.createStreamAdapter(imageInput)));
|
||||
}
|
||||
throw new IIOException("Unsupported bit depth for type: " + asASCII(header.getFileType()));
|
||||
case PNM.PBM:
|
||||
case PNM.PGM:
|
||||
case PNM.PPM:
|
||||
case PNM.PAM:
|
||||
case PNM.PFM_GRAY:
|
||||
case PNM.PFM_RGB:
|
||||
return imageInput;
|
||||
default:
|
||||
throw new AssertionError("Unknown input type: " + asASCII(header.getFileType()));
|
||||
}
|
||||
}
|
||||
|
||||
private Raster clipRowToRect(final Raster raster, final Rectangle rect, final int[] bands, final int xSub) {
|
||||
if (rect.contains(raster.getMinX(), 0, raster.getWidth(), 1)
|
||||
&& xSub == 1
|
||||
&& bands == null /* TODO: Compare bands with that of raster */) {
|
||||
return raster;
|
||||
}
|
||||
|
||||
return raster.createChild(rect.x / xSub, 0, rect.width / xSub, 1, 0, 0, bands);
|
||||
}
|
||||
|
||||
private WritableRaster clipToRect(final WritableRaster raster, final Rectangle rect, final int[] bands) {
|
||||
if (rect.contains(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight())
|
||||
&& bands == null /* TODO: Compare bands with that of raster */) {
|
||||
return raster;
|
||||
}
|
||||
|
||||
return raster.createWritableChild(rect.x, rect.y, rect.width, rect.height, 0, 0, bands);
|
||||
}
|
||||
|
||||
private void readRowByte(final WritableRaster destRaster,
|
||||
Raster rowRaster,
|
||||
final ColorConvertOp colorConvert,
|
||||
final byte[] rowDataByte,
|
||||
final int samplesPerPixel,
|
||||
final DataInput input, final int y,
|
||||
final Rectangle srcRegion,
|
||||
final int xSub, final int ySub) throws IOException {
|
||||
// If subsampled or outside source region, skip entire row
|
||||
if (y % ySub != 0 || y < srcRegion.y || y >= srcRegion.y + srcRegion.height) {
|
||||
input.skipBytes(rowDataByte.length);
|
||||
return;
|
||||
}
|
||||
|
||||
input.readFully(rowDataByte);
|
||||
|
||||
// Subsample (horizontal)
|
||||
subsampleHorizontal(rowDataByte, rowDataByte.length, samplesPerPixel, xSub);
|
||||
|
||||
normalize(rowDataByte, 0, rowDataByte.length / xSub);
|
||||
|
||||
int destY = (y - srcRegion.y) / ySub;
|
||||
if (colorConvert != null) {
|
||||
colorConvert.filter(rowRaster, destRaster.createWritableChild(0, destY, rowRaster.getWidth(), 1, 0, 0, null));
|
||||
} else {
|
||||
destRaster.setDataElements(0, destY, rowRaster);
|
||||
}
|
||||
}
|
||||
|
||||
private void readRowUShort(final WritableRaster destRaster,
|
||||
Raster rowRaster,
|
||||
final short[] rowDataUShort,
|
||||
final int samplesPerPixel, final DataInput input, final int y,
|
||||
final Rectangle srcRegion, final int xSub, final int ySub) throws IOException {
|
||||
// If subsampled or outside source region, skip entire row
|
||||
if (y % ySub != 0 || y < srcRegion.y || y >= srcRegion.y + srcRegion.height) {
|
||||
input.skipBytes(rowDataUShort.length * 2);
|
||||
return;
|
||||
}
|
||||
|
||||
readFully(input, rowDataUShort);
|
||||
|
||||
// Subsample (horizontal)
|
||||
subsampleHorizontal(rowDataUShort, rowDataUShort.length, samplesPerPixel, xSub);
|
||||
|
||||
normalize(rowDataUShort);
|
||||
|
||||
int destY = (y - srcRegion.y) / ySub;
|
||||
// TODO: ColorConvertOp if needed
|
||||
destRaster.setDataElements(0, destY, rowRaster);
|
||||
}
|
||||
|
||||
private void readRowFloat(final WritableRaster destRaster,
|
||||
Raster rowRaster,
|
||||
final float[] rowDataFloat,
|
||||
final int samplesPerPixel, final DataInput input, final int y,
|
||||
final Rectangle srcRegion, final int xSub, final int ySub) throws IOException {
|
||||
// If subsampled or outside source region, skip entire row
|
||||
if (y % ySub != 0 || y < srcRegion.y || y >= srcRegion.y + srcRegion.height) {
|
||||
input.skipBytes(rowDataFloat.length * 4);
|
||||
return;
|
||||
}
|
||||
|
||||
readFully(input, rowDataFloat);
|
||||
|
||||
// Subsample (horizontal)
|
||||
subsampleHorizontal(rowDataFloat, rowDataFloat.length, samplesPerPixel, xSub);
|
||||
|
||||
normalize(rowDataFloat);
|
||||
|
||||
int destY = (y - srcRegion.y) / ySub;
|
||||
// TODO: ColorConvertOp if needed
|
||||
destRaster.setDataElements(0, destY, rowRaster);
|
||||
}
|
||||
|
||||
// TODO: Candidate util method
|
||||
private static void readFully(final DataInput input, final short[] shorts) throws IOException {
|
||||
if (input instanceof ImageInputStream) {
|
||||
// Optimization for ImageInputStreams, read all in one go
|
||||
((ImageInputStream) input).readFully(shorts, 0, shorts.length);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < shorts.length; i++) {
|
||||
shorts[i] = input.readShort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Candidate util method
|
||||
private static void readFully(final DataInput input, final float[] floats) throws IOException {
|
||||
if (input instanceof ImageInputStream) {
|
||||
// Optimization for ImageInputStreams, read all in one go
|
||||
((ImageInputStream) input).readFully(floats, 0, floats.length);
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < floats.length; i++) {
|
||||
floats[i] = input.readFloat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SuspiciousSystemArraycopy")
|
||||
private void subsampleHorizontal(final Object data, final int length, final int samplesPerPixel, final int xSub) {
|
||||
if (xSub == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Super-special 1 bit subsampling handling for PBM
|
||||
|
||||
for (int x = 0; x < length / xSub; x += samplesPerPixel) {
|
||||
System.arraycopy(data, x * xSub, data, x, samplesPerPixel);
|
||||
}
|
||||
}
|
||||
|
||||
private void normalize(final byte[] rowData, final int start, final int length) {
|
||||
switch (header.getTupleType()) {
|
||||
case BLACKANDWHITE:
|
||||
case BLACKANDWHITE_ALPHA:
|
||||
// Do nothing
|
||||
break;
|
||||
case BLACKANDWHITE_WHITE_IS_ZERO:
|
||||
// Invert
|
||||
for (int i = start; i < length; i++) {
|
||||
rowData[i] = (byte) ~rowData[i];
|
||||
}
|
||||
break;
|
||||
case GRAYSCALE:
|
||||
case GRAYSCALE_ALPHA:
|
||||
case RGB:
|
||||
case RGB_ALPHA:
|
||||
case CMYK:
|
||||
case CMYK_ALPHA:
|
||||
// Normalize
|
||||
for (int i = start; i < length; i++) {
|
||||
rowData[i] = (byte) ((rowData[i] * PNM.MAX_VAL_8BIT) / header.getMaxSample());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void normalize(final short[] rowData) {
|
||||
// Normalize
|
||||
for (int i = 0; i < rowData.length; i++) {
|
||||
rowData[i] = (short) ((rowData[i] * PNM.MAX_VAL_16BIT) / header.getMaxSample());
|
||||
}
|
||||
}
|
||||
|
||||
private void normalize(final float[] rowData) {
|
||||
// TODO: Do the real thing, find min/max and normalize to range 0...255? But only if not reading raster..? Only support reading as raster?
|
||||
// Normalize
|
||||
for (int i = 0; i < rowData.length; i++) {
|
||||
// if (rowData[i] > 275f /*header.getMaxSampleFloat()*/) {
|
||||
// System.out.println("rowData[" + i + "]: " + rowData[i]);
|
||||
// }
|
||||
// rowData[i] = rowData[i] / 275f /*header.getMaxSampleFloat()*/;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
|
||||
checkBounds(imageIndex);
|
||||
readHeader();
|
||||
|
||||
return new PNMMetadata(header);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
PNMImageReader reader = new PNMImageReader(null);
|
||||
|
||||
for (String arg : args) {
|
||||
File in = new File(arg);
|
||||
reader.setInput(ImageIO.createImageInputStream(in));
|
||||
|
||||
ImageReadParam param = reader.getDefaultReadParam();
|
||||
param.setDestinationType(reader.getImageTypes(0).next());
|
||||
// param.setSourceSubsampling(2, 3, 0, 0);
|
||||
//
|
||||
// int width = reader.getWidth(0);
|
||||
// int height = reader.getHeight(0);
|
||||
//
|
||||
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
|
||||
// param.setSourceRegion(new Rectangle(width / 2, height / 2));
|
||||
|
||||
showIt(reader.read(0, param), in.getName());
|
||||
|
||||
// new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(reader.getImageMetadata(0).getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||
|
||||
// File reference = new File(in.getParent() + "/../reference", in.getName().replaceAll("\\.p(a|b|g|p)m", ".png"));
|
||||
// if (reference.exists()) {
|
||||
// System.err.println("reference.getAbsolutePath(): " + reference.getAbsolutePath());
|
||||
// showIt(ImageIO.read(reference), reference.getName());
|
||||
// }
|
||||
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
public final class PNMImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
/**
|
||||
* Creates a {@code PNMImageReaderSpi}.
|
||||
*/
|
||||
public PNMImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(PNMImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private PNMImageReaderSpi(final ProviderInfo providerInfo) {
|
||||
super(
|
||||
providerInfo.getVendorName(),
|
||||
providerInfo.getVersion(),
|
||||
new String[]{
|
||||
"pnm", "pbm", "pgm", "ppm", "pam",
|
||||
"PNM", "PBM", "PGM", "PPM", "PAM"
|
||||
},
|
||||
new String[]{"pbm", "pgm", "ppm", "pam"},
|
||||
new String[]{
|
||||
// No official IANA record exists, these are conventional
|
||||
"image/x-portable-pixmap",
|
||||
"image/x-portable-anymap",
|
||||
"image/x-portable-arbitrarymap" // PAM
|
||||
},
|
||||
"com.twelvemkonkeys.imageio.plugins.pnm.PNMImageReader",
|
||||
new Class[] {ImageInputStream.class},
|
||||
new String[]{
|
||||
"com.twelvemkonkeys.imageio.plugins.pnm.PNMImageWriterSpi",
|
||||
"com.twelvemkonkeys.imageio.plugins.pnm.PAMImageWriterSpi"
|
||||
},
|
||||
true, // supports standard stream metadata
|
||||
null, null, // native stream format name and class
|
||||
null, null, // extra stream formats
|
||||
true, // supports standard image metadata
|
||||
null, null,
|
||||
null, null // extra image metadata formats
|
||||
);
|
||||
}
|
||||
|
||||
@Override public boolean canDecodeInput(final Object source) throws IOException {
|
||||
if (!(source instanceof ImageInputStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ImageInputStream stream = (ImageInputStream) source;
|
||||
|
||||
stream.mark();
|
||||
|
||||
try {
|
||||
short magic = stream.readShort();
|
||||
|
||||
switch (magic) {
|
||||
case PNM.PBM_PLAIN:
|
||||
case PNM.PBM:
|
||||
case PNM.PGM_PLAIN:
|
||||
case PNM.PGM:
|
||||
case PNM.PPM_PLAIN:
|
||||
case PNM.PPM:
|
||||
case PNM.PFM_GRAY:
|
||||
case PNM.PFM_RGB:
|
||||
return true;
|
||||
case PNM.PAM:
|
||||
return stream.readInt() != PNM.XV_THUMBNAIL_MAGIC;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
stream.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public ImageReader createReaderInstance(final Object extension) throws IOException {
|
||||
return new PNMImageReader(this);
|
||||
}
|
||||
|
||||
@Override public String getDescription(final Locale locale) {
|
||||
return "NetPBM Portable Any Map (PNM and PAM) image reader";
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.SampleModel;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
public final class PNMImageWriter extends ImageWriterBase {
|
||||
|
||||
PNMImageWriter(final ImageWriterSpi originatingProvider) {
|
||||
super(originatingProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getDefaultImageMetadata(final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata convertImageMetadata(final IIOMetadata inData, final ImageTypeSpecifier imageType, final ImageWriteParam param) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWriteRasters() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final IIOMetadata streamMetadata, final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||
// TODO: Issue warning if streamMetadata is non-null?
|
||||
// TODO: Issue warning if IIOImage contains thumbnails or other data we can't store?
|
||||
|
||||
HeaderWriter.write(image, getOriginatingProvider(), imageOutput);
|
||||
|
||||
// TODO: Sub region
|
||||
// TODO: Subsampling
|
||||
// TODO: Source bands
|
||||
|
||||
processImageStarted(0);
|
||||
writeImageData(image);
|
||||
processImageComplete();
|
||||
}
|
||||
|
||||
private void writeImageData(final IIOImage image) throws IOException {
|
||||
// - dump data as is (or convert, if TYPE_INT_xxx)
|
||||
// Enforce RGB/CMYK order for such data!
|
||||
|
||||
// TODO: Loop over x/y tiles, using 0,0 is only valid for BufferedImage
|
||||
// TODO: PNM/PAM does not support tiling, we must iterate all tiles along the x-axis for each row we write
|
||||
Raster tile = image.hasRaster() ? image.getRaster() : image.getRenderedImage().getTile(0, 0);
|
||||
|
||||
SampleModel sampleModel = tile.getSampleModel();
|
||||
|
||||
DataBuffer dataBuffer = tile.getDataBuffer();
|
||||
|
||||
int tileWidth = tile.getWidth();
|
||||
int tileHeight = tile.getHeight();
|
||||
|
||||
final int transferType = sampleModel.getTransferType();
|
||||
Object data = null;
|
||||
for (int y = 0; y < tileHeight; y++) {
|
||||
data = sampleModel.getDataElements(0, y, tileWidth, 1, data, dataBuffer);
|
||||
// TODO: Support other (short, float) data types
|
||||
if (transferType == DataBuffer.TYPE_BYTE) {
|
||||
imageOutput.write((byte[]) data);
|
||||
}
|
||||
else if (transferType == DataBuffer.TYPE_USHORT) {
|
||||
short[] shortData = (short[]) data;
|
||||
imageOutput.writeShorts(shortData, 0, shortData.length);
|
||||
}
|
||||
|
||||
processImageProgress(y * 100f / tileHeight); // TODO: Take tile y into account
|
||||
if (abortRequested()) {
|
||||
processWriteAborted();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
File input = new File(args[0]);
|
||||
File output = new File(input.getParentFile(), input.getName().replace('.', '_') + ".ppm");
|
||||
|
||||
BufferedImage image = ImageIO.read(input);
|
||||
if (image == null) {
|
||||
System.err.println("input Image == null");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
System.out.println("image: " + image);
|
||||
|
||||
ImageWriter writer = new PNMImageWriterSpi().createWriterInstance();
|
||||
|
||||
if (!output.exists()) {
|
||||
writer.setOutput(ImageIO.createImageOutputStream(output));
|
||||
writer.write(image);
|
||||
}
|
||||
else {
|
||||
System.err.println("Output file " + output + " already exists.");
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
public final class PNMImageWriterSpi extends ImageWriterSpi {
|
||||
|
||||
// TODO: Consider one Spi for each sub-format, as it makes no sense for the writer to write PPM if client code requested PBM or PGM format.
|
||||
// ...Then again, what if user asks for PNM? :-P
|
||||
/**
|
||||
* Creates a {@code PNMImageWriterSpi}.
|
||||
*/
|
||||
public PNMImageWriterSpi() {
|
||||
this(IIOUtil.getProviderInfo(PNMImageWriterSpi.class));
|
||||
}
|
||||
|
||||
private PNMImageWriterSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{
|
||||
"pnm", "pbm", "pgm", "ppm",
|
||||
"PNM", "PBM", "PGM", "PPM"
|
||||
},
|
||||
new String[]{"pbm", "pgm", "ppm"},
|
||||
new String[]{
|
||||
// No official IANA record exists, these are conventional
|
||||
"image/x-portable-pixmap",
|
||||
"image/x-portable-anymap"
|
||||
},
|
||||
"com.twelvemonkeys.imageio.plugins.pnm.PNMImageWriter",
|
||||
new Class[] {ImageOutputStream.class},
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.pnm.PNMImageReaderSpi"},
|
||||
true, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
);
|
||||
}
|
||||
|
||||
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
|
||||
// TODO: FixMe: Support only 1 bit b/w, 8-16 bit gray and 8-16 bit/sample RGB
|
||||
return true;
|
||||
}
|
||||
|
||||
public ImageWriter createWriterInstance(final Object pExtension) {
|
||||
return new PNMImageWriter(this);
|
||||
}
|
||||
|
||||
@Override public String getDescription(final Locale locale) {
|
||||
return "NetPBM Portable Any Map (PNM) image writer";
|
||||
}
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.awt.Transparency;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
final class PNMMetadata extends IIOMetadata {
|
||||
// TODO: Clean up & extend AbstractMetadata (after moving from PSD -> Core)
|
||||
|
||||
private final PNMHeader header;
|
||||
|
||||
PNMMetadata(final PNMHeader header) {
|
||||
this.header = header;
|
||||
standardFormatSupported = true;
|
||||
}
|
||||
|
||||
@Override public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override public Node getAsTree(final String formatName) {
|
||||
if (formatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported metadata format: " + formatName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void mergeTree(final String formatName, final Node root) {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void reset() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||
|
||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
||||
switch (header.getTupleType()) {
|
||||
case BLACKANDWHITE:
|
||||
case BLACKANDWHITE_ALPHA:
|
||||
case BLACKANDWHITE_WHITE_IS_ZERO:
|
||||
case GRAYSCALE:
|
||||
case GRAYSCALE_ALPHA:
|
||||
csType.setAttribute("name", "GRAY");
|
||||
break;
|
||||
case RGB:
|
||||
case RGB_ALPHA:
|
||||
csType.setAttribute("name", "RGB");
|
||||
break;
|
||||
case CMYK:
|
||||
case CMYK_ALPHA:
|
||||
csType.setAttribute("name", "CMYK");
|
||||
break;
|
||||
}
|
||||
|
||||
if (csType.getAttribute("name") != null) {
|
||||
chroma.appendChild(csType);
|
||||
}
|
||||
|
||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
||||
numChannels.setAttribute("value", Integer.toString(header.getSamplesPerPixel()));
|
||||
chroma.appendChild(numChannels);
|
||||
|
||||
// TODO: Might make sense to set gamma?
|
||||
|
||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO ? "FALSE" : "TRUE");
|
||||
chroma.appendChild(blackIsZero);
|
||||
|
||||
return chroma;
|
||||
}
|
||||
|
||||
// No compression
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT ? "Real" : "UnsignedIntegral");
|
||||
node.appendChild(sampleFormat);
|
||||
|
||||
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(header.getBitsPerSample())));
|
||||
node.appendChild(bitsPerSample);
|
||||
|
||||
IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample");
|
||||
significantBitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(computeSignificantBits())));
|
||||
node.appendChild(significantBitsPerSample);
|
||||
|
||||
String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN ? "0" : Integer.toString(header.getBitsPerSample() - 1);
|
||||
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
|
||||
sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private int computeSignificantBits() {
|
||||
if (header.getTransferType() == DataBuffer.TYPE_FLOAT) {
|
||||
return header.getBitsPerSample();
|
||||
}
|
||||
|
||||
int significantBits = 0;
|
||||
|
||||
int maxSample = header.getMaxSample();
|
||||
|
||||
while (maxSample > 0) {
|
||||
maxSample >>>= 1;
|
||||
significantBits++;
|
||||
}
|
||||
|
||||
return significantBits;
|
||||
}
|
||||
|
||||
private String createListValue(final int itemCount, final String... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@Override protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
||||
|
||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientation.setAttribute("value", "Normal");
|
||||
dimension.appendChild(imageOrientation);
|
||||
|
||||
return dimension;
|
||||
}
|
||||
|
||||
// No document node
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTextNode() {
|
||||
if (!header.getComments().isEmpty()) {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
for (String comment : header.getComments()) {
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
textEntry.setAttribute("keyword", "comment");
|
||||
textEntry.setAttribute("value", comment);
|
||||
text.appendChild(textEntry);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// No tiling
|
||||
|
||||
@Override protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
|
||||
|
||||
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
|
||||
alpha.setAttribute("value", header.getTransparency() == Transparency.OPAQUE ? "none" : "nonpremultiplied");
|
||||
transparency.appendChild(alpha);
|
||||
|
||||
return transparency;
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import com.twelvemonkeys.util.StringTokenIterator;
|
||||
|
||||
final class Plain16BitDecoder extends InputStream {
|
||||
private final BufferedReader reader;
|
||||
|
||||
private StringTokenIterator currentLine;
|
||||
private int leftOver = -1;
|
||||
|
||||
public Plain16BitDecoder(final InputStream in) {
|
||||
reader = new BufferedReader(new InputStreamReader(in, Charset.forName("ASCII")));
|
||||
}
|
||||
|
||||
@Override public int read() throws IOException {
|
||||
if (leftOver != -1) {
|
||||
int next = leftOver;
|
||||
leftOver = -1;
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
// Each number is one byte. Skip whitespace.
|
||||
if (currentLine == null || !currentLine.hasNext()) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
currentLine = new StringTokenIterator(line);
|
||||
|
||||
if (!currentLine.hasNext()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int next = Integer.parseInt(currentLine.next()) & 0xffff;
|
||||
leftOver = next & 0xff;
|
||||
|
||||
return (next >> 8) & 0xff;
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
reader.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
final class Plain1BitDecoder extends InputStream {
|
||||
private final InputStream stream;
|
||||
private final int samplesPerRow; // Padded to byte boundary
|
||||
private int pos = 0;
|
||||
|
||||
public Plain1BitDecoder(final InputStream in, final int samplesPerRow) {
|
||||
this.stream = in;
|
||||
this.samplesPerRow = samplesPerRow;
|
||||
}
|
||||
|
||||
@Override public int read() throws IOException {
|
||||
// Each 0 or 1 represents one bit, whitespace is ignored. Padded to byte boundary for each row.
|
||||
// NOTE: White is 0, black is 1!
|
||||
int result = 0;
|
||||
|
||||
for (int bitPos = 7; bitPos >= 0; bitPos--) {
|
||||
|
||||
int read;
|
||||
while ((read = stream.read()) != -1 && Character.isWhitespace(read)) {
|
||||
// Skip whitespace
|
||||
}
|
||||
|
||||
if (read == -1) {
|
||||
if (bitPos == 7) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
int val = read - '0';
|
||||
|
||||
result |= val << bitPos;
|
||||
|
||||
if (++pos >= samplesPerRow) {
|
||||
pos = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
stream.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import com.twelvemonkeys.util.StringTokenIterator;
|
||||
|
||||
final class Plain8BitDecoder extends InputStream {
|
||||
private final BufferedReader reader;
|
||||
|
||||
private StringTokenIterator currentLine;
|
||||
|
||||
public Plain8BitDecoder(final InputStream in) {
|
||||
reader = new BufferedReader(new InputStreamReader(in, Charset.forName("ASCII")));
|
||||
}
|
||||
|
||||
@Override public int read() throws IOException {
|
||||
// Each number is one byte. Skip whitespace.
|
||||
if (currentLine == null || !currentLine.hasNext()) {
|
||||
String line = reader.readLine();
|
||||
if (line == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
currentLine = new StringTokenIterator(line);
|
||||
|
||||
if (!currentLine.hasNext()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return Integer.parseInt(currentLine.next()) & 0xff;
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
reader.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import java.awt.Transparency;
|
||||
|
||||
enum TupleType {
|
||||
// Official:
|
||||
/** B/W, but uses 1 byte (8 bits) per pixel. Black is zero (oposite of PBM) */
|
||||
BLACKANDWHITE(1, 1, PNM.MAX_VAL_1BIT, Transparency.OPAQUE),
|
||||
/** B/W + bit mask, uses 2 bytes per pixel. Black is zero (oposite of PBM) */
|
||||
BLACKANDWHITE_ALPHA(2, PNM.MAX_VAL_1BIT, PNM.MAX_VAL_1BIT, Transparency.BITMASK),
|
||||
/** Grayscale, as PGM. */
|
||||
GRAYSCALE(1, 2, PNM.MAX_VAL_16BIT, Transparency.OPAQUE),
|
||||
/** Grayscale + alpha. YA order. */
|
||||
GRAYSCALE_ALPHA(2, 2, PNM.MAX_VAL_16BIT, Transparency.TRANSLUCENT),
|
||||
/** RGB color, as PPM. RGB order. */
|
||||
RGB(3, 1, PNM.MAX_VAL_16BIT, Transparency.OPAQUE),
|
||||
/** RGB color + alpha. RGBA order. */
|
||||
RGB_ALPHA(4, 1, PNM.MAX_VAL_16BIT, Transparency.TRANSLUCENT),
|
||||
|
||||
// De facto (documented on the interwebs):
|
||||
/** CMYK color. CMYK order. */
|
||||
CMYK(4, 2, PNM.MAX_VAL_16BIT, Transparency.OPAQUE),
|
||||
/** CMYK color + alpha. CMYKA order. */
|
||||
CMYK_ALPHA(5, 1, PNM.MAX_VAL_16BIT, Transparency.TRANSLUCENT),
|
||||
|
||||
// Custom for PBM compatibility
|
||||
/** 1 bit B/W. White is zero (as PBM) */
|
||||
BLACKANDWHITE_WHITE_IS_ZERO(1, 1, PNM.MAX_VAL_1BIT, Transparency.OPAQUE);
|
||||
|
||||
private final int samplesPerPixel;
|
||||
private final int minMaxSample;
|
||||
private final int maxMaxSample;
|
||||
private final int transparency;
|
||||
|
||||
TupleType(int samplesPerPixel, int minMaxSample, int maxMaxSample, int transparency) {
|
||||
this.samplesPerPixel = samplesPerPixel;
|
||||
this.minMaxSample = minMaxSample;
|
||||
this.maxMaxSample = maxMaxSample;
|
||||
this.transparency = transparency;
|
||||
}
|
||||
|
||||
public int getTransparency() {
|
||||
return transparency;
|
||||
}
|
||||
|
||||
public int getSamplesPerPixel() {
|
||||
return samplesPerPixel;
|
||||
}
|
||||
|
||||
public boolean isValidMaxSample(int maxSample) {
|
||||
return maxSample >= minMaxSample && maxSample <= maxMaxSample;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.pnm.PNMImageReaderSpi
|
@ -0,0 +1,2 @@
|
||||
com.twelvemonkeys.imageio.plugins.pnm.PNMImageWriterSpi
|
||||
com.twelvemonkeys.imageio.plugins.pnm.PAMImageWriterSpi
|
@ -0,0 +1,110 @@
|
||||
package com.twelvemonkeys.imageio.plugins.pnm;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class PNMImageReaderTest extends ImageReaderAbstractTestCase<PNMImageReader>{
|
||||
@Override protected List<TestData> getTestData() {
|
||||
return Arrays.asList(
|
||||
new TestData(getClassLoaderResource("/ppm/lena.ppm"), new Dimension(128, 128)), // P6 (PPM RAW)
|
||||
new TestData(getClassLoaderResource("/ppm/colors.ppm"), new Dimension(3, 2)), // P3 (PPM PLAIN)
|
||||
new TestData(getClassLoaderResource("/pbm/j.pbm"), new Dimension(6, 10)), // P1 (PBM PLAIN)
|
||||
new TestData(getClassLoaderResource("/pgm/feep.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN)
|
||||
new TestData(getClassLoaderResource("/pgm/feep16.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN, 16 bits/sample)
|
||||
new TestData(getClassLoaderResource("/pgm/house.l.pgm"), new Dimension(367, 241)), // P5 (PGM RAW)
|
||||
new TestData(getClassLoaderResource("/ppm/lighthouse_rgb48.ppm"), new Dimension(768, 512)), // P6 (PPM RAW, 16 bits/sample)
|
||||
new TestData(getClassLoaderResource("/pam/lena.pam"), new Dimension(128, 128)), // P7 RGB
|
||||
new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension(4, 2)) // P7 RGB_ALPHA
|
||||
);
|
||||
}
|
||||
|
||||
@Override protected ImageReaderSpi createProvider() {
|
||||
return new PNMImageReaderSpi();
|
||||
}
|
||||
|
||||
@Override protected Class<PNMImageReader> getReaderClass() {
|
||||
return PNMImageReader.class;
|
||||
}
|
||||
|
||||
@Override protected PNMImageReader createReader() {
|
||||
return new PNMImageReader(createProvider());
|
||||
}
|
||||
|
||||
@Override protected List<String> getFormatNames() {
|
||||
return Arrays.asList(
|
||||
"pnm", "pbm", "pgm", "ppm", "pam",
|
||||
"PNM", "PBM", "PGM", "PPM", "PAM"
|
||||
);
|
||||
}
|
||||
|
||||
@Override protected List<String> getSuffixes() {
|
||||
return Arrays.asList(
|
||||
"pbm", "pgm", "ppm", "pam"
|
||||
);
|
||||
}
|
||||
|
||||
@Override protected List<String> getMIMETypes() {
|
||||
return Arrays.asList(
|
||||
"image/x-portable-pixmap",
|
||||
"image/x-portable-anymap",
|
||||
"image/x-portable-arbitrarymap"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testColorsVsReference() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
TestData data = new TestData(getClassLoaderResource("/ppm/colors.ppm"), new Dimension(3, 2));
|
||||
reader.setInput(data.getInputStream());
|
||||
|
||||
BufferedImage expected = new BufferedImage(3, 2, BufferedImage.TYPE_3BYTE_BGR);
|
||||
|
||||
expected.setRGB(0, 0, new Color(255, 0, 0).getRGB());
|
||||
expected.setRGB(1, 0, new Color(0, 255, 0).getRGB());
|
||||
expected.setRGB(2, 0, new Color(0, 0, 255).getRGB());
|
||||
|
||||
expected.setRGB(0, 1, new Color(255, 255, 0).getRGB());
|
||||
expected.setRGB(1, 1, new Color(255, 255, 255).getRGB());
|
||||
expected.setRGB(2, 1, new Color(0, 0, 0).getRGB());
|
||||
|
||||
assertImageDataEquals("Images differ from reference", expected, reader.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRGBAVsReference() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
TestData data = new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension(4, 2));
|
||||
reader.setInput(data.getInputStream());
|
||||
|
||||
BufferedImage expected = new BufferedImage(4, 2, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
|
||||
expected.setRGB(0, 0, new Color(0, 0, 255).getRGB());
|
||||
expected.setRGB(1, 0, new Color(0, 255, 0).getRGB());
|
||||
expected.setRGB(2, 0, new Color(255, 0, 0).getRGB());
|
||||
expected.setRGB(3, 0, new Color(255, 255, 255).getRGB());
|
||||
|
||||
expected.setRGB(0, 1, new Color(0, 0, 255, 127).getRGB());
|
||||
expected.setRGB(1, 1, new Color(0, 255, 0, 127).getRGB());
|
||||
expected.setRGB(2, 1, new Color(255, 0, 0, 127).getRGB());
|
||||
expected.setRGB(3, 1, new Color(255, 255, 255, 127).getRGB());
|
||||
|
||||
assertImageDataEquals("Images differ from reference", expected, reader.read(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testXVThumbNotIncorrectlyRecognizedAsPAM() throws IOException {
|
||||
ImageReaderSpi provider = createProvider();
|
||||
assertTrue("Should recognize PAM format", provider.canDecodeInput(new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension()).getInputStream())); // Sanity
|
||||
assertFalse("Should distinguish xv-thumbs from PAM format", provider.canDecodeInput(new TestData(getClassLoaderResource("/xv-thumb/xv-thumb.xvt"), new Dimension()).getInputStream()));
|
||||
}
|
||||
}
|
8
imageio/imageio-pnm/src/test/resources/pam/lena.pam
Executable file
8
imageio/imageio-pnm/src/test/resources/pam/lena.pam
Executable file
File diff suppressed because one or more lines are too long
BIN
imageio/imageio-pnm/src/test/resources/pam/rgba.pam
Executable file
BIN
imageio/imageio-pnm/src/test/resources/pam/rgba.pam
Executable file
Binary file not shown.
BIN
imageio/imageio-pnm/src/test/resources/pbm/MARBLES.PBM
Executable file
BIN
imageio/imageio-pnm/src/test/resources/pbm/MARBLES.PBM
Executable file
Binary file not shown.
13
imageio/imageio-pnm/src/test/resources/pbm/j.pbm
Executable file
13
imageio/imageio-pnm/src/test/resources/pbm/j.pbm
Executable file
@ -0,0 +1,13 @@
|
||||
P1
|
||||
# This is an example bitmap of the letter "J"
|
||||
6 10
|
||||
0 0 0 0 1 0
|
||||
0 0 0 0 1 0
|
||||
0 0 0 0 1 0
|
||||
0 0 0 0 1 0
|
||||
0 0 0 0 1 0
|
||||
0 0 0 0 1 0
|
||||
1 0 0 0 1 0
|
||||
0 1 1 1 0 0
|
||||
0 0 0 0 0 0
|
||||
0 0 0 0 0 0
|
BIN
imageio/imageio-pnm/src/test/resources/pfm/memorial.pfm
Executable file
BIN
imageio/imageio-pnm/src/test/resources/pfm/memorial.pfm
Executable file
Binary file not shown.
11
imageio/imageio-pnm/src/test/resources/pgm/feep.pgm
Executable file
11
imageio/imageio-pnm/src/test/resources/pgm/feep.pgm
Executable file
@ -0,0 +1,11 @@
|
||||
P2
|
||||
# Shows the word "FEEP" (example from Netpbm man page on PGM)
|
||||
24 7
|
||||
15
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0
|
||||
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0
|
||||
0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0
|
||||
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0
|
||||
0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
14
imageio/imageio-pnm/src/test/resources/pgm/feep16.pgm
Executable file
14
imageio/imageio-pnm/src/test/resources/pgm/feep16.pgm
Executable file
@ -0,0 +1,14 @@
|
||||
P2
|
||||
# Shows the word "FEEP" (modified example from Netpbm man page on PGM)
|
||||
24 7
|
||||
4095
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 1023 1023 1023 1023 0 0 2047 2047 2047 2047 0 0 3071
|
||||
3071 3071 3071 0 0 4095 4095 4095 4095 0
|
||||
0 1023 0 0 0 0 0 2047 0 0 0 0 0 3071 0 0 0 0 0 4095
|
||||
0 0 4095 0 0 1023 1023 1023 0 0 0 2047 2047 2047 0 0 0
|
||||
3071 3071 3071 0 0 0 4095 4095 4095 4095 0
|
||||
0 1023 0 0 0 0 0 2047 0 0 0 0 0 3071 0 0 0 0 0 4095
|
||||
0 0 0 0 0 1023 0 0 0 0 0 2047 2047 2047 2047 0 0 3071
|
||||
3071 3071 3071 0 0 4095 0 0 0 0
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
BIN
imageio/imageio-pnm/src/test/resources/pgm/house.l.pgm
Executable file
BIN
imageio/imageio-pnm/src/test/resources/pgm/house.l.pgm
Executable file
Binary file not shown.
BIN
imageio/imageio-pnm/src/test/resources/ppm/Matisse-Small.ppm
Executable file
BIN
imageio/imageio-pnm/src/test/resources/ppm/Matisse-Small.ppm
Executable file
Binary file not shown.
7
imageio/imageio-pnm/src/test/resources/ppm/colors.ppm
Executable file
7
imageio/imageio-pnm/src/test/resources/ppm/colors.ppm
Executable file
@ -0,0 +1,7 @@
|
||||
P3
|
||||
# The P3 means colors are in ASCII, then 3 columns and 2 rows,
|
||||
# then 255 for max color, then RGB triplets
|
||||
3 2
|
||||
255
|
||||
255 0 0 0 255 0 0 0 255
|
||||
255 255 0 255 255 255 0 0 0
|
5
imageio/imageio-pnm/src/test/resources/ppm/gogol.ppm
Executable file
5
imageio/imageio-pnm/src/test/resources/ppm/gogol.ppm
Executable file
File diff suppressed because one or more lines are too long
4
imageio/imageio-pnm/src/test/resources/ppm/lena.ppm
Executable file
4
imageio/imageio-pnm/src/test/resources/ppm/lena.ppm
Executable file
File diff suppressed because one or more lines are too long
8225
imageio/imageio-pnm/src/test/resources/ppm/lighthouse_rgb48.ppm
Executable file
8225
imageio/imageio-pnm/src/test/resources/ppm/lighthouse_rgb48.ppm
Executable file
File diff suppressed because one or more lines are too long
2
imageio/imageio-pnm/src/test/resources/xv-thumb/xv-thumb.xvt
Executable file
2
imageio/imageio-pnm/src/test/resources/xv-thumb/xv-thumb.xvt
Executable file
@ -0,0 +1,2 @@
|
||||
P7 332
|
||||
Garbage here, that we don't really care about.
|
@ -34,6 +34,7 @@
|
||||
<module>imageio-jpeg</module>
|
||||
<module>imageio-pdf</module>
|
||||
<module>imageio-pict</module>
|
||||
<module>imageio-pnm</module>
|
||||
<module>imageio-psd</module>
|
||||
<module>imageio-thumbsdb</module>
|
||||
<module>imageio-tiff</module>
|
||||
|
Loading…
x
Reference in New Issue
Block a user