com.twelvemonkeys.imageio.plugins.pnm.PNMImageReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of imageio-pnm Show documentation
Show all versions of imageio-pnm Show documentation
ImageIO plugin for NetPBM Portable Any Map (PNM)
The newest version!
/*
* 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.
*/
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
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 java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
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;
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 ImageTypeSpecifiers.createInterleaved(gray, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
}
if (header.getMaxSample() <= PNM.MAX_VAL_16BIT) {
return hasAlpha ? ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType, false)
: ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType);
}
return ImageTypeSpecifiers.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 ImageTypeSpecifiers.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
}
return ImageTypeSpecifiers.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
case CMYK:
case CMYK_ALPHA:
ColorSpace cmyk = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK);
return ImageTypeSpecifiers.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 getImageTypes(final int imageIndex) throws IOException {
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
List specifiers = new ArrayList();
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 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;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy