Fix BlackIsZero handling in CCITTFaxEncoderStream

This commit is contained in:
Schmidor 2015-10-11 17:59:01 +02:00
parent 306a8ae166
commit e5c0fead38
3 changed files with 65 additions and 26 deletions

View File

@ -190,7 +190,7 @@ public class CCITTFaxEncoderStream extends OutputStream {
int index = 0; int index = 0;
boolean white = true; boolean white = true;
while (index < columns) { while (index < columns) {
int[] nextChanges = getNextChanges(index); int[] nextChanges = getNextChanges(index, white);
int runLength = nextChanges[0] - index; int runLength = nextChanges[0] - index;
writeRun(runLength, white); writeRun(runLength, white);
index += runLength; index += runLength;
@ -198,10 +198,10 @@ public class CCITTFaxEncoderStream extends OutputStream {
} }
} }
private int[] getNextChanges(int pos) { private int[] getNextChanges(int pos, boolean white) {
int[] result = new int[] {columns, columns}; int[] result = new int[] {columns, columns};
for (int i = 0; i < changesCurrentRowLength; i++) { for (int i = 0; i < changesCurrentRowLength; i++) {
if (pos < changesCurrentRow[i]) { if (pos < changesCurrentRow[i] || (pos == 0 && white)) {
result[0] = changesCurrentRow[i]; result[0] = changesCurrentRow[i];
if ((i + 1) < changesCurrentRowLength) { if ((i + 1) < changesCurrentRowLength) {
result[1] = changesCurrentRow[i + 1]; result[1] = changesCurrentRow[i + 1];
@ -235,7 +235,7 @@ public class CCITTFaxEncoderStream extends OutputStream {
boolean white = true; boolean white = true;
int index = 0; // a0 int index = 0; // a0
while (index < columns) { while (index < columns) {
int[] nextChanges = getNextChanges(index); // a1, a2 int[] nextChanges = getNextChanges(index, white); // a1, a2
int[] nextRefs = getNextRefChanges(index, white); // b1, b2 int[] nextRefs = getNextRefChanges(index, white); // b1, b2
@ -287,7 +287,7 @@ public class CCITTFaxEncoderStream extends OutputStream {
private int[] getNextRefChanges(int a0, boolean white) { private int[] getNextRefChanges(int a0, boolean white) {
int[] result = new int[] {columns, columns}; int[] result = new int[] {columns, columns};
for (int i = (white ? 0 : 1); i < changesReferenceRowLength; i += 2) { for (int i = (white ? 0 : 1); i < changesReferenceRowLength; i += 2) {
if (changesReferenceRow[i] > a0) { if (changesReferenceRow[i] > a0 || (a0 == 0 && i == 0)) {
result[0] = changesReferenceRow[i]; result[0] = changesReferenceRow[i];
if ((i + 1) < changesReferenceRowLength) { if ((i + 1) < changesReferenceRowLength) {
result[1] = changesReferenceRow[i + 1]; result[1] = changesReferenceRow[i + 1];

View File

@ -377,7 +377,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
else { else {
// Write image data // Write image data
writeImageData(createCompressorStream(renderedImage, param, entries), renderedImage, numComponents, bandOffsets, writeImageData(createCompressorStream(renderedImage, param, entries), renderedImage, numComponents, bandOffsets,
bitOffsets, photometric); bitOffsets);
} }
// Update IFD0-pointer, and write IFD // Update IFD0-pointer, and write IFD
@ -587,7 +587,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
return shorts; return shorts;
} }
private void writeImageData(DataOutput stream, RenderedImage renderedImage, int numComponents, int[] bandOffsets, int[] bitOffsets, int photometric) throws IOException { private void writeImageData(DataOutput stream, RenderedImage renderedImage, int numComponents, int[] bandOffsets, int[] bitOffsets) throws IOException {
// Store 3BYTE, 4BYTE as is (possibly need to re-arrange to RGB order) // Store 3BYTE, 4BYTE as is (possibly need to re-arrange to RGB order)
// Store INT_RGB as 3BYTE, INT_ARGB as 4BYTE?, INT_ABGR must be re-arranged // Store INT_RGB as 3BYTE, INT_ARGB as 4BYTE?, INT_ABGR must be re-arranged
// Store IndexColorModel as is // Store IndexColorModel as is
@ -635,7 +635,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
final int xOff = yOff + x * numBands; final int xOff = yOff + x * numBands;
for (int s = 0; s < numBands; s++) { for (int s = 0; s < numBands; s++) {
buffer.put(normalizeBlack(photometric, (byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff))); buffer.put((byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff));
} }
} }
@ -748,14 +748,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
processImageComplete(); processImageComplete();
} }
private byte normalizeBlack(int photometricInterpretation, byte data) {
if (photometricInterpretation == TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO) {
// Inverse values
return (byte) (0xff - data & 0xff);
}
return data;
}
// TODO: Would be better to solve this on stream level... But writers would then have to explicitly flush the buffer before done. // TODO: Would be better to solve this on stream level... But writers would then have to explicitly flush the buffer before done.
private void flushBuffer(final ByteBuffer buffer, final DataOutput stream) throws IOException { private void flushBuffer(final ByteBuffer buffer, final DataOutput stream) throws IOException {
buffer.flip(); buffer.flip();

View File

@ -29,12 +29,20 @@
package com.twelvemonkeys.imageio.plugins.tiff; package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.imageio.plugins.tiff.CCITTFaxEncoderStream.Code; import com.twelvemonkeys.imageio.plugins.tiff.CCITTFaxEncoderStream.Code;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte; import java.awt.image.DataBufferByte;
import java.io.*; import java.io.*;
import java.net.URL;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -88,30 +96,40 @@ public class CCITTFaxEncoderStreamTest {
@Test @Test
public void testType2() throws IOException { public void testType2() throws IOException {
testImage(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L); testStreamEncodeDecode(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 1, 0L);
} }
@Test @Test
public void testType4() throws IOException { public void testType4() throws IOException {
testImage(TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L); testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, 0L);
testImage(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS); testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_FILLBITS);
testImage(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING); testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1, TIFFExtension.GROUP3OPT_2DENCODING);
testImage(TIFFExtension.COMPRESSION_CCITT_T4, 1, testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T4, 1,
TIFFExtension.GROUP3OPT_FILLBITS | TIFFExtension.GROUP3OPT_2DENCODING); TIFFExtension.GROUP3OPT_FILLBITS | TIFFExtension.GROUP3OPT_2DENCODING);
} }
@Test @Test
public void testType6() throws IOException { public void testType6() throws IOException {
testImage(TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L); testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T6, 1, 0L);
} }
@Test @Test
public void restReversedFillOrder() throws IOException { public void testReversedFillOrder() throws IOException {
testImage(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 2, 0L); testStreamEncodeDecode(TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE, 2, 0L);
testImage(TIFFExtension.COMPRESSION_CCITT_T6, 2, 0L); testStreamEncodeDecode(TIFFExtension.COMPRESSION_CCITT_T6, 2, 0L);
} }
private void testImage(int type, int fillOrder, long options) throws IOException { @Test
public void testReencodeImages() throws IOException {
testImage(getClassLoaderResource("/tiff/fivepages-scan-causingerrors.tif"));
// testImage(getClassLoaderResource("/tiff/test-single-gray-compression-type-2.tiff"));
}
protected URL getClassLoaderResource(final String pName) {
return getClass().getResource(pName);
}
private void testStreamEncodeDecode(int type, int fillOrder, long options) throws IOException {
byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData(); byte[] imageData = ((DataBufferByte) image.getData().getDataBuffer()).getData();
byte[] redecodedData = new byte[imageData.length]; byte[] redecodedData = new byte[imageData.length];
ByteArrayOutputStream imageOutput = new ByteArrayOutputStream(); ByteArrayOutputStream imageOutput = new ByteArrayOutputStream();
@ -127,4 +145,33 @@ public class CCITTFaxEncoderStreamTest {
assertArrayEquals(imageData, redecodedData); assertArrayEquals(imageData, redecodedData);
} }
private void testImage(URL imageUrl) throws IOException {
ImageInputStream iis = ImageIO.createImageInputStream(imageUrl.openStream());
ImageReader reader = ImageIO.getImageReadersByFormatName("TIFF").next();
reader.setInput(iis, true);
ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
ImageWriter writer = ImageIO.getImageWritersByFormatName("TIFF").next();
ImageOutputStream output = ImageIO.createImageOutputStream(outputBuffer);
writer.setOutput(output);
BufferedImage originalImage = reader.read(0);
IIOImage outputImage = new IIOImage(originalImage, null, reader.getImageMetadata(0));
writer.write(outputImage);
FileOutputStream stream = new FileOutputStream("H:\\tmp\\test.tif");
try {
stream.write(outputBuffer.toByteArray());
}
finally {
stream.close();
}
BufferedImage reencodedImage = ImageIO.read(new ByteArrayInputStream(outputBuffer.toByteArray()));
byte[] reencodedData = ((DataBufferByte) reencodedImage.getData().getDataBuffer()).getData();
Assert.assertArrayEquals(((DataBufferByte) originalImage.getData().getDataBuffer()).getData(),
reencodedData);
}
} }