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);
}
}
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 int width;
private final int height;
private final float maxSampleFloat;
private final int maxSample;
private final List<String> comments;
@ -57,6 +58,7 @@ final class PNMHeader {
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.maxSampleFloat = this.maxSample;
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");
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.comments = Collections.unmodifiableList(new ArrayList<String>(comments));
@ -118,11 +121,8 @@ final class PNMHeader {
if (maxSample <= PNM.MAX_VAL_16BIT) {
return 16;
}
if ((maxSample & 0xffffffffL) <= PNM.MAX_VAL_32BIT) {
return 32;
}
throw new AssertionError("maxSample exceeds 32 bit");
return 32;
}
public int getTransferType() {
@ -135,11 +135,8 @@ final class PNMHeader {
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");
return DataBuffer.TYPE_INT;
}
public List<String> getComments() {

View File

@ -49,11 +49,13 @@ import java.io.DataInput;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
public final class PNMImageReader extends ImageReaderBase {
// TODO: Allow reading unknown tuple types as Raster!
// TODO: readAsRenderedImage?
@ -83,7 +85,7 @@ public final class PNMImageReader extends ImageReaderBase {
static String asASCII(final short type) {
byte[] asciiBytes = {(byte) ((type >> 8) & 0xff), (byte) (type & 0xff)};
return new String(asciiBytes, Charset.forName("ASCII"));
return new String(asciiBytes, StandardCharsets.US_ASCII);
}
@Override
@ -150,7 +152,6 @@ public final class PNMImageReader extends ImageReaderBase {
default:
// TODO: Allow reading unknown tuple types as Raster!
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++) {
switch (transferType) {
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;
case DataBuffer.TYPE_USHORT:
readRowUShort(destRaster, clippedRow, rowDataUShort, samplesPerPixel, input, y, srcRegion, xSub, ySub);
@ -339,7 +340,7 @@ public final class PNMImageReader extends ImageReaderBase {
Raster rowRaster,
final ColorConvertOp colorConvert,
final byte[] rowDataByte,
final int samplesPerPixel,
final int bitsPerSample, final int samplesPerPixel,
final DataInput input, final int y,
final Rectangle srcRegion,
final int xSub, final int ySub) throws IOException {
@ -352,7 +353,9 @@ public final class PNMImageReader extends ImageReaderBase {
input.readFully(rowDataByte);
// 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);
@ -379,7 +382,9 @@ public final class PNMImageReader extends ImageReaderBase {
readFully(input, rowDataUShort);
// Subsample (horizontal)
subsampleHorizontal(rowDataUShort, rowDataUShort.length, samplesPerPixel, xSub);
if (xSub > 1) {
subsampleRow(rowDataUShort, srcRegion.x, srcRegion.width, rowDataUShort, 0, samplesPerPixel, 16, xSub);
}
normalize(rowDataUShort);
@ -402,11 +407,14 @@ public final class PNMImageReader extends ImageReaderBase {
readFully(input, rowDataFloat);
// Subsample (horizontal)
subsampleHorizontal(rowDataFloat, rowDataFloat.length, samplesPerPixel, xSub);
if (xSub > 1) {
subsampleRow(rowDataFloat, srcRegion.x, srcRegion.width, rowDataFloat, 0, samplesPerPixel, 32, xSub);
}
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
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) {
switch (header.getTupleType()) {
case BLACKANDWHITE:
@ -484,13 +479,9 @@ public final class PNMImageReader extends ImageReaderBase {
}
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()*/;
rowData[i] = Math.min(1, rowData[i]); // Clamp
}
}

View File

@ -151,7 +151,10 @@ final class PNMMetadata extends AbstractMetadata {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
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);
return dimension;