jogamp.opengl.util.jpeg.JPEGDecoder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jogl-all Show documentation
Show all versions of jogl-all Show documentation
Java™ Binding for the OpenGL® API
/**
* Original JavaScript code from ,
* ported to Java for JogAmp Community.
*
* Enhancements:
* * InputStream instead of memory buffer
* * User provided memory handler
* * Fixed JPEG Component ID/Index mapping
* * Color space conversion (YCCK, CMYK -> RGB)
* * More error tolerant
*
* *****************
*
* Copyright 2011 notmasteryet
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* *****************
*
* Copyright 2013 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are
* permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer.
*
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY JogAmp Community ``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 JogAmp Community 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.
*
* The views and conclusions contained in the software and documentation are those of the
* authors and should not be interpreted as representing official policies, either expressed
* or implied, of JogAmp Community.
*/
package jogamp.opengl.util.jpeg;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import jogamp.opengl.Debug;
import com.jogamp.common.util.ArrayHashSet;
import com.jogamp.common.util.VersionNumber;
import com.jogamp.opengl.util.texture.TextureData;
import com.jogamp.opengl.util.texture.TextureData.ColorSpace;
/**
*
*
* - The JPEG specification can be found in the ITU CCITT Recommendation T.81
* (www.w3.org/Graphics/JPEG/itu-t81.pdf)
* - The JFIF specification can be found in the JPEG File Interchange Format
* (www.w3.org/Graphics/JPEG/jfif3.pdf)
* - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
* in PostScript Level 2, Technical Note #5116
* (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
* - http://halicery.com/jpeg/huffman.html
* - https://en.wikipedia.org/wiki/Jpg#Syntax_and_structure
* - http://www.cs.sfu.ca/CourseCentral/365/mark/material/notes/Chap4/Chap4.2/Chap4.2.html
* - https://github.com/notmasteryet/jpgjs/blob/master/jpg.js
*
*/
public class JPEGDecoder {
private static final boolean DEBUG = Debug.debug("JPEGImage");
private static final boolean DEBUG_IN = false;
/** Allows user to hook a {@link ColorSink} to another toolkit to produce {@link TextureData}. */
public static interface ColorSink {
/**
* @param width
* @param height
* @param sourceCS the color-space of the decoded JPEG
* @param sourceComponents number of components used for the given source color-space
* @return Either {@link TextureData.ColorSpace#RGB} or {@link TextureData.ColorSpace#YCbCr}. {@link TextureData.ColorSpace#YCCK} and {@link TextureData.ColorSpace#CMYK} will throw an exception!
* @throws RuntimeException
*/
public TextureData.ColorSpace allocate(int width, int height, TextureData.ColorSpace sourceCS, int sourceComponents) throws RuntimeException;
public void store2(int x, int y, byte c1, byte c2);
public void storeRGB(int x, int y, byte r, byte g, byte b);
public void storeYCbCr(int x, int y, byte Y, byte Cb, byte Cr);
}
public static class JFIF {
final VersionNumber version;
final int densityUnits;
final int xDensity;
final int yDensity;
final int thumbWidth;
final int thumbHeight;
final byte[] thumbData;
private JFIF(final byte data[]) {
version = new VersionNumber(data[5], data[6], 0);
densityUnits = data[7];
xDensity = (data[8] << 8) | data[9];
yDensity = (data[10] << 8) | data[11];
thumbWidth = data[12];
thumbHeight = data[13];
if( 0 < thumbWidth && 0 < thumbHeight ) {
final int len = 14 + 3 * thumbWidth * thumbHeight;
thumbData = new byte[len];
System.arraycopy(data, 14, thumbData, 0, len);
} else {
thumbData = null;
}
}
public static final JFIF get(final byte[] data) throws RuntimeException {
if ( data[0] == (byte)0x4A && data[1] == (byte)0x46 && data[2] == (byte)0x49 &&
data[3] == (byte)0x46 && data[4] == (byte)0x0) { // 'JFIF\x00'
final JFIF r = new JFIF(data);
return r;
} else {
return null;
}
}
public final String toString() {
return "JFIF[ver "+version+", density[units "+densityUnits+", "+xDensity+"x"+yDensity+"], thumb "+thumbWidth+"x"+thumbHeight+"]";
}
}
public static class Adobe {
final short version;
final short flags0;
final short flags1;
final short colorCode;
final ColorSpace colorSpace;
private Adobe(final byte[] data) {
version = data[6];
flags0 = (short) ( (data[7] << 8) | data[8] ) ;
flags1 = (short) ( (data[9] << 8) | data[10] ) ;
colorCode = data[11];
switch( colorCode ) {
case 2: colorSpace = ColorSpace.YCCK; break;
case 1: colorSpace = ColorSpace.YCbCr; break;
default: colorSpace = ColorSpace.CMYK; break;
}
}
public static final Adobe get(final byte[] data) throws RuntimeException {
if (data[0] == (byte)0x41 && data[1] == (byte)0x64 && data[2] == (byte)0x6F &&
data[3] == (byte)0x62 && data[4] == (byte)0x65 && data[5] == (byte)0) { // 'Adobe\x00'
final Adobe r = new Adobe(data);
return r;
} else {
return null;
}
}
public final String toString() {
return "Adobe[ver "+version+", flags["+toHexString(flags0)+", "+toHexString(flags1)+"], colorSpace/Code "+colorSpace+"/"+toHexString(colorCode)+"]";
}
}
/** TODO */
public static class EXIF {
private EXIF(final byte data[]) {
}
public static final EXIF get(final byte[] data) throws RuntimeException {
if ( data[0] == (byte)0x45 && data[1] == (byte)0x78 && data[2] == (byte)0x69 &&
data[3] == (byte)0x66 && data[4] == (byte)0x0) { // 'Exif\x00'
final EXIF r = new EXIF(data);
return r;
} else {
return null;
}
}
public final String toString() {
return "EXIF[]";
}
}
@SuppressWarnings("serial")
public static class CodecException extends RuntimeException {
CodecException(String message) {
super(message);
}
}
@SuppressWarnings("serial")
public static class MarkerException extends CodecException {
final int marker;
MarkerException(int marker, String message) {
super(message+" - Marker "+toHexString(marker));
this.marker = marker;
}
public int getMarker() { return marker; }
}
/** Start of Image */
private static final int M_SOI = 0xFFD8;
/** End of Image */
private static final int M_EOI = 0xFFD9;
/** Start of Frame - Baseline DCT */
private static final int M_SOF0 = 0xFFC0;
/** Start of Frame - Extended sequential DCT */
// private static final int M_SOF1 = 0xFFC1;
/** Start of Frame - Progressive DCT */
private static final int M_SOF2 = 0xFFC2;
/** DHT (Define Huffman Tables) */
private static final int M_DHT = 0xFFC4;
// private static final int M_DAC = 0xFFCC;
/** SOS (Start of Scan) */
private static final int M_SOS = 0xFFDA;
/** DQT (Define Quantization Tables) */
private static final int M_QTT = 0xFFDB;
/** DRI (Define Restart Interval) */
private static final int M_DRI = 0xFFDD;
/** APP0 (Application Specific) - JFIF Header */
private static final int M_APP00 = 0xFFE0;
/** APP1 (Application Specific) - Exif Header */
private static final int M_APP01 = 0xFFE1;
/** APP2 (Application Specific) */
private static final int M_APP02 = 0xFFE2;
/** APP3 (Application Specific) */
private static final int M_APP03 = 0xFFE3;
/** APP4 (Application Specific) */
private static final int M_APP04 = 0xFFE4;
/** APP5 (Application Specific) */
private static final int M_APP05 = 0xFFE5;
/** APP6 (Application Specific) */
private static final int M_APP06 = 0xFFE6;
/** APP7 (Application Specific) */
private static final int M_APP07 = 0xFFE7;
/** APP8 (Application Specific) */
private static final int M_APP08 = 0xFFE8;
/** APP9 (Application Specific) */
private static final int M_APP09 = 0xFFE9;
/** APP10 (Application Specific) */
private static final int M_APP10 = 0xFFEA;
/** APP11 (Application Specific) */
private static final int M_APP11 = 0xFFEB;
/** APP12 (Application Specific) */
private static final int M_APP12 = 0xFFEC;
/** APP13 (Application Specific) */
private static final int M_APP13 = 0xFFED;
/** APP14 (Application Specific) - ADOBE Header */
private static final int M_APP14 = 0xFFEE;
/** APP15 (Application Specific) */
private static final int M_APP15 = 0xFFEF;
/** Annotation / Comment */
private static final int M_ANO = 0xFFFE;
static final int[] dctZigZag = new int[] {
0,
1, 8,
16, 9, 2,
3, 10, 17, 24,
32, 25, 18, 11, 4,
5, 12, 19, 26, 33, 40,
48, 41, 34, 27, 20, 13, 6,
7, 14, 21, 28, 35, 42, 49, 56,
57, 50, 43, 36, 29, 22, 15,
23, 30, 37, 44, 51, 58,
59, 52, 45, 38, 31,
39, 46, 53, 60,
61, 54, 47,
55, 62,
63
};
static final int dctCos1 = 4017; // cos(pi/16)
static final int dctSin1 = 799; // sin(pi/16)
static final int dctCos3 = 3406; // cos(3*pi/16)
static final int dctSin3 = 2276; // sin(3*pi/16)
static final int dctCos6 = 1567; // cos(6*pi/16)
static final int dctSin6 = 3784; // sin(6*pi/16)
static final int dctSqrt2 = 5793; // sqrt(2)
static final int dctSqrt1d2 = 2896; // sqrt(2) / 2
static class Frame {
final boolean progressive;
final int precision;
final int scanLines;
final int samplesPerLine;
private final ArrayHashSet compIDs;
private final ComponentIn[] comps;
private final int compCount;
/** quantization tables */
final int[][] qtt;
int maxCompID;
int maxH;
int maxV;
int mcusPerLine;
int mcusPerColumn;
Frame(boolean progressive, int precision, int scanLines, int samplesPerLine, int componentsCount, int[][] qtt) {
this.progressive = progressive;
this.precision = precision;
this.scanLines = scanLines;
this.samplesPerLine = samplesPerLine;
compIDs = new ArrayHashSet(componentsCount);
comps = new ComponentIn[componentsCount];
this.compCount = componentsCount;
this.qtt = qtt;
}
private final void checkBounds(int idx) {
if( 0 > idx || idx >= compCount ) {
throw new CodecException("Idx out of bounds "+idx+", "+this);
}
}
public final void validateComponents() {
for(int i=0; i null QTT");
}
}
}
public final int getCompCount() { return compCount; }
public final int getMaxCompID() { return maxCompID; }
public final void putOrdered(int compID, ComponentIn component) {
if( maxCompID < compID ) {
maxCompID = compID;
}
final int idx = compIDs.size();
checkBounds(idx);
compIDs.add(compID);
comps[idx] = component;
}
public final ComponentIn getCompByIndex(int i) {
checkBounds(i);
return comps[i];
}
public final ComponentIn getCompByID(int componentID) {
return getCompByIndex( compIDs.indexOf(componentID) );
}
public final int getCompID(int idx) {
return compIDs.get(idx);
}
public final boolean hasCompID(int componentID) {
return compIDs.contains(componentID);
}
public final String toString() {
return "Frame[progressive "+progressive+", precision "+precision+", scanLines "+scanLines+", samplesPerLine "+samplesPerLine+
", components[count "+compCount+", maxID "+maxCompID+", componentIDs "+compIDs+", comps "+Arrays.asList(comps)+"]]";
}
}
/** The JPEG encoded components */
class ComponentIn {
final int h, v;
/** index to frame.qtt[] */
final int qttIdx;
int blocksPerColumn;
int blocksPerColumnForMcu;
int blocksPerLine;
int blocksPerLineForMcu;
/** [blocksPerColumnForMcu][blocksPerLineForMcu][64]; */
int[][][] blocks;
int pred;
BinObj huffmanTableAC;
BinObj huffmanTableDC;
ComponentIn(int h, int v, int qttIdx) {
this.h = h;
this.v = v;
this.qttIdx = qttIdx;
}
public final void allocateBlocks(int blocksPerColumn, int blocksPerColumnForMcu, int blocksPerLine, int blocksPerLineForMcu) {
this.blocksPerColumn = blocksPerColumn;
this.blocksPerColumnForMcu = blocksPerColumnForMcu;
this.blocksPerLine = blocksPerLine;
this.blocksPerLineForMcu = blocksPerLineForMcu;
this.blocks = new int[blocksPerColumnForMcu][blocksPerLineForMcu][64];
}
public final int[] getBlock(int row, int col) {
if( row >= blocksPerColumnForMcu || col >= blocksPerLineForMcu ) {
throw new CodecException("Out of bounds given ["+row+"]["+col+"] - "+this);
}
return blocks[row][col];
}
public final String toString() {
return "CompIn[h "+h+", v "+v+", qttIdx "+qttIdx+", blocks["+blocksPerColumn+", mcu "+blocksPerColumnForMcu+"]["+blocksPerLine+", mcu "+blocksPerLineForMcu+"][64]]";
}
}
/** The decoded components */
class ComponentOut {
private final ArrayList lines;
final float scaleX;
final float scaleY;
ComponentOut(ArrayList lines, float scaleX, float scaleY) {
this.lines = lines;
this.scaleX = scaleX;
this.scaleY = scaleY;
}
/** Safely returning a line, if index exceeds number of lines, last line is returned. */
public final byte[] getLine(int i) {
final int sz = lines.size();
return lines.get( i < sz ? i : sz - 1);
}
public final String toString() {
return "CompOut[lines "+lines.size()+", scale "+scaleX+"x"+scaleY+"]";
}
}
public String toString() {
final String jfifS = null != jfif ? jfif.toString() : "JFIF nil";
final String exifS = null != exif ? exif.toString() : "Exif nil";
final String adobeS = null != adobe ? adobe.toString() : "Adobe nil";
final String compOuts = null != components ? Arrays.asList(components).toString() : "nil";
return "JPEG[size "+width+"x"+height+", compOut "+compOuts+", "+jfifS+", "+exifS+", "+adobeS+"]";
}
private BufferedInputStream istream;
private int _ipos = 0;
private int _iposSave = 0;
private int width = 0;
private int height = 0;
private JFIF jfif = null;
private EXIF exif = null;
private Adobe adobe = null;
private ComponentOut[] components = null;
public final JFIF getJFIFHeader() { return jfif; }
public final EXIF getEXIFHeader() { return exif; }
public final Adobe getAdobeHeader() { return adobe; }
public final int getWidth() { return width; }
public final int getHeight() { return height; }
private final void resetInput(InputStream is) {
if( is instanceof BufferedInputStream ) {
istream = (BufferedInputStream) is;
} else {
istream = new BufferedInputStream(is);
}
_ipos = 0;
}
private final void markStream(int readLimit) {
istream.mark(readLimit);
_iposSave = _ipos;
}
private final void rewindStream() throws IOException {
if(DEBUG_IN) { System.err.println("JPG.rewindStream: "+_ipos+" -> "+_iposSave); }
istream.reset();
_ipos = _iposSave;
_iposSave = 0;
}
private final int readUint8() throws IOException {
final int r = istream.read();
if( -1 < r ) {
if(DEBUG_IN) { System.err.println("u8["+_ipos+"]: "+toHexString(r)); }
_ipos++;
} else if(DEBUG_IN) {
System.err.println("u8["+_ipos+"]: EOS");
}
return r;
}
private final int readUint16() throws IOException {
final int hi = istream.read();
if( -1 < hi ) {
_ipos++;
final int lo = istream.read();
if( -1 < lo ) {
_ipos++;
final int r = hi << 8 | lo ;
if(DEBUG_IN) { System.err.println("u16["+(_ipos-2)+"]: "+toHexString(r)); }
return r;
}
}
if(DEBUG_IN) { System.err.println("u16["+_ipos+"]: EOS"); }
return -1;
}
private final int readNumber() throws IOException {
final int len=readUint16();
if(len!=4){
throw new CodecException("ERROR: Define number format error [Len!=4, but "+len+"]");
}
return readUint16();
}
private final byte[] readDataBlock() throws IOException {
int count=0, i=0;
final int len=readUint16(); count+=2;
byte[] data = new byte[len-2];
while(count frames = new ArrayList(); // JAU: max 1-frame
Frame frame = null;
int resetInterval = 0;
int fileMarker = readUint16();
if ( fileMarker != M_SOI ) {
throw new CodecException("SOI not found, but has marker "+toHexString(fileMarker));
}
fileMarker = readUint16();
while (fileMarker != M_EOI) {
if(DEBUG) { System.err.println("JPG.parse got marker "+toHexString(fileMarker)); }
switch(fileMarker) {
case M_APP00:
case M_APP01:
case M_APP02:
case M_APP03:
case M_APP04:
case M_APP05:
case M_APP06:
case M_APP07:
case M_APP08:
case M_APP09:
case M_APP10:
case M_APP11:
case M_APP12:
case M_APP13:
case M_APP14:
case M_APP15:
case M_ANO: {
final byte[] appData = readDataBlock();
if ( fileMarker == M_APP00 ) {
jfif = JFIF.get( appData );
}
if ( fileMarker == M_APP01 ) {
exif = EXIF.get(appData);
}
if (fileMarker == M_APP14) {
adobe = Adobe.get(appData);
}
fileMarker = 0; // consumed and get-next
}
break;
case M_QTT: {
int count = 0;
final int quantizationTablesLength = readUint16(); count+=2;
while( count < quantizationTablesLength ) {
final int quantizationTableSpec = readUint8(); count++;
final int precisionID = quantizationTableSpec >> 4;
final int tableIdx = quantizationTableSpec & 0x0F;
final int[] tableData = new int[64];
if ( precisionID == 0 ) { // 8 bit values
for (int j = 0; j < 64; j++) {
final int z = dctZigZag[j];
tableData[z] = readUint8(); count++;
}
} else if ( precisionID == 1) { //16 bit
for (int j = 0; j < 64; j++) {
final int z = dctZigZag[j];
tableData[z] = readUint16(); count+=2;
}
} else {
throw new CodecException("DQT: invalid table precision "+precisionID+", quantizationTableSpec "+quantizationTableSpec+", idx "+tableIdx);
}
quantizationTables[tableIdx] = tableData;
if( DEBUG ) {
System.err.println("JPEG.parse.QTT["+tableIdx+"]: spec "+quantizationTableSpec+", precision "+precisionID+", data "+count+"/"+quantizationTablesLength);
}
}
if(count!=quantizationTablesLength){
throw new CodecException("ERROR: QTT format error [count!=Length]: "+count+"/"+quantizationTablesLength);
}
fileMarker = 0; // consumed and get-next
}
break;
case M_SOF0:
case M_SOF2: {
if( null != frame ) { // JAU: max 1-frame
throw new CodecException("only single frame JPEGs supported");
}
int count = 0;
final int sofLen = readUint16(); count+=2; // header length;
final int componentsCount;
{
final boolean progressive = (fileMarker == M_SOF2);
final int precision = readUint8(); count++;
final int scanLines = readUint16(); count+=2;
final int samplesPerLine = readUint16(); count+=2;
componentsCount = readUint8(); count++;
frame = new Frame(progressive, precision, scanLines, samplesPerLine, componentsCount, quantizationTables);
width = frame.samplesPerLine;
height = frame.scanLines;
}
for (int i = 0; i < componentsCount; i++) {
final int componentId = readUint8(); count++;
final int temp = readUint8(); count++;
final int h = temp >> 4;
final int v = temp & 0x0F;
final int qttIdx = readUint8(); count++;
final ComponentIn compIn = new ComponentIn(h, v, qttIdx);
frame.putOrdered(componentId, compIn);
}
if(count!=sofLen){
throw new CodecException("ERROR: SOF format error [count!=Length]");
}
prepareComponents(frame);
// frames.add(frame); // JAU: max 1-frame
if(DEBUG) { System.err.println("JPG.parse.SOF[02]: Got frame "+frame); }
fileMarker = 0; // consumed and get-next
}
break;
case M_DHT: {
int count = 0;
final int huffmanLength = readUint16(); count+=2;
int i=count, codeLengthTotal = 0;
while( i < huffmanLength ) {
final int huffmanTableSpec = readUint8(); count++;
final int[] codeLengths = new int[16];
int codeLengthSum = 0;
for (int j = 0; j < 16; j++) {
codeLengthSum += (codeLengths[j] = readUint8()); count++;
}
final byte[] huffmanValues = new byte[codeLengthSum];
for (int j = 0; j < codeLengthSum; j++) {
huffmanValues[j] = (byte)readUint8(); count++;
}
codeLengthTotal += codeLengthSum;
i += 17 + codeLengthSum;
final BinObj[] table = ( huffmanTableSpec >> 4 ) == 0 ? huffmanTablesDC : huffmanTablesAC;
table[huffmanTableSpec & 0x0F] = buildHuffmanTable(codeLengths, huffmanValues);
}
if(count!=huffmanLength || i!=count){
throw new CodecException("ERROR: Huffman table format error [count!=Length]");
}
if(DEBUG) { System.err.println("JPG.parse.DHT: Got Huffman CodeLengthTotal "+codeLengthTotal); }
fileMarker = 0; // consumed and get-next
}
break;
case M_DRI:
resetInterval = readNumber();
if(DEBUG) { System.err.println("JPG.parse.DRI: Got Reset Interval "+resetInterval); }
fileMarker = 0; // consumed and get-next
break;
case M_SOS: {
int count = 0;
final int sosLen = readUint16(); count+=2;
final int selectorsCount = readUint8(); count++;
ArrayList components = new ArrayList();
if(DEBUG) { System.err.println("JPG.parse.SOS: selectorCount [0.."+(selectorsCount-1)+"]: "+frame); }
for (int i = 0; i < selectorsCount; i++) {
final int compID = readUint8(); count++;
final ComponentIn component = frame.getCompByID(compID);
final int tableSpec = readUint8(); count++;
component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
components.add(component);
}
final int spectralStart = readUint8(); count++;
final int spectralEnd = readUint8(); count++;
final int successiveApproximation = readUint8(); count++;
if(count!=sosLen){
throw new CodecException("ERROR: scan header format error [count!=Length]");
}
fileMarker = decoder.decodeScan(frame, components, resetInterval,
spectralStart, spectralEnd,
successiveApproximation >> 4, successiveApproximation & 15);
if(DEBUG) { System.err.println("JPG.parse.SOS.decode result "+toHexString(fileMarker)); }
}
break;
default:
/**
if (data[offset - 3] == 0xFF &&
data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
// could be incorrect encoding -- last 0xFF byte of the previous
// block was eaten by the encoder
offset -= 3;
break;
} */
throw new CodecException("unknown JPEG marker " + toHexString(fileMarker));
}
if( 0 == fileMarker ) {
fileMarker = readUint16();
}
}
if(DEBUG) { System.err.println("JPG.parse.2: End of parsing input "+this); }
/** // JAU: max 1-frame
if ( frames.size() != 1 ) {
throw new CodecException("only single frame JPEGs supported "+this);
} */
if( null == frame ) {
throw new CodecException("no single frame found in stream "+this);
}
frame.validateComponents();
final int compCount = frame.getCompCount();
this.components = new ComponentOut[compCount];
for (int i = 0; i < compCount; i++) {
final ComponentIn component = frame.getCompByIndex(i);
// System.err.println("JPG.parse.buildComponentData["+i+"]: "+component); // JAU
// System.err.println("JPG.parse.buildComponentData["+i+"]: "+frame); // JAU
this.components[i] = new ComponentOut( output.buildComponentData(frame, component),
(float)component.h / (float)frame.maxH,
(float)component.v / (float)frame.maxV );
}
if(DEBUG) { System.err.println("JPG.parse.X: End of processing input "+this); }
return this;
}
private void prepareComponents(Frame frame) {
int maxH = 0, maxV = 0;
// for (componentId in frame.components) {
final int compCount = frame.getCompCount();
for (int i=0; i code = new ArrayList();
while (length > 0 && 0==codeLengths[length - 1]) {
length--;
}
code.add(new BinObjIdxed());
BinObjIdxed p = code.get(0), q;
for (int i = 0; i < length; i++) {
for (int j = 0; j < codeLengths[i]; j++) {
p = code.remove(code.size()-1);
p.children.set(p.index, values[k]);
while (p.index > 0) {
p = code.remove(code.size()-1);
}
p.index++;
code.add(p);
while (code.size() <= i) {
q = new BinObjIdxed();
code.add(q);
p.children.set(p.index, q.children);
p = q;
}
k++;
}
if (i + 1 < length) {
// p here points to last code
q = new BinObjIdxed();
code.add(q);
p.children.set(p.index, q.children);
p = q;
}
}
return code.get(0).children;
}
private final Output output = new Output();
private static class Output {
private int blocksPerLine;
private int blocksPerColumn;
private int samplesPerLine;
private ArrayList buildComponentData(Frame frame, ComponentIn component) {
ArrayList lines = new ArrayList();
blocksPerLine = component.blocksPerLine;
blocksPerColumn = component.blocksPerColumn;
samplesPerLine = blocksPerLine << 3;
final int[] R = new int[64];
final byte[] r = new byte[64];
for (int blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
final int scanLine = blockRow << 3;
// System.err.println("JPG.buildComponentData: row "+blockRow+"/"+blocksPerColumn+" -> scanLine "+scanLine); // JAU
for (int i = 0; i < 8; i++) {
lines.add(new byte[samplesPerLine]);
}
for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) {
// System.err.println("JPG.buildComponentData: col "+blockCol+"/"+blocksPerLine+", comp.qttIdx "+component.qttIdx+", qtt "+frame.qtt[component.qttIdx]); // JAU
quantizeAndInverse(component.getBlock(blockRow, blockCol), r, R, frame.qtt[component.qttIdx]);
final int sample = blockCol << 3;
int offset = 0;
for (int j = 0; j < 8; j++) {
final byte[] line = lines.get(scanLine + j);
for (int i = 0; i < 8; i++)
line[sample + i] = r[offset++];
}
}
}
return lines;
}
// A port of poppler's IDCT method which in turn is taken from:
// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
// "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
// IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
// 988-991.
private void quantizeAndInverse(int[] zz, byte[] dataOut, int[] dataIn, int[] qt) {
int v0, v1, v2, v3, v4, v5, v6, v7, t;
int[] p = dataIn;
int i;
// dequant
for (i = 0; i < 64; i++) {
p[i] = zz[i] * qt[i];
}
// inverse DCT on rows
for (i = 0; i < 8; ++i) {
int row = 8 * i;
// check for all-zero AC coefficients
if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 &&
p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 &&
p[7 + row] == 0) {
t = (dctSqrt2 * p[0 + row] + 512) >> 10;
p[0 + row] = t;
p[1 + row] = t;
p[2 + row] = t;
p[3 + row] = t;
p[4 + row] = t;
p[5 + row] = t;
p[6 + row] = t;
p[7 + row] = t;
continue;
}
// stage 4
v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
v2 = p[2 + row];
v3 = p[6 + row];
v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
v5 = p[3 + row] << 4;
v6 = p[5 + row] << 4;
// stage 3
t = (v0 - v1+ 1) >> 1;
v0 = (v0 + v1 + 1) >> 1;
v1 = t;
t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
v3 = t;
t = (v4 - v6 + 1) >> 1;
v4 = (v4 + v6 + 1) >> 1;
v6 = t;
t = (v7 + v5 + 1) >> 1;
v5 = (v7 - v5 + 1) >> 1;
v7 = t;
// stage 2
t = (v0 - v3 + 1) >> 1;
v0 = (v0 + v3 + 1) >> 1;
v3 = t;
t = (v1 - v2 + 1) >> 1;
v1 = (v1 + v2 + 1) >> 1;
v2 = t;
t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
v7 = t;
t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
v6 = t;
// stage 1
p[0 + row] = v0 + v7;
p[7 + row] = v0 - v7;
p[1 + row] = v1 + v6;
p[6 + row] = v1 - v6;
p[2 + row] = v2 + v5;
p[5 + row] = v2 - v5;
p[3 + row] = v3 + v4;
p[4 + row] = v3 - v4;
}
// inverse DCT on columns
for (i = 0; i < 8; ++i) {
int col = i;
// check for all-zero AC coefficients
if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 &&
p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 &&
p[7*8 + col] == 0) {
t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14;
p[0*8 + col] = t;
p[1*8 + col] = t;
p[2*8 + col] = t;
p[3*8 + col] = t;
p[4*8 + col] = t;
p[5*8 + col] = t;
p[6*8 + col] = t;
p[7*8 + col] = t;
continue;
}
// stage 4
v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12;
v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12;
v2 = p[2*8 + col];
v3 = p[6*8 + col];
v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12;
v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12;
v5 = p[3*8 + col];
v6 = p[5*8 + col];
// stage 3
t = (v0 - v1 + 1) >> 1;
v0 = (v0 + v1 + 1) >> 1;
v1 = t;
t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
v3 = t;
t = (v4 - v6 + 1) >> 1;
v4 = (v4 + v6 + 1) >> 1;
v6 = t;
t = (v7 + v5 + 1) >> 1;
v5 = (v7 - v5 + 1) >> 1;
v7 = t;
// stage 2
t = (v0 - v3 + 1) >> 1;
v0 = (v0 + v3 + 1) >> 1;
v3 = t;
t = (v1 - v2 + 1) >> 1;
v1 = (v1 + v2 + 1) >> 1;
v2 = t;
t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
v7 = t;
t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
v6 = t;
// stage 1
p[0*8 + col] = v0 + v7;
p[7*8 + col] = v0 - v7;
p[1*8 + col] = v1 + v6;
p[6*8 + col] = v1 - v6;
p[2*8 + col] = v2 + v5;
p[5*8 + col] = v2 - v5;
p[3*8 + col] = v3 + v4;
p[4*8 + col] = v3 - v4;
}
// convert to 8-bit integers
for (i = 0; i < 64; ++i) {
int sample = 128 + ((p[i] + 8) >> 4);
dataOut[i] = (byte) ( sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample );
}
}
}
private static interface DecoderFunction {
void decode(ComponentIn component, int[] zz) throws IOException;
}
private class Decoder {
// private int precision;
// private int samplesPerLine;
// private int scanLines;
private int mcusPerLine;
private boolean progressive;
// private int maxH, maxV;
private int bitsData, bitsCount;
private int spectralStart, spectralEnd;
private int successive;
private int eobrun;
private int successiveACState, successiveACNextValue;
private int decodeScan(Frame frame, ArrayList components, int resetInterval,
int spectralStart, int spectralEnd, int successivePrev, int successive) throws IOException {
// this.precision = frame.precision;
// this.samplesPerLine = frame.samplesPerLine;
// this.scanLines = frame.scanLines;
this.mcusPerLine = frame.mcusPerLine;
this.progressive = frame.progressive;
// this.maxH = frame.maxH;
// this.maxV = frame.maxV;
this.bitsData = 0;
this.bitsCount = 0;
this.spectralStart = spectralStart;
this.spectralEnd = spectralEnd;
this.successive = successive;
final int componentsLength = components.size();
final DecoderFunction decodeFn;
if (progressive) {
if (spectralStart == 0) {
decodeFn = successivePrev == 0 ? decodeDCFirst : decodeDCSuccessive;
} else {
decodeFn = successivePrev == 0 ? decodeACFirst : decodeACSuccessive;
}
} else {
decodeFn = decodeBaseline;
}
int mcu = 0;
int mcuExpected;
if (componentsLength == 1) {
final ComponentIn c = components.get(0);
mcuExpected = c.blocksPerLine * c.blocksPerColumn;
} else {
mcuExpected = mcusPerLine * frame.mcusPerColumn;
}
if (0 == resetInterval) {
resetInterval = mcuExpected;
}
if(DEBUG) {
System.err.println("JPEG.decodeScan.1 resetInterval "+resetInterval+", mcuExpected "+mcuExpected+", sA "+spectralStart+", sP "+successivePrev+", sE "+spectralEnd+", suc "+successive+", decodeFn "+decodeFn.getClass().getSimpleName());
}
int marker = 0;
while ( /* untilMarker || */ mcu < mcuExpected) {
// reset interval stuff
for (int i = 0; i < componentsLength; i++) {
components.get(i).pred = 0;
}
eobrun = 0;
try {
if (componentsLength == 1) {
final ComponentIn component = components.get(0);
for (int n = 0; n < resetInterval; n++) {
decodeBlock(component, decodeFn, mcu);
mcu++;
}
} else {
for (int n = 0; n < resetInterval; n++) {
for (int i = 0; i < componentsLength; i++) {
final ComponentIn component = components.get(i);
final int h = component.h;
final int v = component.v;
for (int j = 0; j < v; j++) {
for (int k = 0; k < h; k++) {
decodeMcu(component, decodeFn, mcu, j, k);
}
}
}
mcu++;
}
}
} catch (MarkerException markerException) {
if(DEBUG) { System.err.println("JPEG.decodeScan: Marker exception: "+markerException.getMessage()); markerException.printStackTrace(); }
return markerException.getMarker();
} catch (CodecException codecException) {
if(DEBUG) { System.err.println("JPEG.decodeScan: Codec exception: "+codecException.getMessage()); codecException.printStackTrace(); }
bitsCount = 0;
return M_EOI; // force end !
}
// find marker
bitsCount = 0;
markStream(2);
marker = readUint16();
if( marker < 0xFF00 ) {
rewindStream();
throw new CodecException("marker not found @ mcu "+mcu+"/"+mcuExpected+", u16: "+toHexString(marker));
}
final boolean isRSTx = 0xFFD0 <= marker && marker <= 0xFFD7; // !RSTx
if(DEBUG) {
System.err.println("JPEG.decodeScan: MCUs "+mcu+"/"+mcuExpected+", u16 "+toHexString(marker)+", RSTx "+isRSTx+", "+frame);
}
if ( !isRSTx ) {
break; // handle !RSTx marker in caller
}
}
return marker;
}
private int readBit() throws MarkerException, IOException {
if (bitsCount > 0) {
bitsCount--;
return (bitsData >> bitsCount) & 1;
}
bitsData = readUint8();
if( -1 == bitsData ) {
return -1;
}
if (bitsData == 0xFF) { // marker prefix
final int nextByte = readUint8(); // marker signature
if( -1 == nextByte ) {
throw new CodecException("marked prefix 0xFF, then EOF");
}
if (0 != nextByte) {
final int marker = (bitsData << 8) | nextByte;
throw new MarkerException(marker, "Marker at readBit file pos " + _ipos);
}
// unstuff 0
}
bitsCount = 7;
return bitsData >>> 7;
}
private int decodeHuffman(BinObj tree) throws IOException {
BinObj node = tree;
int bit;
while ( ( bit = readBit() ) != -1 ) {
node = node.get(bit);
if ( node.isValue ) {
return 0x000000FF & node.getValue();
}
}
throw new CodecException("EOF reached at "+_ipos);
}
private int receive(int length) throws IOException {
int n = 0;
while (length > 0) {
final int bit = readBit();
if (bit == -1) {
return -1;
}
n = (n << 1) | bit;
length--;
}
return n;
}
private int receiveAndExtend(int length) throws IOException {
final int n = receive(length);
if (n >= 1 << (length - 1)) {
return n;
}
return n + (-1 << length) + 1;
}
final DecoderFunction decodeBaseline = new BaselineDecoder();
final DecoderFunction decodeDCFirst = new DCFirstDecoder();
final DecoderFunction decodeDCSuccessive = new DCSuccessiveDecoder();
final DecoderFunction decodeACFirst = new ACFirstDecoder();
final DecoderFunction decodeACSuccessive = new ACSuccessiveDecoder();
class BaselineDecoder implements DecoderFunction {
public void decode(ComponentIn component, int[] zz) throws IOException {
final int t = decodeHuffman(component.huffmanTableDC);
final int diff = ( t == 0 ) ? 0 : receiveAndExtend(t);
zz[0] = ( component.pred += diff );
int k = 1;
while (k < 64) {
final int rs = decodeHuffman(component.huffmanTableAC);
final int s = rs & 15, r = rs >> 4;
if (s == 0) {
if (r < 15) {
break;
}
k += 16;
continue;
}
k += r;
final int z = dctZigZag[k];
zz[z] = receiveAndExtend(s);
k++;
}
}
}
class DCFirstDecoder implements DecoderFunction {
public void decode(ComponentIn component, int[] zz) throws IOException {
final int t = decodeHuffman(component.huffmanTableDC);
final int diff = ( t == 0 ) ? 0 : (receiveAndExtend(t) << successive);
zz[0] = ( component.pred += diff );
}
}
class DCSuccessiveDecoder implements DecoderFunction {
public void decode(ComponentIn component, int[] zz) throws IOException {
zz[0] |= readBit() << successive;
}
}
class ACFirstDecoder implements DecoderFunction {
public void decode(ComponentIn component, int[] zz) throws IOException {
if (eobrun > 0) {
eobrun--;
return;
}
int k = spectralStart, e = spectralEnd;
while (k <= e) {
final int rs = decodeHuffman(component.huffmanTableAC);
final int s = rs & 15, r = rs >> 4;
if (s == 0) {
if (r < 15) {
eobrun = receive(r) + (1 << r) - 1;
break;
}
k += 16;
continue;
}
k += r;
final int z = dctZigZag[k];
zz[z] = receiveAndExtend(s) * (1 << successive);
k++;
}
}
}
class ACSuccessiveDecoder implements DecoderFunction {
public void decode(ComponentIn component, int[] zz) throws IOException {
int k = spectralStart, e = spectralEnd, r = 0;
while (k <= e) {
final int z = dctZigZag[k];
switch (successiveACState) {
case 0: // initial state
final int rs = decodeHuffman(component.huffmanTableAC);
final int s = rs & 15;
r = rs >> 4;
if (s == 0) {
if (r < 15) {
eobrun = receive(r) + (1 << r);
successiveACState = 4;
} else {
r = 16;
successiveACState = 1;
}
} else {
// if (s !== 1) {
if (s != 1) {
throw new CodecException("invalid ACn encoding");
}
successiveACNextValue = receiveAndExtend(s);
successiveACState = r != 0 ? 2 : 3;
}
continue;
case 1: // skipping r zero items
case 2:
if ( zz[z] != 0 ) {
zz[z] += (readBit() << successive);
} else {
r--;
if (r == 0) {
successiveACState = successiveACState == 2 ? 3 : 0;
}
}
break;
case 3: // set value for a zero item
if ( zz[z] != 0 ) {
zz[z] += (readBit() << successive);
} else {
zz[z] = successiveACNextValue << successive;
successiveACState = 0;
}
break;
case 4: // eob
if ( zz[z] != 0 ) {
zz[z] += (readBit() << successive);
}
break;
}
k++;
}
if (successiveACState == 4) {
eobrun--;
if (eobrun == 0) {
successiveACState = 0;
}
}
}
}
void decodeMcu(ComponentIn component, DecoderFunction decoder, int mcu, int row, int col) throws IOException {
final int mcuRow = (mcu / mcusPerLine) | 0;
final int mcuCol = mcu % mcusPerLine;
final int blockRow = mcuRow * component.v + row;
final int blockCol = mcuCol * component.h + col;
decoder.decode(component, component.getBlock(blockRow, blockCol));
}
void decodeBlock(ComponentIn component, DecoderFunction decoder, int mcu) throws IOException {
final int blockRow = (mcu / component.blocksPerLine) | 0;
final int blockCol = mcu % component.blocksPerLine;
decoder.decode(component, component.getBlock(blockRow, blockCol));
}
}
private final Decoder decoder = new Decoder();
/** wrong color space ..
private final void storeYCbCr2BGR(final PixelStorage pixelStorage, int x, int y, int Y, final int Cb, final int Cr)
{
if(Y<0) Y=0;
int B = Y + ( ( 116130 * Cb ) >> 16 ) ;
if(B<0) B=0;
else if(B>255) B=255;
int G = Y - ( ( 22554 * Cb + 46802 * Cr ) >> 16 ) ;
if(G<0) G=0;
else if(G>255) G=255;
int R = Y + ( ( 91881 * Cr ) >> 16 );
if(R<0) R=0;
else if(R>255) R=255;
pixelStorage.storeRGB(x, y, (byte)R, (byte)G, (byte)B);
} */
public synchronized void getPixel(JPEGDecoder.ColorSink pixelStorage, int width, int height) {
final int scaleX = this.width / width, scaleY = this.height / height;
final int componentCount = this.components.length;
final ColorSpace sourceCS = ( null != adobe ) ? adobe.colorSpace : ColorSpace.YCbCr;
final ColorSpace storageCS = pixelStorage.allocate(width, height, sourceCS, componentCount);
if( ColorSpace.RGB != storageCS && ColorSpace.YCbCr != storageCS ) {
throw new IllegalArgumentException("Unsupported storage color space: "+storageCS);
}
switch (componentCount) {
case 1: {
// Grayscale
final ComponentOut component1 = this.components[0];
for (int y = 0; y < height; y++) {
final byte[] component1Line = component1.getLine((int)(y * component1.scaleY * scaleY));
for (int x = 0; x < width; x++) {
final byte Y = component1Line[(int)(x * component1.scaleX * scaleX)];
if( ColorSpace.YCbCr == storageCS ) {
pixelStorage.storeYCbCr(x, y, Y, (byte)0, (byte)0);
} else {
pixelStorage.storeRGB(x, y, Y, Y, Y);
}
}
}
}
break;
case 2: {
// PDF might compress two component data in custom colorspace
final ComponentOut component1 = this.components[0];
final ComponentOut component2 = this.components[1];
for (int y = 0; y < height; y++) {
final int ys = y * scaleY;
final byte[] component1Line = component1.getLine((int)(ys * component1.scaleY));
final byte[] component2Line = component1.getLine((int)(ys * component2.scaleY));
for (int x = 0; x < width; x++) {
final int xs = x * scaleX;
final byte Y1 = component1Line[(int)(xs * component1.scaleX)];
final byte Y2 = component2Line[(int)(xs * component2.scaleX)];
pixelStorage.store2(x, y, Y1, Y2);
}
}
}
break;
case 3: {
if (ColorSpace.YCbCr != sourceCS) {
throw new CodecException("Unsupported source color space w 3 components: "+sourceCS);
}
final ComponentOut component1 = this.components[0];
final ComponentOut component2 = this.components[1];
final ComponentOut component3 = this.components[2];
for (int y = 0; y < height; y++) {
final int ys = y * scaleY;
final byte[] component1Line = component1.getLine((int)(ys * component1.scaleY));
final byte[] component2Line = component2.getLine((int)(ys * component2.scaleY));
final byte[] component3Line = component3.getLine((int)(ys * component3.scaleY));
if( ColorSpace.YCbCr == storageCS ) {
for (int x = 0; x < width; x++) {
final int xs = x * scaleX;
final byte Y = component1Line[(int)(xs * component1.scaleX)];
final byte Cb = component2Line[(int)(xs * component2.scaleX)];
final byte Cr = component3Line[(int)(xs * component3.scaleX)];
pixelStorage.storeYCbCr(x, y, Y, Cb, Cr);
}
} else {
for (int x = 0; x < width; x++) {
final int xs = x * scaleX;
final int Y = 0x000000FF & component1Line[(int)(xs * component1.scaleX)];
final int Cb = 0x000000FF & component2Line[(int)(xs * component2.scaleX)];
final int Cr = 0x000000FF & component3Line[(int)(xs * component3.scaleX)];
// storeYCbCr2BGR(pixelStorage, x, y, Y, Cb, Cr);
final byte R = clampTo8bit(Y + 1.402f * (Cr - 128f));
final byte G = clampTo8bit(Y - 0.3441363f * (Cb - 128f) - 0.71413636f * (Cr - 128f));
final byte B = clampTo8bit(Y + 1.772f * (Cb - 128f));
pixelStorage.storeRGB(x, y, R, G, B);
}
}
}
}
break;
case 4: {
if (ColorSpace.YCCK != sourceCS && ColorSpace.CMYK != sourceCS) {
throw new CodecException("Unsupported source color space w 4 components: "+sourceCS);
}
final ComponentOut component1 = this.components[0];
final ComponentOut component2 = this.components[1];
final ComponentOut component3 = this.components[2];
final ComponentOut component4 = this.components[3];
for (int y = 0; y < height; y++) {
final int ys = y * scaleY;
final byte[] component1Line = component1.getLine((int)(ys * component1.scaleY));
final byte[] component2Line = component2.getLine((int)(ys * component2.scaleY));
final byte[] component3Line = component3.getLine((int)(ys * component3.scaleY));
final byte[] component4Line = component4.getLine((int)(ys * component4.scaleY));
if( ColorSpace.YCbCr == storageCS ) {
if (ColorSpace.YCCK != sourceCS) {
throw new CodecException("Unsupported storage color space "+storageCS+" with source color space "+sourceCS);
}
for (int x = 0; x < width; x++) {
final int xs = x * scaleX;
final byte Y1 = component1Line[(int)(xs * component1.scaleX)];
final byte C1 = component2Line[(int)(xs * component2.scaleX)];
final byte C2 = component3Line[(int)(xs * component3.scaleX)];
// final byte K = component4Line[(int)(xs * component4.scaleX)];
// FIXME: YCCK is not really YCbCr, since K (black) is missing!
pixelStorage.storeYCbCr(x, y, Y1, C1, C2);
}
} else {
if (ColorSpace.CMYK == sourceCS) {
for (int x = 0; x < width; x++) {
final int xs = x * scaleX;
final int cC = 0x000000FF & component1Line[(int)(xs * component1.scaleX)];
final int cM = 0x000000FF & component2Line[(int)(xs * component2.scaleX)];
final int cY = 0x000000FF & component3Line[(int)(xs * component3.scaleX)];
final int cK = 0x000000FF & component4Line[(int)(xs * component4.scaleX)];
// CMYK -> RGB
final byte R = clampTo8bit( ( cC * cK ) / 255f );
final byte G = clampTo8bit( ( cM * cK ) / 255f );
final byte B = clampTo8bit( ( cY * cK ) / 255f );
pixelStorage.storeRGB(x, y, R, G, B);
}
} else { // ColorModel.YCCK == sourceCM
for (int x = 0; x < width; x++) {
final int xs = x * scaleX;
final int Y = 0x000000FF & component1Line[(int)(xs * component1.scaleX)];
final int Cb = 0x000000FF & component2Line[(int)(xs * component2.scaleX)];
final int Cr = 0x000000FF & component3Line[(int)(xs * component3.scaleX)];
final int cK = 0x000000FF & component4Line[(int)(xs * component4.scaleX)];
// YCCK -> 255f - [ R'G'B' ] -> CMYK
final float cC = 255f - ( Y + 1.402f * (Cr - 128f) );
final float cM = 255f - ( Y - 0.3441363f * (Cb - 128f) - 0.71413636f * (Cr - 128f) );
final float cY = 255f - ( Y + 1.772f * (Cb - 128f) );
// CMYK -> RGB
final byte R = clampTo8bit( ( cC * cK ) / 255f );
final byte G = clampTo8bit( ( cM * cK ) / 255f );
final byte B = clampTo8bit( ( cY * cK ) / 255f );
pixelStorage.storeRGB(x, y, R, G, B);
}
}
}
}
}
break;
default:
throw new CodecException("Unsupported color model: Space "+sourceCS+", components "+componentCount);
}
}
private static byte clampTo8bit(float a) {
return (byte) ( a < 0f ? 0 : a > 255f ? 255 : a );
}
private static String toHexString(int v) {
return "0x"+Integer.toHexString(v);
}
}