diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCTSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java similarity index 68% rename from imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCTSegment.java rename to imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java index 1d0651b7..19b6dc06 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCTSegment.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AdobeDCT.java @@ -28,6 +28,11 @@ package com.twelvemonkeys.imageio.plugins.jpeg; +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; + +import java.io.DataInput; +import java.io.IOException; + /** * AdobeDCTSegment * @@ -35,7 +40,7 @@ package com.twelvemonkeys.imageio.plugins.jpeg; * @author last modified by $Author: haraldk$ * @version $Id: AdobeDCTSegment.java,v 1.0 23.04.12 16:55 haraldk Exp$ */ -class AdobeDCTSegment { +final class AdobeDCT extends AppSegment { public static final int Unknown = 0; public static final int YCC = 1; public static final int YCCK = 2; @@ -45,34 +50,34 @@ class AdobeDCTSegment { final int flags1; final int transform; - AdobeDCTSegment(int version, int flags0, int flags1, int transform) { + AdobeDCT(int version, int flags0, int flags1, int transform) { + super(JPEG.APP14, "Adobe", new byte[]{'A', 'd', 'o', 'b', 'e', 0, (byte) version, (byte) (flags0 >> 8), (byte) (flags0 & 0xff), (byte) (flags1 >> 8), (byte) (flags1 & 0xff), (byte) transform}); + this.version = version; // 100 or 101 this.flags0 = flags0; this.flags1 = flags1; this.transform = transform; } - public int getVersion() { - return version; - } - - public int getFlags0() { - return flags0; - } - - public int getFlags1() { - return flags1; - } - - public int getTransform() { - return transform; - } - @Override public String toString() { return String.format( "AdobeDCT[ver: %d.%02d, flags: %s %s, transform: %d]", - getVersion() / 100, getVersion() % 100, Integer.toBinaryString(getFlags0()), Integer.toBinaryString(getFlags1()), getTransform() + version / 100, version % 100, Integer.toBinaryString(flags0), Integer.toBinaryString(flags1), transform + ); + } + + public static AdobeDCT read(final DataInput data, final int length) throws IOException { + // TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers + + data.skipBytes(6); + + // version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK) + return new AdobeDCT( + data.readUnsignedByte(), + data.readUnsignedShort(), + data.readUnsignedShort(), + data.readUnsignedByte() ); } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AppSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AppSegment.java new file mode 100644 index 00000000..e77472be --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/AppSegment.java @@ -0,0 +1,70 @@ +package com.twelvemonkeys.imageio.plugins.jpeg; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.lang.Validate; + +import java.io.ByteArrayInputStream; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; + +/** + * AppSegment. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: AppSegment.java,v 1.0 22/08/16 harald.kuhr Exp$ + */ +class AppSegment extends Segment { + + final String identifier; + final byte[] data; + + AppSegment(int marker, final String identifier, final byte[] data) { + super(marker); + + this.identifier = Validate.notEmpty(identifier, "identifier"); + this.data = data; + } + + protected AppSegment(int marker, final String identifier) { + this(marker, identifier, null); + } + + InputStream data() { + int offset = identifier.length() + 1; + return new ByteArrayInputStream(data, offset, data.length - offset); + } + + public static AppSegment read(final int marker, final String identifier, final DataInput data, final int length) throws IOException { + switch (marker) { + case JPEG.APP0: + // JFIF + if ("JFIF".equals(identifier)) { + return JFIF.read(data, length); + } + case JPEG.APP1: + // JFXX + if ("JFXX".equals(identifier)) { + return JFXX.read(data, length); + } + // TODO: Exif? + case JPEG.APP2: + // ICC_PROFILE + if ("ICC_PROFILE".equals(identifier)) { + return ICCProfile.read(data, length); + } + case JPEG.APP14: + // Adobe + if ("Adobe".equals(identifier)) { + return AdobeDCT.read(data, length); + } + + default: + // Generic APPn segment + byte[] bytes = new byte[length - 2]; + data.readFully(bytes); + return new AppSegment(marker, identifier, bytes); + } + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Comment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Comment.java new file mode 100644 index 00000000..1b4c5c04 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Comment.java @@ -0,0 +1,30 @@ +package com.twelvemonkeys.imageio.plugins.jpeg; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; + +import java.io.DataInput; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** + * Comment. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: Comment.java,v 1.0 23/08/16 harald.kuhr Exp$ + */ +class Comment extends Segment { + final String comment; + + private Comment(final String comment) { + super(JPEG.COM); + this.comment = comment; + } + + public static Segment read(final DataInput data, final int length) throws IOException { + byte[] ascii = new byte[length]; + data.readFully(ascii); + + return new Comment(new String(ascii, StandardCharsets.UTF_8)); + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Frame.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Frame.java new file mode 100644 index 00000000..b3788669 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Frame.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.jpeg; + +import com.twelvemonkeys.imageio.stream.SubImageInputStream; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import java.io.DataInput; +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; + +/** + * Frame + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: SOFSegment.java,v 1.0 22.04.13 16:40 haraldk Exp$ + */ +final class Frame extends Segment { + final int samplePrecision; // Sample precision + final int lines; // Height + final int samplesPerLine; // Width + + final Component[] components; // Components specifications + + Frame(final int marker, final int samplePrecision, final int lines, final int samplesPerLine, final Component[] components) { + super(marker); + + this.samplePrecision = samplePrecision; + this.lines = lines; + this.samplesPerLine = samplesPerLine; + this.components = components; + } + + int process() { + return marker & 0xff - 0xc0; + } + + int componentsInFrame() { + return components.length; + } + + Component getComponent(final int id) { + for (Component component : components) { + if (component.id == id) { + return component; + } + } + + throw new IllegalArgumentException(String.format("No such component id: %d", id)); + } + + @Override + public String toString() { + return String.format( + "SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]", + process(), marker, samplePrecision, lines, samplesPerLine, Arrays.toString(components) + ); + } + + static Frame read(final int marker, final DataInput data, final int length) throws IOException { + int samplePrecision = data.readUnsignedByte(); + int lines = data.readUnsignedShort(); + int samplesPerLine = data.readUnsignedShort(); + int componentsInFrame = data.readUnsignedByte(); + + int expected = 8 + componentsInFrame * 3; + if (length != expected) { + throw new IIOException(String.format("Unexpected SOF length: %d != %d", length, expected)); + } + + Component[] components = new Component[componentsInFrame]; + + for (int i = 0; i < componentsInFrame; i++) { + int id = data.readUnsignedByte(); + int sub = data.readUnsignedByte(); + int qtSel = data.readUnsignedByte(); + + components[i] = new Component(id, ((sub & 0xF0) >> 4), (sub & 0xF), qtSel); + } + + return new Frame(marker, samplePrecision, lines, samplesPerLine, components); + } + + static Frame read(final int marker, final ImageInputStream data) throws IOException { + int length = data.readUnsignedShort(); + + return read(marker, new SubImageInputStream(data, length), length); + } + + static final class Component { + final int id; + final int hSub; // Horizontal sampling factor + final int vSub; // Vertical sampling factor + final int qtSel; // Quantization table destination selector + + Component(int id, int hSub, int vSub, int qtSel) { + this.id = id; + this.hSub = hSub; + this.vSub = vSub; + this.qtSel = qtSel; + } + + @Override + public String toString() { + // Use id either as component number or component name, based on value + Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id; + return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel); + } + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/HuffmanTable.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/HuffmanTable.java new file mode 100644 index 00000000..8bd4b465 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/HuffmanTable.java @@ -0,0 +1,175 @@ +/* + * 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; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; + +import javax.imageio.stream.ImageInputStream; +import java.io.DataInput; +import java.io.IOException; + +final class HuffmanTable extends Segment { + + final int l[][][] = new int[4][2][16]; + final int th[] = new int[4]; // 1: this table is presented + final int v[][][][] = new int[4][2][16][200]; // tables + final int[][] tc = new int[4][2]; // 1: this table is presented + + public static final int MSB = 0x80000000; + + public HuffmanTable() { + super(JPEG.DHT); + + tc[0][0] = 0; + tc[1][0] = 0; + tc[2][0] = 0; + tc[3][0] = 0; + tc[0][1] = 0; + tc[1][1] = 0; + tc[2][1] = 0; + tc[3][1] = 0; + th[0] = 0; + th[1] = 0; + th[2] = 0; + th[3] = 0; + } + + protected void buildHuffTables(final int[][][] HuffTab) throws IOException { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 2; j++) { + if (tc[i][j] != 0) { + buildHuffTable(HuffTab[i][j], l[i][j], v[i][j]); + } + } + } + } + + // 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("Huffman table error"); + } + + k = 0; + currentTable++; + } + } + } + } + + public static Segment read(DataInput data, int length) throws IOException { + int count = 0; + count += 2; + + HuffmanTable table = new HuffmanTable(); + + while (count < length) { + int temp = data.readUnsignedByte(); + count++; + int t = temp & 0x0F; + if (t > 3) { + throw new IOException("Huffman table Id > 3:" + t); + } + + int c = temp >> 4; + if (c > 2) { + throw new IOException("Huffman table class > 2: " + c); + } + + table.th[t] = 1; + table.tc[t][c] = 1; + + for (int i = 0; i < 16; i++) { + table.l[t][c][i] = data.readUnsignedByte(); + count++; + } + + for (int i = 0; i < 16; i++) { + for (int j = 0; j < table.l[t][c][i]; j++) { + if (count > length) { + throw new IOException("Huffman table format error [count>Lh]"); + } + table.v[t][c][i][j] = data.readUnsignedByte(); + count++; + } + } + } + + if (count != length) { + throw new IOException("Huffman table format error [count!=Lf]"); + } + +// 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]); +// } +// } +// } +// +// return 1; + return table; + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ICCProfile.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ICCProfile.java new file mode 100644 index 00000000..bdea9621 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ICCProfile.java @@ -0,0 +1,28 @@ +package com.twelvemonkeys.imageio.plugins.jpeg; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; + +import java.io.DataInput; +import java.io.IOException; + +/** + * ICCProfile. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: ICCProfile.java,v 1.0 22/08/16 harald.kuhr Exp$ + */ +final class ICCProfile extends AppSegment { + protected ICCProfile(final byte[] data) { + super(JPEG.APP2, "ICC_PROFILE", data); + } + + // TODO: Create util method to concat all ICC segments to one and return ICC_Profile (move from JPEGImageReader) + + public static ICCProfile read(DataInput data, int length) throws IOException { + byte[] bytes = new byte[length - 2]; + data.readFully(bytes); + + return new ICCProfile(bytes); + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIF.java similarity index 64% rename from imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFSegment.java rename to imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIF.java index 373e1476..832105f2 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFSegment.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIF.java @@ -28,9 +28,11 @@ package com.twelvemonkeys.imageio.plugins.jpeg; -import java.io.DataInputStream; -import java.io.IOException; -import java.io.InputStream; +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; + +import java.io.*; +import java.nio.ByteBuffer; +import java.util.Arrays; /** * JFIFSegment @@ -39,7 +41,7 @@ import java.io.InputStream; * @author last modified by $Author: haraldk$ * @version $Id: JFIFSegment.java,v 1.0 23.04.12 16:52 haraldk Exp$ */ -class JFIFSegment { +final class JFIF extends AppSegment { final int majorVersion; final int minorVersion; final int units; @@ -49,7 +51,9 @@ class JFIFSegment { final int yThumbnail; final byte[] thumbnail; - private JFIFSegment(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail) { + private JFIF(int majorVersion, int minorVersion, int units, int xDensity, int yDensity, int xThumbnail, int yThumbnail, byte[] thumbnail, byte[] data) { + super(JPEG.APP0, "JFIF", data); + this.majorVersion = majorVersion; this.minorVersion = minorVersion; this.units = units; @@ -86,20 +90,51 @@ class JFIFSegment { return String.format("thumbnail: %dx%d", xThumbnail, yThumbnail); } - public static JFIFSegment read(final InputStream data) throws IOException { - DataInputStream stream = new DataInputStream(data); + public static JFIF read(final DataInput data, int length) throws IOException { + if (length < 2 + 5 + 9) { + throw new EOFException(); + } + + data.readFully(new byte[5]); + + byte[] bytes = new byte[length - 2 - 5]; + data.readFully(bytes); int x, y; - return new JFIFSegment( - stream.readUnsignedByte(), - stream.readUnsignedByte(), - stream.readUnsignedByte(), - stream.readUnsignedShort(), - stream.readUnsignedShort(), - x = stream.readUnsignedByte(), - y = stream.readUnsignedByte(), - JPEGImageReader.readFully(stream, x * y * 3) + ByteBuffer buffer = ByteBuffer.wrap(bytes); + + return new JFIF( + buffer.get() & 0xff, + buffer.get() & 0xff, + buffer.get() & 0xff, + buffer.getShort() & 0xffff, + buffer.getShort() & 0xffff, + x = buffer.get() & 0xff, + y = buffer.get() & 0xff, + getBytes(buffer, x * y * 3), + bytes ); +// return new JFIF( +// data.readUnsignedByte(), +// data.readUnsignedByte(), +// data.readUnsignedByte(), +// data.readUnsignedShort(), +// data.readUnsignedShort(), +// x = data.readUnsignedByte(), +// y = data.readUnsignedByte(), +// JPEGImageReader.readFully(data, x * y * 3) +// ); + } + + private static byte[] getBytes(ByteBuffer buffer, int len) { + if (len == 0) { + return null; + } + + byte[] dst = new byte[len]; + buffer.get(dst); + + return dst; } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java index 71e25bb4..ef16d460 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReader.java @@ -39,9 +39,9 @@ import java.io.IOException; * @version $Id: JFIFThumbnailReader.java,v 1.0 18.04.12 12:19 haraldk Exp$ */ final class JFIFThumbnailReader extends ThumbnailReader { - private final JFIFSegment segment; + private final JFIF segment; - public JFIFThumbnailReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, JFIFSegment segment) { + public JFIFThumbnailReader(ThumbnailReadProgressListener progressListener, int imageIndex, int thumbnailIndex, JFIF segment) { super(progressListener, imageIndex, thumbnailIndex); this.segment = segment; } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXSegment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXX.java similarity index 80% rename from imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXSegment.java rename to imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXX.java index 00d33534..01099fa7 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXSegment.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXX.java @@ -28,9 +28,11 @@ package com.twelvemonkeys.imageio.plugins.jpeg; +import java.io.DataInput; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Arrays; /** * JFXXSegment @@ -39,7 +41,7 @@ import java.io.InputStream; * @author last modified by $Author: haraldk$ * @version $Id: JFXXSegment.java,v 1.0 23.04.12 16:54 haraldk Exp$ */ -class JFXXSegment { +final class JFXX extends AppSegment { public static final int JPEG = 0x10; public static final int INDEXED = 0x11; public static final int RGB = 0x13; @@ -47,7 +49,9 @@ class JFXXSegment { final int extensionCode; final byte[] thumbnail; - private JFXXSegment(int extensionCode, byte[] thumbnail) { + private JFXX(final int extensionCode, final byte[] thumbnail, final byte[] data) { + super(com.twelvemonkeys.imageio.metadata.jpeg.JPEG.APP0, "JFXX", data); + this.extensionCode = extensionCode; this.thumbnail = thumbnail; } @@ -70,12 +74,16 @@ class JFXXSegment { } } - public static JFXXSegment read(InputStream data, int length) throws IOException { - DataInputStream stream = new DataInputStream(data); + public static JFXX read(final DataInput data, final int length) throws IOException { + data.readFully(new byte[5]); - return new JFXXSegment( - stream.readUnsignedByte(), - JPEGImageReader.readFully(stream, length - 1) + byte[] bytes = new byte[length - 2 - 5]; + data.readFully(bytes); + + return new JFXX( + bytes[0] & 0xff, + bytes.length - 1 > 0 ? Arrays.copyOfRange(bytes, 1, bytes.length - 1) : null, + bytes ); } } diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java index 8de14dcb..0d47d1ca 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReader.java @@ -50,11 +50,11 @@ import java.lang.ref.SoftReference; final class JFXXThumbnailReader extends ThumbnailReader { private final ImageReader reader; - private final JFXXSegment segment; + private final JFXX segment; private transient SoftReference cachedThumbnail; - protected JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXXSegment segment) { + protected JFXXThumbnailReader(final ThumbnailReadProgressListener progressListener, ImageReader jpegReader, final int imageIndex, final int thumbnailIndex, final JFXX segment) { super(progressListener, imageIndex, thumbnailIndex); this.reader = Validate.notNull(jpegReader); this.segment = segment; @@ -66,13 +66,13 @@ final class JFXXThumbnailReader extends ThumbnailReader { BufferedImage thumbnail; switch (segment.extensionCode) { - case JFXXSegment.JPEG: + case JFXX.JPEG: thumbnail = readJPEGCached(true); break; - case JFXXSegment.INDEXED: + case JFXX.INDEXED: thumbnail = readIndexed(); break; - case JFXXSegment.RGB: + case JFXX.RGB: thumbnail = readRGB(); break; default: @@ -119,10 +119,10 @@ final class JFXXThumbnailReader extends ThumbnailReader { @Override public int getWidth() throws IOException { switch (segment.extensionCode) { - case JFXXSegment.RGB: - case JFXXSegment.INDEXED: + case JFXX.RGB: + case JFXX.INDEXED: return segment.thumbnail[0] & 0xff; - case JFXXSegment.JPEG: + case JFXX.JPEG: return readJPEGCached(false).getWidth(); default: throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode)); @@ -132,10 +132,10 @@ final class JFXXThumbnailReader extends ThumbnailReader { @Override public int getHeight() throws IOException { switch (segment.extensionCode) { - case JFXXSegment.RGB: - case JFXXSegment.INDEXED: + case JFXX.RGB: + case JFXX.INDEXED: return segment.thumbnail[1] & 0xff; - case JFXXSegment.JPEG: + case JFXX.JPEG: return readJPEGCached(false).getHeight(); default: throw new IIOException(String.format("Unsupported JFXX extension code: %d", segment.extensionCode)); diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java new file mode 100644 index 00000000..46ebb233 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10Metadata.java @@ -0,0 +1,215 @@ +package com.twelvemonkeys.imageio.plugins.jpeg; + +import com.twelvemonkeys.imageio.AbstractMetadata; +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; +import org.w3c.dom.Node; + +import javax.imageio.metadata.IIOMetadataNode; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * JPEGImage10Metadata. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: JPEGImage10Metadata.java,v 1.0 10/08/16 harald.kuhr Exp$ + */ +class JPEGImage10Metadata extends AbstractMetadata { + + // TODO: Clean up. Consider just making the meta data classes we were trying to avoid in the first place.... + + private final List segments; + + JPEGImage10Metadata(List segments) { + super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null); + + this.segments = segments; + } + + @Override + protected Node getNativeTree() { + IIOMetadataNode root = new IIOMetadataNode(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0); + + IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety"); + root.appendChild(jpegVariety); + // TODO: If we have JFIF, append in JPEGvariety, but can't happen for lossless + + IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence"); + root.appendChild(markerSequence); + + for (Segment segment : segments) + switch (segment.marker) { + // SOF3 is the only one supported by now + case JPEG.SOF3: + Frame sofSegment = (Frame) segment; + + IIOMetadataNode sof = new IIOMetadataNode("sof"); + sof.setAttribute("process", String.valueOf(sofSegment.marker & 0xf)); + sof.setAttribute("samplePrecision", String.valueOf(sofSegment.samplePrecision)); + sof.setAttribute("numLines", String.valueOf(sofSegment.lines)); + sof.setAttribute("samplesPerLine", String.valueOf(sofSegment.samplesPerLine)); + sof.setAttribute("numFrameComponents", String.valueOf(sofSegment.componentsInFrame())); + + for (Frame.Component component : sofSegment.components) { + IIOMetadataNode componentSpec = new IIOMetadataNode("componentSpec"); + componentSpec.setAttribute("componentId", String.valueOf(component.id)); + componentSpec.setAttribute("HsamplingFactor", String.valueOf(component.hSub)); + componentSpec.setAttribute("VsamplingFactor", String.valueOf(component.vSub)); + componentSpec.setAttribute("QtableSelector", String.valueOf(component.qtSel)); + + sof.appendChild(componentSpec); + } + + markerSequence.appendChild(sof); + break; + + case JPEG.DHT: + HuffmanTable huffmanTable = (HuffmanTable) segment; + IIOMetadataNode dht = new IIOMetadataNode("dht"); + + // Uses fixed tables... + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 2; j++) { + if (huffmanTable.tc[i][j] != 0) { + IIOMetadataNode dhtable = new IIOMetadataNode("dhtable"); + dhtable.setAttribute("class", String.valueOf(j)); + dhtable.setAttribute("htableId", String.valueOf(i)); + dht.appendChild(dhtable); + } + } + } + + markerSequence.appendChild(dht); + break; + + case JPEG.DQT: + markerSequence.appendChild(new IIOMetadataNode("dqt")); + // TODO: + break; + + case JPEG.SOS: + Scan scan = (Scan) segment; + IIOMetadataNode sos = new IIOMetadataNode("sos"); + sos.setAttribute("numScanComponents", String.valueOf(scan.components.length)); + sos.setAttribute("startSpectralSelection", String.valueOf(scan.selection)); + sos.setAttribute("endSpectralSelection", String.valueOf(scan.spectralEnd)); + sos.setAttribute("approxHigh", String.valueOf(scan.ah)); + sos.setAttribute("approxLow", String.valueOf(scan.al)); + + for (Scan.Component component : scan.components) { + IIOMetadataNode spec = new IIOMetadataNode("scanComponentSpec"); + spec.setAttribute("componentSelector", String.valueOf(component.scanCompSel)); + spec.setAttribute("dcHuffTable", String.valueOf(component.dcTabSel)); + spec.setAttribute("acHuffTable", String.valueOf(component.acTabSel)); + sos.appendChild(spec); + } + + markerSequence.appendChild(sos); + break; + + case JPEG.COM: + IIOMetadataNode com = new IIOMetadataNode("com"); + com.setAttribute("comment", ((Comment) segment).comment); + + markerSequence.appendChild(com); + + break; + + case JPEG.APP14: + if (segment instanceof AdobeDCT) { + AdobeDCT adobe = (AdobeDCT) segment; + IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe"); + app14Adobe.setAttribute("version", String.valueOf(adobe.version)); + app14Adobe.setAttribute("flags0", String.valueOf(adobe.flags0)); + app14Adobe.setAttribute("flags1", String.valueOf(adobe.flags1)); + app14Adobe.setAttribute("transform", String.valueOf(adobe.transform)); + markerSequence.appendChild(app14Adobe); + break; + } + // Else, fall through to unknown segment + + default: + IIOMetadataNode unknown = new IIOMetadataNode("unknown"); + unknown.setAttribute("MarkerTag", String.valueOf(segment.marker & 0xFF)); + unknown.setUserObject(((AppSegment) segment).data); + markerSequence.appendChild(unknown); + + break; + } + + return root; + } + + @Override + protected IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); + + for (Segment segment : segments) { + if (segment instanceof Frame) { + Frame sofSegment = (Frame) segment; + IIOMetadataNode colorSpaceType = new IIOMetadataNode("ColorSpaceType"); + colorSpaceType.setAttribute("name", sofSegment.componentsInFrame() == 1 ? "Gray" : "RGB"); // TODO YCC, YCCK, CMYK etc + chroma.appendChild(colorSpaceType); + + IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels"); + numChannels.setAttribute("value", String.valueOf(sofSegment.componentsInFrame())); + chroma.appendChild(numChannels); + + break; + } + } + + return chroma; + } + + @Override + protected IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode compression = new IIOMetadataNode("Compression"); + + IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName"); + compressionTypeName.setAttribute("value", "JPEG"); // ...or "JPEG-LOSSLESS" (which is the name used by the JAI JPEGImageWriter for it's compression name)? + compression.appendChild(compressionTypeName); + + IIOMetadataNode lossless = new IIOMetadataNode("Lossless"); + lossless.setAttribute("value", "TRUE"); // TODO: For lossless only + compression.appendChild(lossless); + + IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans"); + numProgressiveScans.setAttribute("value", "1"); // TODO! + compression.appendChild(numProgressiveScans); + + return compression; + } + + @Override + protected IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension = new IIOMetadataNode("Dimension"); + + IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation"); + imageOrientation.setAttribute("value", "normal"); // TODO + dimension.appendChild(imageOrientation); + + return dimension; + } + + @Override + protected IIOMetadataNode getStandardTextNode() { + IIOMetadataNode text = new IIOMetadataNode("Text"); + + for (Segment segment : segments) { + if (segment instanceof Comment) { + IIOMetadataNode com = new IIOMetadataNode("TextEntry"); + com.setAttribute("keyword", "comment"); + com.setAttribute("value", ((Comment) segment).comment); + + text.appendChild(com); + } + } + + return text.hasChildNodes() ? text : null; + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10MetadataCleaner.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10MetadataCleaner.java index d78e091d..ca2b3728 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10MetadataCleaner.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImage10MetadataCleaner.java @@ -1,7 +1,6 @@ package com.twelvemonkeys.imageio.plugins.jpeg; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; -import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; import com.twelvemonkeys.xml.XMLSerializer; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -11,6 +10,7 @@ import javax.imageio.metadata.IIOInvalidTreeException; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import java.awt.color.ICC_Profile; +import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; import java.nio.charset.Charset; @@ -39,7 +39,7 @@ final class JPEGImage10MetadataCleaner { IIOMetadata cleanMetadata(final IIOMetadata imageMetadata) throws IOException { // We filter out pretty much everything from the stream.. // Meaning we have to read get *all APP segments* and re-insert into metadata. - List appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null); + List appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null); // NOTE: There's a bug in the merging code in JPEGMetadata mergeUnknownNode that makes sure all "unknown" nodes are added twice in certain conditions.... ARGHBL... // DONE: 1: Work around @@ -70,11 +70,11 @@ final class JPEGImage10MetadataCleaner { IIOMetadataNode jpegVariety = (IIOMetadataNode) tree.getElementsByTagName("JPEGvariety").item(0); IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0); - JFIFSegment jfifSegment = reader.getJFIF(); - JFXXSegment jfxxSegment = reader.getJFXX(); - AdobeDCTSegment adobeDCT = reader.getAdobeDCT(); + JFIF jfifSegment = reader.getJFIF(); + JFXX jfxx = reader.getJFXX(); + AdobeDCT adobeDCT = reader.getAdobeDCT(); ICC_Profile embeddedICCProfile = reader.getEmbeddedICCProfile(true); - SOFSegment sof = reader.getSOF(); + Frame sof = reader.getSOF(); boolean hasRealJFIF = false; boolean hasRealJFXX = false; @@ -104,17 +104,17 @@ final class JPEGImage10MetadataCleaner { hasRealICC = true; } - if (jfxxSegment != null) { + if (jfxx != null) { IIOMetadataNode JFXX = new IIOMetadataNode("JFXX"); jfif.appendChild(JFXX); IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX"); - app0JFXX.setAttribute("extensionCode", String.valueOf(jfxxSegment.extensionCode)); + app0JFXX.setAttribute("extensionCode", String.valueOf(jfxx.extensionCode)); - JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxxSegment); + JFXXThumbnailReader thumbnailReader = new JFXXThumbnailReader(null, reader.getThumbnailReader(), 0, 0, jfxx); IIOMetadataNode jfifThumb; - switch (jfxxSegment.extensionCode) { - case JFXXSegment.JPEG: + switch (jfxx.extensionCode) { + case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.JPEG: jfifThumb = new IIOMetadataNode("JFIFthumbJPEG"); // Contains it's own "markerSequence" with full DHT, DQT, SOF etc... IIOMetadata thumbMeta = thumbnailReader.readMetadata(); @@ -123,14 +123,14 @@ final class JPEGImage10MetadataCleaner { app0JFXX.appendChild(jfifThumb); break; - case JFXXSegment.INDEXED: + case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.INDEXED: jfifThumb = new IIOMetadataNode("JFIFthumbPalette"); jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth())); jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight())); app0JFXX.appendChild(jfifThumb); break; - case JFXXSegment.RGB: + case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.RGB: jfifThumb = new IIOMetadataNode("JFIFthumbRGB"); jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth())); jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight())); @@ -138,7 +138,7 @@ final class JPEGImage10MetadataCleaner { break; default: - reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxxSegment.extensionCode)); + reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxx.extensionCode)); } JFXX.appendChild(app0JFXX); @@ -156,12 +156,12 @@ final class JPEGImage10MetadataCleaner { } // Special case: Broken AdobeDCT segment, inconsistent with SOF, use values from SOF - if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() < 4 || - adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() < 3)) { + if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() < 4 || + adobeDCT.transform == AdobeDCT.YCC && sof.componentsInFrame() < 3)) { reader.processWarningOccurred(String.format( "Invalid Adobe App14 marker. Indicates %s data, but SOF%d has %d color component(s). " + "Ignoring Adobe App14 marker.", - adobeDCT.getTransform() == AdobeDCTSegment.YCCK ? "YCCK/CMYK" : "YCC/RGB", + adobeDCT.transform == AdobeDCT.YCCK ? "YCCK/CMYK" : "YCC/RGB", sof.marker & 0xf, sof.componentsInFrame() )); @@ -176,41 +176,43 @@ final class JPEGImage10MetadataCleaner { } Node next = null; - for (JPEGSegment segment : appSegments) { + for (AppSegment segment : appSegments) { // Except real app0JFIF, app0JFXX, app2ICC and app14Adobe, add all the app segments that we filtered away as "unknown" markers - if (segment.marker() == JPEG.APP0 && "JFIF".equals(segment.identifier()) && hasRealJFIF) { + if (segment.marker == JPEG.APP0 && "JFIF".equals(segment.identifier) && hasRealJFIF) { continue; } - else if (segment.marker() == JPEG.APP0 && "JFXX".equals(segment.identifier()) && hasRealJFXX) { + else if (segment.marker == JPEG.APP0 && "JFXX".equals(segment.identifier) && hasRealJFXX) { continue; } - else if (segment.marker() == JPEG.APP1 && "Exif".equals(segment.identifier()) /* always inserted */) { + else if (segment.marker == JPEG.APP1 && "Exif".equals(segment.identifier) /* always inserted */) { continue; } - else if (segment.marker() == JPEG.APP2 && "ICC_PROFILE".equals(segment.identifier()) && hasRealICC) { + else if (segment.marker == JPEG.APP2 && "ICC_PROFILE".equals(segment.identifier) && hasRealICC) { continue; } - else if (segment.marker() == JPEG.APP14 && "Adobe".equals(segment.identifier()) /* always inserted */) { + else if (segment.marker == JPEG.APP14 && "Adobe".equals(segment.identifier) /* always inserted */) { continue; } IIOMetadataNode unknown = new IIOMetadataNode("unknown"); - unknown.setAttribute("MarkerTag", Integer.toString(segment.marker() & 0xff)); + unknown.setAttribute("MarkerTag", Integer.toString(segment.marker & 0xff)); - try (DataInputStream stream = new DataInputStream(segment.data())) { - String identifier = segment.identifier(); - int off = identifier != null ? identifier.length() + 1 : 0; + unknown.setUserObject(segment.data); - byte[] data = new byte[off + segment.length()]; - - if (identifier != null) { - System.arraycopy(identifier.getBytes(Charset.forName("ASCII")), 0, data, 0, identifier.length()); - } - - stream.readFully(data, off, segment.length()); - - unknown.setUserObject(data); - } +// try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(segment.data))) { +// String identifier = segment.identifier; +// int off = identifier != null ? identifier.length() + 1 : 0; +// +// byte[] data = new byte[off + segment.data.length]; +// +// if (identifier != null) { +// System.arraycopy(identifier.getBytes(Charset.forName("ASCII")), 0, data, 0, identifier.length()); +// } +// +// stream.readFully(data, off, segment.data.length); +// +// unknown.setUserObject(data); +// } if (next == null) { // To be semi-compatible with the functionality in mergeTree, 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 8335ee40..54a15409 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,7 +28,6 @@ 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; @@ -45,12 +44,10 @@ 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; @@ -158,7 +155,7 @@ public class JPEGImageReader extends ImageReaderBase { private JPEGImage10MetadataCleaner metadataCleaner; /** Cached list of JPEG segments we filter from the underlying stream */ - private List segments; + private List segments; protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) { super(provider); @@ -226,13 +223,16 @@ public class JPEGImageReader extends ImageReaderBase { assertInput(); try { - SOFSegment sof = getSOF(); + Frame 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 + if (DEBUG) { + ignore.printStackTrace(); + } } return false; @@ -242,7 +242,7 @@ public class JPEGImageReader extends ImageReaderBase { public int getWidth(int imageIndex) throws IOException { checkBounds(imageIndex); - SOFSegment sof = getSOF(); + Frame sof = getSOF(); if (sof.marker == JPEG.SOF3) { return sof.samplesPerLine; } @@ -254,7 +254,7 @@ public class JPEGImageReader extends ImageReaderBase { public int getHeight(int imageIndex) throws IOException { checkBounds(imageIndex); - SOFSegment sof = getSOF(); + Frame sof = getSOF(); if (sof.marker == JPEG.SOF3) { return sof.lines; } @@ -362,17 +362,17 @@ public class JPEGImageReader extends ImageReaderBase { assertInput(); checkBounds(imageIndex); - SOFSegment sof = getSOF(); + Frame sof = getSOF(); ICC_Profile profile = getEmbeddedICCProfile(false); - AdobeDCTSegment adobeDCT = getAdobeDCT(); + AdobeDCT adobeDCT = getAdobeDCT(); boolean bogusAdobeDCT = false; - if (adobeDCT != null && (adobeDCT.getTransform() == AdobeDCTSegment.YCC && sof.componentsInFrame() != 3 || - adobeDCT.getTransform() == AdobeDCTSegment.YCCK && sof.componentsInFrame() != 4)) { + if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCC && sof.componentsInFrame() != 3 || + adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() != 4)) { processWarningOccurred(String.format( "Invalid Adobe App14 marker. Indicates %s data, but SOF%d has %d color component(s). " + "Ignoring Adobe App14 marker.", - adobeDCT.getTransform() == AdobeDCTSegment.YCCK ? "YCCK/CMYK" : "YCC/RGB", + adobeDCT.transform == AdobeDCT.YCCK ? "YCCK/CMYK" : "YCC/RGB", sof.marker & 0xf, sof.componentsInFrame() )); @@ -413,7 +413,7 @@ public class JPEGImageReader extends ImageReaderBase { return delegate.read(imageIndex, param); } - private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, SOFSegment startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException { + private BufferedImage readImageAsRasterAndReplaceColorProfile(int imageIndex, ImageReadParam param, Frame startOfFrame, JPEGColorSpace csType, ICC_Profile profile) throws IOException { int origWidth = getWidth(imageIndex); int origHeight = getHeight(imageIndex); @@ -548,7 +548,7 @@ public class JPEGImageReader extends ImageReaderBase { return image; } - static JPEGColorSpace getSourceCSType(JFIFSegment jfif, AdobeDCTSegment adobeDCT, final SOFSegment startOfFrame) throws IIOException { + static JPEGColorSpace getSourceCSType(JFIF jfif, AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException { /* ADAPTED from http://download.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html: @@ -590,20 +590,20 @@ public class JPEGImageReader extends ImageReaderBase { */ if (adobeDCT != null) { - switch (adobeDCT.getTransform()) { - case AdobeDCTSegment.YCC: + switch (adobeDCT.transform) { + case AdobeDCT.YCC: if (startOfFrame.components.length != 3) { // This probably means the Adobe marker is bogus break; } return JPEGColorSpace.YCbCr; - case AdobeDCTSegment.YCCK: + case AdobeDCT.YCCK: if (startOfFrame.components.length != 4) { // This probably means the Adobe marker is bogus break; } return JPEGColorSpace.YCCK; - case AdobeDCTSegment.Unknown: + case AdobeDCT.Unknown: if (startOfFrame.components.length == 1) { return JPEGColorSpace.Gray; } @@ -637,7 +637,7 @@ public class JPEGImageReader extends ImageReaderBase { } else { // If subsampled, YCbCr else RGB - for (SOFComponent component : startOfFrame.components) { + for (Frame.Component component : startOfFrame.components) { if (component.hSub != 1 || component.vSub != 1) { return JPEGColorSpace.YCbCr; } @@ -665,7 +665,7 @@ public class JPEGImageReader extends ImageReaderBase { else { // TODO: JPEGMetadata (standard format) will report YCbCrA for 4 channel subsampled... :-/ // If subsampled, YCCK else CMYK - for (SOFComponent component : startOfFrame.components) { + for (Frame.Component component : startOfFrame.components) { if (component.hSub != 1 || component.vSub != 1) { return JPEGColorSpace.YCCK; } @@ -712,7 +712,27 @@ public class JPEGImageReader extends ImageReaderBase { if (segments == null) { long start = DEBUG ? System.currentTimeMillis() : 0; - readSegments(); + // TODO: Consider just reading the segments here, for better performance... + List jpegSegments = readSegments(); + + List segments = new ArrayList<>(jpegSegments.size()); + + for (JPEGSegment segment : jpegSegments) { + try (DataInputStream data = new DataInputStream(segment.segmentData())) { + segments.add(Segment.read(segment.marker(), segment.identifier(), segment.segmentLength(), data)); + } + catch (IOException e) { + // TODO: Handle bad segments better, for now, just ignore any bad APP markers + if (segment.marker() >= JPEG.APP0 && JPEG.APP15 >= segment.marker()) { + processWarningOccurred("Bogus " +segment.identifier() + " segment, ignoring"); + continue; + } + + throw e; + } + } + + this.segments = segments; if (DEBUG) { System.out.println("Read metadata in " + (System.currentTimeMillis() - start) + " ms"); @@ -720,13 +740,13 @@ public class JPEGImageReader extends ImageReaderBase { } } - private void readSegments() throws IOException { + private List readSegments() throws IOException { imageInput.mark(); try { imageInput.seek(0); // TODO: Seek to wanted image, skip images on the way - segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS); + return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS); } catch (IIOException | IllegalArgumentException ignore) { if (DEBUG) { @@ -738,126 +758,62 @@ public class JPEGImageReader extends ImageReaderBase { } // In case of an exception, avoid NPE when referencing segments later - if (segments == null) { - segments = Collections.emptyList(); - } + return Collections.emptyList(); } - List getAppSegments(final int marker, final String identifier) throws IOException { + List getAppSegments(final int marker, final String identifier) throws IOException { initHeader(); - List appSegments = Collections.emptyList(); + List appSegments = Collections.emptyList(); - for (JPEGSegment segment : segments) { - if ((marker == ALL_APP_MARKERS && segment.marker() >= JPEG.APP0 && segment.marker() <= JPEG.APP15 || segment.marker() == marker) - && (identifier == null || identifier.equals(segment.identifier()))) { + for (Segment segment : segments) { + if (segment instanceof AppSegment + && (marker == ALL_APP_MARKERS || marker == segment.marker) + && (identifier == null || identifier.equals(((AppSegment) segment).identifier))) { if (appSegments == Collections.EMPTY_LIST) { appSegments = new ArrayList<>(segments.size()); } - appSegments.add(segment); + appSegments.add((AppSegment) segment); } } return appSegments; } - SOFSegment getSOF() throws IOException { + Frame getSOF() throws IOException { 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) { - - 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(); -// } + for (Segment segment : segments) { + if (segment instanceof Frame) { + return (Frame) segment; } } throw new IIOException("No SOF segment in stream"); } - AdobeDCTSegment getAdobeDCT() throws IOException { - // TODO: Investigate http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6355567: 33/35 byte Adobe APP14 markers - List adobe = getAppSegments(JPEG.APP14, "Adobe"); - - if (!adobe.isEmpty()) { - // version (byte), flags (4bytes), color transform (byte: 0=unknown, 1=YCC, 2=YCCK) - DataInputStream stream = new DataInputStream(adobe.get(0).data()); - - return new AdobeDCTSegment( - stream.readUnsignedByte(), - stream.readUnsignedShort(), - stream.readUnsignedShort(), - stream.readUnsignedByte() - ); - } - - return null; + AdobeDCT getAdobeDCT() throws IOException { + List adobe = getAppSegments(JPEG.APP14, "Adobe"); + return adobe.isEmpty() ? null : (AdobeDCT) adobe.get(0); } - JFIFSegment getJFIF() throws IOException{ - List jfif = getAppSegments(JPEG.APP0, "JFIF"); + JFIF getJFIF() throws IOException{ + List jfif = getAppSegments(JPEG.APP0, "JFIF"); + return jfif.isEmpty() ? null : (JFIF) jfif.get(0); - if (!jfif.isEmpty()) { - JPEGSegment segment = jfif.get(0); - - if (segment.length() >= 9) { - return JFIFSegment.read(segment.data()); - } - else { - processWarningOccurred("Bogus JFIF segment, ignoring"); - } - } - - return null; } - JFXXSegment getJFXX() throws IOException { - List jfxx = getAppSegments(JPEG.APP0, "JFXX"); - - if (!jfxx.isEmpty()) { - JPEGSegment segment = jfxx.get(0); - if (segment.length() >= 1) { - return JFXXSegment.read(segment.data(), segment.length()); - } - else { - processWarningOccurred("Bogus JFXX segment, ignoring"); - } - } - - return null; + JFXX getJFXX() throws IOException { + List jfxx = getAppSegments(JPEG.APP0, "JFXX"); + return jfxx.isEmpty() ? null : (JFXX) jfxx.get(0); } private CompoundDirectory getExif() throws IOException { - List exifSegments = getAppSegments(JPEG.APP1, "Exif"); + List exifSegments = getAppSegments(JPEG.APP1, "Exif"); if (!exifSegments.isEmpty()) { - JPEGSegment exif = exifSegments.get(0); + AppSegment exif = exifSegments.get(0); InputStream data = exif.data(); if (data.read() == -1) { // Read pad @@ -893,11 +849,13 @@ public class JPEGImageReader extends ImageReaderBase { // TODO: Allow metadata to contain the wrongly indexed profiles, if readable // NOTE: We ignore any profile with wrong index for reading and image types, just to be on the safe side - List segments = getAppSegments(JPEG.APP2, "ICC_PROFILE"); + List segments = getAppSegments(JPEG.APP2, "ICC_PROFILE"); + + // TODO: Possibly move this logic to the ICCProfile class... if (segments.size() == 1) { // Faster code for the common case - JPEGSegment segment = segments.get(0); + AppSegment segment = segments.get(0); DataInputStream stream = new DataInputStream(segment.data()); int chunkNumber = stream.readUnsignedByte(); int chunkCount = stream.readUnsignedByte(); @@ -1023,18 +981,18 @@ public class JPEGImageReader extends ImageReaderBase { ThumbnailReadProgressListener thumbnailProgressDelegator = new ThumbnailProgressDelegate(); // Read JFIF thumbnails if present - JFIFSegment jfif = getJFIF(); + JFIF jfif = getJFIF(); if (jfif != null && jfif.thumbnail != null) { thumbnails.add(new JFIFThumbnailReader(thumbnailProgressDelegator, imageIndex, thumbnails.size(), jfif)); } // Read JFXX thumbnails if present - JFXXSegment jfxx = getJFXX(); + JFXX jfxx = getJFXX(); if (jfxx != null && jfxx.thumbnail != null) { switch (jfxx.extensionCode) { - case JFXXSegment.JPEG: - case JFXXSegment.INDEXED: - case JFXXSegment.RGB: + case JFXX.JPEG: + case JFXX.INDEXED: + case JFXX.RGB: thumbnails.add(new JFXXThumbnailReader(thumbnailProgressDelegator, getThumbnailReader(), imageIndex, thumbnails.size(), jfxx)); break; default: @@ -1043,9 +1001,9 @@ public class JPEGImageReader extends ImageReaderBase { } // Read Exif thumbnails if present - List exifSegments = getAppSegments(JPEG.APP1, "Exif"); + List exifSegments = getAppSegments(JPEG.APP1, "Exif"); if (!exifSegments.isEmpty()) { - JPEGSegment exif = exifSegments.get(0); + AppSegment exif = exifSegments.get(0); InputStream data = exif.data(); if (data.read() == -1) { @@ -1131,36 +1089,7 @@ public class JPEGImageReader extends ImageReaderBase { IIOMetadata imageMetadata; 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); - - 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; - } - }; + return new JPEGImage10Metadata(segments); } else { try { diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/QuantizationTable.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/QuantizationTable.java new file mode 100644 index 00000000..2d3a53d5 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/QuantizationTable.java @@ -0,0 +1,151 @@ +/* + * 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; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; + +import javax.imageio.stream.ImageInputStream; +import java.io.DataInput; +import java.io.IOException; + +final class QuantizationTable extends Segment { + + 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 + + QuantizationTable() { + super(JPEG.DQT); + + tq[0] = 0; + tq[1] = 0; + tq[2] = 0; + tq[3] = 0; + } + + // TODO: Get rid of table param, make it a member? + protected void enhanceTables(final int[] table) throws IOException { + for (int t = 0; t < 4; t++) { + if (tq[t] != 0) { + enhanceQuantizationTable(quantTables[t], table); + } + } + } + + 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; + } + } + + public static QuantizationTable read(final DataInput data, final int length) throws IOException { + int count = 0; // TODO: Could probably use data.getPosition for this + + QuantizationTable table = new QuantizationTable(); + 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"); + } + + table.precision[t] = temp >> 4; + + if (table.precision[t] == 0) { + table.precision[t] = 8; + } + else if (table.precision[t] == 1) { + table.precision[t] = 16; + } + else { + throw new IOException("ERROR: Quantization table precision error"); + } + + table.tq[t] = 1; + + if (table.precision[t] == 8) { + for (int i = 0; i < 64; i++) { + if (count > length) { + throw new IOException("ERROR: Quantization table format error"); + } + + table.quantTables[t][i] = data.readUnsignedByte(); + count++; + } + +// table.enhanceQuantizationTable(table.quantTables[t], table); + } + else { + for (int i = 0; i < 64; i++) { + if (count > length) { + throw new IOException("ERROR: Quantization table format error"); + } + + table.quantTables[t][i] = data.readUnsignedShort(); + count += 2; + } + +// table.enhanceQuantizationTable(table.quantTables[t], table); + } + } + + if (count != length) { + throw new IOException("ERROR: Quantization table error [count!=Lq]"); + } + + return table; + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFComponent.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFComponent.java deleted file mode 100644 index 3416aef8..00000000 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFComponent.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2013, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name "TwelveMonkeys" nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.twelvemonkeys.imageio.plugins.jpeg; - -import java.io.Serializable; - -/** -* SOFComponent -* -* @author Harald Kuhr -* @author last modified by $Author: haraldk$ -* @version $Id: SOFComponent.java,v 1.0 22.04.13 16:40 haraldk Exp$ -*/ -final class SOFComponent { - final int id; - final int hSub; - final int vSub; - final int qtSel; - - SOFComponent(int id, int hSub, int vSub, int qtSel) { - this.id = id; - this.hSub = hSub; - this.vSub = vSub; - this.qtSel = qtSel; - } - - @Override - public String toString() { - // Use id either as component number or component name, based on value - Serializable idStr = (id >= 'a' && id <= 'z' || id >= 'A' && id <= 'Z') ? "'" + (char) id + "'" : id; - return String.format("id: %s, sub: %d/%d, sel: %d", idStr, hSub, vSub, qtSel); - } -} 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 deleted file mode 100644 index 31d5314e..00000000 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/SOFSegment.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2013, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name "TwelveMonkeys" nor the - * names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.twelvemonkeys.imageio.plugins.jpeg; - -import java.io.DataInput; -import java.io.IOException; -import java.util.Arrays; - -/** -* SOFSegment -* -* @author Harald Kuhr -* @author last modified by $Author: haraldk$ -* @version $Id: SOFSegment.java,v 1.0 22.04.13 16:40 haraldk Exp$ -*/ -final class SOFSegment { - final int marker; - final int samplePrecision; - final int lines; // height - final int samplesPerLine; // width - final SOFComponent[] components; - - SOFSegment(int marker, int samplePrecision, int lines, int samplesPerLine, SOFComponent[] components) { - this.marker = marker; - this.samplePrecision = samplePrecision; - this.lines = lines; - this.samplesPerLine = samplesPerLine; - this.components = components; - } - - final int componentsInFrame() { - return components.length; - } - - @Override - public String toString() { - return String.format( - "SOF%d[%04x, precision: %d, lines: %d, samples/line: %d, components: %s]", - 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/Scan.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Scan.java new file mode 100644 index 00000000..5e3612e6 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Scan.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2013, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name "TwelveMonkeys" nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.plugins.jpeg; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.imageio.stream.SubImageInputStream; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import java.io.DataInput; +import java.io.IOException; +import java.util.Arrays; + +final class Scan extends Segment { + final int selection; // Start of spectral or predictor selection + final int spectralEnd; // End of spectral selection + final int ah; + final int al; + + final Component[] components; + + Scan(final Component[] components, final int selection, final int spectralEnd, final int ah, final int al) { + super(JPEG.SOS); + + this.components = components; + this.selection = selection; + this.spectralEnd = spectralEnd; + this.ah = ah; + this.al = al; + } + + @Override + public String toString() { + return String.format( + "SOS[selection: %d, spectralEnd: %d, ah: %d, al: %d, components: %s]", + selection, spectralEnd, ah, al, Arrays.toString(components) + ); + } + + public static Scan read(final ImageInputStream data) throws IOException { + int length = data.readUnsignedShort(); + + return read(new SubImageInputStream(data, length), length); + } + + public static Scan read(final DataInput data, final int length) throws IOException { + int numComp = data.readUnsignedByte(); + + int expected = 6 + numComp * 2; + if (expected != length) { + throw new IIOException(String.format("Unexpected SOS length: %d != %d", length, expected)); + } + + Component[] components = new Component[numComp]; + + for (int i = 0; i < numComp; i++) { + int scanCompSel = data.readUnsignedByte(); + final int temp = data.readUnsignedByte(); + + components[i] = new Component(scanCompSel, temp & 0x0F, temp >> 4); + } + + int selection = data.readUnsignedByte(); + int spectralEnd = data.readUnsignedByte(); + int temp = data.readUnsignedByte(); + + return new Scan(components, selection, spectralEnd, temp >> 4, temp & 0x0F); + } + + final static class Component { + final int scanCompSel; // Scan component selector + final int acTabSel; // AC table selector + final int dcTabSel; // DC table selector + + Component(final int scanCompSel, final int acTabSel, final int dcTabSel) { + this.scanCompSel = scanCompSel; + this.acTabSel = acTabSel; + this.dcTabSel = dcTabSel; + } + + @Override + public String toString() { + return String.format("scanCompSel: %d, acTabSel: %d, dcTabSel: %d", scanCompSel, acTabSel, dcTabSel); + } + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Segment.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Segment.java new file mode 100644 index 00000000..bb57b22d --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Segment.java @@ -0,0 +1,72 @@ +package com.twelvemonkeys.imageio.plugins.jpeg; + +import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; +import com.twelvemonkeys.lang.Validate; + +import java.io.DataInput; +import java.io.IOException; + +/** + * Segment. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: Segment.java,v 1.0 22/08/16 harald.kuhr Exp$ + */ +abstract class Segment { + final int marker; + + protected Segment(final int marker) { + this.marker = Validate.isTrue(marker >> 8 == 0xFF, marker, "Unknown JPEG marker: 0x%04x"); + } + + public static Segment read(int marker, String identifier, int length, DataInput data) throws IOException { + // TODO: Fix length inconsistencies... +// System.err.print("marker: " + marker); +// System.err.println(" length: " + length); + switch (marker) { + case JPEG.DHT: + return HuffmanTable.read(data, length); + case JPEG.DQT: + return QuantizationTable.read(data, length - 2); + case JPEG.SOF0: + case JPEG.SOF1: + case JPEG.SOF2: + case JPEG.SOF3: + case JPEG.SOF5: + case JPEG.SOF6: + case JPEG.SOF7: + case JPEG.SOF9: + case JPEG.SOF10: + case JPEG.SOF11: + case JPEG.SOF13: + case JPEG.SOF14: + case JPEG.SOF15: + return Frame.read(marker, data, length); + case JPEG.SOS: + return Scan.read(data, length); + case JPEG.COM: + return Comment.read(data, length); + 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: + return AppSegment.read(marker, identifier, data, length); + // TODO: JPEG.DRI? + default: + return Unknown.read(marker, length, data); + } + } +} diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ThumbnailReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ThumbnailReader.java index 370022c6..be3e7b28 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ThumbnailReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/ThumbnailReader.java @@ -42,7 +42,6 @@ import java.io.IOException; * @author last modified by $Author: haraldk$ * @version $Id: ThumbnailReader.java,v 1.0 18.04.12 12:22 haraldk Exp$ */ -// TODO: Get rid of the com.sun import!! abstract class ThumbnailReader { private final ThumbnailReadProgressListener progressListener; @@ -68,19 +67,9 @@ abstract class ThumbnailReader { } static protected BufferedImage readJPEGThumbnail(final ImageReader reader, final ImageInputStream stream) throws IOException { -// try { -// try { - reader.setInput(stream); + reader.setInput(stream); - return reader.read(0); -// } -// finally { -// input.close(); -// } -// } -// finally { -// reader.dispose(); -// } + return reader.read(0); } static protected BufferedImage readRawThumbnail(final byte[] thumbnail, final int size, final int offset, int w, int h) { diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Unknown.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Unknown.java new file mode 100644 index 00000000..9c59e504 --- /dev/null +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/Unknown.java @@ -0,0 +1,27 @@ +package com.twelvemonkeys.imageio.plugins.jpeg; + +import java.io.DataInput; +import java.io.IOException; + +/** + * Unknown. + * + * @author Harald Kuhr + * @author last modified by $Author: harald.kuhr$ + * @version $Id: Unknown.java,v 1.0 22/08/16 harald.kuhr Exp$ + */ +final class Unknown extends Segment { + final byte[] data; + + private Unknown(final int marker, final byte[] data) { + super(marker); + + this.data = data; + } + + public static Segment read(int marker, int length, DataInput data) throws IOException { + byte[] bytes = new byte[length - 2]; + data.readFully(bytes); + return new Unknown(marker, bytes); + } +} 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 a22a4fac..3df58427 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,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; public class ComponentSpec { + protected int id; 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/FrameHeader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/lossless/FrameHeader.java index 6bf554f8..2443426b 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 @@ -64,7 +64,7 @@ public class FrameHeader { protected int read(final ImageInputStream data) throws IOException { int count = 0; - final int length = data.readUnsignedShort(); + int length = data.readUnsignedShort(); count += 2; precision = data.readUnsignedByte(); @@ -79,31 +79,31 @@ public class FrameHeader { numComp = data.readUnsignedByte(); count++; - //components = new ComponentSpec[numComp]; // some image exceed this range... - components = new ComponentSpec[256]; // setting to 256 -- not sure what it should be. + components = new ComponentSpec[numComp]; - for (int i = 1; i <= numComp; i++) { + for (int i = 0; i < numComp; i++) { if (count > length) { throw new IOException("ERROR: frame format error"); } - final int c = data.readUnsignedByte(); + int cid = data.readUnsignedByte(); count++; if (count >= length) { throw new IOException("ERROR: frame format error [c>=Lf]"); } - final int temp = data.readUnsignedByte(); + int temp = data.readUnsignedByte(); count++; - if (components[c] == null) { - components[c] = new ComponentSpec(); + if (components[i] == null) { + components[i] = new ComponentSpec(); } - components[c].hSamp = temp >> 4; - components[c].vSamp = temp & 0x0F; - components[c].quantTableSel = data.readUnsignedByte(); + components[i].id = cid; + components[i].hSamp = temp >> 4; + components[i].vSamp = temp & 0x0F; + components[i].quantTableSel = data.readUnsignedByte(); count++; } 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 2a93766f..2fe8d16e 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 @@ -38,10 +38,14 @@ import java.io.IOException; public class JPEGLosslessDecoder { private final ImageInputStream input; + + // TODO: Merge these classes with similar classes from the main package + // (FrameHeader == Frame, ComponentSpec == Frame.Component, ScanHeader == Scan etc) 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 @@ -224,9 +228,9 @@ public class JPEGLosslessDecoder { 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; + ComponentSpec component = getComponentSpec(components, scanComps[i].getScanCompSel()); + qTab[i] = quantTables[component.quantTableSel]; + nBlock[i] = component.vSamp * component.hSamp; dcTab[i] = HuffTab[scanComps[i].getDcTabSel()][0]; acTab[i] = HuffTab[scanComps[i].getAcTabSel()][1]; } @@ -310,6 +314,16 @@ public class JPEGLosslessDecoder { return outputRef; } + private ComponentSpec getComponentSpec(ComponentSpec[] components, int sel) { + for (ComponentSpec component : components) { + if (component.id == sel) { + return component; + } + } + + throw new IllegalArgumentException("No such component id: " + sel); + } + private int readScan() throws IOException { return scan.read(input); } 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 13061b51..2a554fdd 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,18 +1,17 @@ package com.twelvemonkeys.imageio.plugins.jpeg.lossless; import javax.imageio.stream.ImageInputStream; -import java.awt.image.*; -import java.io.ByteArrayOutputStream; -import java.io.DataInput; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.DataBufferUShort; +import java.awt.image.Raster; import java.io.IOException; /** - * 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 ) + * This class provides the conversion of input data + * containing a JPEG Lossless to an BufferedImage. *

- * Take care, that only the following lossless formats are supported + * 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]) *

@@ -26,10 +25,9 @@ import java.io.IOException; 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 + * Decodes a JPEG Lossless stream to a {@code BufferedImage}. + * Currently the following conversions are supported: + * - 24Bit, RGB -> BufferedImage.TYPE_3BYTE_BGR * - 8Bit, Grayscale -> BufferedImage.TYPE_BYTE_GRAY * - 16Bit, Grayscale -> BufferedImage.TYPE_USHORT_GRAY * @@ -47,9 +45,9 @@ public class JPEGLosslessDecoderWrapper { if (decoder.getNumComponents() == 1) { switch (decoder.getPrecision()) { case 8: - return read8Bit1ComponentGrayScale(decoded, width, height); + return to8Bit1ComponentGrayScale(decoded, width, height); case 16: - return read16Bit1ComponentGrayScale(decoded, width, height); + return to16Bit1ComponentGrayScale(decoded, width, height); default: throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 1 component cannot be decoded"); } @@ -58,7 +56,7 @@ public class JPEGLosslessDecoderWrapper { if (decoder.getNumComponents() == 3) { switch (decoder.getPrecision()) { case 8: - return read24Bit3ComponentRGB(decoded, width, height); + return to24Bit3ComponentRGB(decoded, width, height); default: throw new IOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and 3 components cannot be decoded"); @@ -66,15 +64,15 @@ public class JPEGLosslessDecoderWrapper { } 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 { + // TODO: Can perhaps be implemented faster return readImage(input).getRaster(); } /** - * converts the decoded buffer into a BufferedImage + * Converts the decoded buffer into a BufferedImage. * precision: 16 bit, componentCount = 1 * * @param decoded data buffer @@ -82,18 +80,19 @@ public class JPEGLosslessDecoderWrapper { * @param height of the image * @return a BufferedImage.TYPE_USHORT_GRAY */ - private BufferedImage read16Bit1ComponentGrayScale(int[][] decoded, int width, int height) { + private BufferedImage to16Bit1ComponentGrayScale(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 + * Converts the decoded buffer into a BufferedImage. * precision: 8 bit, componentCount = 1 * * @param decoded data buffer @@ -101,34 +100,37 @@ public class JPEGLosslessDecoderWrapper { * @param height of the image * @return a BufferedImage.TYPE_BYTE_GRAY */ - private BufferedImage read8Bit1ComponentGrayScale(int[][] decoded, int width, int height) { + private BufferedImage to8Bit1ComponentGrayScale(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 + * Converts the decoded buffer into a BufferedImage. + * precision: 8 bit, componentCount = 3 * * @param decoded data buffer * @param width of the image * @param height of the image - * @return a BufferedImage.TYPE_INT_RGB + * @return a BufferedImage.TYPE_3BYTE_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(); + private BufferedImage to24Bit3ComponentRGB(int[][] decoded, int width, int height) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); + byte[] imageBuffer = ((DataBufferByte) 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]); + for (int i = 0; i < imageBuffer.length / 3; i++) { + // Convert to RGB (BGR) + imageBuffer[i * 3 + 2] = (byte) decoded[0][i]; + imageBuffer[i * 3 + 1] = (byte) decoded[1][i]; + imageBuffer[i * 3] = (byte) decoded[2][i]; } + return image; } diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReaderTest.java index 77b69653..4adf6fea 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReaderTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JFIFThumbnailReaderTest.java @@ -36,6 +36,7 @@ import org.mockito.InOrder; import javax.imageio.stream.ImageInputStream; import java.awt.image.BufferedImage; +import java.io.DataInputStream; import java.io.IOException; import java.util.List; @@ -60,7 +61,8 @@ public class JFIFThumbnailReaderTest extends AbstractThumbnailReaderTest { assertNotNull(segments); assertFalse(segments.isEmpty()); - return new JFIFThumbnailReader(progressListener, imageIndex, thumbnailIndex, JFIFSegment.read(segments.get(0).data())); + JPEGSegment segment = segments.get(0); + return new JFIFThumbnailReader(progressListener, imageIndex, thumbnailIndex, JFIF.read(new DataInputStream(segment.segmentData()), segment.segmentLength())); } @Test diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReaderTest.java index 0b8a6402..9b88bb9b 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReaderTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JFXXThumbnailReaderTest.java @@ -37,6 +37,7 @@ import org.mockito.InOrder; import javax.imageio.ImageIO; import javax.imageio.stream.ImageInputStream; import java.awt.image.BufferedImage; +import java.io.DataInputStream; import java.io.IOException; import java.util.List; @@ -64,7 +65,7 @@ public class JFXXThumbnailReaderTest extends AbstractThumbnailReaderTest { assertFalse(segments.isEmpty()); JPEGSegment jfxx = segments.get(0); - return new JFXXThumbnailReader(progressListener, ImageIO.getImageReadersByFormatName("jpeg").next(), imageIndex, thumbnailIndex, JFXXSegment.read(jfxx.data(), jfxx.length())); + return new JFXXThumbnailReader(progressListener, ImageIO.getImageReadersByFormatName("jpeg").next(), imageIndex, thumbnailIndex, JFXX.read(new DataInputStream(jfxx.data()), jfxx.length())); } @Test 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 2156710b..4db02c31 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 @@ -95,7 +95,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest(); + segments = new ArrayList<>(); } segments.add(segment);