mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
#182 Massive refactorings to clean up metadata and segment handling
This commit is contained in:
parent
15ce9d6b64
commit
673f3e5b53
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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()
|
||||
);
|
||||
AdobeDCT getAdobeDCT() throws IOException {
|
||||
List<AppSegment> adobe = getAppSegments(JPEG.APP14, "Adobe");
|
||||
return adobe.isEmpty() ? null : (AdobeDCT) adobe.get(0);
|
||||
}
|
||||
|
||||
return null;
|
||||
JFIF getJFIF() throws IOException{
|
||||
List<AppSegment> jfif = getAppSegments(JPEG.APP0, "JFIF");
|
||||
return jfif.isEmpty() ? null : (JFIF) jfif.get(0);
|
||||
|
||||
}
|
||||
|
||||
JFIFSegment getJFIF() throws IOException{
|
||||
List<JPEGSegment> jfif = getAppSegments(JPEG.APP0, "JFIF");
|
||||
|
||||
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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
return reader.read(0);
|
||||
// }
|
||||
// finally {
|
||||
// input.close();
|
||||
// }
|
||||
// }
|
||||
// finally {
|
||||
// reader.dispose();
|
||||
// }
|
||||
}
|
||||
|
||||
static protected BufferedImage readRawThumbnail(final byte[] thumbnail, final int size, final int offset, int w, int h) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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++;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 |
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user