mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-22 00:00:03 -04:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ce25d0e349 | |||
| e2cc73f276 | |||
| 3623a7c5dd | |||
| ee424583c4 | |||
| 1292c95040 |
+1
-1
@@ -88,7 +88,7 @@ public interface JPEG {
|
|||||||
// Start of Frame segment markers (SOFn).
|
// Start of Frame segment markers (SOFn).
|
||||||
/** SOF0: Baseline DCT, Huffman coding. */
|
/** SOF0: Baseline DCT, Huffman coding. */
|
||||||
int SOF0 = 0xFFC0;
|
int SOF0 = 0xFFC0;
|
||||||
/** SOF0: Extended DCT, Huffman coding. */
|
/** SOF1: Extended DCT, Huffman coding. */
|
||||||
int SOF1 = 0xFFC1;
|
int SOF1 = 0xFFC1;
|
||||||
/** SOF2: Progressive DCT, Huffman coding. */
|
/** SOF2: Progressive DCT, Huffman coding. */
|
||||||
int SOF2 = 0xFFC2;
|
int SOF2 = 0xFFC2;
|
||||||
|
|||||||
@@ -30,6 +30,11 @@
|
|||||||
<artifactId>imageio-jpeg</artifactId>
|
<artifactId>imageio-jpeg</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
|
<artifactId>imageio-webp</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
|
|||||||
+99
@@ -0,0 +1,99 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DelegateTileDecoder.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: DelegateTileDecoder.java,v 1.0 09/11/2023 haraldk Exp$
|
||||||
|
*/
|
||||||
|
class DelegateTileDecoder extends TileDecoder {
|
||||||
|
|
||||||
|
protected final ImageReader delegate;
|
||||||
|
protected final ImageReadParam param;
|
||||||
|
|
||||||
|
private final Predicate<ImageReader> needsRasterConversion;
|
||||||
|
private final RasterConverter converter;
|
||||||
|
private Boolean readRasterAndConvert;
|
||||||
|
|
||||||
|
DelegateTileDecoder(final IIOReadWarningListener warningListener, final String format, final ImageReadParam originalParam) throws IOException {
|
||||||
|
this(warningListener, createDelegate(format), originalParam, imageReader -> false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateTileDecoder(final IIOReadWarningListener warningListener, final String format, final ImageReadParam originalParam, final Predicate<ImageReader> needsRasterConversion, final RasterConverter converter) throws IOException {
|
||||||
|
this(warningListener, createDelegate(format), originalParam, needsRasterConversion, converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DelegateTileDecoder(final IIOReadWarningListener warningListener, final ImageReader delegate, final ImageReadParam originalParam, final Predicate<ImageReader> needsRasterConversion, final RasterConverter converter) {
|
||||||
|
super(warningListener);
|
||||||
|
|
||||||
|
this.delegate = notNull(delegate, "delegate");
|
||||||
|
delegate.addIIOReadWarningListener(warningListener);
|
||||||
|
|
||||||
|
if (TIFFImageReader.DEBUG) {
|
||||||
|
System.out.println("tile reading delegate: " + delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
param = delegate.getDefaultReadParam();
|
||||||
|
param.setSourceSubsampling(originalParam.getSourceXSubsampling(), originalParam.getSourceYSubsampling(), 0, 0);
|
||||||
|
|
||||||
|
this.needsRasterConversion = needsRasterConversion;
|
||||||
|
this.converter = converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImageReader createDelegate(String format) throws IOException {
|
||||||
|
// We'll just use the default (first) reader
|
||||||
|
// If it's the TwelveMonkeys one, we will be able to read JPEG Lossless etc.
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(format);
|
||||||
|
if (!readers.hasNext()) {
|
||||||
|
throw new IIOException("No ImageReader registered for '" + format + "' format");
|
||||||
|
}
|
||||||
|
|
||||||
|
return readers.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void decodeTile(final ImageInputStream input, final Rectangle sourceRegion, final Point destinationOffset, final BufferedImage destination) throws IOException {
|
||||||
|
delegate.setInput(input);
|
||||||
|
param.setSourceRegion(sourceRegion);
|
||||||
|
|
||||||
|
if (readRasterAndConvert == null) {
|
||||||
|
// All tiles in an image will use the same format, test once and cache result
|
||||||
|
readRasterAndConvert = needsRasterConversion.test(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!readRasterAndConvert) {
|
||||||
|
// No conversion needed
|
||||||
|
param.setDestinationOffset(destinationOffset);
|
||||||
|
param.setDestination(destination);
|
||||||
|
delegate.read(0, param);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
|
||||||
|
// We'll have to use readAsRaster and later apply color space conversion ourselves
|
||||||
|
Raster raster = delegate.readRaster(0, param);
|
||||||
|
converter.convert(raster);
|
||||||
|
|
||||||
|
destination.getRaster().setDataElements(destinationOffset.x, destinationOffset.y, raster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
delegate.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
-159
@@ -1,159 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2012, Harald Kuhr
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGQuality;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
|
||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
|
|
||||||
import javax.imageio.plugins.jpeg.JPEGQTable;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JPEGTables
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
|
||||||
* @author last modified by $Author: haraldk$
|
|
||||||
* @version $Id: JPEGTables.java,v 1.0 11.05.12 09:13 haraldk Exp$
|
|
||||||
*/
|
|
||||||
class JPEGTables {
|
|
||||||
private static final int DHT_LENGTH = 16;
|
|
||||||
private static final Map<Integer, List<String>> SEGMENT_IDS = createSegmentIdsMap();
|
|
||||||
|
|
||||||
private JPEGQTable[] qTables;
|
|
||||||
private JPEGHuffmanTable[] dcHTables;
|
|
||||||
private JPEGHuffmanTable[] acHTables;
|
|
||||||
|
|
||||||
private static Map<Integer, List<String>> createSegmentIdsMap() {
|
|
||||||
Map<Integer, List<String>> segmentIds = new HashMap<Integer, List<String>>();
|
|
||||||
segmentIds.put(JPEG.DQT, null);
|
|
||||||
segmentIds.put(JPEG.DHT, null);
|
|
||||||
|
|
||||||
return Collections.unmodifiableMap(segmentIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<JPEGSegment> segments;
|
|
||||||
|
|
||||||
public JPEGTables(ImageInputStream input) throws IOException {
|
|
||||||
segments = JPEGSegmentUtil.readSegments(input, SEGMENT_IDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JPEGQTable[] getQTables() throws IOException {
|
|
||||||
if (qTables == null) {
|
|
||||||
qTables = JPEGQuality.getQTables(segments);
|
|
||||||
}
|
|
||||||
|
|
||||||
return qTables;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getHuffmanTables() throws IOException {
|
|
||||||
if (dcHTables == null || acHTables == null) {
|
|
||||||
List<JPEGHuffmanTable> dc = new ArrayList<JPEGHuffmanTable>();
|
|
||||||
List<JPEGHuffmanTable> ac = new ArrayList<JPEGHuffmanTable>();
|
|
||||||
|
|
||||||
// JPEG may contain multiple DHT marker segments
|
|
||||||
for (JPEGSegment segment : segments) {
|
|
||||||
if (segment.marker() != JPEG.DHT) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataInputStream data = new DataInputStream(segment.data());
|
|
||||||
int read = 0;
|
|
||||||
|
|
||||||
// A single DHT marker segment may contain multiple tables
|
|
||||||
while (read < segment.length()) {
|
|
||||||
int htInfo = data.read();
|
|
||||||
read++;
|
|
||||||
|
|
||||||
int num = htInfo & 0x0f; // 0-3
|
|
||||||
int type = htInfo >> 4; // 0 == DC, 1 == AC
|
|
||||||
|
|
||||||
if (type > 1) {
|
|
||||||
throw new IIOException("Bad DHT type: " + type);
|
|
||||||
}
|
|
||||||
if (num >= 4) {
|
|
||||||
throw new IIOException("Bad DHT table index: " + num);
|
|
||||||
}
|
|
||||||
else if (type == 0 ? dc.size() > num : ac.size() > num) {
|
|
||||||
throw new IIOException("Duplicate DHT table index: " + num);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read lengths as short array
|
|
||||||
short[] lengths = new short[DHT_LENGTH];
|
|
||||||
for (int i = 0; i < DHT_LENGTH; i++) {
|
|
||||||
lengths[i] = (short) data.readUnsignedByte();
|
|
||||||
}
|
|
||||||
read += lengths.length;
|
|
||||||
|
|
||||||
int sum = 0;
|
|
||||||
for (short length : lengths) {
|
|
||||||
sum += length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expand table to short array
|
|
||||||
short[] table = new short[sum];
|
|
||||||
for (int j = 0; j < sum; j++) {
|
|
||||||
table[j] = (short) data.readUnsignedByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
JPEGHuffmanTable hTable = new JPEGHuffmanTable(lengths, table);
|
|
||||||
if (type == 0) {
|
|
||||||
dc.add(num, hTable);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ac.add(num, hTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
read += sum;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dcHTables = dc.toArray(new JPEGHuffmanTable[dc.size()]);
|
|
||||||
acHTables = ac.toArray(new JPEGHuffmanTable[ac.size()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public JPEGHuffmanTable[] getDCHuffmanTables() throws IOException {
|
|
||||||
getHuffmanTables();
|
|
||||||
return dcHTables;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JPEGHuffmanTable[] getACHuffmanTables() throws IOException {
|
|
||||||
getHuffmanTables();
|
|
||||||
return acHTables;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPEGTileDecoder.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: JPEGTileDecoder.java,v 1.0 09/11/2023 haraldk Exp$
|
||||||
|
*/
|
||||||
|
final class JPEGTileDecoder extends DelegateTileDecoder {
|
||||||
|
JPEGTileDecoder(final IIOReadWarningListener warningListener, final int compression, final byte[] jpegTables, final int numTiles, final ImageReadParam originalParam, final Predicate<ImageReader> needsConversion, final RasterConverter converter) throws IOException {
|
||||||
|
super(warningListener, "JPEG", originalParam, needsConversion, converter);
|
||||||
|
|
||||||
|
if (jpegTables != null) {
|
||||||
|
// This initializes the tables and other internal settings for the reader,
|
||||||
|
// and is actually a feature of JPEG, see "abbreviated streams":
|
||||||
|
// http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#abbrev
|
||||||
|
delegate.setInput(new ByteArrayImageInputStream(jpegTables));
|
||||||
|
delegate.getStreamMetadata();
|
||||||
|
}
|
||||||
|
else if (numTiles > 1) {
|
||||||
|
// TODO: This is not really a problem as long as we read ALL tiles, but we can't have random access in this case...
|
||||||
|
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
||||||
|
warningListener.warningOccurred(delegate, "Missing JPEGTables for tiled/striped TIFF with compression: 7 (JPEG)");
|
||||||
|
}
|
||||||
|
// ...and the JPEG reader might choke on missing tables...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -54,6 +54,8 @@ interface TIFFCustom {
|
|||||||
int COMPRESSION_JPEG2000 = 34712;
|
int COMPRESSION_JPEG2000 = 34712;
|
||||||
// TODO: Aperio SVS JPEG2000: 33003 (YCbCr) and 33005 (RGB), see http://openslide.org/formats/aperio/
|
// TODO: Aperio SVS JPEG2000: 33003 (YCbCr) and 33005 (RGB), see http://openslide.org/formats/aperio/
|
||||||
|
|
||||||
|
int COMPRESSION_WEBP = 50001;
|
||||||
|
|
||||||
// PIXTIFF aka DELL PixTools, see https://community.emc.com/message/515755#515755
|
// PIXTIFF aka DELL PixTools, see https://community.emc.com/message/515755#515755
|
||||||
/** PIXTIFF proprietary ZIP compression, identical to Deflate/ZLib. */
|
/** PIXTIFF proprietary ZIP compression, identical to Deflate/ZLib. */
|
||||||
int COMPRESSION_PIXTIFF_ZIP = 50013;
|
int COMPRESSION_PIXTIFF_ZIP = 50013;
|
||||||
|
|||||||
+335
-337
@@ -48,6 +48,7 @@ import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
|||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||||
|
import com.twelvemonkeys.imageio.plugins.tiff.TileDecoder.RasterConverter;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
|
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
@@ -70,7 +71,6 @@ import javax.imageio.ImageTypeSpecifier;
|
|||||||
import javax.imageio.event.IIOReadWarningListener;
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
|
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
@@ -93,6 +93,7 @@ import java.util.HashSet;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
|
|
||||||
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
|
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
|
||||||
@@ -935,6 +936,10 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
int width = getWidth(imageIndex);
|
int width = getWidth(imageIndex);
|
||||||
int height = getHeight(imageIndex);
|
int height = getHeight(imageIndex);
|
||||||
|
|
||||||
|
if (param == null) {
|
||||||
|
param = getDefaultReadParam();
|
||||||
|
}
|
||||||
|
|
||||||
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
|
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
|
||||||
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
|
||||||
checkReadParamBandSettings(param, rawType.getNumBands(), destination.getSampleModel().getNumBands());
|
checkReadParamBandSettings(param, rawType.getNumBands(), destination.getSampleModel().getNumBands());
|
||||||
@@ -943,10 +948,10 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
final Rectangle dstRegion = new Rectangle();
|
final Rectangle dstRegion = new Rectangle();
|
||||||
computeRegions(param, width, height, destination, srcRegion, dstRegion);
|
computeRegions(param, width, height, destination, srcRegion, dstRegion);
|
||||||
|
|
||||||
int xSub = param != null ? param.getSourceXSubsampling() : 1;
|
int xSub = param.getSourceXSubsampling();
|
||||||
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
int ySub = param.getSourceYSubsampling();
|
||||||
|
|
||||||
WritableRaster destRaster = clipToRect(destination.getRaster(), dstRegion, param != null ? param.getDestinationBands() : null);
|
WritableRaster destRaster = clipToRect(destination.getRaster(), dstRegion, param.getDestinationBands());
|
||||||
|
|
||||||
final int interpretation = getPhotometricInterpretationWithFallback();
|
final int interpretation = getPhotometricInterpretationWithFallback();
|
||||||
final int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
|
final int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
|
||||||
@@ -1000,8 +1005,6 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
WritableRaster rowRaster = rawType.createBufferedImage(stripTileWidth, 1).getRaster();
|
WritableRaster rowRaster = rawType.createBufferedImage(stripTileWidth, 1).getRaster();
|
||||||
|
|
||||||
Rectangle clip = new Rectangle(srcRegion);
|
Rectangle clip = new Rectangle(srcRegion);
|
||||||
int srcRow = 0;
|
|
||||||
Boolean needsCSConversion = null;
|
|
||||||
|
|
||||||
switch (compression) {
|
switch (compression) {
|
||||||
case TIFFBaseline.COMPRESSION_NONE:
|
case TIFFBaseline.COMPRESSION_NONE:
|
||||||
@@ -1020,8 +1023,9 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
// CCITT modified Huffman
|
// CCITT modified Huffman
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T4:
|
case TIFFExtension.COMPRESSION_CCITT_T4:
|
||||||
// CCITT Group 3 fax encoding
|
// CCITT Group 3 fax encoding
|
||||||
case TIFFExtension.COMPRESSION_CCITT_T6:
|
case TIFFExtension.COMPRESSION_CCITT_T6: {
|
||||||
// CCITT Group 4 fax encoding
|
// CCITT Group 4 fax encoding
|
||||||
|
int srcRow = 0;
|
||||||
|
|
||||||
int[] yCbCrSubsampling = null;
|
int[] yCbCrSubsampling = null;
|
||||||
int yCbCrPos = 1;
|
int yCbCrPos = 1;
|
||||||
@@ -1090,10 +1094,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// Clip the stripTile rowRaster to not exceed the srcRegion
|
// Clip the stripTile rowRaster to not exceed the srcRegion
|
||||||
clip.width = Math.min(colsInTile, srcRegion.width);
|
clip.width = Math.min(colsInTile, srcRegion.width);
|
||||||
Raster clippedRow = clipRowToRect(rowRaster, clip,
|
Raster clippedRow = clipRowToRect(rowRaster, clip, param.getSourceBands(), param.getSourceXSubsampling());
|
||||||
param != null ? param.getSourceBands() : null,
|
|
||||||
param != null ? param.getSourceXSubsampling() : 1);
|
|
||||||
|
|
||||||
imageInput.seek(stripTileOffsets[i]);
|
imageInput.seek(stripTileOffsets[i]);
|
||||||
|
|
||||||
ImageInputStream input;
|
ImageInputStream input;
|
||||||
@@ -1170,95 +1171,83 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case TIFFExtension.COMPRESSION_JPEG:
|
case TIFFExtension.COMPRESSION_JPEG:
|
||||||
// JPEG ('new-style' JPEG)
|
case TIFFCustom.COMPRESSION_WEBP:
|
||||||
// TODO: Refactor all JPEG reading out to separate JPEG support class?
|
case TIFFCustom.COMPRESSION_JBIG:
|
||||||
// TODO: Cache the JPEG reader for later use? Remember to reset to avoid resource leaks
|
case TIFFCustom.COMPRESSION_JPEG2000:
|
||||||
|
readUsingDelegate(imageIndex, compression, interpretation, width, height, tilesAcross, tilesDown, stripTileWidth, stripTileHeight, srcRegion,
|
||||||
|
new PlainTileStreamFactory(stripTileOffsets, stripTileByteCounts), param, destination, samplesInTile);
|
||||||
|
break;
|
||||||
|
|
||||||
ImageReader jpegReader = createJPEGDelegate();
|
case TIFFExtension.COMPRESSION_OLD_JPEG:
|
||||||
// TODO: Use proper inner class + add case for old JPEG
|
boolean interChangeFormat = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1) >= 0;
|
||||||
jpegReader.addIIOReadWarningListener(new IIOReadWarningListener() {
|
|
||||||
@Override
|
|
||||||
public void warningOccurred(final ImageReader source, final String warning) {
|
|
||||||
processWarningOccurred(warning);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
JPEGImageReadParam jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
|
|
||||||
|
|
||||||
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
|
if (!interChangeFormat) {
|
||||||
// SOI, DQT, DHT, (optional markers that we ignore)..., EOI
|
processWarningOccurred("Old-style JPEG compressed TIFF without JPEGInterchangeFormat encountered. Attempting to re-create JFIF stream.");
|
||||||
Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES);
|
|
||||||
byte[] tablesValue = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null;
|
|
||||||
if (tablesValue != null) {
|
|
||||||
// Whatever values I pass the reader as the read param, it never gets the same quality as if
|
|
||||||
// I just invoke jpegReader.getStreamMetadata(), so we'll do that...
|
|
||||||
jpegReader.setInput(new ByteArrayImageInputStream(tablesValue));
|
|
||||||
|
|
||||||
// This initializes the tables and other internal settings for the reader,
|
|
||||||
// and is actually a feature of JPEG, see abbreviated streams:
|
|
||||||
// http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html#abbrev
|
|
||||||
jpegReader.getStreamMetadata();
|
|
||||||
}
|
|
||||||
else if (tilesDown * tilesAcross > 1) {
|
|
||||||
processWarningOccurred("Missing JPEGTables for tiled/striped TIFF with compression: 7 (JPEG)");
|
|
||||||
// ...and the JPEG reader will probably choke on missing tables...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Perhaps use the jpegTables param to the tiledecoder instead of re-creating a full JFIF stream...
|
||||||
|
TileStreamFactory tileStreamFactory = interChangeFormat
|
||||||
|
? new OldJPEGInterchangeFormatTileStreamFactory(stripTileOffsets, stripTileByteCounts)
|
||||||
|
: new OldJPEGTablesStreamFactory(stripTileOffsets, stripTileByteCounts, stripTileWidth, stripTileHeight, destRaster.getNumBands());
|
||||||
|
readUsingDelegate(imageIndex, compression, interpretation, width, height, tilesAcross, tilesDown, stripTileWidth, stripTileHeight, srcRegion,
|
||||||
|
tileStreamFactory, param, destination, samplesInTile);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TIFFCustom.COMPRESSION_NEXT:
|
||||||
|
case TIFFCustom.COMPRESSION_CCITTRLEW:
|
||||||
|
case TIFFCustom.COMPRESSION_THUNDERSCAN:
|
||||||
|
case TIFFCustom.COMPRESSION_IT8CTPAD:
|
||||||
|
case TIFFCustom.COMPRESSION_IT8LW:
|
||||||
|
case TIFFCustom.COMPRESSION_IT8MP:
|
||||||
|
case TIFFCustom.COMPRESSION_IT8BL:
|
||||||
|
case TIFFCustom.COMPRESSION_PIXARFILM:
|
||||||
|
case TIFFCustom.COMPRESSION_PIXARLOG:
|
||||||
|
case TIFFCustom.COMPRESSION_DCS:
|
||||||
|
case TIFFCustom.COMPRESSION_SGILOG:
|
||||||
|
case TIFFCustom.COMPRESSION_SGILOG24:
|
||||||
|
throw new IIOException("Unsupported TIFF Compression value: " + compression);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IIOException("Unknown TIFF Compression value: " + compression);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Convert color space from source to destination
|
||||||
|
|
||||||
|
processImageComplete();
|
||||||
|
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readUsingDelegate(int imageIndex, int compression, int interpretation, int width, int height,
|
||||||
|
int tilesAcross, int tilesDown, int stripTileWidth, int stripTileHeight, Rectangle srcRegion,
|
||||||
|
TileStreamFactory factory,
|
||||||
|
ImageReadParam param, BufferedImage destination, int samplesInTile) throws IOException {
|
||||||
// Read data
|
// Read data
|
||||||
|
try (TileDecoder tileDecoder = createTileDecoder(param, compression, interpretation, tilesAcross * tilesDown, samplesInTile)) {
|
||||||
processImageStarted(imageIndex); // Better yet, would be to delegate read progress here...
|
processImageStarted(imageIndex); // Better yet, would be to delegate read progress here...
|
||||||
|
|
||||||
|
int row = 0;
|
||||||
|
|
||||||
for (int y = 0; y < tilesDown; y++) {
|
for (int y = 0; y < tilesDown; y++) {
|
||||||
int col = 0;
|
int col = 0;
|
||||||
int rowsInTile = Math.min(stripTileHeight, height - srcRow);
|
int rowsInTile = Math.min(stripTileHeight, height - row);
|
||||||
|
|
||||||
for (int x = 0; x < tilesAcross; x++) {
|
for (int x = 0; x < tilesAcross; x++) {
|
||||||
int i = y * tilesAcross + x;
|
|
||||||
int colsInTile = Math.min(stripTileWidth, width - col);
|
int colsInTile = Math.min(stripTileWidth, width - col);
|
||||||
|
|
||||||
// Read only tiles that lies within region
|
// Read only tiles that lies within region
|
||||||
Rectangle tileRect = new Rectangle(col, srcRow, colsInTile, rowsInTile);
|
Rectangle tileRect = new Rectangle(col, row, colsInTile, rowsInTile);
|
||||||
Rectangle intersection = tileRect.intersection(srcRegion);
|
Rectangle intersection = tileRect.intersection(srcRegion);
|
||||||
|
|
||||||
if (!intersection.isEmpty()) {
|
if (!intersection.isEmpty()) {
|
||||||
imageInput.seek(stripTileOffsets[i]);
|
int tileIndex = y * tilesAcross + x;
|
||||||
|
|
||||||
int length = stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE;
|
try (ImageInputStream tileStream = factory.createTileStream(tileIndex)) {
|
||||||
|
Point destinationOffset = new Point((intersection.x - srcRegion.x) / param.getSourceXSubsampling(), (intersection.y - srcRegion.y) / param.getSourceYSubsampling());
|
||||||
try (ImageInputStream subStream = new SubImageInputStream(imageInput, length)) {
|
Rectangle sourceRegion = new Rectangle(intersection.x - col, intersection.y - row, intersection.width, intersection.height);
|
||||||
jpegReader.setInput(subStream);
|
tileDecoder.decodeTile(tileStream, sourceRegion, destinationOffset, destination);
|
||||||
jpegParam.setSourceRegion(new Rectangle(intersection.x - col, intersection.y - srcRow, intersection.width, intersection.height));
|
|
||||||
jpegParam.setSourceSubsampling(xSub, ySub, 0, 0);
|
|
||||||
Point offset = new Point((intersection.x - srcRegion.x) / xSub, (intersection.y - srcRegion.y) / ySub);
|
|
||||||
|
|
||||||
// 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(compression, interpretation, readJPEGMetadataSafe(jpegReader));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!needsCSConversion) {
|
|
||||||
jpegParam.setDestinationOffset(offset);
|
|
||||||
jpegParam.setDestination(destination);
|
|
||||||
jpegReader.read(0, jpegParam);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
|
|
||||||
// We'll have to use readAsRaster and later apply color space conversion ourselves
|
|
||||||
Raster raster = jpegReader.readRaster(0, jpegParam);
|
|
||||||
// TODO: Refactor + duplicate this for all JPEG-in-TIFF cases
|
|
||||||
switch (raster.getTransferType()) {
|
|
||||||
case DataBuffer.TYPE_BYTE:
|
|
||||||
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
|
|
||||||
break;
|
|
||||||
case DataBuffer.TYPE_USHORT:
|
|
||||||
normalizeColor(interpretation, samplesInTile, ((DataBufferUShort) raster.getDataBuffer()).getData());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType());
|
|
||||||
}
|
|
||||||
|
|
||||||
destination.getRaster().setDataElements(offset.x, offset.y, raster);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1269,47 +1258,63 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
col += colsInTile;
|
col += colsInTile;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100f * srcRow / height);
|
processImageProgress(100f * row / height);
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
srcRow += rowsInTile;
|
row += rowsInTile;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
static abstract class TileStreamFactory {
|
||||||
|
final long[] stripTileOffsets;
|
||||||
|
final long[] stripTileByteCounts;
|
||||||
|
|
||||||
case TIFFExtension.COMPRESSION_OLD_JPEG:
|
TileStreamFactory(final long[] stripTileOffsets, final long[] stripTileByteCounts) {
|
||||||
// JPEG ('old-style' JPEG, later overridden in Technote2)
|
this.stripTileOffsets = stripTileOffsets;
|
||||||
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
|
this.stripTileByteCounts = stripTileByteCounts;
|
||||||
|
|
||||||
// 512/JPEGProc: 1=Baseline, 14=Lossless (with Huffman coding), no default, although 1 is assumed if absent
|
|
||||||
int mode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, TIFFExtension.JPEG_PROC_BASELINE);
|
|
||||||
switch (mode) {
|
|
||||||
case TIFFExtension.JPEG_PROC_BASELINE:
|
|
||||||
case TIFFExtension.JPEG_PROC_LOSSLESS:
|
|
||||||
break; // Supported
|
|
||||||
default:
|
|
||||||
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jpegReader = createJPEGDelegate();
|
abstract ImageInputStream createTileStream(int tileIndex) throws IOException;
|
||||||
jpegParam = (JPEGImageReadParam) jpegReader.getDefaultReadParam();
|
}
|
||||||
|
|
||||||
|
final class PlainTileStreamFactory extends TileStreamFactory {
|
||||||
|
PlainTileStreamFactory(final long[] stripTileOffsets, final long[] stripTileByteCounts) {
|
||||||
|
super(stripTileOffsets, stripTileByteCounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageInputStream createTileStream(final int tileIndex) throws IOException {
|
||||||
|
imageInput.seek(stripTileOffsets[tileIndex]);
|
||||||
|
|
||||||
|
int length = stripTileByteCounts != null ? (int) stripTileByteCounts[tileIndex] : Short.MAX_VALUE;
|
||||||
|
|
||||||
|
return new SubImageInputStream(imageInput, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class OldJPEGInterchangeFormatTileStreamFactory extends TileStreamFactory {
|
||||||
|
private final byte[] jpegHeader;
|
||||||
|
private long realJPEGOffset;
|
||||||
|
|
||||||
|
OldJPEGInterchangeFormatTileStreamFactory(final long[] stripTileOffsets, final long[] stripTileByteCounts) throws IOException {
|
||||||
|
super(stripTileOffsets, stripTileByteCounts);
|
||||||
|
|
||||||
// 513/JPEGInterchangeFormat (may be absent or 0)
|
// 513/JPEGInterchangeFormat (may be absent or 0)
|
||||||
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
|
int jpegOffset = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT, -1);
|
||||||
// 514/JPEGInterchangeFormatLength (may be absent, or incorrect)
|
// 514/JPEGInterchangeFormatLength (may be absent, or incorrect)
|
||||||
// TODO: We used to issue a warning if the value was incorrect, should we still do that?
|
|
||||||
int jpegLength = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
|
int jpegLength = getValueAsIntWithDefault(TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, -1);
|
||||||
|
|
||||||
// TODO: 515/JPEGRestartInterval (may be absent)
|
// TODO: 515/JPEGRestartInterval (may be absent)
|
||||||
|
|
||||||
// Currently ignored (for lossless only)
|
// Currently ignored (for lossless only)
|
||||||
// 517/JPEGLosslessPredictors
|
// 517/JPEGLosslessPredictors
|
||||||
// 518/JPEGPointTransforms
|
// 518/JPEGPointTransforms
|
||||||
|
|
||||||
if (jpegOffset > 0) {
|
|
||||||
if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null
|
if (currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_Q_TABLES) != null
|
||||||
|| currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null
|
|| currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_DC_TABLES) != null
|
||||||
|| currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES) != null) {
|
|| currentIFD.getEntryById(TIFF.TAG_OLD_JPEG_AC_TABLES) != null) {
|
||||||
@@ -1323,15 +1328,15 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// NOTE: Some known TIFF encoder encodes bad JPEGInterchangeFormat tags,
|
// NOTE: Some known TIFF encoder encodes bad JPEGInterchangeFormat tags,
|
||||||
// but has the correct offset to the JPEG stream in the StripOffsets tag.
|
// but has the correct offset to the JPEG stream in the StripOffsets tag.
|
||||||
long realJPEGOffset = jpegOffset;
|
realJPEGOffset = jpegOffset;
|
||||||
|
|
||||||
short expectedSOI = (short) (imageInput.readByte() << 8 | imageInput.readByte());
|
int expectedSOI = ((imageInput.readByte() & 0xFF) << 8 | (imageInput.readByte() & 0xFF));
|
||||||
if (expectedSOI != (short) JPEG.SOI) {
|
if (expectedSOI != JPEG.SOI) {
|
||||||
if (stripTileOffsets != null && stripTileOffsets.length == 1) {
|
if (stripTileOffsets != null && stripTileOffsets.length == 1) {
|
||||||
imageInput.seek(stripTileOffsets[0]);
|
imageInput.seek(stripTileOffsets[0]);
|
||||||
|
|
||||||
expectedSOI = (short) (imageInput.readByte() << 8 | imageInput.readByte());
|
expectedSOI = ((imageInput.readByte() & 0xFF) << 8 | (imageInput.readByte() & 0xFF));
|
||||||
if (expectedSOI == (short) JPEG.SOI) {
|
if (expectedSOI == JPEG.SOI) {
|
||||||
realJPEGOffset = stripTileOffsets[0];
|
realJPEGOffset = stripTileOffsets[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1345,28 +1350,40 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] jpegHeader;
|
if (stripTileOffsets == null || stripTileOffsets.length == 1) {
|
||||||
|
|
||||||
if (stripTileOffsets == null || stripTileOffsets.length == 1 && realJPEGOffset == stripTileOffsets[0]) {
|
|
||||||
// In this case, we'll just read everything as a single tile
|
// In this case, we'll just read everything as a single tile
|
||||||
jpegHeader = new byte[0];
|
jpegHeader = new byte[0];
|
||||||
|
|
||||||
|
if (stripTileOffsets != null) {
|
||||||
|
stripTileOffsets[0] = realJPEGOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Wang TIFF weirdness, see http://www.eztwain.com/wangtiff.htm
|
|
||||||
// If the first tile stream starts with SOS, we'll correct offset/length
|
|
||||||
imageInput.seek(stripTileOffsets[0]);
|
imageInput.seek(stripTileOffsets[0]);
|
||||||
|
|
||||||
if ((short) (imageInput.readByte() << 8 | imageInput.readByte()) == (short) JPEG.SOS) {
|
if (((imageInput.readByte() & 0xFF) << 8 | (imageInput.readByte() & 0xFF)) == JPEG.SOS) {
|
||||||
|
// Wang TIFF weirdness, see http://www.eztwain.com/wangtiff.htm
|
||||||
|
// If the first tile stream starts with SOS, we'll correct offset/length
|
||||||
processWarningOccurred("Incorrect StripOffsets/TileOffsets, points to SOS marker, ignoring offsets/byte counts.");
|
processWarningOccurred("Incorrect StripOffsets/TileOffsets, points to SOS marker, ignoring offsets/byte counts.");
|
||||||
int len = 2 + (imageInput.readUnsignedByte() << 8 | imageInput.readUnsignedByte());
|
|
||||||
stripTileOffsets[0] += len;
|
|
||||||
stripTileByteCounts[0] -= len;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll prepend each tile with a JFIF "header" (SOI...SOS)
|
int sosLength = 2 + ((imageInput.readByte() & 0xFF) << 8 | (imageInput.readByte() & 0xFF));
|
||||||
|
|
||||||
|
// TODO: Validate that values make sense?
|
||||||
|
|
||||||
|
// We'll prepend each tile with a JFIF "header" (SOI...
|
||||||
|
jpegHeader = new byte[Math.max(0, jpegLength + sosLength)];
|
||||||
imageInput.seek(realJPEGOffset);
|
imageInput.seek(realJPEGOffset);
|
||||||
jpegHeader = new byte[Math.max(0, (int) (stripTileOffsets[0] - realJPEGOffset))];
|
imageInput.readFully(jpegHeader, 0, jpegLength);
|
||||||
imageInput.readFully(jpegHeader);
|
// ...SOS)
|
||||||
|
imageInput.seek(stripTileOffsets[0]);
|
||||||
|
imageInput.readFully(jpegHeader, jpegLength, sosLength);
|
||||||
|
|
||||||
|
stripTileOffsets[0] += sosLength;
|
||||||
|
stripTileByteCounts[0] -= sosLength;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jpegHeader = new byte[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In case of single tile, make sure we read the entire JFIF stream
|
// In case of single tile, make sure we read the entire JFIF stream
|
||||||
@@ -1374,74 +1391,103 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
processWarningOccurred("Incorrect StripByteCounts/TileByteCounts for single tile, using JPEGInterchangeFormatLength instead.");
|
processWarningOccurred("Incorrect StripByteCounts/TileByteCounts for single tile, using JPEGInterchangeFormatLength instead.");
|
||||||
stripTileByteCounts[0] = jpegLength;
|
stripTileByteCounts[0] = jpegLength;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read data
|
@Override
|
||||||
processImageStarted(imageIndex);
|
public ImageInputStream createTileStream(final int tileIndex) throws IOException {
|
||||||
|
long length = stripTileByteCounts != null ? stripTileByteCounts[tileIndex] : Integer.MAX_VALUE;
|
||||||
|
|
||||||
for (int y = 0; y < tilesDown; y++) {
|
imageInput.seek(stripTileOffsets != null ? stripTileOffsets[tileIndex] : realJPEGOffset);
|
||||||
int col = 0;
|
|
||||||
int rowsInTile = Math.min(stripTileHeight, height - srcRow);
|
|
||||||
|
|
||||||
for (int x = 0; x < tilesAcross; x++) {
|
return ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(asList(
|
||||||
int colsInTile = Math.min(stripTileWidth, width - col);
|
|
||||||
int i = y * tilesAcross + x;
|
|
||||||
|
|
||||||
// Read only tiles that lies within region
|
|
||||||
if (new Rectangle(col, srcRow, colsInTile, rowsInTile).intersects(srcRegion)) {
|
|
||||||
int len = stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Integer.MAX_VALUE;
|
|
||||||
imageInput.seek(stripTileOffsets != null ? stripTileOffsets[i] : realJPEGOffset);
|
|
||||||
|
|
||||||
try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(asList(
|
|
||||||
new ByteArrayInputStream(jpegHeader),
|
new ByteArrayInputStream(jpegHeader),
|
||||||
createStreamAdapter(imageInput, len),
|
createStreamAdapter(imageInput, length),
|
||||||
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
|
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
|
||||||
))))) {
|
))));
|
||||||
jpegReader.setInput(stream);
|
|
||||||
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
|
|
||||||
jpegParam.setSourceSubsampling(xSub, ySub, 0, 0);
|
|
||||||
Point offset = new Point(col - srcRegion.x, srcRow - srcRegion.y);
|
|
||||||
|
|
||||||
if (needsCSConversion == null) {
|
|
||||||
needsCSConversion = needsCSConversion(compression, interpretation, readJPEGMetadataSafe(jpegReader));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!needsCSConversion) {
|
|
||||||
jpegParam.setDestinationOffset(offset);
|
|
||||||
jpegParam.setDestination(destination);
|
|
||||||
jpegReader.read(0, jpegParam);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
|
|
||||||
// We'll have to use readAsRaster and later apply color space conversion ourselves
|
|
||||||
Raster raster = jpegReader.readRaster(0, jpegParam);
|
|
||||||
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
|
|
||||||
destination.getRaster().setDataElements(offset.x, offset.y, raster);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abortRequested()) {
|
final class OldJPEGTablesStreamFactory extends TileStreamFactory {
|
||||||
break;
|
private final int stripTileWidth;
|
||||||
}
|
private final int stripTileHeight;
|
||||||
|
private final int numBands;
|
||||||
|
private final int subsampling;
|
||||||
|
private final int processingMode;
|
||||||
|
|
||||||
col += colsInTile;
|
OldJPEGTablesStreamFactory(final long[] stripTileOffsets, final long[] stripTileByteCounts, final int stripTileWidth, final int stripTileHeight, final int numBands) throws IOException {
|
||||||
}
|
super(stripTileOffsets, stripTileByteCounts);
|
||||||
|
this.stripTileWidth = stripTileWidth;
|
||||||
|
this.stripTileHeight = stripTileHeight;
|
||||||
|
this.numBands = numBands;
|
||||||
|
|
||||||
processImageProgress(100f * srcRow / height);
|
|
||||||
|
|
||||||
if (abortRequested()) {
|
|
||||||
processReadAborted();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
srcRow += rowsInTile;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// The hard way: Read tables and re-create a full JFIF stream
|
// The hard way: Read tables and re-create a full JFIF stream
|
||||||
processWarningOccurred("Old-style JPEG compressed TIFF without JPEGInterchangeFormat encountered. Attempting to re-create JFIF stream.");
|
|
||||||
|
|
||||||
|
// 512/JPEGProc: 1=Baseline, 14=Lossless (with Huffman coding), no default, although 1 is assumed if absent
|
||||||
|
processingMode = getValueAsIntWithDefault(TIFF.TAG_OLD_JPEG_PROC, TIFFExtension.JPEG_PROC_BASELINE);
|
||||||
|
switch (processingMode) {
|
||||||
|
case TIFFExtension.JPEG_PROC_BASELINE:
|
||||||
|
case TIFFExtension.JPEG_PROC_LOSSLESS:
|
||||||
|
break; // Supported
|
||||||
|
default:
|
||||||
|
throw new IIOException("Unknown TIFF JPEGProcessingMode value: " + processingMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
long[] yCbCrSubSampling = getValueAsLongArray(TIFF.TAG_YCBCR_SUB_SAMPLING, "YCbCrSubSampling", false);
|
||||||
|
subsampling = yCbCrSubSampling != null
|
||||||
|
? (int) ((yCbCrSubSampling[0] & 0xf) << 4 | yCbCrSubSampling[1] & 0xf)
|
||||||
|
: 0x22;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageInputStream createTileStream(final int tileIndex) throws IOException {
|
||||||
|
long length = stripTileByteCounts != null ? stripTileByteCounts[tileIndex] : Integer.MAX_VALUE;
|
||||||
|
|
||||||
|
imageInput.seek(stripTileOffsets[tileIndex]);
|
||||||
|
|
||||||
|
// If the tile stream starts with SOS...
|
||||||
|
if (tileIndex == 0) {
|
||||||
|
if (((imageInput.readByte() & 0xFF) << 8 | (imageInput.readByte() & 0xFF)) == JPEG.SOS) {
|
||||||
|
int sosLength = 2 + ((imageInput.readByte() & 0xFF) << 8 | (imageInput.readByte() & 0xFF));
|
||||||
|
|
||||||
|
imageInput.seek(stripTileOffsets[tileIndex] + sosLength);
|
||||||
|
length -= sosLength;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
imageInput.seek(stripTileOffsets[tileIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
||||||
|
asList(
|
||||||
|
createJFIFStream(numBands, stripTileWidth, stripTileHeight, processingMode, subsampling),
|
||||||
|
createStreamAdapter(imageInput, length),
|
||||||
|
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
|
||||||
|
)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TileDecoder createTileDecoder(ImageReadParam param, int compression, final int interpretation, final int numTiles, final int samplesInTile) throws IOException {
|
||||||
|
try {
|
||||||
|
IIOReadWarningListener warningListener = (source, warning) -> processWarningOccurred(warning);
|
||||||
|
|
||||||
|
switch (compression) {
|
||||||
|
case TIFFExtension.COMPRESSION_OLD_JPEG:
|
||||||
|
// JPEG ('old-style' JPEG, later overridden in Technote2)
|
||||||
|
// http://www.remotesensing.org/libtiff/TIFFTechNote2.html
|
||||||
|
case TIFFExtension.COMPRESSION_JPEG:
|
||||||
|
// New style JPEG
|
||||||
|
|
||||||
|
byte[] jpegTables = null;
|
||||||
|
|
||||||
|
if (compression == TIFFExtension.COMPRESSION_JPEG) {
|
||||||
|
// JPEG_TABLES should be a full JPEG 'abbreviated table specification', containing:
|
||||||
|
// SOI, DQT, DHT, (optional markers that we ignore)..., EOI
|
||||||
|
Entry tablesEntry = currentIFD.getEntryById(TIFF.TAG_JPEG_TABLES);
|
||||||
|
jpegTables = tablesEntry != null ? (byte[]) tablesEntry.getValue() : null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (currentIFD.getEntryById(TIFF.TAG_JPEG_INTERCHANGE_FORMAT) == null) {
|
||||||
// 519/JPEGQTables
|
// 519/JPEGQTables
|
||||||
// 520/JPEGDCTables
|
// 520/JPEGDCTables
|
||||||
// 521/JPEGACTables
|
// 521/JPEGACTables
|
||||||
@@ -1459,37 +1505,63 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
// JPEGDCTables, and JPEGACTables tags are incorrect values beyond EOF. However, these files do always
|
// JPEGDCTables, and JPEGACTables tags are incorrect values beyond EOF. However, these files do always
|
||||||
// seem to contain a useful JPEGInterchangeFormat tag. Therefore, we recommend a careful attempt to read
|
// seem to contain a useful JPEGInterchangeFormat tag. Therefore, we recommend a careful attempt to read
|
||||||
// the Tables tags only as a last resort, if no table data is found in a JPEGInterchangeFormat stream.
|
// the Tables tags only as a last resort, if no table data is found in a JPEGInterchangeFormat stream.
|
||||||
|
long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_Q_TABLES, "JPEGQTables", true);
|
||||||
|
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_AC_TABLES, "JPEGACTables", true);
|
||||||
|
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DC_TABLES, "JPEGDCTables", true);
|
||||||
|
|
||||||
|
jpegTables = createJPEGTables(imageInput, qTablesOffsets, acTablesOffsets, dcTablesOffsets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Predicate<ImageReader> needsConversion = (reader) -> needsCSConversion(compression, interpretation, readJPEGMetadataSafe(reader));
|
||||||
|
RasterConverter normalize = (raster) -> normalizeColor(interpretation, samplesInTile, raster);
|
||||||
|
|
||||||
|
return new JPEGTileDecoder(warningListener, compression, jpegTables, numTiles, param, needsConversion, normalize);
|
||||||
|
|
||||||
|
case TIFFCustom.COMPRESSION_JBIG:
|
||||||
|
// TODO: Create interop test suite using third party plugin.
|
||||||
|
// LEAD Tools have one sample file: https://leadtools.com/support/forum/resource.ashx?a=545&b=1
|
||||||
|
// Haven't found any plugins. There is however a JBIG2 plugin...
|
||||||
|
return new DelegateTileDecoder(warningListener, "JBIG", param);
|
||||||
|
|
||||||
|
case TIFFCustom.COMPRESSION_JPEG2000:
|
||||||
|
// TODO: Create interop test suite using third party plugin
|
||||||
|
// LEAD Tools have one sample file: https://leadtools.com/support/forum/resource.ashx?a=545&b=1
|
||||||
|
// The open source JAI JP2K reader decodes this as a fully black image...
|
||||||
|
return new DelegateTileDecoder(warningListener, "JPEG2000", param);
|
||||||
|
|
||||||
|
case TIFFCustom.COMPRESSION_WEBP:
|
||||||
|
return new DelegateTileDecoder(warningListener, "WebP", param);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new AssertionError("Unexpected TIFF Compression value: " + compression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IIOException e) {
|
||||||
|
throw new IIOException("Unsupported TIFF Compression value: " + compression, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] createJPEGTables(ImageInputStream imageInput, long[] qTablesOffsets, long[] acTablesOffsets, long[] dcTablesOffsets) throws IOException {
|
||||||
|
// FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
|
||||||
|
// 2 +
|
||||||
|
// 5 * qTablesOffsets.length + qTablesOffsets.length * 64 +
|
||||||
|
// 5 * acTablesOffsets.length + acTablesOffsets.length * dcTables[0].length +
|
||||||
|
// 5 * dcTablesOffsets.length + dcTablesOffsets.length * acTables[0].length +
|
||||||
|
// 2
|
||||||
|
// );
|
||||||
|
|
||||||
|
// TODO: Create stream with DQT, DHT directly, instead of first building in-memory tables...
|
||||||
|
|
||||||
// TODO: If any of the q/dc/ac tables are equal (or have same offset, even if "spec" violation),
|
// TODO: If any of the q/dc/ac tables are equal (or have same offset, even if "spec" violation),
|
||||||
// use only the first occurrence, and update selectors in SOF0 and SOS
|
// use only the first occurrence, and update selectors in SOF0 and SOS
|
||||||
|
|
||||||
long[] qTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_Q_TABLES, "JPEGQTables", true);
|
|
||||||
byte[][] qTables = new byte[qTablesOffsets.length][64];
|
byte[][] qTables = new byte[qTablesOffsets.length][64];
|
||||||
for (int j = 0; j < qTables.length; j++) {
|
for (int j = 0; j < qTables.length; j++) {
|
||||||
imageInput.seek(qTablesOffsets[j]);
|
imageInput.seek(qTablesOffsets[j]);
|
||||||
imageInput.readFully(qTables[j]);
|
imageInput.readFully(qTables[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
long[] dcTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_DC_TABLES, "JPEGDCTables", true);
|
|
||||||
byte[][] dcTables = new byte[dcTablesOffsets.length][];
|
|
||||||
|
|
||||||
for (int j = 0; j < dcTables.length; j++) {
|
|
||||||
imageInput.seek(dcTablesOffsets[j]);
|
|
||||||
byte[] lengths = new byte[16];
|
|
||||||
|
|
||||||
imageInput.readFully(lengths);
|
|
||||||
|
|
||||||
int length = 0;
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
length += lengths[i] & 0xff;
|
|
||||||
}
|
|
||||||
|
|
||||||
dcTables[j] = new byte[16 + length];
|
|
||||||
System.arraycopy(lengths, 0, dcTables[j], 0, 16);
|
|
||||||
imageInput.readFully(dcTables[j], 16, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
long[] acTablesOffsets = getValueAsLongArray(TIFF.TAG_OLD_JPEG_AC_TABLES, "JPEGACTables", true);
|
|
||||||
byte[][] acTables = new byte[acTablesOffsets.length][];
|
byte[][] acTables = new byte[acTablesOffsets.length][];
|
||||||
for (int j = 0; j < acTables.length; j++) {
|
for (int j = 0; j < acTables.length; j++) {
|
||||||
imageInput.seek(acTablesOffsets[j]);
|
imageInput.seek(acTablesOffsets[j]);
|
||||||
@@ -1507,115 +1579,69 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
imageInput.readFully(acTables[j], 16, length);
|
imageInput.readFully(acTables[j], 16, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
long[] yCbCrSubSampling = getValueAsLongArray(TIFF.TAG_YCBCR_SUB_SAMPLING, "YCbCrSubSampling", false);
|
byte[][] dcTables = new byte[dcTablesOffsets.length][];
|
||||||
int subsampling = yCbCrSubSampling != null
|
for (int j = 0; j < dcTables.length; j++) {
|
||||||
? (int) ((yCbCrSubSampling[0] & 0xf) << 4 | yCbCrSubSampling[1] & 0xf)
|
imageInput.seek(dcTablesOffsets[j]);
|
||||||
: 0x22;
|
byte[] lengths = new byte[16];
|
||||||
|
|
||||||
// Read data
|
imageInput.readFully(lengths);
|
||||||
processImageStarted(imageIndex);
|
|
||||||
|
|
||||||
for (int y = 0; y < tilesDown; y++) {
|
int length = 0;
|
||||||
int col = 0;
|
for (int i = 0; i < 16; i++) {
|
||||||
int rowsInTile = Math.min(stripTileHeight, height - srcRow);
|
length += lengths[i] & 0xff;
|
||||||
|
|
||||||
for (int x = 0; x < tilesAcross; x++) {
|
|
||||||
int colsInTile = Math.min(stripTileWidth, width - col);
|
|
||||||
int i = y * tilesAcross + x;
|
|
||||||
|
|
||||||
// Read only tiles that lies within region
|
|
||||||
if (new Rectangle(col, srcRow, colsInTile, rowsInTile).intersects(srcRegion)) {
|
|
||||||
int length = stripTileByteCounts != null ? (int) stripTileByteCounts[i] : Short.MAX_VALUE;
|
|
||||||
imageInput.seek(stripTileOffsets[i]);
|
|
||||||
|
|
||||||
// If the tile stream starts with SOS...
|
|
||||||
if (x == 0 && y == 0) {
|
|
||||||
if ((short) (imageInput.readByte() << 8 | imageInput.readByte()) == (short) JPEG.SOS) {
|
|
||||||
imageInput.seek(stripTileOffsets[i] + 14); // TODO: Read from SOS length from stream, in case of gray/CMYK
|
|
||||||
length -= 14;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
imageInput.seek(stripTileOffsets[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try (ImageInputStream stream = ImageIO.createImageInputStream(new SequenceInputStream(Collections.enumeration(
|
dcTables[j] = new byte[16 + length];
|
||||||
asList(
|
System.arraycopy(lengths, 0, dcTables[j], 0, 16);
|
||||||
createJFIFStream(destRaster.getNumBands(), stripTileWidth, stripTileHeight, qTables, dcTables, acTables, subsampling),
|
imageInput.readFully(dcTables[j], 16, length);
|
||||||
createStreamAdapter(imageInput, length),
|
|
||||||
new ByteArrayInputStream(new byte[] {(byte) 0xff, (byte) 0xd9}) // EOI
|
|
||||||
)
|
|
||||||
)))) {
|
|
||||||
jpegReader.setInput(stream);
|
|
||||||
jpegParam.setSourceRegion(new Rectangle(0, 0, colsInTile, rowsInTile));
|
|
||||||
jpegParam.setSourceSubsampling(xSub, ySub, 0, 0);
|
|
||||||
Point offset = new Point(col - srcRegion.x, srcRow - srcRegion.y);
|
|
||||||
|
|
||||||
if (needsCSConversion == null) {
|
|
||||||
needsCSConversion = needsCSConversion(compression, interpretation, readJPEGMetadataSafe(jpegReader));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!needsCSConversion) {
|
return createJPEGTables(qTables, dcTables, acTables);
|
||||||
jpegParam.setDestinationOffset(offset);
|
|
||||||
jpegParam.setDestination(destination);
|
|
||||||
jpegReader.read(0, jpegParam);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
|
|
||||||
// We'll have to use readAsRaster and later apply color space conversion ourselves
|
|
||||||
Raster raster = jpegReader.readRaster(0, jpegParam);
|
|
||||||
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
|
|
||||||
destination.getRaster().setDataElements(offset.x, offset.y, raster);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abortRequested()) {
|
private static byte[] createJPEGTables(byte[][] qTables, byte[][] dcTables, byte[][] acTables) throws IOException {
|
||||||
break;
|
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
|
||||||
|
2 +
|
||||||
|
5 * qTables.length + qTables.length * qTables[0].length +
|
||||||
|
5 * dcTables.length + dcTables.length * dcTables[0].length +
|
||||||
|
5 * acTables.length + acTables.length * acTables[0].length +
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
try (DataOutputStream out = new DataOutputStream(stream)) {
|
||||||
|
out.writeShort(JPEG.SOI);
|
||||||
|
|
||||||
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < qTables.length; tableIndex++) {
|
||||||
|
byte[] table = qTables[tableIndex];
|
||||||
|
out.writeShort(JPEG.DQT);
|
||||||
|
out.writeShort(3 + table.length); // DQT length
|
||||||
|
out.writeByte(tableIndex); // Q table id
|
||||||
|
out.write(table); // Table data
|
||||||
}
|
}
|
||||||
|
|
||||||
col += colsInTile;
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < dcTables.length; tableIndex++) {
|
||||||
|
byte[] table = dcTables[tableIndex];
|
||||||
|
out.writeShort(JPEG.DHT);
|
||||||
|
out.writeShort(3 + table.length); // DHT length
|
||||||
|
out.writeByte(tableIndex & 0xf); // Huffman table id
|
||||||
|
out.write(table); // Table data
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100f * srcRow / height);
|
// TODO: Consider merging if tables are equal
|
||||||
|
for (int tableIndex = 0; tableIndex < acTables.length; tableIndex++) {
|
||||||
if (abortRequested()) {
|
byte[] table = acTables[tableIndex];
|
||||||
processReadAborted();
|
out.writeShort(JPEG.DHT);
|
||||||
break;
|
out.writeShort(3 + table.length); // DHT length
|
||||||
|
out.writeByte(0x10 + (tableIndex & 0xf)); // Huffman table id
|
||||||
|
out.write(table); // Table data
|
||||||
}
|
}
|
||||||
|
|
||||||
srcRow += rowsInTile;
|
out.writeShort(JPEG.EOI);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
return stream.toByteArray();
|
||||||
|
|
||||||
// Known, but unsupported compression types
|
|
||||||
case TIFFCustom.COMPRESSION_NEXT:
|
|
||||||
case TIFFCustom.COMPRESSION_CCITTRLEW:
|
|
||||||
case TIFFCustom.COMPRESSION_THUNDERSCAN:
|
|
||||||
case TIFFCustom.COMPRESSION_IT8CTPAD:
|
|
||||||
case TIFFCustom.COMPRESSION_IT8LW:
|
|
||||||
case TIFFCustom.COMPRESSION_IT8MP:
|
|
||||||
case TIFFCustom.COMPRESSION_IT8BL:
|
|
||||||
case TIFFCustom.COMPRESSION_PIXARFILM:
|
|
||||||
case TIFFCustom.COMPRESSION_PIXARLOG:
|
|
||||||
case TIFFCustom.COMPRESSION_DCS:
|
|
||||||
case TIFFCustom.COMPRESSION_JBIG: // Doable with JBIG plugin?
|
|
||||||
case TIFFCustom.COMPRESSION_SGILOG:
|
|
||||||
case TIFFCustom.COMPRESSION_SGILOG24:
|
|
||||||
case TIFFCustom.COMPRESSION_JPEG2000: // Doable with JPEG2000 plugin?
|
|
||||||
throw new IIOException("Unsupported TIFF Compression value: " + compression);
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IIOException("Unknown TIFF Compression value: " + compression);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Convert color space from source to destination
|
|
||||||
|
|
||||||
processImageComplete();
|
|
||||||
|
|
||||||
return destination;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputStream createYCbCrUpsamplerStream(int photometricInterpretation, int planarConfiguration, int plane, int transferType,
|
private InputStream createYCbCrUpsamplerStream(int photometricInterpretation, int planarConfiguration, int plane, int transferType,
|
||||||
@@ -1650,11 +1676,11 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IIOMetadata readJPEGMetadataSafe(final ImageReader jpegReader) throws IOException {
|
private IIOMetadata readJPEGMetadataSafe(final ImageReader jpegReader) {
|
||||||
try {
|
try {
|
||||||
return jpegReader.getImageMetadata(0);
|
return jpegReader.getImageMetadata(0);
|
||||||
}
|
}
|
||||||
catch (IIOException e) {
|
catch (IOException e) {
|
||||||
processWarningOccurred(String.format("Could not read metadata for JPEG compressed TIFF (%s). Colors may look incorrect", e.getMessage()));
|
processWarningOccurred(String.format("Could not read metadata for JPEG compressed TIFF (%s). Colors may look incorrect", e.getMessage()));
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -1817,59 +1843,17 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
return nodes.getLength() >= 1 ? (IIOMetadataNode) nodes.item(0) : null;
|
return nodes.getLength() >= 1 ? (IIOMetadataNode) nodes.item(0) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageReader createJPEGDelegate() throws IOException {
|
private static InputStream createJFIFStream(int bands, int stripTileWidth, int stripTileHeight, int process, int subsampling) throws IOException {
|
||||||
// We'll just use the default (first) reader
|
|
||||||
// If it's the TwelveMonkeys one, we will be able to read JPEG Lossless etc.
|
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
|
|
||||||
if (!readers.hasNext()) {
|
|
||||||
throw new IIOException("Could not instantiate JPEGImageReader");
|
|
||||||
}
|
|
||||||
|
|
||||||
return readers.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InputStream createJFIFStream(int bands, int stripTileWidth, int stripTileHeight, byte[][] qTables, byte[][] dcTables, byte[][] acTables, int subsampling) throws IOException {
|
|
||||||
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
|
FastByteArrayOutputStream stream = new FastByteArrayOutputStream(
|
||||||
2 +
|
2 +
|
||||||
5 * qTables.length + qTables.length * qTables[0].length +
|
|
||||||
5 * dcTables.length + dcTables.length * dcTables[0].length +
|
|
||||||
5 * acTables.length + acTables.length * acTables[0].length +
|
|
||||||
2 + 2 + 6 + 3 * bands +
|
2 + 2 + 6 + 3 * bands +
|
||||||
8 + 2 * bands
|
2 + 6 + 2 * bands
|
||||||
);
|
);
|
||||||
|
|
||||||
DataOutputStream out = new DataOutputStream(stream);
|
try (DataOutputStream out = new DataOutputStream(stream)) {
|
||||||
|
|
||||||
out.writeShort(JPEG.SOI);
|
out.writeShort(JPEG.SOI);
|
||||||
|
|
||||||
// TODO: Consider merging if tables are equal
|
out.writeShort(process == 1 ? JPEG.SOF0 : JPEG.SOF3);
|
||||||
for (int tableIndex = 0; tableIndex < qTables.length; tableIndex++) {
|
|
||||||
byte[] table = qTables[tableIndex];
|
|
||||||
out.writeShort(JPEG.DQT);
|
|
||||||
out.writeShort(3 + table.length); // DQT length
|
|
||||||
out.writeByte(tableIndex); // Q table id
|
|
||||||
out.write(table); // Table data
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Consider merging if tables are equal
|
|
||||||
for (int tableIndex = 0; tableIndex < dcTables.length; tableIndex++) {
|
|
||||||
byte[] table = dcTables[tableIndex];
|
|
||||||
out.writeShort(JPEG.DHT);
|
|
||||||
out.writeShort(3 + table.length); // DHT length
|
|
||||||
out.writeByte(tableIndex & 0xf); // Huffman table id
|
|
||||||
out.write(table); // Table data
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Consider merging if tables are equal
|
|
||||||
for (int tableIndex = 0; tableIndex < acTables.length; tableIndex++) {
|
|
||||||
byte[] table = acTables[tableIndex];
|
|
||||||
out.writeShort(JPEG.DHT);
|
|
||||||
out.writeShort(3 + table.length); // DHT length
|
|
||||||
out.writeByte(0x10 + (tableIndex & 0xf)); // Huffman table id
|
|
||||||
out.write(table); // Table data
|
|
||||||
}
|
|
||||||
|
|
||||||
out.writeShort(JPEG.SOF0); // TODO: Use correct process for data
|
|
||||||
out.writeShort(2 + 6 + 3 * bands); // SOF0 len
|
out.writeShort(2 + 6 + 3 * bands); // SOF0 len
|
||||||
out.writeByte(8); // bits TODO: Consult raster/transfer type or BitsPerSample for 12/16 bits support
|
out.writeByte(8); // bits TODO: Consult raster/transfer type or BitsPerSample for 12/16 bits support
|
||||||
out.writeShort(stripTileHeight); // height
|
out.writeShort(stripTileHeight); // height
|
||||||
@@ -1894,6 +1878,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
out.writeByte(0); // Spectral selection start
|
out.writeByte(0); // Spectral selection start
|
||||||
out.writeByte(0); // Spectral selection end
|
out.writeByte(0); // Spectral selection end
|
||||||
out.writeByte(0); // Approx high & low
|
out.writeByte(0); // Approx high & low
|
||||||
|
}
|
||||||
|
|
||||||
return stream.createInputStream();
|
return stream.createInputStream();
|
||||||
}
|
}
|
||||||
@@ -2178,6 +2163,19 @@ public final class TIFFImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void normalizeColor(int interpretation, int numBands, Raster raster) throws IOException {
|
||||||
|
switch (raster.getTransferType()) {
|
||||||
|
case DataBuffer.TYPE_BYTE:
|
||||||
|
normalizeColor(interpretation, numBands, ((DataBufferByte) raster.getDataBuffer()).getData());
|
||||||
|
break;
|
||||||
|
case DataBuffer.TYPE_USHORT:
|
||||||
|
normalizeColor(interpretation, numBands, ((DataBufferUShort) raster.getDataBuffer()).getData());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void normalizeColor(int photometricInterpretation, int numBands, byte[] data) throws IOException {
|
private void normalizeColor(int photometricInterpretation, int numBands, byte[] data) throws IOException {
|
||||||
switch (photometricInterpretation) {
|
switch (photometricInterpretation) {
|
||||||
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
|
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
|
||||||
|
|||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TileDecoder.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: TileDecoder.java,v 1.0 09/11/2023 haraldk Exp$
|
||||||
|
*/
|
||||||
|
abstract class TileDecoder implements AutoCloseable {
|
||||||
|
|
||||||
|
protected final IIOReadWarningListener warningListener;
|
||||||
|
|
||||||
|
public TileDecoder(IIOReadWarningListener warningListener) {
|
||||||
|
this.warningListener = notNull(warningListener, "warningListener");
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract void decodeTile(ImageInputStream input, Rectangle sourceRegion, Point destinationOffset, BufferedImage destination) throws IOException;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void close();
|
||||||
|
|
||||||
|
interface RasterConverter {
|
||||||
|
void convert(Raster raster) throws IOException;
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-23
@@ -29,28 +29,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||||
|
|
||||||
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
|
||||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
|
||||||
import javax.imageio.spi.IIORegistry;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
import org.w3c.dom.NodeList;
|
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||||
@@ -60,6 +38,27 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
|||||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import javax.imageio.spi.IIORegistry;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TIFFImageMetadataTest.
|
* TIFFImageMetadataTest.
|
||||||
*
|
*
|
||||||
@@ -305,7 +304,7 @@ public class TIFFImageMetadataTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMergeTreeStandardFormat() throws IOException {
|
public void testMergeTreeStandardFormat() throws IOException {
|
||||||
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/zackthecat.tif");
|
TIFFImageMetadata metadata = (TIFFImageMetadata) createMetadata("/tiff/old-style-jpeg-zackthecat.tif");
|
||||||
|
|
||||||
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
String standardFormat = IIOMetadataFormatImpl.standardMetadataFormatName;
|
||||||
|
|
||||||
|
|||||||
+6
-4
@@ -94,7 +94,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
new TestData(getClassLoaderResource("/tiff/ycbcr-cat.tif"), new Dimension(250, 325)), // YCbCr, LZW compressed
|
new TestData(getClassLoaderResource("/tiff/ycbcr-cat.tif"), new Dimension(250, 325)), // YCbCr, LZW compressed
|
||||||
new TestData(getClassLoaderResource("/tiff/quad-jpeg.tif"), new Dimension(512, 384)), // YCbCr, JPEG compressed, striped
|
new TestData(getClassLoaderResource("/tiff/quad-jpeg.tif"), new Dimension(512, 384)), // YCbCr, JPEG compressed, striped
|
||||||
new TestData(getClassLoaderResource("/tiff/smallliz.tif"), new Dimension(160, 160)), // YCbCr, Old-Style JPEG compressed (full JFIF stream)
|
new TestData(getClassLoaderResource("/tiff/smallliz.tif"), new Dimension(160, 160)), // YCbCr, Old-Style JPEG compressed (full JFIF stream)
|
||||||
new TestData(getClassLoaderResource("/tiff/zackthecat.tif"), new Dimension(234, 213)), // YCbCr, Old-Style JPEG compressed (tables, no JFIF stream)
|
new TestData(getClassLoaderResource("/tiff/old-style-jpeg-zackthecat.tif"), new Dimension(234, 213)), // YCbCr, Old-Style JPEG compressed (tables, no JFIF stream)
|
||||||
new TestData(getClassLoaderResource("/tiff/test-single-gray-compression-type-2.tiff"), new Dimension(1728, 1146)), // Gray, CCITT type 2 compressed
|
new TestData(getClassLoaderResource("/tiff/test-single-gray-compression-type-2.tiff"), new Dimension(1728, 1146)), // Gray, CCITT type 2 compressed
|
||||||
new TestData(getClassLoaderResource("/tiff/cramps-tile.tif"), new Dimension(800, 607)), // Gray/WhiteIsZero, uncompressed, striped & tiled...
|
new TestData(getClassLoaderResource("/tiff/cramps-tile.tif"), new Dimension(800, 607)), // Gray/WhiteIsZero, uncompressed, striped & tiled...
|
||||||
new TestData(getClassLoaderResource("/tiff/lzw-long-strings-sample.tif"), new Dimension(316, 173)), // RGBA, LZW compressed w/predictor
|
new TestData(getClassLoaderResource("/tiff/lzw-long-strings-sample.tif"), new Dimension(316, 173)), // RGBA, LZW compressed w/predictor
|
||||||
@@ -191,7 +191,9 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
new TestData(getClassLoaderResource("/tiff/planar-yuv420-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
|
new TestData(getClassLoaderResource("/tiff/planar-yuv420-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
|
||||||
new TestData(getClassLoaderResource("/tiff/planar-yuv420-jpeg-lzw.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients,LZW compressed, striped
|
new TestData(getClassLoaderResource("/tiff/planar-yuv420-jpeg-lzw.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients,LZW compressed, striped
|
||||||
new TestData(getClassLoaderResource("/tiff/planar-yuv410-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
|
new TestData(getClassLoaderResource("/tiff/planar-yuv410-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
|
||||||
new TestData(getClassLoaderResource("/tiff/planar-yuv410-jpeg-lzw.tif"), new Dimension(256, 64)) // YCbCr, JPEG coefficients,LZW compressed, striped
|
new TestData(getClassLoaderResource("/tiff/planar-yuv410-jpeg-lzw.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients,LZW compressed, striped
|
||||||
|
// WebP compressed
|
||||||
|
new TestData(getClassLoaderResource("/tiff/webp_lossless_rgba_alpha_fully_opaque.tif"), new Dimension(20, 20)) // RGBA, WebP lossless
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +370,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadOldStyleWangMultiStrip2() throws IOException {
|
public void testReadOldStyleWangMultiStrip2() throws IOException {
|
||||||
TestData testData = new TestData(getClassLoaderResource("/tiff/662260-color.tif"), new Dimension(1600, 1200));
|
TestData testData = new TestData(getClassLoaderResource("/tiff/old-style-jpeg-662260-color.tif"), new Dimension(1600, 1200));
|
||||||
|
|
||||||
try (ImageInputStream stream = testData.getInputStream()) {
|
try (ImageInputStream stream = testData.getInputStream()) {
|
||||||
TIFFImageReader reader = createReader();
|
TIFFImageReader reader = createReader();
|
||||||
@@ -382,7 +384,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
|||||||
assertNotNull(image);
|
assertNotNull(image);
|
||||||
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
|
assertEquals(testData.getDimension(0), new Dimension(image.getWidth(), image.getHeight()));
|
||||||
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("Old-style JPEG"), contains("tables")));
|
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("Old-style JPEG"), contains("tables")));
|
||||||
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("Incorrect StripOffsets/TileOffsets"), contains("SOS marker")));
|
verify(warningListener, atLeastOnce()).warningOccurred(eq(reader), and(contains("Incorrect StripByteCounts/TileByteCounts"), contains("JPEGInterchangeFormatLength")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
Binary file not shown.
+12
-2
@@ -418,8 +418,18 @@ final class WebPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
types.add(rawImageType);
|
types.add(rawImageType);
|
||||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(header.containsALPH ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB));
|
|
||||||
types.add(ImageTypeSpecifiers.createFromBufferedImageType(header.containsALPH ? BufferedImage.TYPE_INT_ARGB_PRE : BufferedImage.TYPE_INT_BGR));
|
if (!header.containsALPH) {
|
||||||
|
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB));
|
||||||
|
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR));
|
||||||
|
|
||||||
|
// We can always decode into types with alpha
|
||||||
|
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
|
||||||
|
}
|
||||||
|
|
||||||
|
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB));
|
||||||
|
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE));
|
||||||
|
types.add(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE));
|
||||||
|
|
||||||
return types.iterator();
|
return types.iterator();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,6 +161,12 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>${project.groupId}</groupId>
|
||||||
|
<artifactId>imageio-webp</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${project.groupId}</groupId>
|
<groupId>${project.groupId}</groupId>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
|
|||||||
Reference in New Issue
Block a user