JPEG Exif rotation in metadata + support

This commit is contained in:
Harald Kuhr
2020-07-10 22:05:46 +02:00
parent 7e55d7765d
commit 5cc201b46d
31 changed files with 1091 additions and 285 deletions

View File

@@ -35,27 +35,27 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException;
import javax.imageio.plugins.jpeg.JPEGHuffmanTable;
import java.io.DataInput;
import java.io.IOException;
final class HuffmanTable extends Segment {
private final int l[][][] = new int[4][2][16];
private final int th[] = new int[4]; // 1: this table is present
final int v[][][][] = new int[4][2][16][200]; // tables
final int[][] tc = new int[4][2]; // 1: this table is present
private final short[][][] l = new short[4][2][16];
private final short[][][][] v = new short[4][2][16][200]; // tables
private final boolean[][] tc = new boolean[4][2]; // 1: this table is present
static final int MSB = 0x80000000;
private static final int MSB = 0x80000000;
private HuffmanTable() {
super(JPEG.DHT);
}
void buildHuffTables(final int[][][] HuffTab) throws IOException {
void buildHuffTables(final int[][][] huffTab) throws IOException {
for (int t = 0; t < 4; t++) {
for (int c = 0; c < 2; c++) {
if (tc[t][c] != 0) {
buildHuffTable(HuffTab[t][c], l[t][c], v[t][c]);
if (tc[t][c]) {
buildHuffTable(huffTab[t][c], l[t][c], v[t][c]);
}
}
}
@@ -68,7 +68,7 @@ final class HuffmanTable extends Segment {
// 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 {
private void buildHuffTable(final int[] tab, final short[] L, final short[][] V) throws IOException {
int temp = 256;
int k = 0;
@@ -112,7 +112,7 @@ final class HuffmanTable extends Segment {
for (int t = 0; t < tc.length; t++) {
for (int c = 0; c < tc[t].length; c++) {
if (tc[t][c] != 0) {
if (tc[t][c]) {
if (builder.length() > 4) {
builder.append(", ");
}
@@ -149,11 +149,10 @@ final class HuffmanTable extends Segment {
throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
}
table.th[t] = 1;
table.tc[t][c] = 1;
table.tc[t][c] = true;
for (int i = 0; i < 16; i++) {
table.l[t][c][i] = data.readUnsignedByte();
table.l[t][c][i] = (short) data.readUnsignedByte();
count++;
}
@@ -162,7 +161,7 @@ final class HuffmanTable extends Segment {
if (count > length) {
throw new IIOException("JPEG Huffman Table format error");
}
table.v[t][c][i][j] = data.readUnsignedByte();
table.v[t][c][i][j] = (short) data.readUnsignedByte();
count++;
}
}
@@ -174,4 +173,41 @@ final class HuffmanTable extends Segment {
return table;
}
public boolean isPresent(int tableId, int tableClass) {
return tc[tableId][tableClass];
}
private short[] lengths(int tableId, int tableClass) {
// TODO: Consider stripping the 0s?
return l[tableId][tableClass];
}
private short[] tables(int tableId, int tableClass) {
// Find sum of lengths
short[] lengths = lengths(tableId, tableClass);
int sumOfLengths = 0;
for (int length : lengths) {
sumOfLengths += length;
}
// Flatten the tables
short[] tables = new short[sumOfLengths];
int pos = 0;
for (int i = 0; i < 16; i++) {
short[] table = v[tableId][tableClass][i];
short length = lengths[i];
System.arraycopy(table, 0, tables, pos, length);
pos += length;
}
return tables;
}
JPEGHuffmanTable toNativeTable(int tableId, int tableClass) {
return new JPEGHuffmanTable(lengths(tableId, tableClass), tables(tableId, tableClass));
}
}

View File

@@ -113,7 +113,7 @@ final class JFXXThumbnailReader extends ThumbnailReader {
}
}
cachedThumbnail = pixelsExposed ? null : new SoftReference<BufferedImage>(thumbnail);
cachedThumbnail = pixelsExposed ? null : new SoftReference<>(thumbnail);
return thumbnail;
}

View File

@@ -30,10 +30,16 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import org.w3c.dom.Node;
import javax.imageio.IIOException;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.color.ICC_Profile;
import java.util.List;
/**
@@ -45,14 +51,30 @@ import java.util.List;
*/
class JPEGImage10Metadata extends AbstractMetadata {
// TODO: Clean up. Consider just making the meta data classes we were trying to avoid in the first place....
// TODO: Create our own native format, which is simply markerSequence from the Sun format, with the segments as-is, in sequence...
// + add special case for app segments, containing appXX + identifier (ie. <app0JFIF /> to <app0 identifier="JFIF" /> or <app app="0" identifier="JFIF" />
private final List<Segment> segments;
JPEGImage10Metadata(List<Segment> segments) {
private final Frame frame;
private final JFIF jfif;
private final AdobeDCT adobeDCT;
private final JFXX jfxx;
private final ICC_Profile embeddedICCProfile;
private final CompoundDirectory exif;
// TODO: Consider moving all the metadata stuff from the reader, over here...
JPEGImage10Metadata(final List<Segment> segments, Frame frame, JFIF jfif, JFXX jfxx, ICC_Profile embeddedICCProfile, AdobeDCT adobeDCT, final CompoundDirectory exif) {
super(true, JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, null, null, null);
this.segments = segments;
this.frame = frame;
this.jfif = jfif;
this.adobeDCT = adobeDCT;
this.jfxx = jfxx;
this.embeddedICCProfile = embeddedICCProfile;
this.exif = exif;
}
@Override
@@ -60,16 +82,53 @@ class JPEGImage10Metadata extends AbstractMetadata {
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
boolean isJFIF = jfif != null;
if (isJFIF) {
IIOMetadataNode app0JFIF = new IIOMetadataNode("app0JFIF");
app0JFIF.setAttribute("majorVersion", Integer.toString(jfif.majorVersion));
app0JFIF.setAttribute("minorVersion", Integer.toString(jfif.minorVersion));
app0JFIF.setAttribute("resUnits", Integer.toString(jfif.units));
app0JFIF.setAttribute("Xdensity", Integer.toString(jfif.xDensity));
app0JFIF.setAttribute("Ydensity", Integer.toString(jfif.yDensity));
app0JFIF.setAttribute("thumbWidth", Integer.toString(jfif.xThumbnail));
app0JFIF.setAttribute("thumbHeight", Integer.toString(jfif.yThumbnail));
jpegVariety.appendChild(app0JFIF);
// Due to format oddity, add JFXX and app2ICC as subnodes here...
// ...and ignore them below, if added...
apendJFXX(app0JFIF);
appendICCProfile(app0JFIF);
}
root.appendChild(jpegVariety);
appendMarkerSequence(root, segments, isJFIF);
return root;
}
private void appendMarkerSequence(IIOMetadataNode root, List<Segment> segments, boolean isJFIF) {
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.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:
Frame sofSegment = (Frame) segment;
IIOMetadataNode sof = new IIOMetadataNode("sof");
@@ -96,13 +155,13 @@ class JPEGImage10Metadata extends AbstractMetadata {
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) {
for (int c = 0; c < 2; c++) {
if (huffmanTable.isPresent(i, c)) {
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
dhtable.setAttribute("class", String.valueOf(j));
dhtable.setAttribute("class", String.valueOf(c));
dhtable.setAttribute("htableId", String.valueOf(i));
dhtable.setUserObject(huffmanTable.toNativeTable(i, c));
dht.appendChild(dhtable);
}
}
@@ -112,8 +171,28 @@ class JPEGImage10Metadata extends AbstractMetadata {
break;
case JPEG.DQT:
markerSequence.appendChild(new IIOMetadataNode("dqt"));
// TODO:
QuantizationTable quantizationTable = (QuantizationTable) segment;
IIOMetadataNode dqt = new IIOMetadataNode("dqt");
for (int i = 0; i < 4; i++) {
if (quantizationTable.isPresent(i)) {
IIOMetadataNode dqtable = new IIOMetadataNode("dqtable");
dqtable.setAttribute("elementPrecision", quantizationTable.precision(i) != 16 ? "0" : "1"); // 0 = 8 bits, 1 = 16 bits
dqtable.setAttribute("qtableId", Integer.toString(i));
dqtable.setUserObject(quantizationTable.toNativeTable(i));
dqt.appendChild(dqtable);
}
}
markerSequence.appendChild(dqt);
break;
case JPEG.DRI:
RestartInterval restartInterval = (RestartInterval) segment;
IIOMetadataNode dri = new IIOMetadataNode("dri");
dri.setAttribute("interval", Integer.toString(restartInterval.interval));
markerSequence.appendChild(dri);
break;
case JPEG.SOS:
@@ -144,6 +223,25 @@ class JPEGImage10Metadata extends AbstractMetadata {
break;
case JPEG.APP0:
if (segment instanceof JFIF) {
// Either already added, or we'll ignore it anyway...
break;
}
else if (isJFIF && segment instanceof JFXX) {
// Already added
break;
}
// Else, fall through to unknown segment
case JPEG.APP2:
if (isJFIF && segment instanceof ICCProfile) {
// Already added
break;
}
// Else, fall through to unknown segment
case JPEG.APP14:
if (segment instanceof AdobeDCT) {
AdobeDCT adobe = (AdobeDCT) segment;
@@ -165,32 +263,149 @@ class JPEGImage10Metadata extends AbstractMetadata {
break;
}
}
return root;
private void appendICCProfile(IIOMetadataNode app0JFIF) {
if (embeddedICCProfile != null) {
IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
app2ICC.setUserObject(embeddedICCProfile);
app0JFIF.appendChild(app2ICC);
}
}
private void apendJFXX(IIOMetadataNode app0JFIF) {
if (jfxx != null) {
IIOMetadataNode jfxxNode = new IIOMetadataNode("JFXX");
app0JFIF.appendChild(jfxxNode);
IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
app0JFXX.setAttribute("extensionCode", Integer.toString(jfxx.extensionCode));
jfxxNode.appendChild(app0JFXX);
switch (jfxx.extensionCode) {
case JFXX.JPEG:
IIOMetadataNode thumbJPEG = new IIOMetadataNode("JFIFthumbJPEG");
thumbJPEG.appendChild(new IIOMetadataNode("markerSequence"));
// TODO: Insert segments in marker sequence...
// List<JPEGSegment> segments = JPEGSegmentUtil.readSegments(new ByteArrayImageInputStream(jfxx.thumbnail), JPEGSegmentUtil.ALL_SEGMENTS);
// Convert to Segment as in JPEGImageReader...
// appendMarkerSequence(thumbJPEG, segments, false);
app0JFXX.appendChild(thumbJPEG);
break;
case JFXX.INDEXED:
IIOMetadataNode thumbPalette = new IIOMetadataNode("JFIFthumbPalette");
thumbPalette.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF));
thumbPalette.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF));
app0JFXX.appendChild(thumbPalette);
break;
case JFXX.RGB:
IIOMetadataNode thumbRGB = new IIOMetadataNode("JFIFthumbRGB");
thumbRGB.setAttribute("thumbWidth", Integer.toString(jfxx.thumbnail[0] & 0xFF));
thumbRGB.setAttribute("thumbHeight", Integer.toString(jfxx.thumbnail[1] & 0xFF));
app0JFXX.appendChild(thumbRGB);
break;
}
}
}
@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 colorSpaceType = new IIOMetadataNode("ColorSpaceType");
colorSpaceType.setAttribute("name", getColorSpaceType());
chroma.appendChild(colorSpaceType);
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", String.valueOf(sofSegment.componentsInFrame()));
chroma.appendChild(numChannels);
break;
}
}
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", String.valueOf(frame.componentsInFrame()));
chroma.appendChild(numChannels);
return chroma;
}
private String getColorSpaceType() {
try {
JPEGColorSpace csType = JPEGImageReader.getSourceCSType(jfif, adobeDCT, frame);
switch (csType) {
case Gray:
case GrayA:
return "GRAY";
case YCbCr:
case YCbCrA:
return "YCbCr";
case RGB:
case RGBA:
return "RGB";
case PhotoYCC:
case PhotoYCCA:
return "PhotoYCC";
case YCCK:
return "YCCK";
case CMYK:
return "CMYK";
default:
}
}
catch (IIOException ignore) {
}
return Integer.toString(frame.componentsInFrame(), 16) + "CLR";
}
private boolean hasAlpha() {
try {
JPEGColorSpace csType = JPEGImageReader.getSourceCSType(jfif, adobeDCT, frame);
switch (csType) {
case GrayA:
case YCbCrA:
case RGBA:
case PhotoYCCA:
return true;
default:
}
}
catch (IIOException ignore) {
}
return false;
}
private boolean isLossess() {
switch (frame.marker) {
case JPEG.SOF3:
case JPEG.SOF7:
case JPEG.SOF11:
case JPEG.SOF15:
return true;
default:
return false;
}
}
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
if (hasAlpha()) {
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", "nonpremultipled");
transparency.appendChild(alpha);
return transparency;
}
return null;
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compression = new IIOMetadataNode("Compression");
@@ -200,7 +415,7 @@ class JPEGImage10Metadata extends AbstractMetadata {
compression.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE"); // TODO: For lossless only
lossless.setAttribute("value", isLossess() ? "TRUE" : "FALSE");
compression.appendChild(lossless);
IIOMetadataNode numProgressiveScans = new IIOMetadataNode("NumProgressiveScans");
@@ -215,12 +430,67 @@ class JPEGImage10Metadata extends AbstractMetadata {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "normal"); // TODO
imageOrientation.setAttribute("value", getExifOrientation(exif));
dimension.appendChild(imageOrientation);
if (jfif != null) {
// Aspect ratio
float xDensity = Math.max(1, jfif.xDensity);
float yDensity = Math.max(1, jfif.yDensity);
float aspectRatio = jfif.units == 0 ? xDensity / yDensity : yDensity / xDensity;
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatio.setAttribute("value", Float.toString(aspectRatio));
dimension.insertBefore(pixelAspectRatio, imageOrientation); // Keep order
if (jfif.units != 0) {
// Pixel size
float scale = jfif.units == 1 ? 25.4F : 10.0F; // DPI or DPcm
IIOMetadataNode horizontalPixelSize = new IIOMetadataNode("HorizontalPixelSize");
horizontalPixelSize.setAttribute("value", Float.toString(scale / xDensity));
dimension.appendChild(horizontalPixelSize);
IIOMetadataNode verticalPixelSize = new IIOMetadataNode("VerticalPixelSize");
verticalPixelSize.setAttribute("value", Float.toString(scale / yDensity));
dimension.appendChild(verticalPixelSize);
}
}
return dimension;
}
private String getExifOrientation(Directory exif) {
if (exif != null) {
Entry orientationEntry = exif.getEntryById(TIFF.TAG_ORIENTATION);
if (orientationEntry != null) {
switch (((Number) orientationEntry.getValue()).intValue()) {
case 2:
return "FlipH";
case 3:
return "Rotate180";
case 4:
return "FlipV";
case 5:
return "FlipVRotate90";
case 6:
return "Rotate270";
case 7:
return "FlipHRotate90";
case 8:
return "Rotate90";
case 0:
case 1:
default:
// Fall-through
}
}
}
return "Normal";
}
@Override
protected IIOMetadataNode getStandardTextNode() {
IIOMetadataNode text = new IIOMetadataNode("Text");
@@ -235,6 +505,10 @@ class JPEGImage10Metadata extends AbstractMetadata {
}
}
// TODO: Add the following from Exif (as in TIFFMetadata)
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
return text.hasChildNodes() ? text : null;
}
}

View File

@@ -113,9 +113,6 @@ public final class JPEGImageReader extends ImageReaderBase {
/** Internal constant for referring all APP segments */
static final int ALL_APP_MARKERS = -1;
/** Segment identifiers for the JPEG segments we care about reading. */
private static final Map<Integer, List<String>> SEGMENT_IDENTIFIERS = JPEGSegmentUtil.ALL_SEGMENTS;
/** Our JPEG reading delegate */
private final ImageReader delegate;
@@ -534,7 +531,7 @@ public final class JPEGImageReader extends ImageReaderBase {
return image;
}
private JPEGColorSpace getSourceCSType(final JFIF jfif, final AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException {
static JPEGColorSpace getSourceCSType(final JFIF jfif, final AdobeDCT adobeDCT, final Frame startOfFrame) throws IIOException {
// Adapted from libjpeg jdapimin.c:
// Guess the input colorspace
// (Wish JPEG committee had provided a real way to specify this...)
@@ -717,7 +714,7 @@ public final class JPEGImageReader extends ImageReaderBase {
private void initHeader(final int imageIndex) throws IOException {
if (imageIndex < 0) {
throw new IllegalArgumentException("imageIndex < 0: " + imageIndex);
throw new IndexOutOfBoundsException("imageIndex < 0: " + imageIndex);
}
if (imageIndex == currentStreamIndex) {
@@ -837,7 +834,7 @@ public final class JPEGImageReader extends ImageReaderBase {
try {
imageInput.seek(streamOffsets.get(currentStreamIndex));
return JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS);
return JPEGSegmentUtil.readSegments(imageInput, JPEGSegmentUtil.ALL_SEGMENTS);
}
catch (IIOException | IllegalArgumentException ignore) {
if (DEBUG) {
@@ -1218,38 +1215,9 @@ public final class JPEGImageReader extends ImageReaderBase {
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
// checkBounds needed, as we catch the IndexOutOfBoundsException below.
checkBounds(imageIndex);
initHeader(imageIndex);
IIOMetadata imageMetadata;
if (isLossless()) {
return new JPEGImage10Metadata(segments);
}
else {
try {
imageMetadata = delegate.getImageMetadata(0);
}
catch (IndexOutOfBoundsException knownIssue) {
// TMI-101: com.sun.imageio.plugins.jpeg.JPEGBuffer doesn't do proper sanity check of input data.
throw new IIOException("Corrupt JPEG data: Bad segment length", knownIssue);
}
catch (NegativeArraySizeException knownIssue) {
// Most likely from com.sun.imageio.plugins.jpeg.SOSMarkerSegment
throw new IIOException("Corrupt JPEG data: Bad component count", knownIssue);
}
if (imageMetadata != null && Arrays.asList(imageMetadata.getMetadataFormatNames()).contains(JPEGImage10MetadataCleaner.JAVAX_IMAGEIO_JPEG_IMAGE_1_0)) {
if (metadataCleaner == null) {
metadataCleaner = new JPEGImage10MetadataCleaner(this);
}
return metadataCleaner.cleanMetadata(imageMetadata);
}
}
return imageMetadata;
return new JPEGImage10Metadata(segments, getSOF(), getJFIF(), getJFXX(), getEmbeddedICCProfile(true), getAdobeDCT(), getExif());
}
@Override

View File

@@ -39,6 +39,7 @@ import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
final class JPEGLosslessDecoder {
@@ -51,12 +52,12 @@ final class JPEGLosslessDecoder {
private final QuantizationTable quantTable;
private Scan scan;
private final int HuffTab[][][] = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
private final int IDCT_Source[] = new int[64];
private final int nBlock[] = new int[10]; // number of blocks in the i-th Comp in a scan
private final int[] acTab[] = new int[10][]; // ac HuffTab for the i-th Comp in a scan
private final int[] dcTab[] = new int[10][]; // dc HuffTab for the i-th Comp in a scan
private final int[] qTab[] = new int[10][]; // quantization table for the i-th Comp in a scan
private final int[][][] HuffTab = new int[4][2][MAX_HUFFMAN_SUBTREE * 256];
private final int[] IDCT_Source = new int[64];
private final int[] nBlock = new int[10]; // number of blocks in the i-th Comp in a scan
private final int[][] acTab = new int[10][]; // ac HuffTab for the i-th Comp in a scan
private final int[][] dcTab = new int[10][]; // dc HuffTab for the i-th Comp in a scan
private final int[][] qTab = new int[10][]; // quantization table for the i-th Comp in a scan
private boolean restarting;
private int marker;
@@ -70,7 +71,7 @@ final class JPEGLosslessDecoder {
private int mask;
private int[][] outputData;
private static final int IDCT_P[] = {
private static final int[] IDCT_P = {
0, 5, 40, 16, 45, 2, 7, 42,
21, 56, 8, 61, 18, 47, 1, 4,
41, 23, 58, 13, 32, 24, 37, 10,
@@ -80,16 +81,6 @@ final class JPEGLosslessDecoder {
50, 55, 25, 36, 11, 62, 14, 35,
28, 49, 52, 27, 38, 30, 51, 54
};
private static final int TABLE[] = {
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
};
private static final int RESTART_MARKER_BEGIN = 0xFFD0;
private static final int RESTART_MARKER_END = 0xFFD7;
@@ -158,7 +149,7 @@ final class JPEGLosslessDecoder {
huffTable.buildHuffTables(HuffTab);
}
quantTable.enhanceTables(TABLE);
quantTable.enhanceTables();
current = input.readUnsignedShort();
@@ -185,11 +176,10 @@ final class JPEGLosslessDecoder {
selection = scan.spectralSelStart;
final Scan.Component[] scanComps = scan.components;
final int[][] quantTables = quantTable.quantTables;
for (int i = 0; i < numComp; i++) {
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
qTab[i] = quantTables[component.qtSel];
qTab[i] = quantTable.qTable(component.qtSel);
nBlock[i] = component.vSub * component.hSub;
int dcTabSel = scanComps[i].dcTabSel;
@@ -220,18 +210,18 @@ final class JPEGLosslessDecoder {
outputData[componentIndex] = new int[xDim * yDim];
}
final int firstValue[] = new int[numComp];
final int[] firstValue = new int[numComp];
for (int i = 0; i < numComp; i++) {
firstValue[i] = (1 << (precision - 1));
}
final int pred[] = new int[numComp];
final int[] pred = new int[numComp];
scanNum++;
while (true) { // Decode one scan
int temp[] = new int[1]; // to store remainder bits
int index[] = new int[1];
int[] temp = new int[1]; // to store remainder bits
int[] index = new int[1];
System.arraycopy(firstValue, 0, pred, 0, numComp);
@@ -288,7 +278,7 @@ final class JPEGLosslessDecoder {
private boolean useACForDC(final int dcTabSel) {
if (isLossless()) {
for (HuffmanTable huffTable : huffTables) {
if (huffTable.tc[dcTabSel][0] == 0 && huffTable.tc[dcTabSel][1] != 0) {
if (!huffTable.isPresent(dcTabSel, 0) && huffTable.isPresent(dcTabSel, 1)) {
return true;
}
}
@@ -324,7 +314,7 @@ final class JPEGLosslessDecoder {
return Scan.read(input, length);
}
private int decode(final int prev[], final int temp[], final int index[]) throws IOException {
private int decode(final int[] prev, final int[] temp, final int[] index) throws IOException {
if (numComp == 1) {
return decodeSingle(prev, temp, index);
}
@@ -336,7 +326,7 @@ final class JPEGLosslessDecoder {
}
}
private int decodeSingle(final int prev[], final int temp[], final int index[]) throws IOException {
private int decodeSingle(final int[] prev, final int[] temp, final int[] index) throws IOException {
// At the beginning of the first line and
// at the beginning of each restart interval the prediction value of 2P 1 is used, where P is the input precision.
if (restarting) {
@@ -390,7 +380,7 @@ final class JPEGLosslessDecoder {
return 0;
}
private int decodeRGB(final int prev[], final int temp[], final int index[]) throws IOException {
private int decodeRGB(final int[] prev, final int[] temp, final int[] index) throws IOException {
final int[] outputRedData = outputData[0];
final int[] outputGreenData = outputData[1];
final int[] outputBlueData = outputData[2];
@@ -435,7 +425,7 @@ final class JPEGLosslessDecoder {
return decode0(prev, temp, index);
}
private int decodeAny(final int prev[], final int temp[], final int index[]) throws IOException {
private int decodeAny(final int[] prev, final int[] temp, final int[] index) throws IOException {
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {
final int[] outputData = this.outputData[componentIndex];
final int previous;
@@ -469,17 +459,17 @@ final class JPEGLosslessDecoder {
}
private int decode0(int[] prev, int[] temp, int[] index) throws IOException {
int value, actab[], dctab[];
int qtab[];
int value;
int[] actab;
int[] dctab;
int[] qtab;
for (int ctrC = 0; ctrC < numComp; ctrC++) {
qtab = qTab[ctrC];
actab = acTab[ctrC];
dctab = dcTab[ctrC];
for (int i = 0; i < nBlock[ctrC]; i++) {
for (int k = 0; k < IDCT_Source.length; k++) {
IDCT_Source[k] = 0;
}
Arrays.fill(IDCT_Source, 0);
value = getHuffmanValue(dctab, temp, index);
@@ -545,7 +535,7 @@ final class JPEGLosslessDecoder {
// and marker_index=9
// If marker_index=9 then index is always > 8, or HuffmanValue()
// will not be called
private int getHuffmanValue(final int table[], final int temp[], final int index[]) throws IOException {
private int getHuffmanValue(final int[] table, final int[] temp, final int[] index) throws IOException {
int code, input;
final int mask = 0xFFFF;
@@ -603,7 +593,7 @@ final class JPEGLosslessDecoder {
return code & 0xFF;
}
private int getn(final int[] pred, final int n, final int temp[], final int index[]) throws IOException {
private int getn(final int[] pred, final int n, final int[] temp, final int[] index) throws IOException {
int result;
final int one = 1;
final int n_one = -1;
@@ -688,7 +678,7 @@ final class JPEGLosslessDecoder {
return result;
}
private int getPreviousX(final int data[]) {
private int getPreviousX(final int[] data) {
if (xLoc > 0) {
return data[((yLoc * xDim) + xLoc) - 1];
}
@@ -700,7 +690,7 @@ final class JPEGLosslessDecoder {
}
}
private int getPreviousXY(final int data[]) {
private int getPreviousXY(final int[] data) {
if ((xLoc > 0) && (yLoc > 0)) {
return data[(((yLoc - 1) * xDim) + xLoc) - 1];
}
@@ -709,7 +699,7 @@ final class JPEGLosslessDecoder {
}
}
private int getPreviousY(final int data[]) {
private int getPreviousY(final int[] data) {
if (yLoc > 0) {
return data[((yLoc - 1) * xDim) + xLoc];
}
@@ -722,7 +712,7 @@ final class JPEGLosslessDecoder {
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
}
private void output(final int pred[]) {
private void output(final int[] pred) {
if (numComp == 1) {
outputSingle(pred);
}
@@ -734,7 +724,7 @@ final class JPEGLosslessDecoder {
}
}
private void outputSingle(final int pred[]) {
private void outputSingle(final int[] pred) {
if ((xLoc < xDim) && (yLoc < yDim)) {
outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0];
xLoc++;
@@ -746,7 +736,7 @@ final class JPEGLosslessDecoder {
}
}
private void outputRGB(final int pred[]) {
private void outputRGB(final int[] pred) {
if ((xLoc < xDim) && (yLoc < yDim)) {
final int index = (yLoc * xDim) + xLoc;
outputData[0][index] = pred[0];
@@ -761,7 +751,7 @@ final class JPEGLosslessDecoder {
}
}
private void outputAny(final int pred[]) {
private void outputAny(final int[] pred) {
if ((xLoc < xDim) && (yLoc < yDim)) {
final int index = (yLoc * xDim) + xLoc;
for (int componentIndex = 0; componentIndex < outputData.length; ++componentIndex) {

View File

@@ -35,35 +35,42 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import javax.imageio.IIOException;
import javax.imageio.plugins.jpeg.JPEGQTable;
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
private static final int[] ZIGZAG = {
0, 1, 5, 6, 14, 15, 27, 28,
2, 4, 7, 13, 16, 26, 29, 42,
3, 8, 12, 17, 25, 30, 41, 43,
9, 11, 18, 24, 31, 40, 44, 53,
10, 19, 23, 32, 39, 45, 52, 54,
20, 22, 33, 38, 46, 51, 55, 60,
21, 34, 37, 47, 50, 56, 59, 61,
35, 36, 48, 49, 57, 58, 62, 63
};
final int quantTables[][] = new int[4][64]; // Tables
private final int[] precision = new int[4]; // Quantization precision 8 or 16
private final boolean[] tq = new boolean[4]; // 1: this table is present
private 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?
void enhanceTables(final int[] table) throws IOException {
// TODO: Consider creating a copy for the decoder here, as we need to keep the original values for the metadata
void enhanceTables() {
for (int t = 0; t < 4; t++) {
if (tq[t] != 0) {
enhanceQuantizationTable(quantTables[t], table);
if (tq[t]) {
enhanceQuantizationTable(quantTables[t], ZIGZAG);
}
}
}
private void enhanceQuantizationTable(final int qtab[], final int[] table) {
private void enhanceQuantizationTable(final int[] qtab, final int[] table) {
for (int i = 0; i < 8; i++) {
qtab[table[ i]] *= 90;
qtab[table[(4 * 8) + i]] *= 90;
@@ -122,7 +129,7 @@ final class QuantizationTable extends Segment {
throw new IIOException("Unexpected JPEG Quantization Table precision: " + table.precision[t]);
}
table.tq[t] = 1;
table.tq[t] = true;
if (table.precision[t] == 8) {
for (int i = 0; i < 64; i++) {
@@ -152,4 +159,28 @@ final class QuantizationTable extends Segment {
return table;
}
public boolean isPresent(int tabelId) {
return tq[tabelId];
}
int precision(int tableId) {
return precision[tableId];
}
int[] qTable(int tabelId) {
return quantTables[tabelId];
}
JPEGQTable toNativeTable(int tableId) {
// TODO: Should de-zigzag (ie. "natural order") while reading
// TODO: ...and make sure the table isn't "enhanced"...
int[] qTable = new int[quantTables[tableId].length];
for (int i = 0; i < qTable.length; i++) {
qTable[i] = quantTables[tableId][ZIGZAG[i]];
}
return new JPEGQTable(qTable);
}
}