aboutsummaryrefslogtreecommitdiffstats
path: root/src/jogl
diff options
context:
space:
mode:
Diffstat (limited to 'src/jogl')
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/texture/TextureData.java15
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java57
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/texture/awt/AWTTextureData.java50
-rw-r--r--src/jogl/classes/com/jogamp/opengl/util/texture/spi/JPEGImage.java175
-rw-r--r--src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java1505
5 files changed, 1782 insertions, 20 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureData.java b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureData.java
index 96ee233fd..5b72bea82 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureData.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureData.java
@@ -54,6 +54,8 @@ import com.jogamp.opengl.util.GLBuffers;
*/
public class TextureData {
+ public static enum ColorSpace { RGB, YCbCr, YCCK, CMYK };
+
protected int width;
protected int height;
private int border;
@@ -77,6 +79,7 @@ public class TextureData {
protected boolean haveEXTABGR;
protected boolean haveGL12;
protected GLProfile glProfile;
+ protected ColorSpace pixelCS = ColorSpace.RGB;
/**
* Constructs a new TextureData object with the specified parameters
@@ -217,6 +220,18 @@ public class TextureData {
}
}
+ /**
+ * Returns the color space of the pixel data.
+ * @see #setColorSpace(ColorSpace)
+ */
+ public ColorSpace getColorSpace() { return pixelCS; }
+
+ /**
+ * Set the color space of the pixel data, which defaults to {@link ColorSpace#RGB}.
+ * @see #getColorSpace()
+ */
+ public void setColorSpace(ColorSpace cs) { pixelCS = cs; }
+
/** Used only by subclasses */
protected TextureData(GLProfile glp) { this.glProfile = glp; }
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java
index b878c6002..0b0af5625 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/TextureIO.java
@@ -63,6 +63,7 @@ import jogamp.opengl.Debug;
import com.jogamp.common.util.IOUtil;
import com.jogamp.opengl.util.texture.spi.DDSImage;
+import com.jogamp.opengl.util.texture.spi.JPEGImage;
import com.jogamp.opengl.util.texture.spi.NetPbmTextureWriter;
import com.jogamp.opengl.util.texture.spi.PNGImage;
import com.jogamp.opengl.util.texture.spi.SGIImage;
@@ -714,8 +715,12 @@ public class TextureIO {
// SPI support
//
- /** Adds a TextureProvider to support reading of a new file
- format. */
+ /**
+ * Adds a TextureProvider to support reading of a new file format.
+ * <p>
+ * The last provider added, will be the first provider to be tested.
+ * </p>
+ */
public static void addTextureProvider(TextureProvider provider) {
// Must always add at the front so the ImageIO provider is last,
// so we don't accidentally use it instead of a user's possibly
@@ -723,8 +728,12 @@ public class TextureIO {
textureProviders.add(0, provider);
}
- /** Adds a TextureWriter to support writing of a new file
- format. */
+ /**
+ * Adds a TextureWriter to support writing of a new file format.
+ * <p>
+ * The last provider added, will be the first provider to be tested.
+ * </p>
+ */
public static void addTextureWriter(TextureWriter writer) {
// Must always add at the front so the ImageIO writer is last,
// so we don't accidentally use it instead of a user's possibly
@@ -768,7 +777,7 @@ public class TextureIO {
private static List<TextureProvider> textureProviders = new ArrayList<TextureProvider>();
private static List<TextureWriter> textureWriters = new ArrayList<TextureWriter>();
- static {
+ static {
// ImageIO provider, the fall-back, must be the first one added
if(GLProfile.isAWTAvailable()) {
try {
@@ -787,6 +796,7 @@ public class TextureIO {
addTextureProvider(new DDSTextureProvider());
addTextureProvider(new SGITextureProvider());
addTextureProvider(new TGATextureProvider());
+ addTextureProvider(new JPGTextureProvider());
addTextureProvider(new PNGTextureProvider());
// ImageIO writer, the fall-back, must be the first one added
@@ -1174,6 +1184,43 @@ public class TextureIO {
}
//----------------------------------------------------------------------
+ // JPEG image provider
+ static class JPGTextureProvider extends StreamBasedTextureProvider {
+ public TextureData newTextureData(GLProfile glp, InputStream stream,
+ int internalFormat,
+ int pixelFormat,
+ boolean mipmap,
+ String fileSuffix) throws IOException {
+ if (JPG.equals(fileSuffix)) {
+ JPEGImage image = JPEGImage.read(/*glp, */ stream);
+ if (pixelFormat == 0) {
+ pixelFormat = image.getGLFormat();
+ }
+ if (internalFormat == 0) {
+ if(glp.isGL2GL3()) {
+ internalFormat = (image.getBytesPerPixel()==4)?GL.GL_RGBA8:GL.GL_RGB8;
+ } else {
+ internalFormat = (image.getBytesPerPixel()==4)?GL.GL_RGBA:GL.GL_RGB;
+ }
+ }
+ return new TextureData(glp, internalFormat,
+ image.getWidth(),
+ image.getHeight(),
+ 0,
+ pixelFormat,
+ image.getGLType(),
+ mipmap,
+ false,
+ false,
+ image.getData(),
+ null);
+ }
+
+ return null;
+ }
+ }
+
+ //----------------------------------------------------------------------
// DDS texture writer
//
static class DDSTextureWriter implements TextureWriter {
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/awt/AWTTextureData.java b/src/jogl/classes/com/jogamp/opengl/util/texture/awt/AWTTextureData.java
index ad96a9939..3b90fad65 100644
--- a/src/jogl/classes/com/jogamp/opengl/util/texture/awt/AWTTextureData.java
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/awt/AWTTextureData.java
@@ -37,15 +37,35 @@
package com.jogamp.opengl.util.texture.awt;
import java.awt.AlphaComposite;
-import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Transparency;
-import java.awt.color.*;
-import java.awt.image.*;
-import java.nio.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.ComponentColorModel;
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.DataBuffer;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferDouble;
+import java.awt.image.DataBufferFloat;
+import java.awt.image.DataBufferInt;
+import java.awt.image.DataBufferShort;
+import java.awt.image.DataBufferUShort;
+import java.awt.image.MultiPixelPackedSampleModel;
+import java.awt.image.SampleModel;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.awt.image.WritableRaster;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
-import javax.media.opengl.*;
-import com.jogamp.opengl.util.texture.*;
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+import javax.media.opengl.GL2GL3;
+import javax.media.opengl.GLException;
+import javax.media.opengl.GLProfile;
+
+import com.jogamp.opengl.util.texture.TextureData;
public class AWTTextureData extends TextureData {
// Mechanism for lazily converting input BufferedImages with custom
@@ -56,13 +76,13 @@ public class AWTTextureData extends TextureData {
private boolean expectingEXTABGR;
private boolean expectingGL12;
- private static final ColorModel rgbaColorModel =
- new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ private static final java.awt.image.ColorModel rgbaColorModel =
+ new ComponentColorModel(java.awt.color.ColorSpace.getInstance(java.awt.color.ColorSpace.CS_sRGB),
new int[] {8, 8, 8, 8}, true, true,
Transparency.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
- private static final ColorModel rgbColorModel =
- new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ private static final java.awt.image.ColorModel rgbColorModel =
+ new ComponentColorModel(java.awt.color.ColorSpace.getInstance(java.awt.color.ColorSpace.CS_sRGB),
new int[] {8, 8, 8, 0}, false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE);
@@ -282,7 +302,7 @@ public class AWTTextureData extends TextureData {
case BufferedImage.TYPE_BYTE_INDEXED:
case BufferedImage.TYPE_CUSTOM:
default:
- ColorModel cm = image.getColorModel();
+ java.awt.image.ColorModel cm = image.getColorModel();
if (cm.equals(rgbColorModel)) {
pixelFormat = GL.GL_RGB;
pixelType = GL.GL_UNSIGNED_BYTE;
@@ -350,7 +370,7 @@ public class AWTTextureData extends TextureData {
case BufferedImage.TYPE_BYTE_INDEXED:
case BufferedImage.TYPE_CUSTOM:
default:
- ColorModel cm = image.getColorModel();
+ java.awt.image.ColorModel cm = image.getColorModel();
if (cm.equals(rgbColorModel)) {
pixelFormat = GL.GL_RGB;
pixelType = GL.GL_UNSIGNED_BYTE;
@@ -409,7 +429,7 @@ public class AWTTextureData extends TextureData {
// create a temporary image that is compatible with OpenGL
boolean hasAlpha = image.getColorModel().hasAlpha();
- ColorModel cm = null;
+ java.awt.image.ColorModel cm = null;
int dataBufferType = image.getRaster().getDataBuffer().getDataType();
// Don't use integer components for packed int images
if (isPackedInt(image)) {
@@ -419,12 +439,12 @@ public class AWTTextureData extends TextureData {
cm = hasAlpha ? rgbaColorModel : rgbColorModel;
} else {
if (hasAlpha) {
- cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ cm = new ComponentColorModel(java.awt.color.ColorSpace.getInstance(java.awt.color.ColorSpace.CS_sRGB),
null, true, true,
Transparency.TRANSLUCENT,
dataBufferType);
} else {
- cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+ cm = new ComponentColorModel(java.awt.color.ColorSpace.getInstance(java.awt.color.ColorSpace.CS_sRGB),
null, false, false,
Transparency.OPAQUE,
dataBufferType);
diff --git a/src/jogl/classes/com/jogamp/opengl/util/texture/spi/JPEGImage.java b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/JPEGImage.java
new file mode 100644
index 000000000..71dd53939
--- /dev/null
+++ b/src/jogl/classes/com/jogamp/opengl/util/texture/spi/JPEGImage.java
@@ -0,0 +1,175 @@
+/**
+ * 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 com.jogamp.opengl.util.texture.spi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import javax.media.opengl.GL;
+
+import jogamp.opengl.Debug;
+import jogamp.opengl.util.jpeg.JPEGDecoder;
+
+import com.jogamp.common.nio.Buffers;
+import com.jogamp.opengl.util.texture.TextureData.ColorSpace;
+
+public class JPEGImage {
+ private static final boolean DEBUG = Debug.debug("JPEGImage");
+
+
+ /**
+ * Reads a JPEG image from the specified InputStream, using the given color space for storage.
+ *
+ * @param in
+ * @param cs Storage color space, either {@link ColorSpace#RGB} or {@link ColorSpace#YCbCr}. {@link ColorSpace#YCCK} and {@link ColorSpace#CMYK} will throw an exception!
+ * @return
+ * @throws IOException
+ */
+ public static JPEGImage read(InputStream in, ColorSpace cs) throws IOException {
+ return new JPEGImage(in, cs);
+ }
+
+ /** Reads a JPEG image from the specified InputStream, using the {@link ColorSpace#RGB}. */
+ public static JPEGImage read(InputStream in) throws IOException {
+ return new JPEGImage(in, ColorSpace.RGB);
+ }
+
+ private static class JPEGPixelStorage implements JPEGDecoder.PixelStorage {
+ int width=0, height=0;
+ int sourceComponents=0;
+ ColorSpace sourceCS = ColorSpace.YCbCr;
+ int storageComponents;
+ final ColorSpace storageCS;
+ ByteBuffer data = null;
+
+ JPEGPixelStorage(ColorSpace storageCM) {
+ this.storageCS = storageCM;
+ switch(storageCS) {
+ case RGB:
+ case YCbCr:
+ storageComponents = 3;
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported storage color-space: "+storageCS);
+ }
+ }
+
+ @Override
+ public final ColorSpace allocate(int width, int height, ColorSpace sourceCM, int sourceComponents) throws RuntimeException {
+ this.width = width;
+ this.height = height;
+ this.sourceComponents = sourceComponents;
+ this.sourceCS = sourceCM;
+ this.data = Buffers.newDirectByteBuffer(width * height * storageComponents);
+ return storageCS;
+ }
+
+ @Override
+ public final void storeRGB(int x, int y, byte r, byte g, byte b) {
+ int i = ( ( height - y - 1 ) * width + x ) * storageComponents;
+ data.put(i++, r);
+ data.put(i++, g);
+ data.put(i++, b);
+ // data.put(i++, (byte)0xff);
+ }
+
+ @Override
+ public final void store2(int x, int y, byte c1, byte c2) {
+ throw new RuntimeException("not supported yet");
+ }
+
+ @Override
+ public final void storeYCbCr(int x, int y, byte Y, byte Cb, byte Cr) {
+ int i = ( ( height - y - 1 ) * width + x ) * storageComponents;
+ data.put(i++, Y);
+ data.put(i++, Cb);
+ data.put(i++, Cr);
+ }
+
+ public String toString() {
+ return "JPEGPixels["+width+"x"+height+", sourceComp "+sourceComponents+", sourceCS "+sourceCS+", storageCS "+storageCS+", storageComp "+storageComponents+"]";
+ }
+ };
+
+ private JPEGImage(InputStream in, ColorSpace cs) throws IOException {
+ pixelStorage = new JPEGPixelStorage(cs);
+ final JPEGDecoder decoder = new JPEGDecoder();
+ decoder.parse(in);
+ pixelWidth = decoder.getWidth();
+ pixelHeight = decoder.getHeight();
+ decoder.getPixel(pixelStorage, pixelWidth, pixelHeight);
+ data = pixelStorage.data;
+ final boolean hasAlpha = false;
+
+ bytesPerPixel = 3;
+ glFormat = GL.GL_RGB;
+ reversedChannels = false; // RGB[A]
+ if(DEBUG) {
+ System.err.println("JPEGImage: alpha "+hasAlpha+", bytesPerPixel "+bytesPerPixel+
+ ", pixels "+pixelWidth+"x"+pixelHeight+", glFormat 0x"+Integer.toHexString(glFormat));
+ System.err.println("JPEGImage: "+decoder);
+ System.err.println("JPEGImage: "+pixelStorage);
+ }
+ decoder.clear(null);
+ }
+ private JPEGPixelStorage pixelStorage;
+ private final int pixelWidth, pixelHeight, glFormat, bytesPerPixel;
+ private boolean reversedChannels;
+ private final ByteBuffer data;
+
+ /** Returns the color space of the pixel data */
+ public ColorSpace getColorSpace() { return pixelStorage.storageCS; }
+
+ /** Returns the number of components of the pixel data */
+ public int getComponentCount() { return pixelStorage.storageComponents; }
+
+ /** Returns the width of the image. */
+ public int getWidth() { return pixelWidth; }
+
+ /** Returns the height of the image. */
+ public int getHeight() { return pixelHeight; }
+
+ /** Returns true if data has the channels reversed to BGR or BGRA, otherwise RGB or RGBA is expected. */
+ public boolean getHasReversedChannels() { return reversedChannels; }
+
+ /** Returns the OpenGL format for this texture; e.g. GL.GL_LUMINANCE, GL.GL_RGB or GL.GL_RGBA. */
+ public int getGLFormat() { return glFormat; }
+
+ /** Returns the OpenGL data type: GL.GL_UNSIGNED_BYTE. */
+ public int getGLType() { return GL.GL_UNSIGNED_BYTE; }
+
+ /** Returns the bytes per pixel */
+ public int getBytesPerPixel() { return bytesPerPixel; }
+
+ /** Returns the raw data for this texture in the correct
+ (bottom-to-top) order for calls to glTexImage2D. */
+ public ByteBuffer getData() { return data; }
+
+ public String toString() { return "JPEGImage["+pixelWidth+"x"+pixelHeight+", bytesPerPixel "+bytesPerPixel+", reversedChannels "+reversedChannels+", "+pixelStorage+", "+data+"]"; }
+}
diff --git a/src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java b/src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java
new file mode 100644
index 000000000..748e70d5b
--- /dev/null
+++ b/src/jogl/classes/jogamp/opengl/util/jpeg/JPEGDecoder.java
@@ -0,0 +1,1505 @@
+/**
+ * Original JavaScript code from <https://github.com/notmasteryet/jpgjs/blob/master/jpg.js>,
+ * 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.ColorSpace;
+
+/**
+ *
+ * <ul>
+ * <li> The JPEG specification can be found in the ITU CCITT Recommendation T.81
+ * (www.w3.org/Graphics/JPEG/itu-t81.pdf) </li>
+ * <li> The JFIF specification can be found in the JPEG File Interchange Format
+ * (www.w3.org/Graphics/JPEG/jfif3.pdf)</li>
+ * <li> 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)</li>
+ * <li> http://halicery.com/jpeg/huffman.html </li>
+ * <li> https://en.wikipedia.org/wiki/Jpg#Syntax_and_structure </li>
+ * <li> http://www.cs.sfu.ca/CourseCentral/365/mark/material/notes/Chap4/Chap4.2/Chap4.2.html </li>
+ * <li> https://github.com/notmasteryet/jpgjs/blob/master/jpg.js </li>
+ * </ul>
+ */
+public class JPEGDecoder {
+ private static final boolean DEBUG = Debug.debug("JPEGImage");
+ private static final boolean DEBUG_IN = false;
+
+ public static interface PixelStorage {
+ /**
+ * @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 ColorSpace#RGB} or {@link ColorSpace#YCbCr}. {@link ColorSpace#YCCK} and {@link ColorSpace#CMYK} will throw an exception!
+ * @throws RuntimeException
+ */
+ public ColorSpace allocate(int width, int height, 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<Integer> compIDs;
+ private final ComponentIn[] comps;
+ private final int compCount;
+ int maxCompID;
+ int maxH;
+ int maxV;
+ int mcusPerLine;
+ int mcusPerColumn;
+
+ Frame(boolean progressive, int precision, int scanLines, int samplesPerLine, int componentsCount) {
+ this.progressive = progressive;
+ this.precision = precision;
+ this.scanLines = scanLines;
+ this.samplesPerLine = samplesPerLine;
+ compIDs = new ArrayHashSet<Integer>(componentsCount);
+ comps = new ComponentIn[componentsCount];
+ this.compCount = componentsCount;
+ }
+
+ private final void checkBounds(int idx) {
+ if( 0 > idx || idx >= compCount ) {
+ throw new CodecException("Idx out of bounds "+idx+", "+this);
+ }
+ }
+
+ 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;
+ final int[] quantizationTable;
+ 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[] quantizationTable) {
+ this.h = h;
+ this.v = v;
+ this.quantizationTable = quantizationTable;
+ }
+
+ 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+", blocks["+blocksPerColumn+", mcu "+blocksPerColumnForMcu+"]["+blocksPerLine+", mcu "+blocksPerLineForMcu+"][64]]";
+ }
+ }
+
+ /** The decoded components */
+ class ComponentOut {
+ private final ArrayList<byte[]> lines;
+ final float scaleX;
+ final float scaleY;
+
+ ComponentOut(ArrayList<byte[]> 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<len){
+ data[i++] = (byte)readUint8(); count++;
+ }
+ if(DEBUG_IN) { System.err.println("JPEG.readDataBlock: net-len "+(len-2)+", "+this); dumpData(data, 0, len-2); }
+ return data;
+ }
+ static final void dumpData(byte[] data, int offset, int len) {
+ for(int i=0; i<len; ) {
+ System.err.print(i%8+": ");
+ for(int j=0; j<8 && i<len; j++, i++) {
+ System.err.println(toHexString(0x000000FF & data[offset+i])+", ");
+ }
+ System.err.println("");
+ }
+ }
+
+ public synchronized void clear(InputStream inputStream) {
+ resetInput(inputStream);
+ width = 0;
+ height = 0;
+ jfif = null;
+ exif = null;
+ adobe = null;
+ components = null;
+ }
+ public synchronized JPEGDecoder parse(final InputStream inputStream) throws IOException {
+ clear(inputStream);
+ Frame frame = null;
+ int resetInterval = 0;
+ int[][] quantizationTables = new int[0x0F][]; // 4 bits
+ ArrayList<Frame> frames = new ArrayList<Frame>();
+ final BinObj[] huffmanTablesAC = new BinObj[0x0F]; // Huffman table spec - 4 bits
+ final BinObj[] huffmanTablesDC = new BinObj[0x0F]; // Huffman table spec - 4 bits
+ 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[] 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);
+ }
+ quantizationTables[quantizationTableSpec & 0x0F] = tableData;
+ }
+ if(count!=quantizationTablesLength){
+ throw new CodecException("ERROR: QTT format error [count!=Length]");
+ }
+ fileMarker = 0; // consumed and get-next
+ }
+ break;
+
+ case M_SOF0:
+ case M_SOF2: {
+ 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);
+ 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 qId = readUint8(); count++;
+ frame.putOrdered(componentId, new ComponentIn(h, v, quantizationTables[qId]));
+ }
+ if(count!=sofLen){
+ throw new CodecException("ERROR: SOF format error [count!=Length]");
+ }
+ prepareComponents(frame);
+ frames.add(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<ComponentIn> components = new ArrayList<ComponentIn>();
+ 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); }
+ if ( frames.size() != 1 ) {
+ throw new CodecException("only single frame JPEGs supported");
+ }
+
+ final int compCount = frame.getCompCount();
+ this.components = new ComponentOut[compCount];
+ for (int i = 0; i < compCount; i++) {
+ final ComponentIn component = frame.getCompByIndex(i);
+ 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<compCount; i++) {
+ final ComponentIn component = frame.getCompByIndex(i);
+ if (maxH < component.h) maxH = component.h;
+ if (maxV < component.v) maxV = component.v;
+ }
+ int mcusPerLine = (int) Math.ceil((float)frame.samplesPerLine / 8f / (float)maxH);
+ int mcusPerColumn = (int) Math.ceil((float)frame.scanLines / 8f / (float)maxV);
+ // for (componentId in frame.components) {
+ for (int i=0; i<compCount; i++) {
+ final ComponentIn component = frame.getCompByIndex(i);
+ final int blocksPerLine = (int) Math.ceil(Math.ceil((float)frame.samplesPerLine / 8f) * component.h / maxH);
+ final int blocksPerColumn = (int) Math.ceil(Math.ceil((float)frame.scanLines / 8f) * component.v / maxV);
+ final int blocksPerLineForMcu = mcusPerLine * component.h;
+ final int blocksPerColumnForMcu = mcusPerColumn * component.v;
+ component.allocateBlocks(blocksPerColumn, blocksPerColumnForMcu, blocksPerLine, blocksPerLineForMcu);
+ }
+ frame.maxH = maxH;
+ frame.maxV = maxV;
+ frame.mcusPerLine = mcusPerLine;
+ frame.mcusPerColumn = mcusPerColumn;
+ }
+
+ private static class BinObjIdxed {
+ final BinObj children;
+ byte index;
+ BinObjIdxed() {
+ this.children = new BinObj();
+ this.index = 0;
+ }
+ }
+ private static class BinObj {
+ final boolean isValue;
+ final BinObj[] tree;
+ final byte b;
+
+ BinObj(byte b) {
+ this.isValue= true;
+ this.b = b;
+ this.tree = null;
+ }
+ BinObj() {
+ this.isValue= false;
+ this.b = (byte)0;
+ this.tree = new BinObj[2];
+ }
+ final byte getValue() { return b; }
+ final BinObj get(int i) { return tree[i]; }
+ final void set(byte i, byte v) { tree[i] = new BinObj(v); }
+ final void set(byte i, BinObj v) { tree[i] = v; }
+ }
+
+ private BinObj buildHuffmanTable(int[] codeLengths, byte[] values) {
+ int k = 0;
+ int length = 16;
+ final ArrayList<BinObjIdxed> code = new ArrayList<BinObjIdxed>();
+ 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<byte[]> buildComponentData(Frame frame, ComponentIn component) {
+ ArrayList<byte[]> lines = new ArrayList<byte[]>();
+ 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++) {
+ int scanLine = blockRow << 3;
+ for (int i = 0; i < 8; i++) {
+ lines.add(new byte[samplesPerLine]);
+ }
+ for (int blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+ quantizeAndInverse(component.getBlock(blockRow, blockCol), r, R, component.quantizationTable);
+
+ 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<ComponentIn> 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(PixelStorage 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);
+ }
+} \ No newline at end of file