mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
#202, #433: Fixes offset issues when reading multiple JPEGs from single stream + embedded case (ie. TIFF).
This commit is contained in:
parent
27fcd495db
commit
2235f6c911
@ -39,6 +39,8 @@ import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
|
|||||||
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
|
||||||
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.stream.BufferedImageInputStream;
|
||||||
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
@ -126,6 +128,9 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
/** Cached list of JPEG segments we filter from the underlying stream */
|
/** Cached list of JPEG segments we filter from the underlying stream */
|
||||||
private List<Segment> segments;
|
private List<Segment> segments;
|
||||||
|
|
||||||
|
private int currentStreamIndex = 0;
|
||||||
|
private List<Long> streamOffsets = new ArrayList<>();
|
||||||
|
|
||||||
protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) {
|
||||||
super(provider);
|
super(provider);
|
||||||
|
|
||||||
@ -142,6 +147,10 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
@Override
|
@Override
|
||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
delegate.reset();
|
delegate.reset();
|
||||||
|
|
||||||
|
currentStreamIndex = 0;
|
||||||
|
streamOffsets.clear();
|
||||||
|
|
||||||
segments = null;
|
segments = null;
|
||||||
thumbnails = null;
|
thumbnails = null;
|
||||||
|
|
||||||
@ -171,29 +180,11 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
return delegate.getFormatName();
|
return delegate.getFormatName();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getNumImages(boolean allowSearch) throws IOException {
|
|
||||||
if (allowSearch) {
|
|
||||||
if (isLossless()) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return delegate.getNumImages(allowSearch);
|
|
||||||
}
|
|
||||||
catch (ArrayIndexOutOfBoundsException ignore) {
|
|
||||||
// This will happen if we find a "tables only" image, with no more images in stream.
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLossless() throws IOException {
|
private boolean isLossless() throws IOException {
|
||||||
assertInput();
|
assertInput();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Frame sof = getSOF();
|
if (getSOF().marker == JPEG.SOF3) {
|
||||||
if (sof.marker == JPEG.SOF3) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,32 +201,27 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
@Override
|
@Override
|
||||||
public int getWidth(int imageIndex) throws IOException {
|
public int getWidth(int imageIndex) throws IOException {
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
|
initHeader(imageIndex);
|
||||||
|
|
||||||
Frame sof = getSOF();
|
return getSOF().samplesPerLine;
|
||||||
if (sof.marker == JPEG.SOF3) {
|
|
||||||
return sof.samplesPerLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
return delegate.getWidth(imageIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeight(int imageIndex) throws IOException {
|
public int getHeight(int imageIndex) throws IOException {
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
|
initHeader(imageIndex);
|
||||||
|
|
||||||
Frame sof = getSOF();
|
return getSOF().lines;
|
||||||
if (sof.marker == JPEG.SOF3) {
|
|
||||||
return sof.lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
return delegate.getHeight(imageIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
initHeader(imageIndex);
|
||||||
|
|
||||||
Iterator<ImageTypeSpecifier> types;
|
Iterator<ImageTypeSpecifier> types;
|
||||||
try {
|
try {
|
||||||
types = delegate.getImageTypes(imageIndex);
|
types = delegate.getImageTypes(0);
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException | NegativeArraySizeException ignore) {
|
catch (IndexOutOfBoundsException | NegativeArraySizeException ignore) {
|
||||||
types = null;
|
types = null;
|
||||||
@ -301,9 +287,12 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||||
|
checkBounds(imageIndex);
|
||||||
|
initHeader(imageIndex);
|
||||||
|
|
||||||
// If delegate can determine the spec, we'll just go with that
|
// If delegate can determine the spec, we'll just go with that
|
||||||
try {
|
try {
|
||||||
ImageTypeSpecifier rawType = delegate.getRawImageType(imageIndex);
|
ImageTypeSpecifier rawType = delegate.getRawImageType(0);
|
||||||
|
|
||||||
if (rawType != null) {
|
if (rawType != null) {
|
||||||
return rawType;
|
return rawType;
|
||||||
@ -332,25 +321,10 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
|
|
||||||
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
|
||||||
|
|
||||||
// JPEGSegmentImageInputStream that filters out/skips bad/unnecessary segments
|
|
||||||
delegate.setInput(imageInput != null
|
|
||||||
? new JPEGSegmentImageInputStream(imageInput, new JPEGSegmentStreamWarningDelegate())
|
|
||||||
: null, seekForwardOnly, ignoreMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isRandomAccessEasy(int imageIndex) throws IOException {
|
|
||||||
return delegate.isRandomAccessEasy(imageIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||||
assertInput();
|
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
|
initHeader(imageIndex);
|
||||||
|
|
||||||
Frame sof = getSOF();
|
Frame sof = getSOF();
|
||||||
ICC_Profile profile = getEmbeddedICCProfile(false);
|
ICC_Profile profile = getEmbeddedICCProfile(false);
|
||||||
@ -401,7 +375,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
sourceCSType == JPEGColorSpace.YCCK ||
|
sourceCSType == JPEGColorSpace.YCCK ||
|
||||||
profile != null && !ColorSpaces.isCS_sRGB(profile) ||
|
profile != null && !ColorSpaces.isCS_sRGB(profile) ||
|
||||||
(long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE ||
|
(long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE ||
|
||||||
!delegate.getImageTypes(imageIndex).hasNext() ||
|
!delegate.getImageTypes(0).hasNext() ||
|
||||||
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null)) { // TODO: Issue warning?
|
sourceCSType == JPEGColorSpace.YCbCr && getRawImageType(imageIndex) != null)) { // TODO: Issue warning?
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
System.out.println("Reading using raster and extra conversion");
|
System.out.println("Reading using raster and extra conversion");
|
||||||
@ -416,7 +390,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
System.out.println("Reading using delegate");
|
System.out.println("Reading using delegate");
|
||||||
}
|
}
|
||||||
|
|
||||||
return delegate.read(imageIndex, param);
|
return delegate.read(0, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, Frame startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
|
private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, Frame startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException {
|
||||||
@ -519,7 +493,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
// for each iteration, so we'll read all at once.
|
// for each iteration, so we'll read all at once.
|
||||||
try {
|
try {
|
||||||
param.setSourceRegion(srcRegion);
|
param.setSourceRegion(srcRegion);
|
||||||
Raster raster = delegate.readRaster(imageIndex, param); // non-converted
|
Raster raster = delegate.readRaster(0, param); // non-converted
|
||||||
|
|
||||||
// Apply source color conversion from implicit color space
|
// Apply source color conversion from implicit color space
|
||||||
if (csType == JPEGColorSpace.YCbCr) {
|
if (csType == JPEGColorSpace.YCbCr) {
|
||||||
@ -642,7 +616,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
|
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
|
||||||
// NOTE: This is probably not the right way to do it... :-P
|
// NOTE: This is probably not the right way to do it... :-P
|
||||||
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
|
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
|
||||||
|
|
||||||
@ -681,6 +655,30 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
array[index + 3] = (byte) (value );
|
array[index + 3] = (byte) (value );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
|
||||||
|
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (imageInput != null) {
|
||||||
|
streamOffsets.add(imageInput.getStreamPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
initDelegate(seekForwardOnly, ignoreMetadata);
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
// TODO: This should ideally be reported as an IOException, but I don't see how
|
||||||
|
throw new IllegalStateException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initDelegate(boolean seekForwardOnly, boolean ignoreMetadata) throws IOException {
|
||||||
|
// JPEGSegmentImageInputStream that filters out/skips bad/unnecessary segments
|
||||||
|
delegate.setInput(imageInput != null
|
||||||
|
? new JPEGSegmentImageInputStream(new SubImageInputStream(imageInput, Long.MAX_VALUE), new JPEGSegmentStreamWarningDelegate())
|
||||||
|
: null, seekForwardOnly, ignoreMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
private void initHeader() throws IOException {
|
private void initHeader() throws IOException {
|
||||||
if (segments == null) {
|
if (segments == null) {
|
||||||
long start = DEBUG ? System.currentTimeMillis() : 0;
|
long start = DEBUG ? System.currentTimeMillis() : 0;
|
||||||
@ -714,11 +712,127 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initHeader(final int imageIndex) throws IOException {
|
||||||
|
if (imageIndex < 0) {
|
||||||
|
throw new IllegalArgumentException("imageIndex < 0: " + imageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageIndex == currentStreamIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gotoImage(imageIndex);
|
||||||
|
|
||||||
|
// Reset segments and re-init the header
|
||||||
|
segments = null;
|
||||||
|
thumbnails = null;
|
||||||
|
|
||||||
|
initDelegate(seekForwardOnly, ignoreMetadata);
|
||||||
|
|
||||||
|
initHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void gotoImage(final int imageIndex) throws IOException {
|
||||||
|
if (imageIndex < streamOffsets.size()) {
|
||||||
|
imageInput.seek(streamOffsets.get(imageIndex));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
long lastKnownSOIOffset = streamOffsets.get(streamOffsets.size() - 1);
|
||||||
|
imageInput.seek(lastKnownSOIOffset);
|
||||||
|
|
||||||
|
try (ImageInputStream stream = new BufferedImageInputStream(imageInput)) { // Extreme (10s -> 50ms) speedup if imageInput is FileIIS
|
||||||
|
for (int i = streamOffsets.size() - 1; i < imageIndex; i++) {
|
||||||
|
long start = 0;
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
start = System.currentTimeMillis();
|
||||||
|
System.out.println(String.format("Start seeking for image index %d", i + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to skip over segments, as they may contain JPEG markers (eg. JFXX or EXIF thumbnail)
|
||||||
|
JPEGSegmentUtil.readSegments(stream, Collections.<Integer, List<String>>emptyMap());
|
||||||
|
|
||||||
|
// Now, search for EOI and following SOI...
|
||||||
|
int marker;
|
||||||
|
while ((marker = stream.read()) != -1) {
|
||||||
|
if (marker == 0xFF && (0xFF00 | stream.readUnsignedByte()) == JPEG.EOI) {
|
||||||
|
// Found EOI, now the SOI should be nearby...
|
||||||
|
while ((marker = stream.read()) != -1) {
|
||||||
|
if (marker == 0xFF && (0xFF00 | stream.readUnsignedByte()) == JPEG.SOI) {
|
||||||
|
long nextSOIOffset = stream.getStreamPosition() - 2;
|
||||||
|
imageInput.seek(nextSOIOffset);
|
||||||
|
streamOffsets.add(nextSOIOffset);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...or we may have missed it, but at least we tried
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(String.format("Seek in %d ms", System.currentTimeMillis() - start));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (EOFException eof) {
|
||||||
|
IndexOutOfBoundsException ioobe = new IndexOutOfBoundsException("Image index " + imageIndex + " not found in stream");
|
||||||
|
ioobe.initCause(eof);
|
||||||
|
throw ioobe;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageIndex >= streamOffsets.size()) {
|
||||||
|
throw new IndexOutOfBoundsException("Image index " + imageIndex + " not found in stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStreamIndex = imageIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getNumImages(boolean allowSearch) throws IOException {
|
||||||
|
assertInput();
|
||||||
|
|
||||||
|
if (allowSearch) {
|
||||||
|
if (seekForwardOnly) {
|
||||||
|
throw new IllegalStateException("seekForwardOnly and allowSearch are both true");
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
int count = 0;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
gotoImage(index++);
|
||||||
|
}
|
||||||
|
catch (IndexOutOfBoundsException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: We should probably optimize this
|
||||||
|
try {
|
||||||
|
getSOF(); // No SOF, no image
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
catch (IIOException ignore) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentStreamIndex = -1;
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't possibly know without searching
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
private List<JPEGSegment> readSegments() throws IOException {
|
private List<JPEGSegment> readSegments() throws IOException {
|
||||||
imageInput.mark();
|
imageInput.mark();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
imageInput.seek(0); // TODO: Seek to wanted image, skip images on the way
|
imageInput.seek(streamOffsets.get(currentStreamIndex));
|
||||||
|
|
||||||
return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
|
||||||
}
|
}
|
||||||
@ -794,7 +908,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
processWarningOccurred("Exif chunk has no data.");
|
processWarningOccurred("Exif chunk has no data.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ImageInputStream stream = ImageIO.createImageInputStream(data);
|
ImageInputStream stream = new MemoryCacheImageInputStream(data);
|
||||||
return (CompoundDirectory) new TIFFReader().read(stream);
|
return (CompoundDirectory) new TIFFReader().read(stream);
|
||||||
|
|
||||||
// TODO: Directory offset of thumbnail is wrong/relative to container stream, causing trouble for the TIFFReader...
|
// TODO: Directory offset of thumbnail is wrong/relative to container stream, causing trouble for the TIFFReader...
|
||||||
@ -933,6 +1047,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
@Override
|
@Override
|
||||||
public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
|
public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
|
initHeader(imageIndex);
|
||||||
|
|
||||||
if (isLossless()) {
|
if (isLossless()) {
|
||||||
// TODO: What about stream position?
|
// TODO: What about stream position?
|
||||||
@ -941,7 +1056,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return delegate.readRaster(imageIndex, param);
|
return delegate.readRaster(0, param);
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException knownIssue) {
|
catch (IndexOutOfBoundsException knownIssue) {
|
||||||
// com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
|
// com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
|
||||||
@ -973,6 +1088,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
private void readThumbnailMetadata(int imageIndex) throws IOException {
|
private void readThumbnailMetadata(int imageIndex) throws IOException {
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
|
initHeader(imageIndex);
|
||||||
|
|
||||||
if (thumbnails == null) {
|
if (thumbnails == null) {
|
||||||
thumbnails = new ArrayList<>();
|
thumbnails = new ArrayList<>();
|
||||||
@ -1098,6 +1214,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
// checkBounds needed, as we catch the IndexOutOfBoundsException below.
|
// checkBounds needed, as we catch the IndexOutOfBoundsException below.
|
||||||
checkBounds(imageIndex);
|
checkBounds(imageIndex);
|
||||||
|
initHeader(imageIndex);
|
||||||
|
|
||||||
IIOMetadata imageMetadata;
|
IIOMetadata imageMetadata;
|
||||||
|
|
||||||
@ -1106,7 +1223,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
try {
|
try {
|
||||||
imageMetadata = delegate.getImageMetadata(imageIndex);
|
imageMetadata = delegate.getImageMetadata(0);
|
||||||
}
|
}
|
||||||
catch (IndexOutOfBoundsException knownIssue) {
|
catch (IndexOutOfBoundsException knownIssue) {
|
||||||
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
|
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
|
||||||
@ -1189,7 +1306,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void imageStarted(ImageReader source, int imageIndex) {
|
public void imageStarted(ImageReader source, int imageIndex) {
|
||||||
processImageStarted(imageIndex);
|
processImageStarted(currentStreamIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1219,7 +1336,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
|
public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) {
|
||||||
processThumbnailStarted(imageIndex, thumbnailIndex);
|
processThumbnailStarted(currentStreamIndex, thumbnailIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void passStarted(ImageReader source, BufferedImage theImage, int pass, int minPass, int maxPass, int minX, int minY, int periodX, int periodY, int[] bands) {
|
public void passStarted(ImageReader source, BufferedImage theImage, int pass, int minPass, int maxPass, int minX, int minY, int periodX, int periodY, int[] bands) {
|
||||||
@ -1274,6 +1391,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
|||||||
processWarningOccurred(warning);
|
processWarningOccurred(warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void showIt(final BufferedImage pImage, final String pTitle) {
|
protected static void showIt(final BufferedImage pImage, final String pTitle) {
|
||||||
ImageReaderBase.showIt(pImage, pTitle);
|
ImageReaderBase.showIt(pImage, pTitle);
|
||||||
}
|
}
|
||||||
|
@ -172,6 +172,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (isSOFMarker(marker)) {
|
else if (isSOFMarker(marker)) {
|
||||||
|
// TODO: Warning + ignore if we already have a SOF
|
||||||
// Replace duplicate SOFn component ids
|
// Replace duplicate SOFn component ids
|
||||||
byte[] data = readReplaceDuplicateSOFnComponentIds(marker, length);
|
byte[] data = readReplaceDuplicateSOFnComponentIds(marker, length);
|
||||||
segment = new ReplacementSegment(marker, realPosition, segment.end(), length, data);
|
segment = new ReplacementSegment(marker, realPosition, segment.end(), length, data);
|
||||||
@ -272,7 +273,7 @@ final class JPEGSegmentImageInputStream extends ImageInputStreamImpl {
|
|||||||
processWarningOccured(String.format("Duplicate component ID %d in SOF", id));
|
processWarningOccured(String.format("Duplicate component ID %d in SOF", id));
|
||||||
|
|
||||||
id++;
|
id++;
|
||||||
while (!componentIds.add(id)) {
|
while (!componentIds.add(id) && componentIds.size() <= 16) {
|
||||||
id++;
|
id++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,6 +100,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
new TestData(getClassLoaderResource("/jpeg/app-marker-missing-null-term.jpg"), new Dimension(200, 150)),
|
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/jfif-16bit-dqt.jpg"), new Dimension(204, 131)),
|
||||||
new TestData(getClassLoaderResource("/jpeg/jfif-grayscale-thumbnail.jpg"), new Dimension(2547, 1537)), // Non-compliant JFIF with 8 bit grayscale thumbnail
|
new TestData(getClassLoaderResource("/jpeg/jfif-grayscale-thumbnail.jpg"), new Dimension(2547, 1537)), // Non-compliant JFIF with 8 bit grayscale thumbnail
|
||||||
|
new TestData(getClassLoaderResource("/jpeg/jfif-with-preview-as-second-image.jpg"), new Dimension(3968, 2976), new Dimension(640, 480)), // JFIF, full size + preview
|
||||||
new TestData(getClassLoaderResource("/jpeg-lossless/8_ls.jpg"), new Dimension(800, 535)), // Lossless gray, 8 bit
|
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/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/24_ls.jpg"), new Dimension(800, 535)), // Lossless RGB, 8 bit per component (24 bit)
|
||||||
@ -1778,4 +1779,32 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
|
|||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadSequenceInverse() throws IOException {
|
||||||
|
JPEGImageReader reader = createReader();
|
||||||
|
|
||||||
|
try {
|
||||||
|
reader.setInput(ImageIO.createImageInputStream(getClassLoaderResource("/jpeg/jfif-with-preview-as-second-image.jpg")));
|
||||||
|
|
||||||
|
BufferedImage image = reader.read(1, null);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(640, image.getWidth());
|
||||||
|
assertEquals(480, image.getHeight());
|
||||||
|
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
|
||||||
|
|
||||||
|
image = reader.read(0, null);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(3968, image.getWidth());
|
||||||
|
assertEquals(2976, image.getHeight());
|
||||||
|
assertEquals(ColorSpace.TYPE_RGB, image.getColorModel().getColorSpace().getType());
|
||||||
|
|
||||||
|
assertEquals(2, reader.getNumImages(true));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 2.7 MiB |
Loading…
x
Reference in New Issue
Block a user