Handle lossless JPEG with non-zero point transform (#1183)

* Handle lossless JPEG with non-zero point transform (Pt parameter)
* Add unit test
* Inserting a fix for last pixel not being output
* Address issues from code review

---------

Co-authored-by: Harald Kuhr <harald.kuhr@gmail.com>
This commit is contained in:
zcronix
2025-10-03 04:49:40 -04:00
committed by GitHub
parent 8bccdef371
commit 1328dbf763
4 changed files with 84 additions and 3 deletions

View File

@@ -212,7 +212,9 @@ final class JPEGLosslessDecoder {
final int[] firstValue = new int[numComp];
for (int i = 0; i < numComp; i++) {
firstValue[i] = (1 << (precision - 1));
// scan.approxLow is the point transformation (Pt) value
// ref. ISO/IEC 10918-1 H.1.2.1
firstValue[i] = (1 << (precision - scan.approxLow - 1));
}
final int[] pred = new int[numComp];
@@ -232,6 +234,11 @@ final class JPEGLosslessDecoder {
output(pred);
current = decode(pred, temp, index);
}
if ((current == JPEG.EOI) && (xLoc == xDim - 1) && (yLoc == yDim - 1)) {
// Output value left in pred if EOI is hit while decoding last pixel
output(pred);
}
break; //current=MARKER
}
@@ -268,6 +275,17 @@ final class JPEGLosslessDecoder {
// TODO oe: 05.05.2018 Is it correct loop? Content of outputData from previous iteration is always lost.
} while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0));
// Apply point transform to output. This must be done after it has finished being
// used for predictive purposes.
if (scan.approxLow != 0) {
for (int componentIndex = 0; componentIndex < numComp; ++componentIndex) {
int[] comp = outputData[componentIndex];
for (int i = 0; i < comp.length; i++) {
comp[i] = mask & (comp[i] << scan.approxLow);
}
}
}
return outputData;
}
@@ -329,9 +347,11 @@ final class JPEGLosslessDecoder {
private int decodeSingle(final int[] prev, final int[] temp, final int[] index) throws IOException {
// At the beginning of the first line and
// at the beginning of each restart interval the prediction value of 2P 1 is used, where P is the input precision.
// If the point transformation parameter (see A.4) is non-zero, the prediction value at the beginning of the first lines and the
// beginning of each restart interval is 2P Pt 1 , where Pt is the value of the point transformation parameter
if (restarting) {
restarting = false;
prev[0] = (1 << (frame.samplePrecision - 1));
prev[0] = (1 << (frame.samplePrecision - scan.approxLow - 1));
}
else {
final int[] outputData = this.outputData[0];
@@ -686,7 +706,7 @@ final class JPEGLosslessDecoder {
return getPreviousY(data);
}
else {
return (1 << (frame.samplePrecision - 1));
return (1 << (frame.samplePrecision - scan.approxLow - 1));
}
}

View File

@@ -2033,4 +2033,65 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
}
});
}
@Test
public void testReadLosslessJPEGGradient() throws IOException {
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg-lossless/gradient_ls.jpg"))) {
reader.setInput(stream);
assertEquals(256, reader.getWidth(0));
assertEquals(256, reader.getHeight(0));
BufferedImage image = reader.read(0, null);
assertNotNull(image);
assertEquals(256, image.getWidth());
assertEquals(256, image.getHeight());
assertEquals(16, image.getColorModel().getComponentSize(0));
for (int y = 0; y < 256; y++) {
for (int x = 0; x < 256; x++) {
assertEquals((y << 8) | x, image.getRaster().getSample(x, y, 0));
}
}
}
finally {
reader.dispose();
}
}
@Test
public void testReadLosslessJPEGGradientWithPointTransform() throws IOException {
JPEGImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/jpeg-lossless/gradient_ls_pt.jpg"))) {
reader.setInput(stream);
assertEquals(256, reader.getWidth(0));
assertEquals(256, reader.getHeight(0));
BufferedImage image = reader.read(0, null);
assertNotNull(image);
assertEquals(256, image.getWidth());
assertEquals(256, image.getHeight());
assertEquals(16, image.getColorModel().getComponentSize(0));
for (int y = 0; y < 256; y++) {
for (int x = 0; x < 256; x++) {
int expected = (y << 8) | x;
// Simulate effect of precision loss due to point transform = 4
expected = (expected >> 4) << 4;
assertEquals(expected, image.getRaster().getSample(x, y, 0));
}
}
}
finally {
reader.dispose();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB