Better PFM support.

(cherry picked from commit 623d13a517b7b727076769976a2a9f4afe2a552f)
This commit is contained in:
Harald Kuhr 2020-09-25 19:35:55 +02:00 committed by Harald Kuhr
parent 15c7cfe9a6
commit 75ff0f265f
4 changed files with 45 additions and 37 deletions

View File

@ -291,4 +291,21 @@ public final class IIOUtil {
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
} }
} }
public static void subsampleRow(float[] srcRow, int srcPos, int srcWidth,
float[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op...
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 32 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0,
"samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 ");
int pixelStride = bitsPerSample * samplesPerPixel / 32;
for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) {
// System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
}
}
} }

View File

@ -45,6 +45,7 @@ final class PNMHeader {
private final TupleType tupleType; private final TupleType tupleType;
private final int width; private final int width;
private final int height; private final int height;
private final float maxSampleFloat;
private final int maxSample; private final int maxSample;
private final List<String> comments; private final List<String> comments;
@ -57,6 +58,7 @@ final class PNMHeader {
this.height = isTrue(height > 0, height, "height must be greater than: %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)); 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.maxSample = isTrue(tupleType.isValidMaxSample(maxSample), maxSample, "maxSample out of range: %d");
this.maxSampleFloat = this.maxSample;
this.comments = Collections.unmodifiableList(new ArrayList<String>(comments)); this.comments = Collections.unmodifiableList(new ArrayList<String>(comments));
@ -70,7 +72,8 @@ final class PNMHeader {
this.height = isTrue(height > 0, height, "height must be greater than: %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)); isTrue(depth == tupleType.getSamplesPerPixel(), depth, String.format("incorrect depth for %s, expected %d: %d", tupleType, tupleType.getSamplesPerPixel(), depth));
this.maxSample = -1; this.maxSample = 1;
this.maxSampleFloat = maxSample;
this.byteOrder = byteOrder; this.byteOrder = byteOrder;
this.comments = Collections.unmodifiableList(new ArrayList<String>(comments)); this.comments = Collections.unmodifiableList(new ArrayList<String>(comments));
@ -118,11 +121,8 @@ final class PNMHeader {
if (maxSample <= PNM.MAX_VAL_16BIT) { if (maxSample <= PNM.MAX_VAL_16BIT) {
return 16; return 16;
} }
if ((maxSample & 0xffffffffL) <= PNM.MAX_VAL_32BIT) {
return 32;
}
throw new AssertionError("maxSample exceeds 32 bit"); return 32;
} }
public int getTransferType() { public int getTransferType() {
@ -135,11 +135,8 @@ final class PNMHeader {
if (maxSample <= PNM.MAX_VAL_16BIT) { if (maxSample <= PNM.MAX_VAL_16BIT) {
return DataBuffer.TYPE_USHORT; return DataBuffer.TYPE_USHORT;
} }
if ((maxSample & 0xffffffffL) <= PNM.MAX_VAL_32BIT) {
return DataBuffer.TYPE_INT;
}
throw new AssertionError("maxSample exceeds 32 bit"); return DataBuffer.TYPE_INT;
} }
public List<String> getComments() { public List<String> getComments() {

View File

@ -49,11 +49,13 @@ import java.io.DataInput;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
public final class PNMImageReader extends ImageReaderBase { public final class PNMImageReader extends ImageReaderBase {
// TODO: Allow reading unknown tuple types as Raster! // TODO: Allow reading unknown tuple types as Raster!
// TODO: readAsRenderedImage? // TODO: readAsRenderedImage?
@ -83,7 +85,7 @@ public final class PNMImageReader extends ImageReaderBase {
static String asASCII(final short type) { static String asASCII(final short type) {
byte[] asciiBytes = {(byte) ((type >> 8) & 0xff), (byte) (type & 0xff)}; byte[] asciiBytes = {(byte) ((type >> 8) & 0xff), (byte) (type & 0xff)};
return new String(asciiBytes, Charset.forName("ASCII")); return new String(asciiBytes, StandardCharsets.US_ASCII);
} }
@Override @Override
@ -150,7 +152,6 @@ public final class PNMImageReader extends ImageReaderBase {
default: default:
// TODO: Allow reading unknown tuple types as Raster! // TODO: Allow reading unknown tuple types as Raster!
throw new AssertionError("Unknown PNM tuple type: " + header.getTupleType()); throw new AssertionError("Unknown PNM tuple type: " + header.getTupleType());
} }
} }
@ -261,7 +262,7 @@ public final class PNMImageReader extends ImageReaderBase {
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
switch (transferType) { switch (transferType) {
case DataBuffer.TYPE_BYTE: case DataBuffer.TYPE_BYTE:
readRowByte(destRaster, clippedRow, colorConvert, rowDataByte, samplesPerPixel, input, y, srcRegion, xSub, ySub); readRowByte(destRaster, clippedRow, colorConvert, rowDataByte, header.getBitsPerSample(), samplesPerPixel, input, y, srcRegion, xSub, ySub);
break; break;
case DataBuffer.TYPE_USHORT: case DataBuffer.TYPE_USHORT:
readRowUShort(destRaster, clippedRow, rowDataUShort, samplesPerPixel, input, y, srcRegion, xSub, ySub); readRowUShort(destRaster, clippedRow, rowDataUShort, samplesPerPixel, input, y, srcRegion, xSub, ySub);
@ -339,7 +340,7 @@ public final class PNMImageReader extends ImageReaderBase {
Raster rowRaster, Raster rowRaster,
final ColorConvertOp colorConvert, final ColorConvertOp colorConvert,
final byte[] rowDataByte, final byte[] rowDataByte,
final int samplesPerPixel, final int bitsPerSample, final int samplesPerPixel,
final DataInput input, final int y, final DataInput input, final int y,
final Rectangle srcRegion, final Rectangle srcRegion,
final int xSub, final int ySub) throws IOException { final int xSub, final int ySub) throws IOException {
@ -352,7 +353,9 @@ public final class PNMImageReader extends ImageReaderBase {
input.readFully(rowDataByte); input.readFully(rowDataByte);
// Subsample (horizontal) // Subsample (horizontal)
subsampleHorizontal(rowDataByte, rowDataByte.length, samplesPerPixel, xSub); if (xSub > 1) {
subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub);
}
normalize(rowDataByte, 0, rowDataByte.length / xSub); normalize(rowDataByte, 0, rowDataByte.length / xSub);
@ -379,7 +382,9 @@ public final class PNMImageReader extends ImageReaderBase {
readFully(input, rowDataUShort); readFully(input, rowDataUShort);
// Subsample (horizontal) // Subsample (horizontal)
subsampleHorizontal(rowDataUShort, rowDataUShort.length, samplesPerPixel, xSub); if (xSub > 1) {
subsampleRow(rowDataUShort, srcRegion.x, srcRegion.width, rowDataUShort, 0, samplesPerPixel, 16, xSub);
}
normalize(rowDataUShort); normalize(rowDataUShort);
@ -402,11 +407,14 @@ public final class PNMImageReader extends ImageReaderBase {
readFully(input, rowDataFloat); readFully(input, rowDataFloat);
// Subsample (horizontal) // Subsample (horizontal)
subsampleHorizontal(rowDataFloat, rowDataFloat.length, samplesPerPixel, xSub); if (xSub > 1) {
subsampleRow(rowDataFloat, srcRegion.x, srcRegion.width, rowDataFloat, 0, samplesPerPixel, 32, xSub);
}
normalize(rowDataFloat); normalize(rowDataFloat);
int destY = (y - srcRegion.y) / ySub; // Note: PFM is stored bottom to top!
int destY = destRaster.getHeight() - 1 - (y - srcRegion.y) / ySub;
// TODO: ColorConvertOp if needed // TODO: ColorConvertOp if needed
destRaster.setDataElements(0, destY, rowRaster); destRaster.setDataElements(0, destY, rowRaster);
} }
@ -437,19 +445,6 @@ public final class PNMImageReader extends ImageReaderBase {
} }
} }
@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) { private void normalize(final byte[] rowData, final int start, final int length) {
switch (header.getTupleType()) { switch (header.getTupleType()) {
case BLACKANDWHITE: case BLACKANDWHITE:
@ -484,13 +479,9 @@ public final class PNMImageReader extends ImageReaderBase {
} }
private void normalize(final float[] rowData) { 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 // Normalize
for (int i = 0; i < rowData.length; i++) { for (int i = 0; i < rowData.length; i++) {
// if (rowData[i] > 275f /*header.getMaxSampleFloat()*/) { rowData[i] = Math.min(1, rowData[i]); // Clamp
// System.out.println("rowData[" + i + "]: " + rowData[i]);
// }
// rowData[i] = rowData[i] / 275f /*header.getMaxSampleFloat()*/;
} }
} }

View File

@ -151,7 +151,10 @@ final class PNMMetadata extends AbstractMetadata {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "Normal"); imageOrientation.setAttribute("value",
header.getFileType() == PNM.PFM_GRAY || header.getFileType() == PNM.PFM_RGB
? "FlipH"
: "Normal");
dimension.appendChild(imageOrientation); dimension.appendChild(imageOrientation);
return dimension; return dimension;