#182 Massive refactorings to clean up metadata and segment handling

This commit is contained in:
Harald Kuhr 2016-08-23 21:21:58 +02:00
parent 15ce9d6b64
commit 673f3e5b53
31 changed files with 1315 additions and 449 deletions

View File

@ -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()
);
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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));
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}
}

View File

@ -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;
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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
);
}
}

View File

@ -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<BufferedImage> 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));

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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<Segment> segments;
JPEGImage10Metadata(List<Segment> 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;
}
}

View File

@ -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<JPEGSegment> appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null);
List<AppSegment> 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,

View File

@ -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<JPEGSegment> segments;
private List<Segment> 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<JPEGSegment> jpegSegments = readSegments();
List<Segment> 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<JPEGSegment> 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<JPEGSegment> getAppSegments(final int marker, final String identifier) throws IOException {
List<AppSegment> getAppSegments(final int marker, final String identifier) throws IOException {
initHeader();
List<JPEGSegment> appSegments = Collections.emptyList();
List<AppSegment> 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<JPEGSegment> 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<AppSegment> adobe = getAppSegments(JPEG.APP14, "Adobe");
return adobe.isEmpty() ? null : (AdobeDCT) adobe.get(0);
}
JFIFSegment getJFIF() throws IOException{
List<JPEGSegment> jfif = getAppSegments(JPEG.APP0, "JFIF");
JFIF getJFIF() throws IOException{
List<AppSegment> 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<JPEGSegment> 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<AppSegment> jfxx = getAppSegments(JPEG.APP0, "JFXX");
return jfxx.isEmpty() ? null : (JFXX) jfxx.get(0);
}
private CompoundDirectory getExif() throws IOException {
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
List<AppSegment> 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<JPEGSegment> segments = getAppSegments(JPEG.APP2, "ICC_PROFILE");
List<AppSegment> 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<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
List<AppSegment> 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 {

View File

@ -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;
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}

View File

@ -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);
}
}
}

View File

@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}
}

View File

@ -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) {

View File

@ -0,0 +1,27 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import java.io.DataInput;
import java.io.IOException;
/**
* Unknown.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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);
}
}

View File

@ -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

View File

@ -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++;
}

View File

@ -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);
}

View File

@ -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.
* <p>
* 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])
* <p>
@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -95,7 +95,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest<JPEGImageReader
new TestData(getClassLoaderResource("/jpeg/0x00-to-0xFF-between-segments.jpg"), new Dimension(16, 16)),
new TestData(getClassLoaderResource("/jpeg/jfif-bogus-empty-jfif-segment.jpg"), new Dimension(942, 714)),
new TestData(getClassLoaderResource("/jpeg/jfif-16bit-dqt.jpg"), new Dimension(204, 131)),
new TestData(getClassLoaderResource("/jpeg-lossless/testimgl.jpg"), new Dimension(227, 149))
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_rgb.jpg"), new Dimension(227, 149)),
new TestData(getClassLoaderResource("/jpeg-lossless/testimg_gray.jpg"), new Dimension(512, 512))
);
// More test data in specific tests below

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -53,11 +53,15 @@ public final class JPEGSegment implements Serializable {
this.length = length;
}
int segmentLength() {
public int segmentLength() {
// This is the length field as read from the stream
return length;
}
public InputStream segmentData() {
return data != null ? new ByteArrayInputStream(data) : null;
}
public int marker() {
return marker;
}

View File

@ -106,7 +106,7 @@ public final class JPEGSegmentUtil {
if (isRequested(segment, segmentIdentifiers)) {
if (segments == Collections.EMPTY_LIST) {
segments = new ArrayList<JPEGSegment>();
segments = new ArrayList<>();
}
segments.add(segment);