Compare commits

...

17 Commits

Author SHA1 Message Date
Harald Kuhr e8d427ae5f [maven-release-plugin] prepare release twelvemonkeys-3.3.2 2017-02-02 21:30:39 +01:00
Harald Kuhr c181f74fb0 #306 TIFF LZW write issue test case fix for 3.3 branch. 2017-02-02 21:18:56 +01:00
Harald Kuhr c0f66b5584 #306 TIFF LZW write EOF issue
(cherry picked from commit 94d6ad1)
2017-02-02 21:11:30 +01:00
Harald Kuhr 69dcfe9713 #307 PIXTIFF ZIP compression 50013 support
(cherry picked from commit d57e414)
2017-02-02 21:11:07 +01:00
Harald Kuhr 5aac07f221 #307 PIXTIFF ZIP compression 50013 support
(cherry picked from commit 9199083)
2017-02-02 21:09:38 +01:00
Harald Kuhr aa3e2cc019 #312, #313, #314 Fixed ClassCastException when rewriting compressed TIFF images.
(cherry picked from commit 6ea1ea8)
2017-02-02 21:06:07 +01:00
Harald Kuhr b0108fe39b #306 TIFF LZW IndexColorModel issue + sequence index
(cherry picked from commit 762b596)
2017-02-02 20:58:11 +01:00
Harald Kuhr 3f6a27b75e Code clean-up.
(cherry picked from commit 10b8c11)
2017-02-02 20:42:54 +01:00
Harald Kuhr cb2cf0721c Added sequence writing illegal state tests
(cherry picked from commit ed6f70f)
2017-02-02 20:42:26 +01:00
Harald Kuhr f49b57b3de #297 JPEGLossless no supports AC tables only image + multiple tables images
(cherry picked from commit 7a0660c)
2017-02-02 20:36:25 +01:00
Harald Kuhr 22593e37f7 Made JPEG lossless decoding seemingly much faster.
(cherry picked from commit 65e3156)
2017-02-02 20:35:00 +01:00
Harald Kuhr ad86bcda7e #300 TIFF old style JPEG inconsistent metadata
(cherry picked from commit 753afd0)
2017-02-02 20:32:01 +01:00
Harald Kuhr 41e6b041c9 [maven-release-plugin] prepare for next development iteration 2016-11-29 20:31:57 +01:00
Harald Kuhr 4e5127404f [maven-release-plugin] prepare release twelvemonkeys-3.3.1 2016-11-29 20:31:51 +01:00
Harald Kuhr 2cede0e8cc Updating POM versions for bugfix release 2016-11-29 20:25:38 +01:00
Harald Kuhr e189b5e14f #294 Fixed regression, reading JPEGs containing strange APPn markers without identifier string 2016-11-29 20:10:28 +01:00
Harald Kuhr f0316f7ec5 #274 Fixed bug in 16 -> 8 bit downsampling of DQT, causing "Bogus marker length" IIOException 2016-11-29 20:09:42 +01:00
50 changed files with 574 additions and 193 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -29,7 +29,6 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.lang.Validate;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
@@ -48,10 +47,10 @@ class Application extends Segment {
final String identifier;
final byte[] data;
Application(int marker, final String identifier, final byte[] data) {
Application(final int marker, final String identifier, final byte[] data) {
super(marker);
this.identifier = Validate.notEmpty(identifier, "identifier");
this.identifier = identifier; // NOTE: Some JPEGs contain APP segments without NULL-terminated identifier
this.data = data;
}
@@ -33,41 +33,27 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInput;
import java.io.IOException;
final class HuffmanTable extends Segment {
final int l[][][] = new int[4][2][16];
final int th[] = new int[4]; // 1: this table is presented
private final int l[][][] = new int[4][2][16];
private final int th[] = new int[4]; // 1: this table is present
final int v[][][][] = new int[4][2][16][200]; // tables
final int[][] tc = new int[4][2]; // 1: this table is presented
final int[][] tc = new int[4][2]; // 1: this table is present
public static final int MSB = 0x80000000;
static final int MSB = 0x80000000;
private HuffmanTable() {
super(JPEG.DHT);
}
tc[0][0] = 0;
tc[1][0] = 0;
tc[2][0] = 0;
tc[3][0] = 0;
tc[0][1] = 0;
tc[1][1] = 0;
tc[2][1] = 0;
tc[3][1] = 0;
th[0] = 0;
th[1] = 0;
th[2] = 0;
th[3] = 0;
}
protected void buildHuffTables(final int[][][] HuffTab) throws IOException {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 2; j++) {
if (tc[i][j] != 0) {
buildHuffTable(HuffTab[i][j], l[i][j], v[i][j]);
void buildHuffTables(final int[][][] HuffTab) throws IOException {
for (int t = 0; t < 4; t++) {
for (int c = 0; c < 2; c++) {
if (tc[t][c] != 0) {
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]);
}
}
}
@@ -81,10 +67,8 @@ final class HuffmanTable extends Segment {
// Effect:
// build up HuffTab[t][c] using L and V.
private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException {
int currentTable, temp;
int k;
temp = 256;
k = 0;
int temp = 256;
int k = 0;
for (int i = 0; i < 8; i++) { // i+1 is Code length
for (int j = 0; j < L[i]; j++) {
@@ -99,7 +83,7 @@ final class HuffmanTable extends Segment {
tab[k] = i | MSB;
}
currentTable = 1;
int currentTable = 1;
k = 0;
for (int i = 8; i < 16; i++) { // i+1 is Code length
@@ -122,8 +106,27 @@ final class HuffmanTable extends Segment {
@Override
public String toString() {
// TODO: Id and class for tables
return "DHT[]";
StringBuilder builder = new StringBuilder("DHT[");
for (int t = 0; t < tc.length; t++) {
for (int c = 0; c < tc[t].length; c++) {
if (tc[t][c] != 0) {
if (builder.length() > 4) {
builder.append(", ");
}
builder.append("id: ");
builder.append(t);
builder.append(", class: ");
builder.append(c);
}
}
}
builder.append(']');
return builder.toString();
}
public static Segment read(final DataInput data, final int length) throws IOException {
@@ -109,37 +109,7 @@ public class JPEGImageReader extends ImageReaderBase {
static final int ALL_APP_MARKERS = -1;
/** Segment identifiers for the JPEG segments we care about reading. */
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS; //createSegmentIds();
private static Map<Integer, List<String>> createSegmentIds() {
Map<Integer, List<String>> map = new LinkedHashMap<>();
// Need all APP markers to be able to re-generate proper metadata later
for (int appMarker = JPEG.APP0; appMarker <= JPEG.APP15; appMarker++) {
map.put(appMarker, JPEGSegmentUtil.ALL_IDS);
}
// SOFn markers
map.put(JPEG.SOF0, null);
map.put(JPEG.SOF1, null);
map.put(JPEG.SOF2, null);
map.put(JPEG.SOF3, null);
map.put(JPEG.SOF5, null);
map.put(JPEG.SOF6, null);
map.put(JPEG.SOF7, null);
map.put(JPEG.SOF9, null);
map.put(JPEG.SOF10, null);
map.put(JPEG.SOF11, null);
map.put(JPEG.SOF13, null);
map.put(JPEG.SOF14, null);
map.put(JPEG.SOF15, null);
map.put(JPEG.DQT, null);
map.put(JPEG.DHT, null);
map.put(JPEG.SOS, null);
return Collections.unmodifiableMap(map);
}
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS;
/** Our JPEG reading delegate */
private final ImageReader delegate;
@@ -385,7 +355,7 @@ public class JPEGImageReader extends ImageReaderBase {
// TODO: What about stream position?
// TODO: Param handling: Source region, offset, subsampling, destination, destination type, etc....
// Read image as lossless
return new JPEGLosslessDecoderWrapper().readImage(segments, imageInput);
return new JPEGLosslessDecoderWrapper(this).readImage(segments, imageInput);
}
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
@@ -723,7 +693,7 @@ public class JPEGImageReader extends ImageReaderBase {
catch (IOException e) {
// TODO: Handle bad segments better, for now, just ignore any bad APP markers
if (segment.marker() >= JPEG.APP0 && JPEG.APP15 >= segment.marker()) {
processWarningOccurred("Bogus " +segment.identifier() + " segment, ignoring");
processWarningOccurred("Bogus APP" + (segment.marker() & 0x0f) + "/" + segment.identifier() + " segment, ignoring");
continue;
}
@@ -944,7 +914,7 @@ public class JPEGImageReader extends ImageReaderBase {
if (isLossless()) {
// TODO: What about stream position?
// TODO: Param handling: Reading as raster should support source region, subsampling etc.
return new JPEGLosslessDecoderWrapper().readRaster(segments, imageInput);
return new JPEGLosslessDecoderWrapper(this).readRaster(segments, imageInput);
}
return delegate.readRaster(imageIndex, param);
@@ -36,14 +36,16 @@ import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
final class JPEGLosslessDecoder {
private final ImageInputStream input;
private final JPEGImageReader listenerDelegate;
private final Frame frame;
private final HuffmanTable huffTable;
private final List<HuffmanTable> huffTables;
private final QuantizationTable quantTable;
private Scan scan;
@@ -103,7 +105,7 @@ final class JPEGLosslessDecoder {
return yDim;
}
JPEGLosslessDecoder(final List<Segment> segments, final ImageInputStream data) {
JPEGLosslessDecoder(final List<Segment> segments, final ImageInputStream data, final JPEGImageReader listenerDelegate) {
Validate.notNull(segments);
frame = get(segments, Frame.class);
@@ -111,11 +113,25 @@ final class JPEGLosslessDecoder {
QuantizationTable qt = get(segments, QuantizationTable.class);
quantTable = qt != null ? qt : new QuantizationTable(); // For lossless there are no DQTs
huffTable = get(segments, HuffmanTable.class); // For non-lossless there can be multiple of DHTs
huffTables = getAll(segments, HuffmanTable.class); // For lossless there's usually only one, and only DC tables
RestartInterval dri = get(segments, RestartInterval.class);
restartInterval = dri != null ? dri.interval : 0;
input = data;
this.listenerDelegate = listenerDelegate;
}
private <T> List<T> getAll(final List<Segment> segments, final Class<T> type) {
ArrayList<T> list = new ArrayList<>();
for (Segment segment : segments) {
if (type.isInstance(segment)) {
list.add(type.cast(segment));
}
}
return list;
}
private <T> T get(final List<Segment> segments, final Class<T> type) {
@@ -138,10 +154,13 @@ final class JPEGLosslessDecoder {
current = input.readUnsignedShort();
if (current != JPEG.SOI) { // SOI
throw new IIOException("Not a JPEG file");
throw new IIOException("Not a JPEG file, does not start with 0xFFD8");
}
for (HuffmanTable huffTable : huffTables) {
huffTable.buildHuffTables(HuffTab);
}
huffTable.buildHuffTables(HuffTab);
quantTable.enhanceTables(TABLE);
current = input.readUnsignedShort();
@@ -175,8 +194,23 @@ final class JPEGLosslessDecoder {
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
qTab[i] = quantTables[component.qtSel];
nBlock[i] = component.vSub * component.hSub;
dcTab[i] = HuffTab[scanComps[i].dcTabSel][0];
acTab[i] = HuffTab[scanComps[i].acTabSel][1];
int dcTabSel = scanComps[i].dcTabSel;
int acTabSel = scanComps[i].acTabSel;
// NOTE: If we don't find any DC tables for lossless operation, this file isn't any good.
// However, we have seen files with AC tables only, we'll treat these as if the AC was DC
if (useACForDC(dcTabSel)) {
processWarningOccured("Lossless JPEG with no DC tables encountered. Assuming only tables present to be DC tables.");
dcTab[i] = HuffTab[dcTabSel][1];
acTab[i] = HuffTab[acTabSel][0];
}
else {
// All good
dcTab[i] = HuffTab[dcTabSel][0];
acTab[i] = HuffTab[acTabSel][1];
}
}
xDim = frame.samplesPerLine;
@@ -202,10 +236,8 @@ final class JPEGLosslessDecoder {
scanNum++;
while (true) { // Decode one scan
final int temp[] = new int[1]; // to store remainder bits
final int index[] = new int[1];
temp[0] = 0;
index[0] = 0;
int temp[] = new int[1]; // to store remainder bits
int index[] = new int[1];
for (int i = 0; i < 10; i++) {
pred[i] = (1 << (precision - 1));
@@ -242,10 +274,7 @@ final class JPEGLosslessDecoder {
}
}
if ((current >= RESTART_MARKER_BEGIN) && (current <= RESTART_MARKER_END)) {
//empty
}
else {
if ((current < RESTART_MARKER_BEGIN) || (current > RESTART_MARKER_END)) {
break; //current=MARKER
}
}
@@ -259,6 +288,34 @@ final class JPEGLosslessDecoder {
return outputRef;
}
private void processWarningOccured(String warning) {
listenerDelegate.processWarningOccurred(warning);
}
private boolean useACForDC(final int dcTabSel) {
if (isLossless()) {
for (HuffmanTable huffTable : huffTables) {
if (huffTable.tc[dcTabSel][0] == 0 && huffTable.tc[dcTabSel][1] != 0) {
return true;
}
}
}
return false;
}
private boolean isLossless() {
switch (frame.marker) {
case JPEG.SOF3:
case JPEG.SOF7:
case JPEG.SOF11:
case JPEG.SOF15:
return true;
default:
return false;
}
}
private Frame.Component getComponentSpec(Frame.Component[] components, int sel) {
for (Frame.Component component : components) {
if (component.id == sel) {
@@ -320,15 +377,15 @@ final class JPEGLosslessDecoder {
}
for (int i = 0; i < nBlock[0]; i++) {
final int value = getHuffmanValue(dcTab[0], temp, index);
int value = getHuffmanValue(dcTab[0], temp, index);
if (value >= 0xFF00) {
return value;
}
final int n = getn(prev, value, temp, index);
int n = getn(prev, value, temp, index);
final int nRestart = (n >> 8);
int nRestart = (n >> 8);
if ((nRestart >= RESTART_MARKER_BEGIN) && (nRestart <= RESTART_MARKER_END)) {
return nRestart;
}
@@ -29,6 +29,9 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
@@ -54,6 +57,12 @@ import java.util.List;
*/
final class JPEGLosslessDecoderWrapper {
private final JPEGImageReader listenerDelegate;
JPEGLosslessDecoderWrapper(final JPEGImageReader listenerDelegate) {
this.listenerDelegate = listenerDelegate;
}
/**
* Decodes a JPEG Lossless stream to a {@code BufferedImage}.
* Currently the following conversions are supported:
@@ -61,14 +70,19 @@ final class JPEGLosslessDecoderWrapper {
* - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY
* - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY
*
* @param segments
* @param segments segments
* @param input input stream which contains a jpeg lossless data
* @return if successfully a BufferedImage is returned
* @throws IOException is thrown if the decoder failed or a conversion is not supported
*/
BufferedImage readImage(final List<Segment> segments, final ImageInputStream input) throws IOException {
JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(segments, input);
JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(segments, createBufferedInput(input), listenerDelegate);
// TODO: Allow 10/12/14 bit (using a ComponentColorModel with correct bits, as in TIFF)
// TODO: Rewrite this to pass a pre-allocated buffer of correct type (byte/short)/correct bands
// TODO: Progress callbacks
// TODO: Warning callbacks
// Callback can then do subsampling etc.
int[][] decoded = decoder.decode();
int width = decoder.getDimX();
int height = decoder.getDimY();
@@ -80,23 +94,21 @@ final class JPEGLosslessDecoderWrapper {
return to8Bit1ComponentGrayScale(decoded, width, height);
case 16:
return to16Bit1ComponentGrayScale(decoded, width, height);
default:
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 1 component cannot be decoded");
}
}
// 3 components, assumed to be RGB
if (decoder.getNumComponents() == 3) {
else if (decoder.getNumComponents() == 3) {
switch (decoder.getPrecision()) {
case 8:
return to24Bit3ComponentRGB(decoded, width, height);
default:
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 3 components cannot be decoded");
}
}
throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) cannot be decoded");
throw new IIOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) not supported");
}
private ImageInputStream createBufferedInput(final ImageInputStream input) throws IOException {
return input instanceof BufferedImageInputStream ? input : new BufferedImageInputStream(input);
}
Raster readRaster(final List<Segment> segments, final ImageInputStream input) throws IOException {
@@ -361,7 +361,6 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
return stream.read(b, off, len);
}
@Override
public String toString() {
return String.format("0x%04x[%d-%d]", marker, realStart, realEnd());
@@ -409,7 +408,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
byte[] replacementData = new byte[length];
int numQTs = length / 128;
int newSegmentLength = 2 + 1 + 64 * numQTs;
int newSegmentLength = 2 + (1 + 64) * numQTs; // Len + (qtInfo + qtSize) * numQTs
replacementData[0] = (byte) ((JPEG.DQT >> 8) & 0xff);
replacementData[1] = (byte) (JPEG.DQT & 0xff);
@@ -94,10 +94,12 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
new TestData(getClassLoaderResource("/jpeg/jfif-padded-segments.jpg"), new Dimension(20, 45)),
new TestData(getClassLoaderResource("/jpeg/0x00-to-0xFF-between-segments.jpg"), new Dimension(16, 16)),
new TestData(getClassLoaderResource("/jpeg/jfif-bogus-empty-jfif-segment.jpg"), new Dimension(942, 714)),
new TestData(getClassLoaderResource("/jpeg/app-marker-missing-null-term.jpg"), new Dimension(200, 150)),
new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131)),
new TestData(getClassLoaderResource("/jpeg-lossless/8_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 8 bit
new TestData(getClassLoaderResource("/jpeg-lossless/16_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 16 bit
new TestData(getClassLoaderResource("/jpeg-lossless/24_ls.jpg"), new Dimension(800, 535)), // Lossless RGB, 8 bit per component (24 bit)
new TestData(getClassLoaderResource("/jpeg-lossless/f-18.jpg"), new Dimension(320, 240)), // Lossless RGB, 3 DHTs
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_rgb.jpg"), new Dimension(227, 149)), // Lossless RGB, 8 bit per component (24 bit)
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_gray.jpg"), new Dimension(512, 512)) // Lossless gray, 16 bit
);
Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: reference test cases</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
@@ -38,6 +38,8 @@ import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import static com.twelvemonkeys.imageio.plugins.tiff.HorizontalDifferencingStream.isValidBPS;
/**
* A decoder for data converted using "horizontal differencing predictor".
*
@@ -67,21 +69,6 @@ final class HorizontalDeDifferencingStream extends InputStream {
buffer.flip();
}
private boolean isValidBPS(final int bitsPerSample) {
switch (bitsPerSample) {
case 1:
case 2:
case 4:
case 8:
case 16:
case 32:
case 64:
return true;
default:
return false;
}
}
@SuppressWarnings("StatementWithEmptyBody")
private boolean fetch() throws IOException {
buffer.clear();
@@ -66,7 +66,7 @@ final class HorizontalDifferencingStream extends OutputStream {
buffer = ByteBuffer.allocate((columns * samplesPerPixel * bitsPerSample + 7) / 8).order(byteOrder);
}
private boolean isValidBPS(final int bitsPerSample) {
static boolean isValidBPS(final int bitsPerSample) {
switch (bitsPerSample) {
case 1:
case 2:
@@ -52,6 +52,10 @@ interface TIFFCustom {
int COMPRESSION_JPEG2000 = 34712;
// TODO: Aperio SVS JPEG2000: 33003 (YCbCr) and 33005 (RGB), see http://openslide.org/formats/aperio/
// PIXTIFF aka DELL PixTools, see https://community.emc.com/message/515755#515755
/** PIXTIFF proprietary ZIP compression, identical to Deflate/ZLib. */
int COMPRESSION_PIXTIFF_ZIP = 50013;
int PHOTOMETRIC_LOGL = 32844;
int PHOTOMETRIC_LOGLUV = 32845;
@@ -815,20 +815,20 @@ public class TIFFImageReader extends ImageReaderBase {
Boolean needsCSConversion = null;
switch (compression) {
// TIFF Baseline
case TIFFBaseline.COMPRESSION_NONE:
// No compression
case TIFFExtension.COMPRESSION_DEFLATE:
// 'PKZIP-style' Deflate
case TIFFBaseline.COMPRESSION_PACKBITS:
// PackBits
case TIFFExtension.COMPRESSION_LZW:
// LZW
case TIFFExtension.COMPRESSION_ZLIB:
// 'Adobe-style' Deflate
case TIFFExtension.COMPRESSION_DEFLATE:
// 'PKZIP-style' Deflate
case TIFFCustom.COMPRESSION_PIXTIFF_ZIP:
// PIXTIFF proprietary 'ZIP' compression, same as Deflate
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
// CCITT modified Huffman
// Additionally, the specification defines these values as part of the TIFF extensions:
case TIFFExtension.COMPRESSION_CCITT_T4:
// CCITT Group 3 fax encoding
case TIFFExtension.COMPRESSION_CCITT_T6:
@@ -1008,7 +1008,7 @@ public class TIFFImageReader extends ImageReaderBase {
// TODO: If we have non-standard reference B/W or yCbCr coefficients,
// we might still have to do extra color space conversion...
if (needsCSConversion == null) {
needsCSConversion = needsCSConversion(interpretation, jpegReader.getImageMetadata(0));
needsCSConversion = needsCSConversion(interpretation, readJPEGMetadataSafe(jpegReader));
}
if (!needsCSConversion) {
@@ -1146,7 +1146,7 @@ public class TIFFImageReader extends ImageReaderBase {
jpegParam.setSourceSubsampling(xSub, ySub, 0, 0);
if (needsCSConversion == null) {
needsCSConversion = needsCSConversion(interpretation, jpegReader.getImageMetadata(0));
needsCSConversion = needsCSConversion(interpretation, readJPEGMetadataSafe(jpegReader));
}
if (!needsCSConversion) {
@@ -1251,7 +1251,7 @@ public class TIFFImageReader extends ImageReaderBase {
Point offset = new Point(col - srcRegion.x, row - srcRegion.y);
if (needsCSConversion == null) {
needsCSConversion = needsCSConversion(interpretation, jpegReader.getImageMetadata(0));
needsCSConversion = needsCSConversion(interpretation, readJPEGMetadataSafe(jpegReader));
}
if (!needsCSConversion) {
@@ -1319,6 +1319,17 @@ public class TIFFImageReader extends ImageReaderBase {
return destination;
}
private IIOMetadata readJPEGMetadataSafe(final ImageReader jpegReader) throws IOException {
try {
return jpegReader.getImageMetadata(0);
}
catch (IIOException e) {
processWarningOccurred("Could not read metadata metadata JPEG compressed TIFF: " + e.getMessage() + " colors may look incorrect");
return null;
}
}
private boolean needsCSConversion(final int photometricInterpretation, final IIOMetadata imageMetadata) throws IOException {
if (imageMetadata == null) {
// Assume we're ok
@@ -1918,6 +1929,7 @@ public class TIFFImageReader extends ImageReaderBase {
case TIFFExtension.COMPRESSION_ZLIB:
case TIFFExtension.COMPRESSION_DEFLATE:
// TIFF specification, supplement 2 says ZLIB (8) and DEFLATE (32946) algorithms are identical
case TIFFCustom.COMPRESSION_PIXTIFF_ZIP:
return new InflaterInputStream(stream, new Inflater(), 1024);
case TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE:
case TIFFExtension.COMPRESSION_CCITT_T4:
@@ -38,11 +38,13 @@ import com.twelvemonkeys.imageio.metadata.exif.Rational;
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.io.enc.EncoderStream;
import com.twelvemonkeys.io.enc.PackBitsEncoder;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.*;
import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
@@ -68,19 +70,12 @@ import java.util.zip.DeflaterOutputStream;
* @version $Id: TIFFImageWriter.java,v 1.0 18.09.13 12:46 haraldk Exp$
*/
public final class TIFFImageWriter extends ImageWriterBase {
// Short term
// TODO: Support more of the ImageIO metadata (ie. compression from metadata, etc)
// Long term
// TODO: Support tiling
// TODO: Support thumbnails
// TODO: Support CCITT Modified Huffman compression (2)
// TODO: Full "Baseline TIFF" support (pending CCITT compression 2)
// TODO: CCITT compressions T.4 and T.6
// TODO: Support JPEG compression of CMYK data (pending JPEGImageWriter CMYK write support)
// ----
// TODO: Support storing multiple images in one stream (multi-page TIFF)
// TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata
// TODO: Support use-case: Transcode multi-layer PSD to multi-page TIFF with metadata (hard, as Photoshop don't store layers as multi-page TIFF...)
// TODO: Support use-case: Transcode multi-page TIFF to multiple single-page TIFFs with metadata
// TODO: Support use-case: Losslessly transcode JPEG to JPEG-in-TIFF with (EXIF) metadata (and back)
@@ -98,13 +93,20 @@ public final class TIFFImageWriter extends ImageWriterBase {
// Support JPEG compression (7) - might need extra input to allow multiple images with single DQT
// Use sensible defaults for compression based on input? None is sensible... :-)
// Support resolution, resolution unit and software tags from ImageIO metadata
// Support CCITT Modified Huffman compression (2)
// Full "Baseline TIFF" support (pending CCITT compression 2)
// CCITT compressions T.4 and T.6
// Support storing multiple images in one stream (multi-page TIFF)
// Support more of the ImageIO metadata (ie. compression from metadata, etc)
public static final Rational STANDARD_DPI = new Rational(72);
private static final Rational STANDARD_DPI = new Rational(72);
/**
* Flag for active sequence writing
*/
private boolean isWritingSequence = false;
private boolean writingSequence = false;
private int sequenceIndex = 0;
/**
* Metadata writer for sequence writing
@@ -203,12 +205,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
EXIFWriter exifWriter = new EXIFWriter();
exifWriter.writeTIFFHeader(imageOutput);
writePage(image, param, exifWriter, imageOutput.getStreamPosition());
writePage(0, image, param, exifWriter, imageOutput.getStreamPosition());
imageOutput.flush();
}
private long writePage(IIOImage image, ImageWriteParam param, EXIFWriter exifWriter, long lastIFDPointerOffset)
private long writePage(int imageIndex, IIOImage image, ImageWriteParam param, EXIFWriter exifWriter, long lastIFDPointerOffset)
throws IOException {
RenderedImage renderedImage = image.getRenderedImage();
@@ -262,7 +264,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
int compression;
if ((param == null || param.getCompressionMode() == TIFFImageWriteParam.MODE_COPY_FROM_METADATA)
&& image.getMetadata() != null && metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION) != null) {
compression = (int) metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION).getValue();
compression = ((Number) metadata.getIFD().getEntryById(TIFF.TAG_COMPRESSION).getValue()).intValue();
}
else {
compression = TIFFImageWriteParam.getCompressionType(param);
@@ -307,12 +309,13 @@ public final class TIFFImageWriter extends ImageWriterBase {
default:
}
// TODO: We might want to support CMYK in JPEG as well... Pending JPEG CMYK write support.
int photometric = compression == TIFFExtension.COMPRESSION_JPEG ? TIFFExtension.PHOTOMETRIC_YCBCR : getPhotometricInterpretation(colorModel);
int photometric = getPhotometricInterpretation(colorModel, compression);
entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, photometric));
if (photometric == TIFFBaseline.PHOTOMETRIC_PALETTE && colorModel instanceof IndexColorModel) {
entries.put(TIFF.TAG_COLOR_MAP, new TIFFEntry(TIFF.TAG_COLOR_MAP, createColorMap((IndexColorModel) colorModel)));
// TODO: Fix consistency between sampleModel.getSampleSize() and colorModel.getPixelSize()...
// We should be able to support 1, 2, 4 and 8 bits per sample at least, and probably 3, 5, 6 and 7 too
entries.put(TIFF.TAG_COLOR_MAP, new TIFFEntry(TIFF.TAG_COLOR_MAP, createColorMap((IndexColorModel) colorModel, sampleModel.getSampleSize(0))));
entries.put(TIFF.TAG_SAMPLES_PER_PIXEL, new TIFFEntry(TIFF.TAG_SAMPLES_PER_PIXEL, 1));
}
else {
@@ -419,6 +422,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
ImageWriter jpegWriter = writers.next();
try {
jpegWriter.setOutput(new SubImageOutputStream(imageOutput));
ListenerDelegate listener = new ListenerDelegate(imageIndex);
jpegWriter.addIIOWriteProgressListener(listener);
jpegWriter.addIIOWriteWarningListener(listener);
jpegWriter.write(renderedImage);
}
finally {
@@ -427,7 +433,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
}
else {
// Write image data
writeImageData(createCompressorStream(renderedImage, param, entries), renderedImage, numBands, bandOffsets, bitOffsets);
writeImageData(createCompressorStream(renderedImage, param, entries), imageIndex, renderedImage, numBands, bandOffsets, bitOffsets);
}
long stripByteCount = imageOutput.getStreamPosition() - stripOffset;
@@ -517,9 +523,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
output.length: 12600399
*/
int samplesPerPixel = (Integer) entries.get(TIFF.TAG_SAMPLES_PER_PIXEL).getValue();
int bitPerSample = ((short[]) entries.get(TIFF.TAG_BITS_PER_SAMPLE).getValue())[0];
// Use predictor by default for LZW and ZLib/Deflate
// TODO: Unless explicitly disabled in TIFFImageWriteParam
int compression = (int) entries.get(TIFF.TAG_COMPRESSION).getValue();
int compression = ((Number) entries.get(TIFF.TAG_COMPRESSION).getValue()).intValue();
OutputStream stream;
switch (compression) {
@@ -555,16 +564,16 @@ public final class TIFFImageWriter extends ImageWriterBase {
stream = IIOUtil.createStreamAdapter(imageOutput);
stream = new DeflaterOutputStream(stream, new Deflater(deflateSetting), 1024);
if (entries.containsKey(TIFF.TAG_PREDICTOR) && entries.get(TIFF.TAG_PREDICTOR).getValue().equals(TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING)) {
stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(), image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), samplesPerPixel, bitPerSample, imageOutput.getByteOrder());
}
return new DataOutputStream(stream);
case TIFFExtension.COMPRESSION_LZW:
stream = IIOUtil.createStreamAdapter(imageOutput);
stream = new EncoderStream(stream, new LZWEncoder((image.getTileWidth() * image.getTileHeight() * image.getColorModel().getPixelSize() + 7) / 8));
stream = new EncoderStream(stream, new LZWEncoder(((image.getTileWidth() * samplesPerPixel * bitPerSample + 7) / 8) * image.getTileHeight()));
if (entries.containsKey(TIFF.TAG_PREDICTOR) && entries.get(TIFF.TAG_PREDICTOR).getValue().equals(TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING)) {
stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), image.getTile(0, 0).getNumBands(), image.getColorModel().getComponentSize(0), imageOutput.getByteOrder());
stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), samplesPerPixel, bitPerSample, imageOutput.getByteOrder());
}
return new DataOutputStream(stream);
@@ -575,7 +584,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
long option = 0L;
if (compression != TIFFBaseline.COMPRESSION_CCITT_MODIFIED_HUFFMAN_RLE) {
option = (long) entries.get(compression == TIFFExtension.COMPRESSION_CCITT_T4 ? TIFF.TAG_GROUP3OPTIONS : TIFF.TAG_GROUP4OPTIONS).getValue();
Entry optionsEntry = entries.get(compression == TIFFExtension.COMPRESSION_CCITT_T4 ? TIFF.TAG_GROUP3OPTIONS : TIFF.TAG_GROUP4OPTIONS);
option = ((Number) optionsEntry.getValue()).longValue();
}
Entry fillOrderEntry = entries.get(TIFF.TAG_FILL_ORDER);
@@ -589,7 +599,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
throw new IllegalArgumentException(String.format("Unsupported TIFF compression: %d", compression));
}
private int getPhotometricInterpretation(final ColorModel colorModel) {
private int getPhotometricInterpretation(final ColorModel colorModel, int compression) {
if (colorModel.getPixelSize() == 1) {
if (colorModel instanceof IndexColorModel) {
if (colorModel.getRGB(0) == 0xFFFFFFFF && colorModel.getRGB(1) == 0xFF000000) {
@@ -611,7 +621,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
case ColorSpace.TYPE_GRAY:
return TIFFBaseline.PHOTOMETRIC_BLACK_IS_ZERO;
case ColorSpace.TYPE_RGB:
return TIFFBaseline.PHOTOMETRIC_RGB;
return compression == TIFFExtension.COMPRESSION_JPEG ? TIFFExtension.PHOTOMETRIC_YCBCR : TIFFBaseline.PHOTOMETRIC_RGB;
case ColorSpace.TYPE_CMYK:
return TIFFExtension.PHOTOMETRIC_SEPARATED;
}
@@ -619,12 +629,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
throw new IllegalArgumentException("Can't determine PhotometricInterpretation for color model: " + colorModel);
}
private short[] createColorMap(final IndexColorModel colorModel) {
private short[] createColorMap(final IndexColorModel colorModel, final int sampleSize) {
// TIFF6.pdf p. 23:
// A TIFF color map is stored as type SHORT, count = 3 * (2^BitsPerSample)
// "In a TIFF ColorMap, all the Red values come first, followed by the Green values, then the Blue values.
// In the ColorMap, black is represented by 0,0,0 and white is represented by 65535, 65535, 65535."
short[] colorMap = new short[(int) (3 * Math.pow(2, colorModel.getPixelSize()))];
short[] colorMap = new short[(int) (3 * Math.pow(2, sampleSize))];
for (int i = 0; i < colorModel.getMapSize(); i++) {
int color = colorModel.getRGB(i);
@@ -650,14 +660,14 @@ public final class TIFFImageWriter extends ImageWriterBase {
return shorts;
}
private void writeImageData(DataOutput stream, RenderedImage renderedImage, int numComponents, int[] bandOffsets, int[] bitOffsets) throws IOException {
private void writeImageData(DataOutput stream, int imageIndex, RenderedImage renderedImage, int numComponents, int[] bandOffsets, int[] bitOffsets) throws IOException {
// 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 IndexColorModel as is
// Store BYTE_GRAY as is
// Store USHORT_GRAY as is
processImageStarted(0);
processImageStarted(imageIndex);
final int minTileY = renderedImage.getMinTileY();
final int maxYTiles = minTileY + renderedImage.getNumYTiles();
@@ -843,7 +853,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
}
// TODO: Report better progress
processImageProgress((100f * yTile) / maxYTiles);
processImageProgress((100f * (yTile + 1)) / maxYTiles);
}
if (stream instanceof DataOutputStream) {
@@ -918,8 +928,17 @@ public final class TIFFImageWriter extends ImageWriterBase {
}
}
// TODO: Set values from imageType
entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, getPhotometricInterpretation(imageType.getColorModel())));
int compression;
if ((param == null || param.getCompressionMode() == TIFFImageWriteParam.MODE_COPY_FROM_METADATA)
&& ifd != null && ifd.getEntryById(TIFF.TAG_COMPRESSION) != null) {
compression = ((Number) ifd.getEntryById(TIFF.TAG_COMPRESSION).getValue()).intValue();
}
else {
compression = TIFFImageWriteParam.getCompressionType(param);
}
int photometricInterpretation = getPhotometricInterpretation(imageType.getColorModel(), compression);
entries.put(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, new TIFFEntry(TIFF.TAG_PHOTOMETRIC_INTERPRETATION, TIFF.TYPE_SHORT, photometricInterpretation));
// TODO: Set values from param if != null + combined values...
@@ -950,13 +969,13 @@ public final class TIFFImageWriter extends ImageWriterBase {
@Override
public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException {
if (isWritingSequence) {
if (writingSequence) {
throw new IllegalStateException("sequence writing has already been started!");
}
// Ignore streamMetadata. ByteOrder is determined from OutputStream
assertOutput();
isWritingSequence = true;
writingSequence = true;
sequenceExifWriter = new EXIFWriter();
sequenceExifWriter.writeTIFFHeader(imageOutput);
sequenceLastIFDPos = imageOutput.getStreamPosition();
@@ -964,7 +983,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
@Override
public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException {
if (!isWritingSequence) {
if (!writingSequence) {
throw new IllegalStateException("prepareWriteSequence() must be called before writeToSequence()!");
}
@@ -972,16 +991,17 @@ public final class TIFFImageWriter extends ImageWriterBase {
imageOutput.flushBefore(sequenceLastIFDPos);
}
sequenceLastIFDPos = writePage(image, param, sequenceExifWriter, sequenceLastIFDPos);
sequenceLastIFDPos = writePage(sequenceIndex++, image, param, sequenceExifWriter, sequenceLastIFDPos);
}
@Override
public void endWriteSequence() throws IOException {
if (!isWritingSequence) {
if (!writingSequence) {
throw new IllegalStateException("prepareWriteSequence() must be called before endWriteSequence()!");
}
isWritingSequence = false;
writingSequence = false;
sequenceIndex = 0;
sequenceExifWriter = null;
sequenceLastIFDPos = -1;
imageOutput.flush();
@@ -991,7 +1011,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
protected void resetMembers() {
super.resetMembers();
isWritingSequence = false;
writingSequence = false;
sequenceIndex = 0;
sequenceExifWriter = null;
sequenceLastIFDPos = -1;
}
@@ -1112,7 +1133,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
System.err.println("output.length: " + output.length());
// TODO: Support writing multipage TIFF
// ImageOutputStream stream = ImageIO.createImageOutputStream(output);
// try {
// writer.setOutput(stream);
@@ -1134,4 +1154,52 @@ public final class TIFFImageWriter extends ImageWriterBase {
TIFFImageReader.showIt(read, output.getName());
}
private class ListenerDelegate extends ProgressListenerBase implements IIOWriteWarningListener {
private final int imageIndex;
public ListenerDelegate(final int imageIndex) {
this.imageIndex = imageIndex;
}
@Override
public void imageComplete(ImageWriter source) {
processImageComplete();
}
@Override
public void imageProgress(ImageWriter source, float percentageDone) {
processImageProgress(percentageDone);
}
@Override
public void imageStarted(ImageWriter source, int imageIndex) {
processImageStarted(this.imageIndex);
}
@Override
public void thumbnailComplete(ImageWriter source) {
processThumbnailComplete();
}
@Override
public void thumbnailProgress(ImageWriter source, float percentageDone) {
processThumbnailProgress(percentageDone);
}
@Override
public void thumbnailStarted(ImageWriter source, int imageIndex, int thumbnailIndex) {
processThumbnailStarted(this.imageIndex, thumbnailIndex);
}
@Override
public void writeAborted(ImageWriter source) {
processWriteAborted();
}
@Override
public void warningOccurred(ImageWriter source, int imageIndex, String warning) {
processWarningOccurred(this.imageIndex, warning);
}
}
}
@@ -129,7 +129,9 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
new TestData(getClassLoaderResource("/tiff/depth/flower-separated-contig-16.tif"), new Dimension(73, 43)), // CMYK 16 bit/sample
// Separated (CMYK) Planar (PlanarConfiguration: 2)
new TestData(getClassLoaderResource("/tiff/depth/flower-separated-planar-08.tif"), new Dimension(73, 43)), // CMYK 8 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-separated-planar-16.tif"), new Dimension(73, 43)) // CMYK 16 bit/sample
new TestData(getClassLoaderResource("/tiff/depth/flower-separated-planar-16.tif"), new Dimension(73, 43)), // CMYK 16 bit/sample
// Custom PIXTIFF ZIP (Compression: 50013)
new TestData(getClassLoaderResource("/tiff/pixtiff/40-8bit-gray-zip.tif"), new Dimension(801, 1313)) // ZIP Gray, 8 bit/sample
);
}
@@ -220,6 +222,25 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
}
}
@Test
public void testReadOldStyleJPEGInconsistentMetadata() throws IOException {
TestData testData = new TestData(getClassLoaderResource("/tiff/old-style-jpeg-inconsistent-metadata.tif"), new Dimension(2483, 3515));
try (ImageInputStream stream = testData.getInputStream()) {
TIFFImageReader reader = createReader();
reader.setInput(stream);
IIOReadWarningListener warningListener = mock(IIOReadWarningListener.class);
reader.addIIOReadWarningListener(warningListener);
BufferedImage image = reader.read(0);
assertNotNull(image);
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), contains("metadata"));
}
}
@Test
public void testReadIncompatibleICCProfileIgnoredWithWarning() throws IOException {
TestData testData = new TestData(getClassLoaderResource("/tiff/rgb-with-embedded-cmyk-icc.tif"), new Dimension(1500, 1500));
@@ -36,10 +36,12 @@ import com.twelvemonkeys.imageio.metadata.exif.TIFF;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTestCase;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.io.NullOutputStream;
import org.junit.Test;
import org.w3c.dom.NodeList;
import javax.imageio.*;
import javax.imageio.event.IIOWriteProgressListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
@@ -52,6 +54,8 @@ import java.io.*;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -59,6 +63,7 @@ import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataTest.creat
import static com.twelvemonkeys.imageio.util.ImageReaderAbstractTest.assertRGBEquals;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeNotNull;
import static org.mockito.Mockito.*;
/**
* TIFFImageWriterTest
@@ -69,7 +74,7 @@ import static org.junit.Assume.assumeNotNull;
*/
public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
public static final TIFFImageWriterSpi PROVIDER = new TIFFImageWriterSpi();
private static final TIFFImageWriterSpi PROVIDER = new TIFFImageWriterSpi();
@Override
protected ImageWriter createImageWriter() {
@@ -289,7 +294,27 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
assertTrue("Writer should support sequence writing", writer.canWriteSequence());
}
// TODO: Test Sequence writing without prepare/end sequence
@Test(expected = IllegalStateException.class)
public void testWriteSequenceWithoutPrepare() throws IOException {
ImageWriter writer = createImageWriter();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(output);
writer.writeToSequence(new IIOImage(new BufferedImage(10, 10, BufferedImage.TYPE_3BYTE_BGR), null, null), null);
}
}
@Test(expected = IllegalStateException.class)
public void testEndSequenceWithoutPrepare() throws IOException {
ImageWriter writer = createImageWriter();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ImageOutputStream output = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(output);
writer.endWriteSequence();
}
}
@Test
public void testWriteSequence() throws IOException {
@@ -361,6 +386,50 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
}
}
@Test
public void testWriteSequenceProgress() throws IOException {
BufferedImage[] images = new BufferedImage[] {
new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB),
new BufferedImage(110, 100, BufferedImage.TYPE_INT_RGB),
new BufferedImage(120, 100, BufferedImage.TYPE_INT_RGB)
};
ImageWriter writer = createImageWriter();
IIOWriteProgressListener progress = mock(IIOWriteProgressListener.class, "progress");
writer.addIIOWriteProgressListener(progress);
try (ImageOutputStream output = ImageIO.createImageOutputStream(new NullOutputStream())) {
writer.setOutput(output);
try {
writer.prepareWriteSequence(null);
for (int i = 0; i < images.length; i++) {
reset(progress);
ImageWriteParam param = writer.getDefaultWriteParam();
if (i == images.length - 1) {
// Make sure that the JPEG delegation outputs the correct indexes
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("JPEG");
}
writer.writeToSequence(new IIOImage(images[i], null, null), param);
verify(progress, times(1)).imageStarted(writer, i);
verify(progress, atLeastOnce()).imageProgress(eq(writer), anyFloat());
verify(progress, times(1)).imageComplete(writer);
}
writer.endWriteSequence();
}
catch (IOException e) {
fail(e.getMessage());
}
}
}
@Test
public void testReadWriteRead1BitLZW() throws IOException {
// Read original LZW compressed TIFF
@@ -789,4 +858,182 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTestCase {
}
}
}
@Test
public void testWriteStreamMetadataDefaultMM() throws IOException {
ImageWriter writer = createImageWriter();
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN); // Should pass through
writer.setOutput(stream);
writer.write(null, new IIOImage(getTestData(0), null, null), null);
}
byte[] bytes = output.toByteArray();
assertArrayEquals(new byte[] {'M', 'M', 0, 42}, Arrays.copyOf(bytes, 4));
}
@Test
public void testWriteStreamMetadataDefaultII() throws IOException {
ImageWriter writer = createImageWriter();
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // Should pass through
writer.setOutput(stream);
writer.write(null, new IIOImage(getTestData(0), null, null), null);
}
byte[] bytes = output.toByteArray();
assertArrayEquals(new byte[] {'I', 'I', 42, 0}, Arrays.copyOf(bytes, 4));
}
@Test
public void testRewrite() throws IOException {
ImageWriter writer = createImageWriter();
ImageReader reader = ImageIO.getImageReader(writer);
List<URL> testData = Arrays.asList(
getClassLoaderResource("/tiff/pixtiff/17-tiff-binary-ccitt-group3.tif"),
getClassLoaderResource("/tiff/pixtiff/36-tiff-8-bit-gray-jpeg.tif"),
getClassLoaderResource("/tiff/pixtiff/51-tiff-24-bit-color-jpeg.tif"),
getClassLoaderResource("/tiff/pixtiff/58-plexustiff-binary-ccitt-group4.tif"),
getClassLoaderResource("/tiff/balloons.tif"),
getClassLoaderResource("/tiff/ColorCheckerCalculator.tif"),
getClassLoaderResource("/tiff/quad-jpeg.tif"),
getClassLoaderResource("/tiff/quad-lzw.tif"),
getClassLoaderResource("/tiff/bali.tif"),
getClassLoaderResource("/tiff/lzw-colormap-iiobe.tif"),
// TODO: FixMe for ColorMap + ExtraSamples (custom ColorModel)
// getClassLoaderResource("/tiff/colormap-with-extrasamples.tif"),
getClassLoaderResource("/tiff/depth/flower-minisblack-02.tif"),
getClassLoaderResource("/tiff/depth/flower-minisblack-04.tif"),
// getClassLoaderResource("/tiff/depth/flower-minisblack-06.tif"),
getClassLoaderResource("/tiff/depth/flower-minisblack-08.tif"),
// getClassLoaderResource("/tiff/depth/flower-minisblack-10.tif"),
// getClassLoaderResource("/tiff/depth/flower-minisblack-12.tif"),
// getClassLoaderResource("/tiff/depth/flower-minisblack-14.tif"),
getClassLoaderResource("/tiff/depth/flower-minisblack-16.tif"),
// getClassLoaderResource("/tiff/depth/flower-minisblack-24.tif"),
getClassLoaderResource("/tiff/depth/flower-minisblack-32.tif"),
getClassLoaderResource("/tiff/depth/flower-palette-02.tif"),
getClassLoaderResource("/tiff/depth/flower-palette-04.tif"),
getClassLoaderResource("/tiff/depth/flower-palette-08.tif"),
getClassLoaderResource("/tiff/depth/flower-palette-16.tif"),
getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"),
// TODO: FixMe for RGB > 8 bits / sample
// getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-contig-14.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-contig-16.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-contig-24.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-contig-32.tif"),
getClassLoaderResource("/tiff/depth/flower-rgb-planar-08.tif"),
// TODO: FixMe for planar RGB > 8 bits / sample
// getClassLoaderResource("/tiff/depth/flower-rgb-planar-10.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-planar-12.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-planar-14.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-planar-16.tif"),
// getClassLoaderResource("/tiff/depth/flower-rgb-planar-24.tif"),
getClassLoaderResource("/tiff/scan-mono-iccgray.tif"),
getClassLoaderResource("/tiff/old-style-jpeg-inconsistent-metadata.tif"),
getClassLoaderResource("/tiff/ccitt/group3_1d.tif"),
getClassLoaderResource("/tiff/ccitt/group3_2d.tif"),
getClassLoaderResource("/tiff/ccitt/group3_1d_fill.tif"),
getClassLoaderResource("/tiff/ccitt/group3_2d_fill.tif"),
getClassLoaderResource("/tiff/ccitt/group4.tif")
);
for (URL url : testData) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (ImageInputStream input = ImageIO.createImageInputStream(url);
ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
reader.setInput(input);
writer.setOutput(stream);
List<ImageInfo> infos = new ArrayList<>(20);
writer.prepareWriteSequence(null);
for (int i = 0; i < reader.getNumImages(true); i++) {
IIOImage image = reader.readAll(i, null);
// If compression is Old JPEG, rewrite as JPEG
// Normally, use the getAsTree method, but we don't care here if we are tied to our impl
TIFFImageMetadata metadata = (TIFFImageMetadata) image.getMetadata();
Directory ifd = metadata.getIFD();
Entry compressionEntry = ifd.getEntryById(TIFF.TAG_COMPRESSION);
int compression = compressionEntry != null ? ((Number) compressionEntry.getValue()).intValue() : TIFFBaseline.COMPRESSION_NONE;
infos.add(new ImageInfo(image.getRenderedImage().getWidth(), image.getRenderedImage().getHeight(), compression));
ImageWriteParam param = writer.getDefaultWriteParam();
if (compression == TIFFExtension.COMPRESSION_OLD_JPEG) {
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // Override the copy from metadata
param.setCompressionType("JPEG");
}
writer.writeToSequence(image, param);
}
writer.endWriteSequence();
// File tempFile = File.createTempFile("foo-", ".tif");
// System.err.println("open " + tempFile.getAbsolutePath());
// FileUtil.write(tempFile, output.toByteArray());
try (ImageInputStream inputAfter = new ByteArrayImageInputStream(output.toByteArray())) {
reader.setInput(inputAfter);
int numImages = reader.getNumImages(true);
assertEquals("Number of pages differs from original", infos.size(), numImages);
for (int i = 0; i < numImages; i++) {
IIOImage after = reader.readAll(i, null);
ImageInfo info = infos.get(i);
TIFFImageMetadata afterMetadata = (TIFFImageMetadata) after.getMetadata();
Directory afterIfd = afterMetadata.getIFD();
Entry afterCompressionEntry = afterIfd.getEntryById(TIFF.TAG_COMPRESSION);
if (info.compression == TIFFExtension.COMPRESSION_OLD_JPEG) {
// Should rewrite this from old-style to new style
assertEquals("Old JPEG compression not rewritten as JPEG", TIFFExtension.COMPRESSION_JPEG, ((Number) afterCompressionEntry.getValue()).intValue());
}
else {
assertEquals("Compression differs from original", info.compression, ((Number) afterCompressionEntry.getValue()).intValue());
}
assertEquals("Image width differs from original", info.width, after.getRenderedImage().getWidth());
assertEquals("Image height differs from original", info.height, after.getRenderedImage().getHeight());
}
}
}
}
}
private class ImageInfo {
final int width;
final int height;
final int compression;
private ImageInfo(int width, int height, int compression) {
this.width = width;
this.height = height;
this.compression = compression;
}
}
}
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId>
+2 -2
View File
@@ -8,7 +8,7 @@
</parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3</version>
<version>3.3.2</version>
<packaging>pom</packaging>
<name>Twelvemonkeys</name>
@@ -83,7 +83,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:https://github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>twelvemonkeys-3.3</tag>
<tag>twelvemonkeys-3.3.2</tag>
</scm>
<properties>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.3</version>
<version>3.3.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>