mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
#228: TIFFImageWriter now correctly writes images with sample model translation.
This commit is contained in:
parent
04a39158e5
commit
c18893184b
@ -1606,10 +1606,15 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
* Slightly fuzzy RGB equals method. Variable tolerance.
|
* Slightly fuzzy RGB equals method. Variable tolerance.
|
||||||
*/
|
*/
|
||||||
public static void assertRGBEquals(String message, int expectedRGB, int actualRGB, int tolerance) {
|
public static void assertRGBEquals(String message, int expectedRGB, int actualRGB, int tolerance) {
|
||||||
assertEquals(message, (expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 0);
|
try {
|
||||||
assertEquals(message, (expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, tolerance);
|
assertEquals((expectedRGB >>> 24) & 0xff, (actualRGB >>> 24) & 0xff, 0);
|
||||||
assertEquals(message, (expectedRGB >> 8) & 0xff, (actualRGB >> 8) & 0xff, tolerance);
|
assertEquals((expectedRGB >> 16) & 0xff, (actualRGB >> 16) & 0xff, tolerance);
|
||||||
assertEquals(message, (expectedRGB ) & 0xff, (actualRGB ) & 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 {
|
static final protected class TestData {
|
||||||
|
@ -956,7 +956,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
int rgb = imageRGB.getRGB(x, y);
|
int rgb = imageRGB.getRGB(x, y);
|
||||||
|
|
||||||
if (rgb != cmykAsRGB) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// NOTE: Allow some slack, as Java 1.7 and 1.8 color management differs slightly
|
||||||
int rgb = image.getRGB(0, 0);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -669,37 +669,55 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
final int tileWidth = renderedImage.getTileWidth();
|
final int tileWidth = renderedImage.getTileWidth();
|
||||||
|
|
||||||
// TODO: SampleSize may differ between bands/banks
|
// TODO: SampleSize may differ between bands/banks
|
||||||
int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
|
final int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
|
||||||
final ByteBuffer buffer;
|
final int numBands = renderedImage.getSampleModel().getNumBands();
|
||||||
if (sampleSize == 1) {
|
|
||||||
buffer = ByteBuffer.allocate((tileWidth + 7) / 8);
|
final ByteBuffer buffer = ByteBuffer.allocate((tileWidth * numBands * sampleSize + 7) / 8);
|
||||||
}
|
|
||||||
else {
|
|
||||||
buffer = ByteBuffer.allocate(tileWidth * renderedImage.getSampleModel().getNumBands() * sampleSize / 8);
|
|
||||||
}
|
|
||||||
// System.err.println("tileWidth: " + tileWidth);
|
|
||||||
|
|
||||||
for (int yTile = minTileY; yTile < maxYTiles; yTile++) {
|
for (int yTile = minTileY; yTile < maxYTiles; yTile++) {
|
||||||
for (int xTile = minTileX; xTile < maxXTiles; xTile++) {
|
for (int xTile = minTileX; xTile < maxXTiles; xTile++) {
|
||||||
final Raster tile = renderedImage.getTile(xTile, yTile);
|
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 DataBuffer dataBuffer = tile.getDataBuffer();
|
||||||
final int numBands = tile.getNumBands();
|
|
||||||
|
|
||||||
switch (dataBuffer.getDataType()) {
|
switch (dataBuffer.getDataType()) {
|
||||||
case DataBuffer.TYPE_BYTE:
|
case DataBuffer.TYPE_BYTE:
|
||||||
|
|
||||||
// System.err.println("Writing " + numBands + "BYTE -> " + numBands + "BYTE");
|
// System.err.println("Writing " + numBands + "BYTE -> " + numBands + "BYTE");
|
||||||
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
|
int steps = (tileWidth * sampleSize + 7) / 8;
|
||||||
for (int y = 0; y < tileHeight; y++) {
|
// Shift needed for "packed" samples with "odd" offset
|
||||||
int steps = sampleSize == 1 ? (tileWidth + 7) / 8 : tileWidth;
|
int shift = offsetX % 8;
|
||||||
final int yOff = y * steps * numBands;
|
|
||||||
|
|
||||||
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;
|
final int xOff = yOff + x * numBands;
|
||||||
|
|
||||||
for (int s = 0; s < numBands; s++) {
|
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));
|
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);
|
flushBuffer(buffer, stream);
|
||||||
@ -716,13 +734,13 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
case DataBuffer.TYPE_USHORT:
|
case DataBuffer.TYPE_USHORT:
|
||||||
case DataBuffer.TYPE_SHORT:
|
case DataBuffer.TYPE_SHORT:
|
||||||
if (numComponents == 1) {
|
if (numComponents == 1) {
|
||||||
// TODO: This is foobar...
|
|
||||||
// System.err.println("Writing USHORT -> " + numBands * 2 + "_BYTES");
|
// 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;
|
final int xOff = yOff + x;
|
||||||
|
|
||||||
buffer.putShort((short) (dataBuffer.getElem(b, xOff) & 0xffff));
|
buffer.putShort((short) (dataBuffer.getElem(b, xOff) & 0xffff));
|
||||||
@ -765,7 +783,31 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
|
|
||||||
case DataBuffer.TYPE_INT:
|
case DataBuffer.TYPE_INT:
|
||||||
// TODO: This is incorrect for 32 bits/sample, only works for packed (INT_(A)RGB)
|
// 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");
|
// System.err.println("Writing INT -> " + numBands + "_BYTES");
|
||||||
|
|
||||||
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
|
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
|
||||||
for (int y = 0; y < tileHeight; y++) {
|
for (int y = 0; y < tileHeight; y++) {
|
||||||
final int yOff = y * tileWidth;
|
final int yOff = y * tileWidth;
|
||||||
@ -786,6 +828,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -34,11 +34,9 @@ import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
|||||||
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
import com.twelvemonkeys.imageio.metadata.exif.Rational;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
|
||||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
|
||||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
@ -50,9 +48,8 @@ import javax.imageio.stream.ImageOutputStream;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.net.URL;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Arrays;
|
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_4BYTE_ABGR),
|
||||||
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_GRAY),
|
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_GRAY),
|
||||||
new BufferedImage(300, 200, BufferedImage.TYPE_USHORT_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)
|
new BufferedImage(300, 200, BufferedImage.TYPE_BYTE_INDEXED)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -429,7 +426,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
// Read original LZW compressed TIFF
|
// Read original LZW compressed TIFF
|
||||||
IIOImage original;
|
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();
|
ImageReader reader = ImageIO.getImageReaders(input).next();
|
||||||
reader.setInput(input);
|
reader.setInput(input);
|
||||||
|
|
||||||
@ -493,7 +490,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
// Read original LZW compressed TIFF
|
// Read original LZW compressed TIFF
|
||||||
IIOImage original;
|
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();
|
ImageReader reader = ImageIO.getImageReaders(input).next();
|
||||||
reader.setInput(input);
|
reader.setInput(input);
|
||||||
|
|
||||||
@ -558,7 +555,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
// Read original LZW compressed TIFF
|
// Read original LZW compressed TIFF
|
||||||
IIOImage original;
|
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();
|
ImageReader reader = ImageIO.getImageReaders(input).next();
|
||||||
reader.setInput(input);
|
reader.setInput(input);
|
||||||
|
|
||||||
@ -618,7 +615,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
// Read original LZW compressed TIFF
|
// Read original LZW compressed TIFF
|
||||||
IIOImage original;
|
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();
|
ImageReader reader = ImageIO.getImageReaders(input).next();
|
||||||
reader.setInput(input);
|
reader.setInput(input);
|
||||||
|
|
||||||
@ -682,7 +679,7 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
// Read original LZW compressed TIFF
|
// Read original LZW compressed TIFF
|
||||||
IIOImage original;
|
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();
|
ImageReader reader = ImageIO.getImageReaders(input).next();
|
||||||
reader.setInput(input);
|
reader.setInput(input);
|
||||||
|
|
||||||
@ -744,4 +741,52 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
|
|||||||
assertTrue("Software metadata not found", softwareFound);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user