diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 1b7a90ec..8335ee40 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -28,6 +28,7 @@ package com.twelvemonkeys.imageio.plugins.jpeg; +import com.twelvemonkeys.imageio.AbstractMetadata; import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.color.ColorSpaces; import com.twelvemonkeys.imageio.color.YCbCrConverter; @@ -39,16 +40,17 @@ import com.twelvemonkeys.imageio.metadata.exif.TIFF; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; -import com.twelvemonkeys.imageio.plugins.jpeg.lossless.DataStream; import com.twelvemonkeys.imageio.plugins.jpeg.lossless.JPEGLosslessDecoderWrapper; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.xml.XMLSerializer; +import org.w3c.dom.Node; import javax.imageio.*; import javax.imageio.event.IIOReadUpdateListener; import javax.imageio.event.IIOReadWarningListener; +import javax.imageio.metadata.IIOInvalidTreeException; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; @@ -136,6 +138,10 @@ public class JPEGImageReader extends ImageReaderBase { map.put(JPEG.SOF14, null); map.put(JPEG.SOF15, null); + map.put(JPEG.DQT, null); + map.put(JPEG.DHT, null); + map.put(JPEG.SOS, null); + return Collections.unmodifiableMap(map); } @@ -201,6 +207,12 @@ public class JPEGImageReader extends ImageReaderBase { @Override public int getNumImages(boolean allowSearch) throws IOException { + if (allowSearch) { + if (isLossless()) { + return 1; + } + } + try { return delegate.getNumImages(allowSearch); } @@ -210,13 +222,43 @@ public class JPEGImageReader extends ImageReaderBase { } } + private boolean isLossless() throws IOException { + assertInput(); + + try { + SOFSegment sof = getSOF(); + if (sof.marker == JPEG.SOF3) { + return true; + } + } + catch (IIOException ignore) { + // May happen if no SOF is found, in case we'll just fall through + } + + return false; + } + @Override public int getWidth(int imageIndex) throws IOException { + checkBounds(imageIndex); + + SOFSegment sof = getSOF(); + if (sof.marker == JPEG.SOF3) { + return sof.samplesPerLine; + } + return delegate.getWidth(imageIndex); } @Override public int getHeight(int imageIndex) throws IOException { + checkBounds(imageIndex); + + SOFSegment sof = getSOF(); + if (sof.marker == JPEG.SOF3) { + return sof.lines; + } + return delegate.getHeight(imageIndex); } @@ -277,7 +319,7 @@ public class JPEGImageReader extends ImageReaderBase { return rawType; } } - catch (NullPointerException ignore) { + catch (IIOException | NullPointerException ignore) { // Fall through } @@ -340,33 +382,11 @@ public class JPEGImageReader extends ImageReaderBase { JPEGColorSpace sourceCSType = getSourceCSType(getJFIF(), adobeDCT, sof); - //try to read an JPEG Lossless image - if(sof.marker == JPEG.SOF3){ - JPEGLosslessDecoderWrapper decoder = new JPEGLosslessDecoderWrapper(); - ImageInputStream inputStream = (ImageInputStream) this.getInput(); - - /* try to read the input stream as once. If length is not supported - * the image is read blockwise - */ - byte[] rawByteData; - long byteLength = inputStream.length(); - if(byteLength > 0 && byteLength < Integer.MAX_VALUE){ - rawByteData = new byte[(int)byteLength]; - inputStream.read(rawByteData); - } else { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(1024 * 1024 /* Provide a size close to what is likely the case for better performance*/); - - byte[] buffer = new byte[1024]; - int count; - while ((count = inputStream.read(buffer)) > 0) { - bytes.write(buffer, 0, count); - } - - rawByteData = bytes.toByteArray(); - } - - //try do read the image, IOException can be thrown - return decoder.readImage(rawByteData); + if (sof.marker == JPEG.SOF3) { + // TODO: What about stream position? + // TODO: Param handling: Source region, offset, subsampling, destination, destination type, etc.... + // Read image as lossless + return new JPEGLosslessDecoderWrapper().readImage(imageInput); } // We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is) @@ -746,34 +766,35 @@ public class JPEGImageReader extends ImageReaderBase { initHeader(); for (JPEGSegment segment : segments) { - if (JPEG.SOF0 >= segment.marker() && segment.marker() <= JPEG.SOF3 || - JPEG.SOF5 >= segment.marker() && segment.marker() <= JPEG.SOF7 || - JPEG.SOF9 >= segment.marker() && segment.marker() <= JPEG.SOF11 || - JPEG.SOF13 >= segment.marker() && segment.marker() <= JPEG.SOF15) { + if (JPEG.SOF0 <= segment.marker() && segment.marker() <= JPEG.SOF3 || + JPEG.SOF5 <= segment.marker() && segment.marker() <= JPEG.SOF7 || + JPEG.SOF9 <= segment.marker() && segment.marker() <= JPEG.SOF11 || + JPEG.SOF13 <= segment.marker() && segment.marker() <= JPEG.SOF15) { - DataInputStream data = new DataInputStream(segment.data()); - - try { - int samplePrecision = data.readUnsignedByte(); - int lines = data.readUnsignedShort(); - int samplesPerLine = data.readUnsignedShort(); - int componentsInFrame = data.readUnsignedByte(); - - SOFComponent[] components = new SOFComponent[componentsInFrame]; - - for (int i = 0; i < componentsInFrame; i++) { - int id = data.readUnsignedByte(); - int sub = data.readUnsignedByte(); - int qtSel = data.readUnsignedByte(); - - components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel); - } - - return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components); - } - finally { - data.close(); + try (DataInputStream data = new DataInputStream(segment.data())) { + return SOFSegment.read(segment.marker(), data); } +// try { +// int samplePrecision = data.readUnsignedByte(); +// int lines = data.readUnsignedShort(); +// int samplesPerLine = data.readUnsignedShort(); +// int componentsInFrame = data.readUnsignedByte(); +// +// SOFComponent[] components = new SOFComponent[componentsInFrame]; +// +// for (int i = 0; i < componentsInFrame; i++) { +// int id = data.readUnsignedByte(); +// int sub = data.readUnsignedByte(); +// int qtSel = data.readUnsignedByte(); +// +// components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel); +// } +// +// return new SOFSegment(segment.marker(), samplePrecision, lines, samplesPerLine, components); +// } +// finally { +// data.close(); +// } } } @@ -960,7 +981,15 @@ public class JPEGImageReader extends ImageReaderBase { } @Override - public Raster readRaster(int imageIndex, ImageReadParam param) throws IOException { + public Raster readRaster(final int imageIndex, final ImageReadParam param) throws IOException { + checkBounds(imageIndex); + + if (isLossless()) { + // TODO: What about stream position? + // TODO: Param handling: Reading as raster should support source region, subsampling etc. + return new JPEGLosslessDecoderWrapper().readRaster(imageInput); + } + return delegate.readRaster(imageIndex, param); } @@ -1101,24 +1130,58 @@ public class JPEGImageReader extends ImageReaderBase { IIOMetadata imageMetadata; - try { - imageMetadata = delegate.getImageMetadata(imageIndex); - } - catch (IndexOutOfBoundsException knownIssue) { - // TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data. - throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue); - } - catch (NegativeArraySizeException knownIssue) { - // Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment - throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue); - } + if (isLossless()) { + return new AbstractMetadata(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null) { + @Override + protected Node getNativeTree() { + IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0); - if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) { - if (metadataCleaner == null) { - metadataCleaner = new JPEGImage10MetadataCleaner(this); + root.appendChild(new IIOMetadataNode("JPEGvariety")); + IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence"); + root.appendChild(markerSequence); + + for (JPEGSegment segment : segments) { + switch (segment.marker()) { + // SOF3 is the only one supported by now + case JPEG.SOF3: + markerSequence.appendChild(new IIOMetadataNode("sof")); + break; + case JPEG.DHT: + markerSequence.appendChild(new IIOMetadataNode("dht")); + break; + case JPEG.DQT: + markerSequence.appendChild(new IIOMetadataNode("dqt")); + break; + case JPEG.SOS: + markerSequence.appendChild(new IIOMetadataNode("sos")); + break; + } + } + + return root; + } + }; + } + else { + try { + imageMetadata = delegate.getImageMetadata(imageIndex); + } + catch (IndexOutOfBoundsException knownIssue) { + // TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data. + throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue); + } + catch (NegativeArraySizeException knownIssue) { + // Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment + throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue); } - return metadataCleaner.cleanMetadata(imageMetadata); + if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) { + if (metadataCleaner == null) { + metadataCleaner = new JPEGImage10MetadataCleaner(this); + } + + return metadataCleaner.cleanMetadata(imageMetadata); + } } return imageMetadata; @@ -1402,7 +1465,8 @@ public class JPEGImageReader extends ImageReaderBase { image = reader.getImageTypes(0).next().createBufferedImage((reader.getWidth(0) + subX - 1)/ subX, (reader.getHeight(0) + subY - 1) / subY); } else { - image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); +// image = reader.getImageTypes(0).next().createBufferedImage(reader.getWidth(0), reader.getHeight(0)); + image = null; } param.setDestination(image); diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java index 526f0c00..31d5314e 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java @@ -28,6 +28,8 @@ package com.twelvemonkeys.imageio.plugins.jpeg; +import java.io.DataInput; +import java.io.IOException; import java.util.Arrays; /** @@ -63,4 +65,23 @@ final class SOFSegment { marker & 0xff - 0xc0, marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components) ); } + + public static SOFSegment read(final int marker, final DataInput data) throws IOException { + int samplePrecision = data.readUnsignedByte(); + int lines = data.readUnsignedShort(); + int samplesPerLine = data.readUnsignedShort(); + int componentsInFrame = data.readUnsignedByte(); + + SOFComponent[] components = new SOFComponent[componentsInFrame]; + + for (int i = 0; i < componentsInFrame; i++) { + int id = data.readUnsignedByte(); + int sub = data.readUnsignedByte(); + int qtSel = data.readUnsignedByte(); + + components[i] = new SOFComponent(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel); + } + + return new SOFSegment(marker, samplePrecision, lines, samplesPerLine, components); + } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ComponentSpec.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ComponentSpec.java index b517de7a..a22a4fac 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ComponentSpec.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ComponentSpec.java @@ -32,7 +32,7 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; public class ComponentSpec { - protected int hSamp; // Horizontal sampling factor - protected int quantTableSel; // Quantization table destination selector - protected int vSamp; // Vertical + protected int hSamp; // Horizontal sampling factor + protected int quantTableSel; // Quantization table destination selector + protected int vSamp; // Vertical } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/DataStream.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/DataStream.java deleted file mode 100644 index 842493f5..00000000 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/DataStream.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2015 Michael Martinez - * Changes: Added support for selection values 2-7, fixed minor bugs & - * warnings, split into multiple class files, and general clean up. - * - * 08-25-2015: Helmut Dersch agreed to a license change from LGPL to MIT. - */ - -/* - * Copyright (C) Helmut Dersch - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -package com.twelvemonkeys.imageio.plugins.jpeg.lossless; - -public interface DataStream { - - int get16(); - - - - int get8(); -} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/FrameHeader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/FrameHeader.java index 52dec2ca..6bf554f8 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/FrameHeader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/FrameHeader.java @@ -30,99 +30,87 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; +import javax.imageio.stream.ImageInputStream; import java.io.IOException; - public class FrameHeader { - private ComponentSpec components[]; // Components - private int dimX; // Number of samples per line - private int dimY; // Number of lines - private int numComp; // Number of component in the frame - private int precision; // Sample Precision (from the original image) + private ComponentSpec components[]; // Components + private int dimX; // Number of samples per line + private int dimY; // Number of lines + private int numComp; // Number of component in the frame + private int precision; // Sample Precision (from the original image) + public ComponentSpec[] getComponents() { + return components.clone(); + } + public int getDimX() { + return dimX; + } - public ComponentSpec[] getComponents() { - return components.clone(); - } + public int getDimY() { + return dimY; + } + public int getNumComponents() { + return numComp; + } + public int getPrecision() { + return precision; + } - public int getDimX() { - return dimX; - } + protected int read(final ImageInputStream data) throws IOException { + int count = 0; + final int length = data.readUnsignedShort(); + count += 2; + precision = data.readUnsignedByte(); + count++; - public int getDimY() { - return dimY; - } + dimY = data.readUnsignedShort(); + count += 2; + dimX = data.readUnsignedShort(); + count += 2; + numComp = data.readUnsignedByte(); + count++; - public int getNumComponents() { - return numComp; - } + //components = new ComponentSpec[numComp]; // some image exceed this range... + components = new ComponentSpec[256]; // setting to 256 -- not sure what it should be. + for (int i = 1; i <= numComp; i++) { + if (count > length) { + throw new IOException("ERROR: frame format error"); + } + final int c = data.readUnsignedByte(); + count++; - public int getPrecision() { - return precision; - } + if (count >= length) { + throw new IOException("ERROR: frame format error [c>=Lf]"); + } + final int temp = data.readUnsignedByte(); + count++; + if (components[c] == null) { + components[c] = new ComponentSpec(); + } - protected int read(final DataStream data) throws IOException { - int count = 0; + components[c].hSamp = temp >> 4; + components[c].vSamp = temp & 0x0F; + components[c].quantTableSel = data.readUnsignedByte(); + count++; + } - final int length = data.get16(); - count += 2; + if (count != length) { + throw new IOException("ERROR: frame format error [Lf!=count]"); + } - precision = data.get8(); - count++; - - dimY = data.get16(); - count += 2; - - dimX = data.get16(); - count += 2; - - numComp = data.get8(); - count++; - - //components = new ComponentSpec[numComp]; // some image exceed this range... - components = new ComponentSpec[256]; // setting to 256 -- not sure what it should be. - - for (int i = 1; i <= numComp; i++) { - if (count > length) { - throw new IOException("ERROR: frame format error"); - } - - final int c = data.get8(); - count++; - - if (count >= length) { - throw new IOException("ERROR: frame format error [c>=Lf]"); - } - - final int temp = data.get8(); - count++; - - if (components[c] == null) { - components[c] = new ComponentSpec(); - } - - components[c].hSamp = temp >> 4; - components[c].vSamp = temp & 0x0F; - components[c].quantTableSel = data.get8(); - count++; - } - - if (count != length) { - throw new IOException("ERROR: frame format error [Lf!=count]"); - } - - return 1; - } + return 1; + } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/HuffmanTable.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/HuffmanTable.java index 33579821..e6517403 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/HuffmanTable.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/HuffmanTable.java @@ -30,134 +30,128 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; +import javax.imageio.stream.ImageInputStream; import java.io.IOException; - public class HuffmanTable { - private final int l[][][] = new int[4][2][16]; - private final int th[] = new int[4]; // 1: this table is presented - private final int v[][][][] = new int[4][2][16][200]; // tables - private final int[][] tc = new int[4][2]; // 1: this table is presented + private final int l[][][] = new int[4][2][16]; + private final int th[] = new int[4]; // 1: this table is presented + private final int v[][][][] = new int[4][2][16][200]; // tables + private final int[][] tc = new int[4][2]; // 1: this table is presented - public static final int MSB = 0x80000000; + public static final int MSB = 0x80000000; + public HuffmanTable() { + tc[0][0] = 0; + tc[1][0] = 0; + tc[2][0] = 0; + tc[3][0] = 0; + tc[0][1] = 0; + tc[1][1] = 0; + tc[2][1] = 0; + tc[3][1] = 0; + th[0] = 0; + th[1] = 0; + th[2] = 0; + th[3] = 0; + } + protected int read(final ImageInputStream data, final int[][][] HuffTab) throws IOException { + int count = 0; + final int length = data.readUnsignedShort(); + count += 2; - public HuffmanTable() { - tc[0][0] = 0; - tc[1][0] = 0; - tc[2][0] = 0; - tc[3][0] = 0; - tc[0][1] = 0; - tc[1][1] = 0; - tc[2][1] = 0; - tc[3][1] = 0; - th[0] = 0; - th[1] = 0; - th[2] = 0; - th[3] = 0; - } + while (count < length) { + final int temp = data.readUnsignedByte(); + count++; + final int t = temp & 0x0F; + if (t > 3) { + throw new IOException("ERROR: Huffman table ID > 3"); + } + final int c = temp >> 4; + if (c > 2) { + throw new IOException("ERROR: Huffman table [Table class > 2 ]"); + } + th[t] = 1; + tc[t][c] = 1; - protected int read(final DataStream data, final int[][][] HuffTab) throws IOException { - int count = 0; - final int length = data.get16(); - count += 2; + for (int i = 0; i < 16; i++) { + l[t][c][i] = data.readUnsignedByte(); + count++; + } - while (count < length) { - final int temp = data.get8(); - count++; - final int t = temp & 0x0F; - if (t > 3) { - throw new IOException("ERROR: Huffman table ID > 3"); - } + for (int i = 0; i < 16; i++) { + for (int j = 0; j < l[t][c][i]; j++) { + if (count > length) { + throw new IOException("ERROR: Huffman table format error [count>Lh]"); + } + v[t][c][i][j] = data.readUnsignedByte(); + count++; + } + } + } - final int c = temp >> 4; - if (c > 2) { - throw new IOException("ERROR: Huffman table [Table class > 2 ]"); - } + if (count != length) { + throw new IOException("ERROR: Huffman table format error [count!=Lf]"); + } - th[t] = 1; - tc[t][c] = 1; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 2; j++) { + if (tc[i][j] != 0) { + buildHuffTable(HuffTab[i][j], l[i][j], v[i][j]); + } + } + } - for (int i = 0; i < 16; i++) { - l[t][c][i] = data.get8(); - count++; - } + return 1; + } - for (int i = 0; i < 16; i++) { - for (int j = 0; j < l[t][c][i]; j++) { - if (count > length) { - throw new IOException("ERROR: Huffman table format error [count>Lh]"); - } - v[t][c][i][j] = data.get8(); - count++; - } - } - } + // Build_HuffTab() + // Parameter: t table ID + // c table class ( 0 for DC, 1 for AC ) + // L[i] # of codewords which length is i + // V[i][j] Huffman Value (length=i) + // Effect: + // build up HuffTab[t][c] using L and V. + private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException { + int currentTable, temp; + int k; + temp = 256; + k = 0; - if (count != length) { - throw new IOException("ERROR: Huffman table format error [count!=Lf]"); - } + for (int i = 0; i < 8; i++) { // i+1 is Code length + for (int j = 0; j < L[i]; j++) { + for (int n = 0; n < (temp >> (i + 1)); n++) { + tab[k] = V[i][j] | ((i + 1) << 8); + k++; + } + } + } - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 2; j++) { - if (tc[i][j] != 0) { - buildHuffTable(HuffTab[i][j], l[i][j], v[i][j]); - } - } - } + for (int i = 1; k < 256; i++, k++) { + tab[k] = i | MSB; + } - return 1; - } + currentTable = 1; + k = 0; - - - // Build_HuffTab() - // Parameter: t table ID - // c table class ( 0 for DC, 1 for AC ) - // L[i] # of codewords which length is i - // V[i][j] Huffman Value (length=i) - // Effect: - // build up HuffTab[t][c] using L and V. - private void buildHuffTable(final int tab[], final int L[], final int V[][]) throws IOException { - int currentTable, temp; - int k; - temp = 256; - k = 0; - - for (int i = 0; i < 8; i++) { // i+1 is Code length - for (int j = 0; j < L[i]; j++) { - for (int n = 0; n < (temp >> (i + 1)); n++) { - tab[k] = V[i][j] | ((i + 1) << 8); - k++; - } - } - } - - for (int i = 1; k < 256; i++, k++) { - tab[k] = i | MSB; - } - - currentTable = 1; - k = 0; - - for (int i = 8; i < 16; i++) { // i+1 is Code length - for (int j = 0; j < L[i]; j++) { - for (int n = 0; n < (temp >> (i - 7)); n++) { - tab[(currentTable * 256) + k] = V[i][j] | ((i + 1) << 8); - k++; - } - if (k >= 256) { - if (k > 256) { - throw new IOException("ERROR: Huffman table error(1)!"); - } - k = 0; - currentTable++; - } - } - } - } + for (int i = 8; i < 16; i++) { // i+1 is Code length + for (int j = 0; j < L[i]; j++) { + for (int n = 0; n < (temp >> (i - 7)); n++) { + tab[(currentTable * 256) + k] = V[i][j] | ((i + 1) << 8); + k++; + } + if (k >= 256) { + if (k > 256) { + throw new IOException("ERROR: Huffman table error(1)!"); + } + k = 0; + currentTable++; + } + } + } + } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/JPEGLosslessDecoder.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/JPEGLosslessDecoder.java index 0ba8c1fe..2a93766f 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/JPEGLosslessDecoder.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/JPEGLosslessDecoder.java @@ -30,751 +30,725 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; * THE SOFTWARE. */ +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; + +import javax.imageio.stream.ImageInputStream; import java.io.IOException; -import java.nio.ByteBuffer; - - -public class JPEGLosslessDecoder implements DataStream { - - private final ByteBuffer buffer; - private final FrameHeader frame; - private final HuffmanTable huffTable; - private final QuantizationTable quantTable; - private final ScanHeader scan; - private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256]; - private final int IDCT_Source[] = new int[64]; - private final int nBlock[] = new int[10]; // number of blocks in the i-th Comp in a scan - private final int[] acTab[] = new int[10][]; // ac HuffTab for the i-th Comp in a scan - private final int[] dcTab[] = new int[10][]; // dc HuffTab for the i-th Comp in a scan - private final int[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan - - private boolean restarting; - private int dataBufferIndex; - private int marker; - private int markerIndex; - private int numComp; - private int restartInterval; - private int selection; - private int xDim, yDim; - private int xLoc; - private int yLoc; - private int mask; - private int[] outputData; - private int[] outputRedData; - private int[] outputGreenData; - private int[] outputBlueData; - - private static final int IDCT_P[] = { 0, 5, 40, 16, 45, 2, 7, 42, 21, 56, 8, 61, 18, 47, 1, 4, 41, 23, 58, 13, 32, 24, 37, 10, 63, 17, 44, 3, 6, 43, 20, - 57, 15, 34, 29, 48, 53, 26, 39, 9, 60, 19, 46, 22, 59, 12, 33, 31, 50, 55, 25, 36, 11, 62, 14, 35, 28, 49, 52, 27, 38, 30, 51, 54 }; - private static final int TABLE[] = { 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53, - 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63 }; - - public static final int RESTART_MARKER_BEGIN = 0xFFD0; - public static final int RESTART_MARKER_END = 0xFFD7; - public static final int MAX_HUFFMAN_SUBTREE = 50; - public static final int MSB = 0x80000000; - - - public int getDimX(){ - return xDim; - } - public int getDimY(){ - return yDim; - } - - public JPEGLosslessDecoder(final byte[] data) { - buffer = ByteBuffer.wrap(data); - frame = new FrameHeader(); - scan = new ScanHeader(); - quantTable = new QuantizationTable(); - huffTable = new HuffmanTable(); - } - - - - public int[][] decode() throws IOException { - int current, scanNum = 0; - final int pred[] = new int[10]; - int[][] outputRef = null; - - xLoc = 0; - yLoc = 0; - current = get16(); - - if (current != 0xFFD8) { // SOI - throw new IOException("Not a JPEG file"); - } - - current = get16(); - - while (((current >> 4) != 0x0FFC) || (current == 0xFFC4)) { // SOF 0~15 - switch (current) { - case 0xFFC4: // DHT - huffTable.read(this, HuffTab); - break; - case 0xFFCC: // DAC - throw new IOException("Program doesn't support arithmetic coding. (format throw new IOException)"); - case 0xFFDB: - quantTable.read(this, TABLE); - break; - case 0xFFDD: - restartInterval = readNumber(); - break; - case 0xFFE0: - case 0xFFE1: - case 0xFFE2: - case 0xFFE3: - case 0xFFE4: - case 0xFFE5: - case 0xFFE6: - case 0xFFE7: - case 0xFFE8: - case 0xFFE9: - case 0xFFEA: - case 0xFFEB: - case 0xFFEC: - case 0xFFED: - case 0xFFEE: - case 0xFFEF: - readApp(); - break; - case 0xFFFE: - readComment(); - break; - default: - if ((current >> 8) != 0xFF) { - throw new IOException("ERROR: format throw new IOException! (decode)"); - } - } - - current = get16(); - } - - if ((current < 0xFFC0) || (current > 0xFFC7)) { - throw new IOException("ERROR: could not handle arithmetic code!"); - } - - frame.read(this); - current = get16(); - - do { - while (current != 0x0FFDA) { //SOS - switch (current) { - case 0xFFC4: //DHT - huffTable.read(this, HuffTab); - break; - case 0xFFCC: //DAC - throw new IOException("Program doesn't support arithmetic coding. (format throw new IOException)"); - case 0xFFDB: - quantTable.read(this, TABLE); - break; - case 0xFFDD: - restartInterval = readNumber(); - break; - case 0xFFE0: - case 0xFFE1: - case 0xFFE2: - case 0xFFE3: - case 0xFFE4: - case 0xFFE5: - case 0xFFE6: - case 0xFFE7: - case 0xFFE8: - case 0xFFE9: - case 0xFFEA: - case 0xFFEB: - case 0xFFEC: - case 0xFFED: - case 0xFFEE: - case 0xFFEF: - readApp(); - break; - case 0xFFFE: - readComment(); - break; - default: - if ((current >> 8) != 0xFF) { - throw new IOException("ERROR: format throw new IOException! (Parser.decode)"); - } - } - - current = get16(); - } - - final int precision = frame.getPrecision(); - - if (precision == 8) { - mask = 0xFF; - } else { - mask = 0xFFFF; - } - - final ComponentSpec[] components = frame.getComponents(); - - scan.read(this); - numComp = scan.getNumComponents(); - selection = scan.getSelection(); - - final ScanComponent[] scanComps = scan.components; - final int[][] quantTables = quantTable.quantTables; - - for (int i = 0; i < numComp; i++) { - final int compN = scanComps[i].getScanCompSel(); - qTab[i] = quantTables[components[compN].quantTableSel]; - nBlock[i] = components[compN].vSamp * components[compN].hSamp; - dcTab[i] = HuffTab[scanComps[i].getDcTabSel()][0]; - acTab[i] = HuffTab[scanComps[i].getAcTabSel()][1]; - } - - xDim = frame.getDimX(); - yDim = frame.getDimY(); - - outputRef = new int[numComp][]; - - if (numComp == 1) { - outputData = new int[xDim * yDim]; - outputRef[0] = outputData; - } else { - outputRedData = new int[xDim * yDim]; // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255. - outputGreenData = new int[xDim * yDim]; - outputBlueData = new int[xDim * yDim]; - - outputRef[0] = outputRedData; - outputRef[1] = outputGreenData; - outputRef[2] = outputBlueData; - } - - scanNum++; - - while (true) { // Decode one scan - final int temp[] = new int[1]; // to store remainder bits - final int index[] = new int[1]; - temp[0] = 0; - index[0] = 0; - - for (int i = 0; i < 10; i++) { - pred[i] = (1 << (precision - 1)); - } - - if (restartInterval == 0) { - current = decode(pred, temp, index); - - while ((current == 0) && ((xLoc < xDim) && (yLoc < yDim))) { - output(pred); - current = decode(pred, temp, index); - } - - break; //current=MARKER - } - - for (int mcuNum = 0; mcuNum < restartInterval; mcuNum++) { - restarting = (mcuNum == 0); - current = decode(pred, temp, index); - output(pred); - - if (current != 0) { - break; - } - } - - if (current == 0) { - if (markerIndex != 0) { - current = (0xFF00 | marker); - markerIndex = 0; - } else { - current = get16(); - } - } - - if ((current >= RESTART_MARKER_BEGIN) && (current <= RESTART_MARKER_END)) { - //empty - } else { - break; //current=MARKER - } - } - - if ((current == 0xFFDC) && (scanNum == 1)) { //DNL - readNumber(); - current = get16(); - } - } while ((current != 0xFFD9) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0)); - - return outputRef; - } - - - - @Override - public final int get16() { - final int value = (buffer.getShort(dataBufferIndex) & 0xFFFF); - dataBufferIndex += 2; - return value; - } - - - - @Override - public final int get8() { - return buffer.get(dataBufferIndex++) & 0xFF; - } - - - - private int decode(final int prev[], final int temp[], final int index[]) throws IOException { - if (numComp == 1) { - return decodeSingle(prev, temp, index); - } else if (numComp == 3) { - return decodeRGB(prev, temp, index); - } else { - return -1; - } - } - - - - private int decodeSingle(final int prev[], final int temp[], final int index[]) throws IOException { - // At the beginning of the first line and - // at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision. - if (restarting) { - restarting = false; - prev[0] = (1 << (frame.getPrecision() - 1)); - } else { - switch (selection) { - case 2: - prev[0] = getPreviousY(outputData); - break; - case 3: - prev[0] = getPreviousXY(outputData); - break; - case 4: - prev[0] = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData); - break; - case 5: - prev[0] = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1); - break; - case 6: - prev[0] = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1); - break; - case 7: - prev[0] = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2); - break; - default: - prev[0] = getPreviousX(outputData); - break; - } - } - - for (int i = 0; i < nBlock[0]; i++) { - final int value = getHuffmanValue(dcTab[0], temp, index); - - if (value >= 0xFF00) { - return value; - } - - final int n = getn(prev, value, temp, index); - - final int nRestart = (n >> 8); - if ((nRestart >= RESTART_MARKER_BEGIN) && (nRestart <= RESTART_MARKER_END)) { - return nRestart; - } - - prev[0] += n; - } - - return 0; - } - - - - private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException { - switch (selection) { - case 2: - prev[0] = getPreviousY(outputRedData); - prev[1] = getPreviousY(outputGreenData); - prev[2] = getPreviousY(outputBlueData); - break; - case 3: - prev[0] = getPreviousXY(outputRedData); - prev[1] = getPreviousXY(outputGreenData); - prev[2] = getPreviousXY(outputBlueData); - break; - case 4: - prev[0] = (getPreviousX(outputRedData) + getPreviousY(outputRedData)) - getPreviousXY(outputRedData); - prev[1] = (getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) - getPreviousXY(outputGreenData); - prev[2] = (getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) - getPreviousXY(outputBlueData); - break; - case 5: - prev[0] = getPreviousX(outputRedData) + ((getPreviousY(outputRedData) - getPreviousXY(outputRedData)) >> 1); - prev[1] = getPreviousX(outputGreenData) + ((getPreviousY(outputGreenData) - getPreviousXY(outputGreenData)) >> 1); - prev[2] = getPreviousX(outputBlueData) + ((getPreviousY(outputBlueData) - getPreviousXY(outputBlueData)) >> 1); - break; - case 6: - prev[0] = getPreviousY(outputRedData) + ((getPreviousX(outputRedData) - getPreviousXY(outputRedData)) >> 1); - prev[1] = getPreviousY(outputGreenData) + ((getPreviousX(outputGreenData) - getPreviousXY(outputGreenData)) >> 1); - prev[2] = getPreviousY(outputBlueData) + ((getPreviousX(outputBlueData) - getPreviousXY(outputBlueData)) >> 1); - break; - case 7: - prev[0] = (int) (((long) getPreviousX(outputRedData) + getPreviousY(outputRedData)) / 2); - prev[1] = (int) (((long) getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) / 2); - prev[2] = (int) (((long) getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) / 2); - break; - default: - prev[0] = getPreviousX(outputRedData); - prev[1] = getPreviousX(outputGreenData); - prev[2] = getPreviousX(outputBlueData); - break; - } - - int value, actab[], dctab[]; - int qtab[]; - - for (int ctrC = 0; ctrC < numComp; ctrC++) { - qtab = qTab[ctrC]; - actab = acTab[ctrC]; - dctab = dcTab[ctrC]; - for (int i = 0; i < nBlock[ctrC]; i++) { - for (int k = 0; k < IDCT_Source.length; k++) { - IDCT_Source[k] = 0; - } - - value = getHuffmanValue(dctab, temp, index); - - if (value >= 0xFF00) { - return value; - } - - prev[ctrC] = IDCT_Source[0] = prev[ctrC] + getn(index, value, temp, index); - IDCT_Source[0] *= qtab[0]; - - for (int j = 1; j < 64; j++) { - value = getHuffmanValue(actab, temp, index); - - if (value >= 0xFF00) { - return value; - } - - j += (value >> 4); - - if ((value & 0x0F) == 0) { - if ((value >> 4) == 0) { - break; - } - } else { - IDCT_Source[IDCT_P[j]] = getn(index, value & 0x0F, temp, index) * qtab[j]; - } - } - } - } - - return 0; - } - - - - // Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8 - // bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256. - // HuffTab[*][*][0-256] is always the only 1st-layer table. - // - // An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal - // - // HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...) - // ): - // return: Huffman Value of table - // 0xFF?? if it receives a MARKER - // Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...) - // temp temp storage for remainded bits - // index index to bit of temp - // in FILE pointer - // Effect: - // temp store new remainded bits - // index change to new index - // in change to new position - // NOTE: - // Initial by temp=0; index=0; - // NOTE: (explain temp and index) - // temp: is always in the form at calling time or returning time - // | byte 4 | byte 3 | byte 2 | byte 1 | - // | 0 | 0 | 00000000 | 00000??? | if not a MARKER - // ^index=3 (from 0 to 15) - // 321 - // NOTE (marker and marker_index): - // If get a MARKER from 'in', marker=the low-byte of the MARKER - // and marker_index=9 - // If marker_index=9 then index is always > 8, or HuffmanValue() - // will not be called - private int getHuffmanValue(final int table[], final int temp[], final int index[]) throws IOException { - int code, input; - final int mask = 0xFFFF; - - if (index[0] < 8) { - temp[0] <<= 8; - input = get8(); - if (input == 0xFF) { - marker = get8(); - if (marker != 0) { - markerIndex = 9; - } - } - temp[0] |= input; - } else { - index[0] -= 8; - } - - code = table[temp[0] >> index[0]]; - - if ((code & MSB) != 0) { - if (markerIndex != 0) { - markerIndex = 0; - return 0xFF00 | marker; - } - - temp[0] &= (mask >> (16 - index[0])); - temp[0] <<= 8; - input = get8(); - - if (input == 0xFF) { - marker = get8(); - if (marker != 0) { - markerIndex = 9; - } - } - - temp[0] |= input; - code = table[((code & 0xFF) * 256) + (temp[0] >> index[0])]; - index[0] += 8; - } - - index[0] += 8 - (code >> 8); - - if (index[0] < 0) { - throw new IOException("index=" + index[0] + " temp=" + temp[0] + " code=" + code + " in HuffmanValue()"); - } - - if (index[0] < markerIndex) { - markerIndex = 0; - return 0xFF00 | marker; - } - - temp[0] &= (mask >> (16 - index[0])); - return code & 0xFF; - } - - - - private int getn(final int[] PRED, final int n, final int temp[], final int index[]) throws IOException { - int result; - final int one = 1; - final int n_one = -1; - final int mask = 0xFFFF; - int input; - - if (n == 0) { - return 0; - } - - if (n == 16) { - if (PRED[0] >= 0) { - return -32768; - } else { - return 32768; - } - } - - index[0] -= n; - - if (index[0] >= 0) { - if ((index[0] < markerIndex) && !isLastPixel()) { // this was corrupting the last pixel in some cases - markerIndex = 0; - return (0xFF00 | marker) << 8; - } - - result = temp[0] >> index[0]; - temp[0] &= (mask >> (16 - index[0])); - } else { - temp[0] <<= 8; - input = get8(); - - if (input == 0xFF) { - marker = get8(); - if (marker != 0) { - markerIndex = 9; - } - } - - temp[0] |= input; - index[0] += 8; - - if (index[0] < 0) { - if (markerIndex != 0) { - markerIndex = 0; - return (0xFF00 | marker) << 8; - } - temp[0] <<= 8; - input = get8(); - - if (input == 0xFF) { - marker = get8(); - if (marker != 0) { - markerIndex = 9; - } - } - - temp[0] |= input; - index[0] += 8; - } - - if (index[0] < 0) { - throw new IOException("index=" + index[0] + " in getn()"); - } - - if (index[0] < markerIndex) { - markerIndex = 0; - return (0xFF00 | marker) << 8; - } - - result = temp[0] >> index[0]; - temp[0] &= (mask >> (16 - index[0])); - } - - if (result < (one << (n - 1))) { - result += (n_one << n) + 1; - } - - return result; - } - - - - private int getPreviousX(final int data[]) { - if (xLoc > 0) { - return data[((yLoc * xDim) + xLoc) - 1]; - } else if (yLoc > 0) { - return getPreviousY(data); - } else { - return (1 << (frame.getPrecision() - 1)); - } - } - - - - private int getPreviousXY(final int data[]) { - if ((xLoc > 0) && (yLoc > 0)) { - return data[(((yLoc - 1) * xDim) + xLoc) - 1]; - } else { - return getPreviousY(data); - } - } - - - - private int getPreviousY(final int data[]) { - if (yLoc > 0) { - return data[((yLoc - 1) * xDim) + xLoc]; - } else { - return getPreviousX(data); - } - } - - - - private boolean isLastPixel() { - return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1)); - } - - - - private void output(final int PRED[]) { - if (numComp == 1) { - outputSingle(PRED); - } else { - outputRGB(PRED); - } - } - - - - private void outputSingle(final int PRED[]) { - if ((xLoc < xDim) && (yLoc < yDim)) { - outputData[(yLoc * xDim) + xLoc] = mask & PRED[0]; - xLoc++; - - if (xLoc >= xDim) { - yLoc++; - xLoc = 0; - } - } - } - - - - private void outputRGB(final int PRED[]) { - if ((xLoc < xDim) && (yLoc < yDim)) { - outputRedData[(yLoc * xDim) + xLoc] = PRED[0]; - outputGreenData[(yLoc * xDim) + xLoc] = PRED[1]; - outputBlueData[(yLoc * xDim) + xLoc] = PRED[2]; - xLoc++; - - if (xLoc >= xDim) { - yLoc++; - xLoc = 0; - } - } - } - - - - private int readApp() throws IOException { - int count = 0; - final int length = get16(); - count += 2; - - while (count < length) { - get8(); - count++; - } - - return length; - } - - - - private String readComment() throws IOException { - final StringBuffer sb = new StringBuffer(); - int count = 0; - - final int length = get16(); - count += 2; - - while (count < length) { - sb.append((char) get8()); - count++; - } - - return sb.toString(); - } - - - - private int readNumber() throws IOException { - final int Ld = get16(); - - if (Ld != 4) { - throw new IOException("ERROR: Define number format throw new IOException [Ld!=4]"); - } - - return get16(); - } - - - - public int getNumComponents() { - return numComp; - } - - - - public int getPrecision() { - return frame.getPrecision(); - } +public class JPEGLosslessDecoder { + + private final ImageInputStream input; + private final FrameHeader frame; + private final HuffmanTable huffTable; + private final QuantizationTable quantTable; + private final ScanHeader scan; + private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256]; + private final int IDCT_Source[] = new int[64]; + private final int nBlock[] = new int[10]; // number of blocks in the i-th Comp in a scan + private final int[] acTab[] = new int[10][]; // ac HuffTab for the i-th Comp in a scan + private final int[] dcTab[] = new int[10][]; // dc HuffTab for the i-th Comp in a scan + private final int[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan + + private boolean restarting; + private int marker; + private int markerIndex; + private int numComp; + private int restartInterval; + private int selection; + private int xDim, yDim; + private int xLoc; + private int yLoc; + private int mask; + private int[] outputData; + private int[] outputRedData; + private int[] outputGreenData; + private int[] outputBlueData; + + private static final int IDCT_P[] = { + 0, 5, 40, 16, 45, 2, 7, 42, 21, 56, 8, 61, 18, 47, 1, 4, 41, 23, 58, 13, 32, 24, 37, 10, 63, 17, 44, 3, 6, 43, 20, + 57, 15, 34, 29, 48, 53, 26, 39, 9, 60, 19, 46, 22, 59, 12, 33, 31, 50, 55, 25, 36, 11, 62, 14, 35, 28, 49, 52, 27, 38, 30, 51, 54 + }; + private static final int TABLE[] = { + 0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34, 37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63 + }; + + public static final int RESTART_MARKER_BEGIN = 0xFFD0; + public static final int RESTART_MARKER_END = 0xFFD7; + public static final int MAX_HUFFMAN_SUBTREE = 50; + public static final int MSB = 0x80000000; + + public int getDimX() { + return xDim; + } + + public int getDimY() { + return yDim; + } + + public JPEGLosslessDecoder(final ImageInputStream data) { + input = data; + frame = new FrameHeader(); + scan = new ScanHeader(); + quantTable = new QuantizationTable(); + huffTable = new HuffmanTable(); + } + + public int[][] decode() throws IOException { + int current, scanNum = 0; + final int pred[] = new int[10]; + int[][] outputRef; + + xLoc = 0; + yLoc = 0; + current = input.readUnsignedShort(); + + if (current != JPEG.SOI) { // SOI + throw new IOException("Not a JPEG file"); + } + + // TODO: Why the two loops?! + + current = input.readUnsignedShort(); + + while (((current >> 4) != 0x0FFC) || (current == JPEG.DHT)) { // SOF 0~15 + switch (current) { + case JPEG.DHT: // DHT + huffTable.read(input, HuffTab); + break; + case JPEG.DAC: // DAC + throw new IOException("Program doesn't support arithmetic coding."); + case JPEG.DQT: + quantTable.read(input, TABLE); + break; + case JPEG.DRI: + restartInterval = readNumber(); + break; + case JPEG.APP0: + case JPEG.APP1: + case JPEG.APP2: + case JPEG.APP3: + case JPEG.APP4: + case JPEG.APP5: + case JPEG.APP6: + case JPEG.APP7: + case JPEG.APP8: + case JPEG.APP9: + case JPEG.APP10: + case JPEG.APP11: + case JPEG.APP12: + case JPEG.APP13: + case JPEG.APP14: + case JPEG.APP15: + readApp(); + break; + case JPEG.COM: + readComment(); + break; + default: + if ((current >> 8) != 0xFF) { + throw new IOException("JPEG Segment marker expected."); + } + } + + current = input.readUnsignedShort(); + } + + if ((current < 0xFFC0) || (current > 0xFFC7)) { + throw new IOException("ERROR: could not handle arithmetic code!"); + } + + frame.read(input); + current = input.readUnsignedShort(); + + do { + while (current != 0x0FFDA) { //SOS + switch (current) { + case 0xFFC4: //DHT + huffTable.read(input, HuffTab); + break; + case 0xFFCC: //DAC + throw new IOException("Program doesn't support arithmetic coding. (format throw new IOException)"); + case 0xFFDB: + quantTable.read(input, TABLE); + break; + case 0xFFDD: + restartInterval = readNumber(); + break; + case 0xFFE0: + case 0xFFE1: + case 0xFFE2: + case 0xFFE3: + case 0xFFE4: + case 0xFFE5: + case 0xFFE6: + case 0xFFE7: + case 0xFFE8: + case 0xFFE9: + case 0xFFEA: + case 0xFFEB: + case 0xFFEC: + case 0xFFED: + case 0xFFEE: + case 0xFFEF: + readApp(); + break; + case 0xFFFE: + readComment(); + break; + default: + if ((current >> 8) != 0xFF) { + throw new IOException("ERROR: format throw new IOException! (Parser.decode)"); + } + } + + current = input.readUnsignedShort(); + } + + final int precision = frame.getPrecision(); + + if (precision == 8) { + mask = 0xFF; + } + else { + mask = 0xFFFF; + } + + final ComponentSpec[] components = frame.getComponents(); + + readScan(); + numComp = scan.getNumComponents(); + selection = scan.getSelection(); + + final ScanComponent[] scanComps = scan.components; + final int[][] quantTables = quantTable.quantTables; + + for (int i = 0; i < numComp; i++) { + final int compN = scanComps[i].getScanCompSel(); + qTab[i] = quantTables[components[compN].quantTableSel]; + nBlock[i] = components[compN].vSamp * components[compN].hSamp; + dcTab[i] = HuffTab[scanComps[i].getDcTabSel()][0]; + acTab[i] = HuffTab[scanComps[i].getAcTabSel()][1]; + } + + xDim = frame.getDimX(); + yDim = frame.getDimY(); + + outputRef = new int[numComp][]; + + if (numComp == 1) { + outputData = new int[xDim * yDim]; + outputRef[0] = outputData; + } + else { + outputRedData = new int[xDim * yDim]; // not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255. + outputGreenData = new int[xDim * yDim]; + outputBlueData = new int[xDim * yDim]; + + outputRef[0] = outputRedData; + outputRef[1] = outputGreenData; + outputRef[2] = outputBlueData; + } + + scanNum++; + + while (true) { // Decode one scan + final int temp[] = new int[1]; // to store remainder bits + final int index[] = new int[1]; + temp[0] = 0; + index[0] = 0; + + for (int i = 0; i < 10; i++) { + pred[i] = (1 << (precision - 1)); + } + + if (restartInterval == 0) { + current = decode(pred, temp, index); + + while ((current == 0) && ((xLoc < xDim) && (yLoc < yDim))) { + output(pred); + current = decode(pred, temp, index); + } + + break; //current=MARKER + } + + for (int mcuNum = 0; mcuNum < restartInterval; mcuNum++) { + restarting = (mcuNum == 0); + current = decode(pred, temp, index); + output(pred); + + if (current != 0) { + break; + } + } + + if (current == 0) { + if (markerIndex != 0) { + current = (0xFF00 | marker); + markerIndex = 0; + } + else { + current = input.readUnsignedShort(); + } + } + + if ((current >= RESTART_MARKER_BEGIN) && (current <= RESTART_MARKER_END)) { + //empty + } + else { + break; //current=MARKER + } + } + + if ((current == JPEG.DNL) && (scanNum == 1)) { //DNL + readNumber(); + current = input.readUnsignedShort(); + } + } while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0)); + + return outputRef; + } + + private int readScan() throws IOException { + return scan.read(input); + } + + private int decode(final int prev[], final int temp[], final int index[]) throws IOException { + if (numComp == 1) { + return decodeSingle(prev, temp, index); + } + else if (numComp == 3) { + return decodeRGB(prev, temp, index); + } + else { + return -1; + } + } + + private int decodeSingle(final int prev[], final int temp[], final int index[]) throws IOException { + // At the beginning of the first line and + // at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision. + if (restarting) { + restarting = false; + prev[0] = (1 << (frame.getPrecision() - 1)); + } + else { + switch (selection) { + case 2: + prev[0] = getPreviousY(outputData); + break; + case 3: + prev[0] = getPreviousXY(outputData); + break; + case 4: + prev[0] = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData); + break; + case 5: + prev[0] = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1); + break; + case 6: + prev[0] = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1); + break; + case 7: + prev[0] = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2); + break; + default: + prev[0] = getPreviousX(outputData); + break; + } + } + + for (int i = 0; i < nBlock[0]; i++) { + final int value = getHuffmanValue(dcTab[0], temp, index); + + if (value >= 0xFF00) { + return value; + } + + final int n = getn(prev, value, temp, index); + + final int nRestart = (n >> 8); + if ((nRestart >= RESTART_MARKER_BEGIN) && (nRestart <= RESTART_MARKER_END)) { + return nRestart; + } + + prev[0] += n; + } + + return 0; + } + + private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException { + switch (selection) { + case 2: + prev[0] = getPreviousY(outputRedData); + prev[1] = getPreviousY(outputGreenData); + prev[2] = getPreviousY(outputBlueData); + break; + case 3: + prev[0] = getPreviousXY(outputRedData); + prev[1] = getPreviousXY(outputGreenData); + prev[2] = getPreviousXY(outputBlueData); + break; + case 4: + prev[0] = (getPreviousX(outputRedData) + getPreviousY(outputRedData)) - getPreviousXY(outputRedData); + prev[1] = (getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) - getPreviousXY(outputGreenData); + prev[2] = (getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) - getPreviousXY(outputBlueData); + break; + case 5: + prev[0] = getPreviousX(outputRedData) + ((getPreviousY(outputRedData) - getPreviousXY(outputRedData)) >> 1); + prev[1] = getPreviousX(outputGreenData) + ((getPreviousY(outputGreenData) - getPreviousXY(outputGreenData)) >> 1); + prev[2] = getPreviousX(outputBlueData) + ((getPreviousY(outputBlueData) - getPreviousXY(outputBlueData)) >> 1); + break; + case 6: + prev[0] = getPreviousY(outputRedData) + ((getPreviousX(outputRedData) - getPreviousXY(outputRedData)) >> 1); + prev[1] = getPreviousY(outputGreenData) + ((getPreviousX(outputGreenData) - getPreviousXY(outputGreenData)) >> 1); + prev[2] = getPreviousY(outputBlueData) + ((getPreviousX(outputBlueData) - getPreviousXY(outputBlueData)) >> 1); + break; + case 7: + prev[0] = (int) (((long) getPreviousX(outputRedData) + getPreviousY(outputRedData)) / 2); + prev[1] = (int) (((long) getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) / 2); + prev[2] = (int) (((long) getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) / 2); + break; + default: + prev[0] = getPreviousX(outputRedData); + prev[1] = getPreviousX(outputGreenData); + prev[2] = getPreviousX(outputBlueData); + break; + } + + int value, actab[], dctab[]; + int qtab[]; + + for (int ctrC = 0; ctrC < numComp; ctrC++) { + qtab = qTab[ctrC]; + actab = acTab[ctrC]; + dctab = dcTab[ctrC]; + for (int i = 0; i < nBlock[ctrC]; i++) { + for (int k = 0; k < IDCT_Source.length; k++) { + IDCT_Source[k] = 0; + } + + value = getHuffmanValue(dctab, temp, index); + + if (value >= 0xFF00) { + return value; + } + + prev[ctrC] = IDCT_Source[0] = prev[ctrC] + getn(index, value, temp, index); + IDCT_Source[0] *= qtab[0]; + + for (int j = 1; j < 64; j++) { + value = getHuffmanValue(actab, temp, index); + + if (value >= 0xFF00) { + return value; + } + + j += (value >> 4); + + if ((value & 0x0F) == 0) { + if ((value >> 4) == 0) { + break; + } + } + else { + IDCT_Source[IDCT_P[j]] = getn(index, value & 0x0F, temp, index) * qtab[j]; + } + } + } + } + + return 0; + } + + // Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8 + // bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256. + // HuffTab[*][*][0-256] is always the only 1st-layer table. + // + // An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal + // + // HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...) + // ): + // return: Huffman Value of table + // 0xFF?? if it receives a MARKER + // Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...) + // temp temp storage for remainded bits + // index index to bit of temp + // in FILE pointer + // Effect: + // temp store new remainded bits + // index change to new index + // in change to new position + // NOTE: + // Initial by temp=0; index=0; + // NOTE: (explain temp and index) + // temp: is always in the form at calling time or returning time + // | byte 4 | byte 3 | byte 2 | byte 1 | + // | 0 | 0 | 00000000 | 00000??? | if not a MARKER + // ^index=3 (from 0 to 15) + // 321 + // NOTE (marker and marker_index): + // If get a MARKER from 'in', marker=the low-byte of the MARKER + // and marker_index=9 + // If marker_index=9 then index is always > 8, or HuffmanValue() + // will not be called + private int getHuffmanValue(final int table[], final int temp[], final int index[]) throws IOException { + int code, input; + final int mask = 0xFFFF; + + if (index[0] < 8) { + temp[0] <<= 8; + input = this.input.readUnsignedByte(); + if (input == 0xFF) { + marker = this.input.readUnsignedByte(); + if (marker != 0) { + markerIndex = 9; + } + } + temp[0] |= input; + } + else { + index[0] -= 8; + } + + code = table[temp[0] >> index[0]]; + + if ((code & MSB) != 0) { + if (markerIndex != 0) { + markerIndex = 0; + return 0xFF00 | marker; + } + + temp[0] &= (mask >> (16 - index[0])); + temp[0] <<= 8; + input = this.input.readUnsignedByte(); + + if (input == 0xFF) { + marker = this.input.readUnsignedByte(); + if (marker != 0) { + markerIndex = 9; + } + } + + temp[0] |= input; + code = table[((code & 0xFF) * 256) + (temp[0] >> index[0])]; + index[0] += 8; + } + + index[0] += 8 - (code >> 8); + + if (index[0] < 0) { + throw new IOException("index=" + index[0] + " temp=" + temp[0] + " code=" + code + " in HuffmanValue()"); + } + + if (index[0] < markerIndex) { + markerIndex = 0; + return 0xFF00 | marker; + } + + temp[0] &= (mask >> (16 - index[0])); + return code & 0xFF; + } + + private int getn(final int[] PRED, final int n, final int temp[], final int index[]) throws IOException { + int result; + final int one = 1; + final int n_one = -1; + final int mask = 0xFFFF; + int input; + + if (n == 0) { + return 0; + } + + if (n == 16) { + if (PRED[0] >= 0) { + return -32768; + } + else { + return 32768; + } + } + + index[0] -= n; + + if (index[0] >= 0) { + if ((index[0] < markerIndex) && !isLastPixel()) { // this was corrupting the last pixel in some cases + markerIndex = 0; + return (0xFF00 | marker) << 8; + } + + result = temp[0] >> index[0]; + temp[0] &= (mask >> (16 - index[0])); + } + else { + temp[0] <<= 8; + input = this.input.readUnsignedByte(); + + if (input == 0xFF) { + marker = this.input.readUnsignedByte(); + if (marker != 0) { + markerIndex = 9; + } + } + + temp[0] |= input; + index[0] += 8; + + if (index[0] < 0) { + if (markerIndex != 0) { + markerIndex = 0; + return (0xFF00 | marker) << 8; + } + + temp[0] <<= 8; + input = this.input.readUnsignedByte(); + + if (input == 0xFF) { + marker = this.input.readUnsignedByte(); + if (marker != 0) { + markerIndex = 9; + } + } + + temp[0] |= input; + index[0] += 8; + } + + if (index[0] < 0) { + throw new IOException("index=" + index[0] + " in getn()"); + } + + if (index[0] < markerIndex) { + markerIndex = 0; + return (0xFF00 | marker) << 8; + } + + result = temp[0] >> index[0]; + temp[0] &= (mask >> (16 - index[0])); + } + + if (result < (one << (n - 1))) { + result += (n_one << n) + 1; + } + + return result; + } + + private int getPreviousX(final int data[]) { + if (xLoc > 0) { + return data[((yLoc * xDim) + xLoc) - 1]; + } + else if (yLoc > 0) { + return getPreviousY(data); + } + else { + return (1 << (frame.getPrecision() - 1)); + } + } + + private int getPreviousXY(final int data[]) { + if ((xLoc > 0) && (yLoc > 0)) { + return data[(((yLoc - 1) * xDim) + xLoc) - 1]; + } + else { + return getPreviousY(data); + } + } + + private int getPreviousY(final int data[]) { + if (yLoc > 0) { + return data[((yLoc - 1) * xDim) + xLoc]; + } + else { + return getPreviousX(data); + } + } + + private boolean isLastPixel() { + return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1)); + } + + private void output(final int PRED[]) { + if (numComp == 1) { + outputSingle(PRED); + } + else { + outputRGB(PRED); + } + } + + private void outputSingle(final int PRED[]) { + if ((xLoc < xDim) && (yLoc < yDim)) { + outputData[(yLoc * xDim) + xLoc] = mask & PRED[0]; + xLoc++; + + if (xLoc >= xDim) { + yLoc++; + xLoc = 0; + } + } + } + + private void outputRGB(final int PRED[]) { + if ((xLoc < xDim) && (yLoc < yDim)) { + outputRedData[(yLoc * xDim) + xLoc] = PRED[0]; + outputGreenData[(yLoc * xDim) + xLoc] = PRED[1]; + outputBlueData[(yLoc * xDim) + xLoc] = PRED[2]; + xLoc++; + + if (xLoc >= xDim) { + yLoc++; + xLoc = 0; + } + } + } + + private int readApp() throws IOException { + int count = 0; + final int length = input.readUnsignedShort(); + count += 2; + + while (count < length) { + input.readUnsignedByte(); + count++; + } + + return length; + } + + private String readComment() throws IOException { + final StringBuffer sb = new StringBuffer(); + int count = 0; + + final int length = input.readUnsignedShort(); + count += 2; + + while (count < length) { + sb.append((char) input.readUnsignedByte()); + count++; + } + + return sb.toString(); + } + + private int readNumber() throws IOException { + final int Ld = input.readUnsignedShort(); + + if (Ld != 4) { + throw new IOException("ERROR: Define number format throw new IOException [Ld!=4]"); + } + + return input.readUnsignedShort(); + } + + public int getNumComponents() { + return numComp; + } + + public int getPrecision() { + return frame.getPrecision(); + } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/JPEGLosslessDecoderWrapper.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/JPEGLosslessDecoderWrapper.java index fb5f5d91..13061b51 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/JPEGLosslessDecoderWrapper.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/JPEGLosslessDecoderWrapper.java @@ -1,130 +1,135 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferByte; -import java.awt.image.DataBufferInt; -import java.awt.image.DataBufferUShort; +import javax.imageio.stream.ImageInputStream; +import java.awt.image.*; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; import java.io.IOException; /** - * This class provides the conversion of a byte buffer - * containing a JPEGLossless to an BufferedImage. + * This class provides the conversion of a byte buffer + * containing a JPEGLossless to an BufferedImage. * Therefore it uses the rii-mango JPEGLosslessDecoder * Library ( https://github.com/rii-mango/JPEGLosslessDecoder ) - * + *

* Take care, that only the following lossless formats are supported * 1.2.840.10008.1.2.4.57 JPEG Lossless, Nonhierarchical (Processes 14) * 1.2.840.10008.1.2.4.70 JPEG Lossless, Nonhierarchical (Processes 14 [Selection 1]) - * + *

* Currently the following conversions are supported - * - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB - * - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY - * - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY - * - * @author Hermann Kroll + * - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB + * - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY + * - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY * + * @author Hermann Kroll */ public class JPEGLosslessDecoderWrapper { - /** - * Converts a byte buffer (containing a jpeg lossless) - * to an Java BufferedImage - * Currently the following conversions are supported - * - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB - * - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY - * - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY - * - * @param data byte buffer which contains a jpeg lossless - * @return if successfully a BufferedImage is returned - * @throws IOException is thrown if the decoder failed or a conversion is not supported - */ - public BufferedImage readImage(byte[] data) throws IOException{ - JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(data); - - - int[][] decoded = decoder.decode(); - int width = decoder.getDimX(); - int height = decoder.getDimY(); - - if(decoder.getNumComponents() == 1){ - switch(decoder.getPrecision()) - { - case 8: - return read8Bit1ComponentGrayScale(decoded, width, height); - case 16: - return read16Bit1ComponentGrayScale(decoded, width, height); - default: - throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 1 component cannot be decoded"); - } - } - //rgb - if(decoder.getNumComponents() == 3){ - switch(decoder.getPrecision()) - { - case 24: - return read24Bit3ComponentRGB(decoded, width, height); + /** + * Converts a byte buffer (containing a jpeg lossless) + * to an Java BufferedImage + * Currently the following conversions are supported + * - 24Bit, RGB -> BufferedImage.TYPE_INT_RGB + * - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY + * - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY + * + * @param input input stream which contains a jpeg lossless data + * @return if successfully a BufferedImage is returned + * @throws IOException is thrown if the decoder failed or a conversion is not supported + */ + public BufferedImage readImage(final ImageInputStream input) throws IOException { + JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(input); - default: - throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 3 components cannot be decoded"); - } - } - - throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) cannot be decoded"); - - } - - /** - * converts the decoded buffer into a BufferedImage - * precision: 16 bit, componentCount = 1 - * @param decoded data buffer - * @param width of the image - * @param height of the image - * @return a BufferedImage.TYPE_USHORT_GRAY - */ - private BufferedImage read16Bit1ComponentGrayScale(int[][] decoded, int width, int height){ - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY); - short[] imageBuffer = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData(); + int[][] decoded = decoder.decode(); + int width = decoder.getDimX(); + int height = decoder.getDimY(); - for(int i = 0; i < imageBuffer.length; i++){ - imageBuffer[i] = (short)decoded[0][i]; - } - return image; - } - /** - * converts the decoded buffer into a BufferedImage - * precision: 8 bit, componentCount = 1 - * @param decoded data buffer - * @param width of the image - * @param height of the image - * @return a BufferedImage.TYPE_BYTE_GRAY - */ - private BufferedImage read8Bit1ComponentGrayScale(int[][] decoded, int width, int height){ - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); - byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + if (decoder.getNumComponents() == 1) { + switch (decoder.getPrecision()) { + case 8: + return read8Bit1ComponentGrayScale(decoded, width, height); + case 16: + return read16Bit1ComponentGrayScale(decoded, width, height); + default: + throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 1 component cannot be decoded"); + } + } + //rgb + if (decoder.getNumComponents() == 3) { + switch (decoder.getPrecision()) { + case 8: + return read24Bit3ComponentRGB(decoded, width, height); - for(int i = 0; i < imageBuffer.length; i++){ - imageBuffer[i] = (byte)decoded[0][i]; - } - return image; - } - - /** - * converts the decoded buffer into a BufferedImage - * precision: 24 bit, componentCount = 3 - * @param decoded data buffer - * @param width of the image - * @param height of the image - * @return a BufferedImage.TYPE_INT_RGB - */ - private BufferedImage read24Bit3ComponentRGB(int[][] decoded, int width, int height){ - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); - int[] imageBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + default: + throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 3 components cannot be decoded"); + } + } + + throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) cannot be decoded"); + + } + + public Raster readRaster(final ImageInputStream input) throws IOException { + return readImage(input).getRaster(); + } + + /** + * converts the decoded buffer into a BufferedImage + * precision: 16 bit, componentCount = 1 + * + * @param decoded data buffer + * @param width of the image + * @param height of the image + * @return a BufferedImage.TYPE_USHORT_GRAY + */ + private BufferedImage read16Bit1ComponentGrayScale(int[][] decoded, int width, int height) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_USHORT_GRAY); + short[] imageBuffer = ((DataBufferUShort) image.getRaster().getDataBuffer()).getData(); + + for (int i = 0; i < imageBuffer.length; i++) { + imageBuffer[i] = (short) decoded[0][i]; + } + return image; + } + + /** + * converts the decoded buffer into a BufferedImage + * precision: 8 bit, componentCount = 1 + * + * @param decoded data buffer + * @param width of the image + * @param height of the image + * @return a BufferedImage.TYPE_BYTE_GRAY + */ + private BufferedImage read8Bit1ComponentGrayScale(int[][] decoded, int width, int height) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); + byte[] imageBuffer = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); + + for (int i = 0; i < imageBuffer.length; i++) { + imageBuffer[i] = (byte) decoded[0][i]; + } + return image; + } + + /** + * converts the decoded buffer into a BufferedImage + * precision: 24 bit, componentCount = 3 + * + * @param decoded data buffer + * @param width of the image + * @param height of the image + * @return a BufferedImage.TYPE_INT_RGB + */ + private BufferedImage read24Bit3ComponentRGB(int[][] decoded, int width, int height) { + // TODO: Consider 3_BYTE_BGR as this is more common? + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + int[] imageBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + + for (int i = 0; i < imageBuffer.length; i++) { + //convert to RGB + imageBuffer[i] = (decoded[0][i] << 16) | (decoded[1][i] << 8) | (decoded[2][i]); + } + return image; + } - for(int i = 0; i < imageBuffer.length; i++){ - //convert to RGB - imageBuffer[i] = (decoded[0][i] << 16) | (decoded[1][i] << 8) | (decoded[2][i]); - } - return image; - } - } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/QuantizationTable.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/QuantizationTable.java index debbd8c4..dbad4683 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/QuantizationTable.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/QuantizationTable.java @@ -30,112 +30,109 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; +import javax.imageio.stream.ImageInputStream; import java.io.IOException; - public class QuantizationTable { - private final int precision[] = new int[4]; // Quantization precision 8 or 16 - private final int[] tq = new int[4]; // 1: this table is presented + private final int precision[] = new int[4]; // Quantization precision 8 or 16 + private final int[] tq = new int[4]; // 1: this table is presented - protected final int quantTables[][] = new int[4][64]; // Tables + protected final int quantTables[][] = new int[4][64]; // Tables + public QuantizationTable() { + tq[0] = 0; + tq[1] = 0; + tq[2] = 0; + tq[3] = 0; + } + protected int read(final ImageInputStream data, final int[] table) throws IOException { + int count = 0; + final int length = data.readUnsignedShort(); + count += 2; - public QuantizationTable() { - tq[0] = 0; - tq[1] = 0; - tq[2] = 0; - tq[3] = 0; - } + while (count < length) { + final int temp = data.readUnsignedByte(); + count++; + final int t = temp & 0x0F; + if (t > 3) { + throw new IOException("ERROR: Quantization table ID > 3"); + } + precision[t] = temp >> 4; - protected int read(final DataStream data, final int[] table) throws IOException { - int count = 0; - final int length = data.get16(); - count += 2; + if (precision[t] == 0) { + precision[t] = 8; + } + else if (precision[t] == 1) { + precision[t] = 16; + } + else { + throw new IOException("ERROR: Quantization table precision error"); + } - while (count < length) { - final int temp = data.get8(); - count++; - final int t = temp & 0x0F; + tq[t] = 1; - if (t > 3) { - throw new IOException("ERROR: Quantization table ID > 3"); - } + if (precision[t] == 8) { + for (int i = 0; i < 64; i++) { + if (count > length) { + throw new IOException("ERROR: Quantization table format error"); + } - precision[t] = temp >> 4; + quantTables[t][i] = data.readUnsignedByte(); + count++; + } - if (precision[t] == 0) { - precision[t] = 8; - } else if (precision[t] == 1) { - precision[t] = 16; - } else { - throw new IOException("ERROR: Quantization table precision error"); - } + enhanceQuantizationTable(quantTables[t], table); + } + else { + for (int i = 0; i < 64; i++) { + if (count > length) { + throw new IOException("ERROR: Quantization table format error"); + } - tq[t] = 1; + quantTables[t][i] = data.readUnsignedShort(); + count += 2; + } - if (precision[t] == 8) { - for (int i = 0; i < 64; i++) { - if (count > length) { - throw new IOException("ERROR: Quantization table format error"); - } + enhanceQuantizationTable(quantTables[t], table); + } + } - quantTables[t][i] = data.get8(); - count++; - } + if (count != length) { + throw new IOException("ERROR: Quantization table error [count!=Lq]"); + } - enhanceQuantizationTable(quantTables[t], table); - } else { - for (int i = 0; i < 64; i++) { - if (count > length) { - throw new IOException("ERROR: Quantization table format error"); - } + return 1; + } - quantTables[t][i] = data.get16(); - count += 2; - } + private void enhanceQuantizationTable(final int qtab[], final int[] table) { + for (int i = 0; i < 8; i++) { + qtab[table[(0 * 8) + i]] *= 90; + qtab[table[(4 * 8) + i]] *= 90; + qtab[table[(2 * 8) + i]] *= 118; + qtab[table[(6 * 8) + i]] *= 49; + qtab[table[(5 * 8) + i]] *= 71; + qtab[table[(1 * 8) + i]] *= 126; + qtab[table[(7 * 8) + i]] *= 25; + qtab[table[(3 * 8) + i]] *= 106; + } - enhanceQuantizationTable(quantTables[t], table); - } - } + for (int i = 0; i < 8; i++) { + qtab[table[0 + (8 * i)]] *= 90; + qtab[table[4 + (8 * i)]] *= 90; + qtab[table[2 + (8 * i)]] *= 118; + qtab[table[6 + (8 * i)]] *= 49; + qtab[table[5 + (8 * i)]] *= 71; + qtab[table[1 + (8 * i)]] *= 126; + qtab[table[7 + (8 * i)]] *= 25; + qtab[table[3 + (8 * i)]] *= 106; + } - if (count != length) { - throw new IOException("ERROR: Quantization table error [count!=Lq]"); - } - - return 1; - } - - - - private void enhanceQuantizationTable(final int qtab[], final int[] table) { - for (int i = 0; i < 8; i++) { - qtab[table[(0 * 8) + i]] *= 90; - qtab[table[(4 * 8) + i]] *= 90; - qtab[table[(2 * 8) + i]] *= 118; - qtab[table[(6 * 8) + i]] *= 49; - qtab[table[(5 * 8) + i]] *= 71; - qtab[table[(1 * 8) + i]] *= 126; - qtab[table[(7 * 8) + i]] *= 25; - qtab[table[(3 * 8) + i]] *= 106; - } - - for (int i = 0; i < 8; i++) { - qtab[table[0 + (8 * i)]] *= 90; - qtab[table[4 + (8 * i)]] *= 90; - qtab[table[2 + (8 * i)]] *= 118; - qtab[table[6 + (8 * i)]] *= 49; - qtab[table[5 + (8 * i)]] *= 71; - qtab[table[1 + (8 * i)]] *= 126; - qtab[table[7 + (8 * i)]] *= 25; - qtab[table[3 + (8 * i)]] *= 106; - } - - for (int i = 0; i < 64; i++) { - qtab[i] >>= 6; - } - } + for (int i = 0; i < 64; i++) { + qtab[i] >>= 6; + } + } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ScanComponent.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ScanComponent.java index 6b9a64ee..94fc6cf9 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ScanComponent.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ScanComponent.java @@ -32,43 +32,31 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; public class ScanComponent { - private int acTabSel; // AC table selector - private int dcTabSel; // DC table selector - private int scanCompSel; // Scan component selector + private int acTabSel; // AC table selector + private int dcTabSel; // DC table selector + private int scanCompSel; // Scan component selector + public int getAcTabSel() { + return acTabSel; + } + public int getDcTabSel() { + return dcTabSel; + } - public int getAcTabSel() { - return acTabSel; - } + public int getScanCompSel() { + return scanCompSel; + } + public void setAcTabSel(final int acTabSel) { + this.acTabSel = acTabSel; + } + public void setDcTabSel(final int dcTabSel) { + this.dcTabSel = dcTabSel; + } - public int getDcTabSel() { - return dcTabSel; - } - - - - public int getScanCompSel() { - return scanCompSel; - } - - - - public void setAcTabSel(final int acTabSel) { - this.acTabSel = acTabSel; - } - - - - public void setDcTabSel(final int dcTabSel) { - this.dcTabSel = dcTabSel; - } - - - - public void setScanCompSel(final int scanCompSel) { - this.scanCompSel = scanCompSel; - } + public void setScanCompSel(final int scanCompSel) { + this.scanCompSel = scanCompSel; + } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ScanHeader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ScanHeader.java index f55217ac..be52e20b 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ScanHeader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/ScanHeader.java @@ -30,117 +30,97 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; +import javax.imageio.stream.ImageInputStream; import java.io.IOException; - public class ScanHeader { - private int ah; - private int al; - private int numComp; // Number of components in the scan - private int selection; // Start of spectral or predictor selection - private int spectralEnd; // End of spectral selection + private int ah; + private int al; + private int numComp; // Number of components in the scan + private int selection; // Start of spectral or predictor selection + private int spectralEnd; // End of spectral selection - protected ScanComponent components[]; + protected ScanComponent components[]; + public int getAh() { + return ah; + } + public int getAl() { + return al; + } - public int getAh() { - return ah; - } + public int getNumComponents() { + return numComp; + } + public int getSelection() { + return selection; + } + public int getSpectralEnd() { + return spectralEnd; + } - public int getAl() { - return al; - } + public void setAh(final int ah) { + this.ah = ah; + } + public void setAl(final int al) { + this.al = al; + } + public void setSelection(final int selection) { + this.selection = selection; + } - public int getNumComponents() { - return numComp; - } + public void setSpectralEnd(final int spectralEnd) { + this.spectralEnd = spectralEnd; + } + protected int read(final ImageInputStream data) throws IOException { + int count = 0; + final int length = data.readUnsignedShort(); + count += 2; + numComp = data.readUnsignedByte(); + count++; - public int getSelection() { - return selection; - } + components = new ScanComponent[numComp]; + for (int i = 0; i < numComp; i++) { + components[i] = new ScanComponent(); + if (count > length) { + throw new IOException("ERROR: scan header format error"); + } - public int getSpectralEnd() { - return spectralEnd; - } + components[i].setScanCompSel(data.readUnsignedByte()); + count++; + final int temp = data.readUnsignedByte(); + count++; + components[i].setDcTabSel(temp >> 4); + components[i].setAcTabSel(temp & 0x0F); + } - public void setAh(final int ah) { - this.ah = ah; - } + setSelection(data.readUnsignedByte()); + count++; + setSpectralEnd(data.readUnsignedByte()); + count++; + final int temp = data.readUnsignedByte(); + setAh(temp >> 4); + setAl(temp & 0x0F); + count++; - public void setAl(final int al) { - this.al = al; - } + if (count != length) { + throw new IOException("ERROR: scan header format error [count!=Ns]"); + } - - - public void setSelection(final int selection) { - this.selection = selection; - } - - - - public void setSpectralEnd(final int spectralEnd) { - this.spectralEnd = spectralEnd; - } - - - - protected int read(final DataStream data) throws IOException { - int count = 0; - final int length = data.get16(); - count += 2; - - numComp = data.get8(); - count++; - - components = new ScanComponent[numComp]; - - for (int i = 0; i < numComp; i++) { - components[i] = new ScanComponent(); - - if (count > length) { - throw new IOException("ERROR: scan header format error"); - } - - components[i].setScanCompSel(data.get8()); - count++; - - final int temp = data.get8(); - count++; - - components[i].setDcTabSel(temp >> 4); - components[i].setAcTabSel(temp & 0x0F); - } - - setSelection(data.get8()); - count++; - - setSpectralEnd(data.get8()); - count++; - - final int temp = data.get8(); - setAh(temp >> 4); - setAl(temp & 0x0F); - count++; - - if (count != length) { - throw new IOException("ERROR: scan header format error [count!=Ns]"); - } - - return 1; - } + return 1; + } } diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java index de654cd1..2156710b 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java @@ -94,7 +94,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest