#432: Alternate fix + more tests + better alpha handling for TIFF

This commit is contained in:
Harald Kuhr 2018-08-18 13:27:38 +02:00
parent 6f9c83a0a9
commit f71bcc5125
3 changed files with 263 additions and 140 deletions

View File

@ -9,6 +9,8 @@ import java.awt.image.ComponentSampleModel;
import java.awt.image.SampleModel; import java.awt.image.SampleModel;
import java.awt.image.WritableRaster; import java.awt.image.WritableRaster;
import static java.awt.image.DataBuffer.getDataTypeSize;
/** /**
* ExtraSamplesColorModel. * ExtraSamplesColorModel.
* *
@ -22,10 +24,22 @@ final class ExtraSamplesColorModel extends ComponentColorModel {
// still thinks it has numComponents == cs.getNumComponents() + 1 for most operations // still thinks it has numComponents == cs.getNumComponents() + 1 for most operations
private final int numComponents; private final int numComponents;
ExtraSamplesColorModel(ColorSpace cs, boolean isAlphaPremultiplied, int dataType, int extraComponents) { ExtraSamplesColorModel(ColorSpace cs, boolean hasAlpha, boolean isAlphaPremultiplied, int dataType, int extraComponents) {
super(cs, true, isAlphaPremultiplied, Transparency.TRANSLUCENT, dataType); super(cs, bitsArrayHelper(cs, dataType, extraComponents + (hasAlpha ? 1 : 0)), hasAlpha, isAlphaPremultiplied, Transparency.TRANSLUCENT, dataType);
Validate.isTrue(extraComponents > 0, "Extra components must be > 0"); Validate.isTrue(extraComponents > 0, "Extra components must be > 0");
this.numComponents = super.getNumComponents() + extraComponents; this.numComponents = cs.getNumComponents() + (hasAlpha ? 1 : 0) + extraComponents;
}
private static int[] bitsArrayHelper(ColorSpace cs, int dataType, int extraComponents) {
int numBits = getDataTypeSize(dataType);
int numComponents = cs.getNumComponents() + extraComponents;
int[] bits = new int[numComponents];
for (int i = 0; i < numComponents; i++) {
bits[i] = numBits;
}
return bits;
} }
@Override @Override
@ -45,16 +59,18 @@ final class ExtraSamplesColorModel extends ComponentColorModel {
@Override @Override
public WritableRaster getAlphaRaster(WritableRaster raster) { public WritableRaster getAlphaRaster(WritableRaster raster) {
if (hasAlpha() == false) { if (!hasAlpha()) {
return null; return null;
} }
int x = raster.getMinX(); int x = raster.getMinX();
int y = raster.getMinY(); int y = raster.getMinY();
int[] band = new int[1]; int[] band = new int[] {getAlphaComponent()};
band[0] = super.getNumComponents() - 1;
return raster.createWritableChild(x, y, raster.getWidth(), return raster.createWritableChild(x, y, raster.getWidth(), raster.getHeight(), x, y, band);
raster.getHeight(), x, y, }
band);
private int getAlphaComponent() {
return super.getNumComponents() - 1;
} }
} }

View File

@ -438,18 +438,20 @@ public final class TIFFImageReader extends ImageReaderBase {
int opaqueSamplesPerPixel = getOpaqueSamplesPerPixel(interpretation); int opaqueSamplesPerPixel = getOpaqueSamplesPerPixel(interpretation);
// Spec says ExtraSamples are mandatory of extra samples, however known encoders // Spec says ExtraSamples are mandatory for extra samples, however known encoders
// (ie. SeaShore) writes ARGB TIFFs without ExtraSamples. // (ie. SeaShore) writes ARGB TIFFs without ExtraSamples.
long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", false); long[] extraSamples = getValueAsLongArray(TIFF.TAG_EXTRA_SAMPLES, "ExtraSamples", false);
if (extraSamples == null && samplesPerPixel > opaqueSamplesPerPixel) { if (extraSamples == null && samplesPerPixel > opaqueSamplesPerPixel) {
// TODO: Log warning! // TODO: Log warning!
// First extra is alpha, rest is "unspecified" // First extra is alpha, rest is "unspecified" (0)
extraSamples = new long[samplesPerPixel - opaqueSamplesPerPixel]; extraSamples = new long[samplesPerPixel - opaqueSamplesPerPixel];
extraSamples[0] = TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA; extraSamples[0] = TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA;
} }
// Determine alpha // Determine alpha
boolean hasAlpha = extraSamples != null; boolean hasAlpha = extraSamples != null
&& (extraSamples[0] == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA
|| extraSamples[0] == TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA);
boolean isAlphaPremultiplied = hasAlpha && extraSamples[0] == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA; boolean isAlphaPremultiplied = hasAlpha && extraSamples[0] == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA;
int significantSamples = opaqueSamplesPerPixel + (hasAlpha ? 1 : 0); int significantSamples = opaqueSamplesPerPixel + (hasAlpha ? 1 : 0);
@ -577,12 +579,12 @@ public final class TIFFImageReader extends ImageReaderBase {
switch (planarConfiguration) { switch (planarConfiguration) {
case TIFFBaseline.PLANARCONFIG_CHUNKY: case TIFFBaseline.PLANARCONFIG_CHUNKY:
// "TYPE_4BYTE_RGBA" if cs.isCS_sRGB() // "TYPE_4BYTE_RGBA" if cs.isCS_sRGB()
if (extraSamples != null && extraSamples.length == 1) { if (hasAlpha && extraSamples.length == 1) {
return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, isAlphaPremultiplied); return ImageTypeSpecifiers.createInterleaved(cs, new int[] {0, 1, 2, 3}, dataType, true, isAlphaPremultiplied);
} }
else { else {
return new ImageTypeSpecifier( return new ImageTypeSpecifier(
new ExtraSamplesColorModel(cs, isAlphaPremultiplied, dataType, samplesPerPixel - significantSamples), new ExtraSamplesColorModel(cs, hasAlpha, isAlphaPremultiplied, dataType, samplesPerPixel - significantSamples),
new PixelInterleavedSampleModel(dataType, 1, 1, samplesPerPixel, samplesPerPixel, createOffsets(samplesPerPixel)) new PixelInterleavedSampleModel(dataType, 1, 1, samplesPerPixel, samplesPerPixel, createOffsets(samplesPerPixel))
); );
} }
@ -614,9 +616,7 @@ public final class TIFFImageReader extends ImageReaderBase {
IndexColorModel icm = createIndexColorModel(bitsPerSample, dataType, (int[]) colorMap.getValue()); IndexColorModel icm = createIndexColorModel(bitsPerSample, dataType, (int[]) colorMap.getValue());
if (extraSamples != null && extraSamples.length > 0 if (hasAlpha) {
&& (extraSamples[0] == TIFFBaseline.EXTRASAMPLE_ASSOCIATED_ALPHA
|| extraSamples[0] == TIFFBaseline.EXTRASAMPLE_UNASSOCIATED_ALPHA)) {
return ImageTypeSpecifiers.createDiscreteAlphaIndexedFromIndexColorModel(icm); return ImageTypeSpecifiers.createDiscreteAlphaIndexedFromIndexColorModel(icm);
} }

View File

@ -0,0 +1,107 @@
package com.twelvemonkeys.imageio.plugins.tiff;
import com.twelvemonkeys.image.ResampleOp;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import org.junit.Test;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.util.Hashtable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class ExtraSamplesColorModelTest {
private BufferedImage createExtraSamplesImage(int w, int h, ColorSpace cs, boolean hasAlpha, int extraComponents) {
int samplesPerPixel = cs.getNumComponents() + (hasAlpha ? 1 : 0) + extraComponents;
ExtraSamplesColorModel colorModel = new ExtraSamplesColorModel(cs, hasAlpha, true, DataBuffer.TYPE_BYTE, extraComponents);
SampleModel sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, samplesPerPixel, samplesPerPixel * w, createOffsets(samplesPerPixel));
WritableRaster raster = Raster.createWritableRaster(sampleModel, new Point(0, 0));
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Hashtable());
}
private static int[] createOffsets(int samplesPerPixel) {
int[] offsets = new int[samplesPerPixel];
for (int i = 0; i < samplesPerPixel; i++) {
offsets[i] = i;
}
return offsets;
}
@Test
public void testImageWithExtraSamplesCanBeResampledGray() {
for (int i = 1; i < 8; i++) {
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
assertEquals(5, resampled.getHeight());
}
}
@Test
public void testImageWithExtraSamplesCanBeResampledGrayAlpha() {
for (int i = 1; i < 8; i++) {
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
assertEquals(5, resampled.getHeight());
}
}
@Test
public void testImageWithExtraSamplesCanBeResampledRGB() {
for (int i = 1; i < 8; i++) {
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
assertEquals(5, resampled.getHeight());
}
}
@Test
public void testImageWithExtraSamplesCanBeResampledRGBAlpha() {
for (int i = 1; i < 8; i++) {
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
assertEquals(5, resampled.getHeight());
}
}
@Test
public void testImageWithExtraSamplesCanBeResampledCMYK() {
for (int i = 1; i < 8; i++) {
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
assertEquals(5, resampled.getHeight());
}
}
@Test
public void testImageWithExtraSamplesCanBeResampledCMYKAlpha() {
for (int i = 1; i < 8; i++) {
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, i);
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
assertNotNull(resampled);
assertEquals(5, resampled.getWidth());
assertEquals(5, resampled.getHeight());
}
}
}