Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.twelvemonkeys.imageio.plugins.jpeg.JPEGLosslessDecoder Maven / Gradle / Ivy
/*
* Copyright (c) 2016, Harald Kuhr
* Copyright (C) 2015, Michael Martinez
* Copyright (C) 2004, Helmut Dersch
* 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 of the copyright holder 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 HOLDER 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.lang.Validate;
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 {
private final ImageInputStream input;
private final JPEGImageReader listenerDelegate;
private final Frame frame;
private final List huffTables;
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 boolean restarting;
private int marker;
private int markerIndex;
private int numComp;
private int restartInterval;
private int selection;
private int xDim, yDim;
private int xLoc;
private int yLoc;
private int mask;
private int[][] outputData;
private static final int[] IDCT_P = {
0, 5, 40, 16, 45, 2, 7, 42,
21, 56, 8, 61, 18, 47, 1, 4,
41, 23, 58, 13, 32, 24, 37, 10,
63, 17, 44, 3, 6, 43, 20, 57,
15, 34, 29, 48, 53, 26, 39, 9,
60, 19, 46, 22, 59, 12, 33, 31,
50, 55, 25, 36, 11, 62, 14, 35,
28, 49, 52, 27, 38, 30, 51, 54
};
private static final int RESTART_MARKER_BEGIN = 0xFFD0;
private static final int RESTART_MARKER_END = 0xFFD7;
private static final int MAX_HUFFMAN_SUBTREE = 50;
private static final int MSB = 0x80000000;
int getDimX() {
return xDim;
}
int getDimY() {
return yDim;
}
JPEGLosslessDecoder(final List segments, final ImageInputStream data, final JPEGImageReader listenerDelegate) {
Validate.notNull(segments);
frame = get(segments, Frame.class);
scan = get(segments, Scan.class);
QuantizationTable qt = get(segments, QuantizationTable.class);
quantTable = qt != null ? qt : new QuantizationTable(); // For lossless there are no DQTs
huffTables = getAll(segments, HuffmanTable.class); // For lossless there's usually only one, and only DC tables
RestartInterval dri = get(segments, RestartInterval.class);
restartInterval = dri != null ? dri.interval : 0;
input = data;
this.listenerDelegate = listenerDelegate;
}
private List getAll(final List segments, final Class type) {
ArrayList list = new ArrayList<>();
for (Segment segment : segments) {
if (type.isInstance(segment)) {
list.add(type.cast(segment));
}
}
return list;
}
private T get(final List segments, final Class type) {
for (Segment segment : segments) {
if (type.isInstance(segment)) {
return type.cast(segment);
}
}
return null;
}
int[][] decode() throws IOException {
int current, scanNum = 0;
xLoc = 0;
yLoc = 0;
current = input.readUnsignedShort();
if (current != JPEG.SOI) { // SOI
throw new IIOException("Not a JPEG file, does not start with 0xFFD8");
}
for (HuffmanTable huffTable : huffTables) {
huffTable.buildHuffTables(HuffTab);
}
quantTable.enhanceTables();
current = input.readUnsignedShort();
do {
// Skip until first SOS
while (current != JPEG.SOS) {
input.skipBytes(input.readUnsignedShort() - 2);
current = input.readUnsignedShort();
}
int precision = frame.samplePrecision;
if (precision == 8) {
mask = 0xFF;
}
else {
mask = 0xFFFF;
}
Frame.Component[] components = frame.components;
scan = readScan();
numComp = scan.components.length;
selection = scan.spectralSelStart;
final Scan.Component[] scanComps = scan.components;
for (int i = 0; i < numComp; i++) {
Frame.Component component = getComponentSpec(components, scanComps[i].scanCompSel);
qTab[i] = quantTable.qTable(component.qtSel);
nBlock[i] = component.vSub * component.hSub;
int dcTabSel = scanComps[i].dcTabSel;
int acTabSel = scanComps[i].acTabSel;
// NOTE: If we don't find any DC tables for lossless operation, this file isn't any good.
// However, we have seen files with AC tables only, we'll treat these as if the AC was DC
if (useACForDC(dcTabSel)) {
processWarningOccured("Lossless JPEG with no DC tables encountered. Assuming only tables present to be DC tables.");
dcTab[i] = HuffTab[dcTabSel][1];
acTab[i] = HuffTab[acTabSel][0];
}
else {
// All good
dcTab[i] = HuffTab[dcTabSel][0];
acTab[i] = HuffTab[acTabSel][1];
}
}
xDim = frame.samplesPerLine;
yDim = frame.lines;
outputData = new int[numComp][];
for (int componentIndex = 0; componentIndex < numComp; ++componentIndex) {
// not a good use of memory, but I had trouble packing bytes into int. some values exceeded 255.
outputData[componentIndex] = new int[xDim * yDim];
}
final int[] firstValue = new int[numComp];
for (int i = 0; i < numComp; i++) {
firstValue[i] = (1 << (precision - 1));
}
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];
System.arraycopy(firstValue, 0, pred, 0, numComp);
if (restartInterval == 0) {
current = decode(pred, temp, index);
while ((current == 0) && ((xLoc < xDim) && (yLoc < yDim))) {
output(pred);
current = decode(pred, temp, index);
}
break; //current=MARKER
}
for (int mcuNum = 0; mcuNum < restartInterval; mcuNum++) {
restarting = (mcuNum == 0);
current = decode(pred, temp, index);
output(pred);
if (current != 0) {
break;
}
}
if (current == 0) {
if (markerIndex != 0) {
current = (0xFF00 | marker);
markerIndex = 0;
}
else {
current = input.readUnsignedShort();
}
}
if ((current < RESTART_MARKER_BEGIN) || (current > RESTART_MARKER_END)) {
break; //current=MARKER
}
}
if ((current == JPEG.DNL) && (scanNum == 1)) { //DNL
readNumber();
current = input.readUnsignedShort();
}
// TODO oe: 05.05.2018 Is it correct loop? Content of outputData from previous iteration is always lost.
} while ((current != JPEG.EOI) && ((xLoc < xDim) && (yLoc < yDim)) && (scanNum == 0));
return outputData;
}
private void processWarningOccured(String warning) {
listenerDelegate.processWarningOccurred(warning);
}
private boolean useACForDC(final int dcTabSel) {
if (isLossless()) {
for (HuffmanTable huffTable : huffTables) {
if (!huffTable.isPresent(dcTabSel, 0) && huffTable.isPresent(dcTabSel, 1)) {
return true;
}
}
}
return false;
}
private boolean isLossless() {
switch (frame.marker) {
case JPEG.SOF3:
case JPEG.SOF7:
case JPEG.SOF11:
case JPEG.SOF15:
return true;
default:
return false;
}
}
private Frame.Component getComponentSpec(Frame.Component[] components, int sel) {
for (Frame.Component component : components) {
if (component.id == sel) {
return component;
}
}
throw new IllegalArgumentException("No such component id: " + sel);
}
private Scan readScan() throws IOException {
int length = input.readUnsignedShort();
return Scan.read(input, length);
}
private int decode(final int[] prev, final int[] temp, final int[] index) throws IOException {
if (numComp == 1) {
return decodeSingle(prev, temp, index);
}
else if (numComp == 3) {
return decodeRGB(prev, temp, index);
}
else {
return decodeAny(prev, temp, index);
}
}
private int decodeSingle(final int[] prev, final int[] temp, final int[] index) throws IOException {
// At the beginning of the first line and
// at the beginning of each restart interval the prediction value of 2P – 1 is used, where P is the input precision.
if (restarting) {
restarting = false;
prev[0] = (1 << (frame.samplePrecision - 1));
}
else {
final int[] outputData = this.outputData[0];
switch (selection) {
case 2:
prev[0] = getPreviousY(outputData);
break;
case 3:
prev[0] = getPreviousXY(outputData);
break;
case 4:
prev[0] = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData);
break;
case 5:
prev[0] = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1);
break;
case 6:
prev[0] = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1);
break;
case 7:
prev[0] = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2);
break;
default:
prev[0] = getPreviousX(outputData);
break;
}
}
for (int i = 0; i < nBlock[0]; i++) {
int value = getHuffmanValue(dcTab[0], temp, index);
if (value >= 0xFF00) {
return value;
}
int n = getn(prev, value, temp, index);
int nRestart = (n >> 8);
if ((nRestart >= RESTART_MARKER_BEGIN) && (nRestart <= RESTART_MARKER_END)) {
return nRestart;
}
prev[0] += n;
}
return 0;
}
private int decodeRGB(final int[] prev, final int[] temp, final int[] index) throws IOException {
final int[] outputRedData = outputData[0];
final int[] outputGreenData = outputData[1];
final int[] outputBlueData = outputData[2];
switch (selection) {
case 2:
prev[0] = getPreviousY(outputRedData);
prev[1] = getPreviousY(outputGreenData);
prev[2] = getPreviousY(outputBlueData);
break;
case 3:
prev[0] = getPreviousXY(outputRedData);
prev[1] = getPreviousXY(outputGreenData);
prev[2] = getPreviousXY(outputBlueData);
break;
case 4:
prev[0] = (getPreviousX(outputRedData) + getPreviousY(outputRedData)) - getPreviousXY(outputRedData);
prev[1] = (getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) - getPreviousXY(outputGreenData);
prev[2] = (getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) - getPreviousXY(outputBlueData);
break;
case 5:
prev[0] = getPreviousX(outputRedData) + ((getPreviousY(outputRedData) - getPreviousXY(outputRedData)) >> 1);
prev[1] = getPreviousX(outputGreenData) + ((getPreviousY(outputGreenData) - getPreviousXY(outputGreenData)) >> 1);
prev[2] = getPreviousX(outputBlueData) + ((getPreviousY(outputBlueData) - getPreviousXY(outputBlueData)) >> 1);
break;
case 6:
prev[0] = getPreviousY(outputRedData) + ((getPreviousX(outputRedData) - getPreviousXY(outputRedData)) >> 1);
prev[1] = getPreviousY(outputGreenData) + ((getPreviousX(outputGreenData) - getPreviousXY(outputGreenData)) >> 1);
prev[2] = getPreviousY(outputBlueData) + ((getPreviousX(outputBlueData) - getPreviousXY(outputBlueData)) >> 1);
break;
case 7:
prev[0] = (int) (((long) getPreviousX(outputRedData) + getPreviousY(outputRedData)) / 2);
prev[1] = (int) (((long) getPreviousX(outputGreenData) + getPreviousY(outputGreenData)) / 2);
prev[2] = (int) (((long) getPreviousX(outputBlueData) + getPreviousY(outputBlueData)) / 2);
break;
default:
prev[0] = getPreviousX(outputRedData);
prev[1] = getPreviousX(outputGreenData);
prev[2] = getPreviousX(outputBlueData);
break;
}
return decode0(prev, temp, index);
}
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;
switch (selection) {
case 2:
previous = getPreviousY(outputData);
break;
case 3:
previous = getPreviousXY(outputData);
break;
case 4:
previous = (getPreviousX(outputData) + getPreviousY(outputData)) - getPreviousXY(outputData);
break;
case 5:
previous = getPreviousX(outputData) + ((getPreviousY(outputData) - getPreviousXY(outputData)) >> 1);
break;
case 6:
previous = getPreviousY(outputData) + ((getPreviousX(outputData) - getPreviousXY(outputData)) >> 1);
break;
case 7:
previous = (int) (((long) getPreviousX(outputData) + getPreviousY(outputData)) / 2);
break;
default:
previous = getPreviousX(outputData);
break;
}
prev[componentIndex] = previous;
}
return decode0(prev, temp, index);
}
private int decode0(int[] prev, int[] temp, int[] index) throws IOException {
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++) {
Arrays.fill(IDCT_Source, 0);
value = getHuffmanValue(dctab, temp, index);
if (value >= 0xFF00) {
return value;
}
prev[ctrC] = IDCT_Source[0] = prev[ctrC] + getn(index, value, temp, index);
IDCT_Source[0] *= qtab[0];
for (int j = 1; j < 64; j++) {
value = getHuffmanValue(actab, temp, index);
if (value >= 0xFF00) {
return value;
}
j += (value >> 4);
if ((value & 0x0F) == 0) {
if ((value >> 4) == 0) {
break;
}
}
else {
IDCT_Source[IDCT_P[j]] = getn(index, value & 0x0F, temp, index) * qtab[j];
}
}
}
}
return 0;
}
// Huffman table for fast search: (HuffTab) 8-bit Look up table 2-layer search architecture, 1st-layer represent 256 node (8 bits) if codeword-length > 8
// bits, then the entry of 1st-layer = (# of 2nd-layer table) | MSB and it is stored in the 2nd-layer Size of tables in each layer are 256.
// HuffTab[*][*][0-256] is always the only 1st-layer table.
//
// An entry can be: (1) (# of 2nd-layer table) | MSB , for code length > 8 in 1st-layer (2) (Code length) << 8 | HuffVal
//
// HuffmanValue(table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
// ):
// return: Huffman Value of table
// 0xFF?? if it receives a MARKER
// Parameter: table HuffTab[x][y] (ex) HuffmanValue(HuffTab[1][0],...)
// temp temp storage for remainded bits
// index index to bit of temp
// in FILE pointer
// Effect:
// temp store new remainded bits
// index change to new index
// in change to new position
// NOTE:
// Initial by temp=0; index=0;
// NOTE: (explain temp and index)
// temp: is always in the form at calling time or returning time
// | byte 4 | byte 3 | byte 2 | byte 1 |
// | 0 | 0 | 00000000 | 00000??? | if not a MARKER
// ^index=3 (from 0 to 15)
// 321
// NOTE (marker and marker_index):
// If get a MARKER from 'in', marker=the low-byte of the MARKER
// and marker_index=9
// If marker_index=9 then index is always > 8, or HuffmanValue()
// will not be called
private int getHuffmanValue(final int[] table, final int[] temp, final int[] index) throws IOException {
int code, input;
final int mask = 0xFFFF;
if (index[0] < 8) {
temp[0] <<= 8;
input = this.input.readUnsignedByte();
if (input == 0xFF) {
marker = this.input.readUnsignedByte();
if (marker != 0) {
markerIndex = 9;
}
}
temp[0] |= input;
}
else {
index[0] -= 8;
}
code = table[temp[0] >> index[0]];
if ((code & MSB) != 0) {
if (markerIndex != 0) {
markerIndex = 0;
return 0xFF00 | marker;
}
temp[0] &= (mask >> (16 - index[0]));
temp[0] <<= 8;
input = this.input.readUnsignedByte();
if (input == 0xFF) {
marker = this.input.readUnsignedByte();
if (marker != 0) {
markerIndex = 9;
}
}
temp[0] |= input;
code = table[((code & 0xFF) * 256) + (temp[0] >> index[0])];
index[0] += 8;
}
index[0] += 8 - (code >> 8);
if (index[0] < 0) {
throw new IIOException("index=" + index[0] + " temp=" + temp[0] + " code=" + code + " in HuffmanValue()");
}
if (index[0] < markerIndex) {
markerIndex = 0;
return 0xFF00 | marker;
}
temp[0] &= (mask >> (16 - index[0]));
return code & 0xFF;
}
private int getn(final int[] pred, final int n, final int[] temp, final int[] index) throws IOException {
int result;
final int one = 1;
final int n_one = -1;
final int mask = 0xFFFF;
int input;
if (n == 0) {
return 0;
}
if (n == 16) {
if (pred[0] >= 0) {
return -32768;
}
else {
return 32768;
}
}
index[0] -= n;
if (index[0] >= 0) {
if ((index[0] < markerIndex) && !isLastPixel()) { // this was corrupting the last pixel in some cases
markerIndex = 0;
return (0xFF00 | marker) << 8;
}
result = temp[0] >> index[0];
temp[0] &= (mask >> (16 - index[0]));
}
else {
temp[0] <<= 8;
input = this.input.readUnsignedByte();
if (input == 0xFF) {
marker = this.input.readUnsignedByte();
if (marker != 0) {
markerIndex = 9;
}
}
temp[0] |= input;
index[0] += 8;
if (index[0] < 0) {
if (markerIndex != 0) {
markerIndex = 0;
return (0xFF00 | marker) << 8;
}
temp[0] <<= 8;
input = this.input.readUnsignedByte();
if (input == 0xFF) {
marker = this.input.readUnsignedByte();
if (marker != 0) {
markerIndex = 9;
}
}
temp[0] |= input;
index[0] += 8;
}
if (index[0] < 0) {
throw new IOException("index=" + index[0] + " in getn()");
}
if (index[0] < markerIndex) {
markerIndex = 0;
return (0xFF00 | marker) << 8;
}
result = temp[0] >> index[0];
temp[0] &= (mask >> (16 - index[0]));
}
if (result < (one << (n - 1))) {
result += (n_one << n) + 1;
}
return result;
}
private int getPreviousX(final int[] data) {
if (xLoc > 0) {
return data[((yLoc * xDim) + xLoc) - 1];
}
else if (yLoc > 0) {
return getPreviousY(data);
}
else {
return (1 << (frame.samplePrecision - 1));
}
}
private int getPreviousXY(final int[] data) {
if ((xLoc > 0) && (yLoc > 0)) {
return data[(((yLoc - 1) * xDim) + xLoc) - 1];
}
else {
return getPreviousY(data);
}
}
private int getPreviousY(final int[] data) {
if (yLoc > 0) {
return data[((yLoc - 1) * xDim) + xLoc];
}
else {
return getPreviousX(data);
}
}
private boolean isLastPixel() {
return (xLoc == (xDim - 1)) && (yLoc == (yDim - 1));
}
private void output(final int[] pred) {
if (numComp == 1) {
outputSingle(pred);
}
else if (numComp == 3) {
outputRGB(pred);
}
else {
outputAny(pred);
}
}
private void outputSingle(final int[] pred) {
if ((xLoc < xDim) && (yLoc < yDim)) {
outputData[0][(yLoc * xDim) + xLoc] = mask & pred[0];
xLoc++;
if (xLoc >= xDim) {
yLoc++;
xLoc = 0;
}
}
}
private void outputRGB(final int[] pred) {
if ((xLoc < xDim) && (yLoc < yDim)) {
final int index = (yLoc * xDim) + xLoc;
outputData[0][index] = pred[0];
outputData[1][index] = pred[1];
outputData[2][index] = pred[2];
xLoc++;
if (xLoc >= xDim) {
yLoc++;
xLoc = 0;
}
}
}
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) {
outputData[componentIndex][index] = pred[componentIndex];
}
xLoc++;
if (xLoc >= xDim) {
yLoc++;
xLoc = 0;
}
}
}
private int readNumber() throws IOException {
final int Ld = input.readUnsignedShort();
if (Ld != 4) {
throw new IOException("ERROR: Define number format throw new IOException [Ld!=4]");
}
return input.readUnsignedShort();
}
int getNumComponents() {
return numComp;
}
int getPrecision() {
return frame.samplePrecision;
}
}