#228: TIFFImageWriter now correctly writes images with sample model translation.

This commit is contained in:
Harald Kuhr 2016-07-07 15:27:08 +02:00
parent 04a39158e5
commit c18893184b
5 changed files with 145 additions and 52 deletions

View File

@ -1606,10 +1606,15 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
* Slightly fuzzy RGB equals method. Variable tolerance.
*/
public static void assertRGBEquals(String message, int expectedRGB, int actualRGB, int tolerance) {
assertEquals(message, (expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 0);
assertEquals(message, (expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, tolerance);
assertEquals(message, (expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, tolerance);
assertEquals(message, (expectedRGB ) & 0xff, (actualRGB ) & 0xff, tolerance);
try {
assertEquals((expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 0);
assertEquals((expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, tolerance);
assertEquals((expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, tolerance);
assertEquals((expectedRGB ) & 0xff, (actualRGB ) & 0xff, tolerance);
}
catch (AssertionError e) {
assertEquals(message, String.format("#%08x", expectedRGB), String.format("#%08x", actualRGB));
}
}
static final protected class TestData {

View File

@ -956,7 +956,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
int rgb = imageRGB.getRGB(x, y);
if (rgb != cmykAsRGB) {
assertRGBEquals(String.format("Diff at [%d, %d]: #%04x != #%04x", x, y, cmykAsRGB, rgb), rgb, cmykAsRGB, 2);
assertRGBEquals(String.format("Diff at [%d, %d]", x, y), rgb, cmykAsRGB, 2);
}
}
}

View File

@ -447,7 +447,7 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
// NOTE: Allow some slack, as Java 1.7 and 1.8 color management differs slightly
int rgb = image.getRGB(0, 0);
assertRGBEquals(String.format("#%04x != #%04x", colors[i], rgb), colors[i], rgb, 1);
assertRGBEquals("Colors differ", colors[i], rgb, 1);
}
}
}

View File

@ -669,37 +669,55 @@ public final class TIFFImageWriter extends ImageWriterBase {
final int tileWidth = renderedImage.getTileWidth();
// TODO: SampleSize may differ between bands/banks
int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
final ByteBuffer buffer;
if (sampleSize == 1) {
buffer = ByteBuffer.allocate((tileWidth + 7) / 8);
}
else {
buffer = ByteBuffer.allocate(tileWidth * renderedImage.getSampleModel().getNumBands() * sampleSize / 8);
}
// System.err.println("tileWidth: " + tileWidth);
final int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
final int numBands = renderedImage.getSampleModel().getNumBands();
final ByteBuffer buffer = ByteBuffer.allocate((tileWidth * numBands * sampleSize + 7) / 8);
for (int yTile = minTileY; yTile < maxYTiles; yTile++) {
for (int xTile = minTileX; xTile < maxXTiles; xTile++) {
final Raster tile = renderedImage.getTile(xTile, yTile);
// Model translation
final int offsetX = tile.getMinX() - tile.getSampleModelTranslateX();
final int offsetY = tile.getMinY() - tile.getSampleModelTranslateY();
// Scanline stride, not accounting for model translation
final int stride = (tile.getSampleModel().getWidth() * sampleSize + 7) / 8;
final DataBuffer dataBuffer = tile.getDataBuffer();
final int numBands = tile.getNumBands();
switch (dataBuffer.getDataType()) {
case DataBuffer.TYPE_BYTE:
// System.err.println("Writing " + numBands + "BYTE -> " + numBands + "BYTE");
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
for (int y = 0; y < tileHeight; y++) {
int steps = sampleSize == 1 ? (tileWidth + 7) / 8 : tileWidth;
final int yOff = y * steps * numBands;
int steps = (tileWidth * sampleSize + 7) / 8;
// Shift needed for "packed" samples with "odd" offset
int shift = offsetX % 8;
for (int x = 0; x < steps; x++) {
// TODO: Generalize this code, to always use row raster
final WritableRaster rowRaster = shift != 0 ? tile.createCompatibleWritableRaster(tile.getWidth(), 1) : null;
final DataBuffer rowBuffer = shift != 0 ? rowRaster.getDataBuffer() : null;
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
for (int y = offsetY; y < tileHeight + offsetY; y++) {
final int yOff = y * stride * numBands;
if (shift != 0) {
rowRaster.setDataElements(0, 0, tile.createChild(0, y - offsetY, tile.getWidth(), 1, 0, 0, null));
}
for (int x = offsetX; x < steps + offsetX; x++) {
final int xOff = yOff + x * numBands;
for (int s = 0; s < numBands; s++) {
if (sampleSize == 8 || shift == 0) {
// Normal interleaved/planar case
buffer.put((byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff));
}
else {
// "Packed" case
buffer.put((byte) (rowBuffer.getElem(b, x - offsetX + bandOffsets[s]) & 0xff));
}
}
}
flushBuffer(buffer, stream);
@ -716,13 +734,13 @@ public final class TIFFImageWriter extends ImageWriterBase {
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
if (numComponents == 1) {
// TODO: This is foobar...
// System.err.println("Writing USHORT -> " + numBands * 2 + "_BYTES");
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
for (int y = 0; y < tileHeight; y++) {
final int yOff = y * tileWidth;
for (int x = 0; x < tileWidth; x++) {
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
for (int y = offsetY; y < tileHeight + offsetY; y++) {
int yOff = y * stride / 2;
for (int x = offsetX; x < tileWidth + offsetX; x++) {
final int xOff = yOff + x;
buffer.putShort((short) (dataBuffer.getElem(b, xOff) & 0xffff));
@ -765,7 +783,31 @@ public final class TIFFImageWriter extends ImageWriterBase {
case DataBuffer.TYPE_INT:
// TODO: This is incorrect for 32 bits/sample, only works for packed (INT_(A)RGB)
if (1 == numComponents) {
// System.err.println("Writing INT -> " + numBands * 4 + "_BYTES");
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
for (int y = offsetY; y < tileHeight + offsetY; y++) {
int yOff = y * stride / 4;
for (int x = offsetX; x < tileWidth + offsetX; x++) {
final int xOff = yOff + x;
buffer.putInt(dataBuffer.getElem(b, xOff));
}
flushBuffer(buffer, stream);
if (stream instanceof DataOutputStream) {
DataOutputStream dataOutputStream = (DataOutputStream) stream;
dataOutputStream.flush();
}
}
}
}
else {
// System.err.println("Writing INT -> " + numBands + "_BYTES");
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
for (int y = 0; y < tileHeight; y++) {
final int yOff = y * tileWidth;
@ -786,6 +828,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
}
}
}
}
break;
default:

View File

@ -34,11 +34,9 @@ import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
import com.twelvemonkeys.imageio.metadata.exif.Rational;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.*;
@ -50,9 +48,8 @@ import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@ -88,7 +85,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
new BufferedImage(300, 200, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(300, 200, BufferedImage.TYPE_USHORT_GRAY),
// new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_BINARY), // TODO!
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_BINARY),
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_INDEXED)
);
}
@ -429,7 +426,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
// Read original LZW compressed TIFF
IIOImage original;
try (ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource("/tiff/a33.tif"))) {
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/a33.tif"))) {
ImageReader reader = ImageIO.getImageReaders(input).next();
reader.setInput(input);
@ -493,7 +490,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
// Read original LZW compressed TIFF
IIOImage original;
try (ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource("/tiff/a33.tif"))) {
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/a33.tif"))) {
ImageReader reader = ImageIO.getImageReaders(input).next();
reader.setInput(input);
@ -558,7 +555,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
// Read original LZW compressed TIFF
IIOImage original;
try (ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource("/tiff/quad-lzw.tif"))) {
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/quad-lzw.tif"))) {
ImageReader reader = ImageIO.getImageReaders(input).next();
reader.setInput(input);
@ -618,7 +615,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
// Read original LZW compressed TIFF
IIOImage original;
try (ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource("/tiff/quad-lzw.tif"))) {
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/quad-lzw.tif"))) {
ImageReader reader = ImageIO.getImageReaders(input).next();
reader.setInput(input);
@ -682,7 +679,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
// Read original LZW compressed TIFF
IIOImage original;
try (ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource("/tiff/quad-lzw.tif"))) {
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/quad-lzw.tif"))) {
ImageReader reader = ImageIO.getImageReaders(input).next();
reader.setInput(input);
@ -744,4 +741,52 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
assertTrue("Software metadata not found", softwareFound);
}
}
@Test
public void testWriteCropped() throws IOException {
List<URL> testData = Arrays.asList(
getClassLoaderResource("/tiff/quad-lzw.tif"),
getClassLoaderResource("/tiff/grayscale-alpha.tiff"),
getClassLoaderResource("/tiff/ccitt/group3_1d.tif"),
getClassLoaderResource("/tiff/depth/flower-palette-02.tif"),
getClassLoaderResource("/tiff/depth/flower-palette-04.tif"),
getClassLoaderResource("/tiff/depth/flower-minisblack-16.tif"),
getClassLoaderResource("/tiff/depth/flower-minisblack-32.tif")
);
for (URL resource : testData) {
// Read it
BufferedImage original = ImageIO.read(resource);
// Crop it
BufferedImage subimage = original.getSubimage(original.getWidth() / 4, original.getHeight() / 4, original.getWidth() / 2, original.getHeight() / 2);
// Store cropped
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(bytes)) {
ImageWriter imageWriter = createImageWriter();
imageWriter.setOutput(output);
imageWriter.write(subimage);
}
// Re-read cropped
BufferedImage cropped = ImageIO.read(new ByteArrayImageInputStream(bytes.toByteArray()));
// Compare
assertImageEquals(String.format("Cropped output differs: %s", resource.getFile()), subimage, cropped, 0);
}
}
private void assertImageEquals(final String message, final BufferedImage expected, final BufferedImage actual, final int tolerance) {
assertNotNull(message, expected);
assertNotNull(message, actual);
assertEquals(message + ", widths differ", expected.getWidth(), actual.getWidth());
assertEquals(message + ", heights differ", expected.getHeight(), actual.getHeight());
for (int y = 0; y < expected.getHeight(); y++) {
for (int x = 0; x < expected.getWidth(); x++) {
assertRGBEquals(String.format("%s, ARGB differs at (%s,%s)", message, x, y), expected.getRGB(x, y), actual.getRGB(x, y), tolerance);
}
}
}
}