diff --git a/.gitignore b/.gitignore
index 43e23f6d5..6724a8300 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,3 +58,5 @@ hs_err_pid*.log
/version.properties
/tools.properties
/nbproject/private/
+/libsrc/cmykjpeg/build/
+/libsrc/cmykjpeg/nbproject/private/
\ No newline at end of file
diff --git a/lib/cmykjpeg.jar b/lib/cmykjpeg.jar
new file mode 100644
index 000000000..179d50c90
Binary files /dev/null and b/lib/cmykjpeg.jar differ
diff --git a/libsrc/cmykjpeg/build.xml b/libsrc/cmykjpeg/build.xml
new file mode 100644
index 000000000..81f02213d
--- /dev/null
+++ b/libsrc/cmykjpeg/build.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+ * Closing a {@code ByteArrayImageInputStream} has no effect. The methods in
+ * this class can be called after the stream has been closed without
+ * generating an {@code IOException}.
+ *
+ * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau
+ * @version $Id: ByteArrayImageInputStream.java 299 2013-01-03 07:40:18Z werner $
+ */
+public class ByteArrayImageInputStream extends ImageInputStreamImpl2 {
+ /**
+ * An array of bytes that was provided
+ * by the creator of the stream. Elements buf[0]
+ * through buf[count-1] are the
+ * only bytes that can ever be read from the
+ * stream; element buf[streamPos] is
+ * the next byte to be read.
+ */
+ protected byte buf[];
+
+ /**
+ * The index one greater than the last valid character in the input
+ * stream buffer.
+ * This value should always be nonnegative
+ * and not larger than the length of buf.
+ * It is one greater than the position of
+ * the last byte within buf that
+ * can ever be read from the input stream buffer.
+ */
+ protected int count;
+
+ /** The offset to the start of the array. */
+ private final int arrayOffset;
+
+ public ByteArrayImageInputStream(byte[] buf) {
+ this(buf, ByteOrder.BIG_ENDIAN);
+ }
+
+ public ByteArrayImageInputStream(byte[] buf, ByteOrder byteOrder) {
+ this(buf, 0, buf.length, byteOrder);
+ }
+
+ public ByteArrayImageInputStream(byte[] buf, int offset, int length, ByteOrder byteOrder) {
+ this.buf = buf;
+ this.streamPos = offset;
+ this.count = Math.min(offset + length, buf.length);
+ this.arrayOffset = offset;
+ this.byteOrder = byteOrder;
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. The value
+ * byte is returned as an int in the range
+ * 0 to 255. If no byte is available
+ * because the end of the stream has been reached, the value
+ * -1 is returned.
+ *
+ * This read method
+ * cannot block.
+ *
+ * @return the next byte of data, or -1 if the end of the
+ * stream has been reached.
+ */
+ @Override
+ public synchronized int read() {
+ flushBits();
+ return (streamPos < count) ? (buf[(int)(streamPos++)] & 0xff) : -1;
+ }
+
+ /**
+ * Reads up to len bytes of data into an array of bytes
+ * from this input stream.
+ * If streamPos equals count,
+ * then -1 is returned to indicate
+ * end of file. Otherwise, the number k
+ * of bytes read is equal to the smaller of
+ * len and count-streamPos.
+ * If k is positive, then bytes
+ * buf[streamPos] through buf[streamPos+k-1]
+ * are copied into b[off] through
+ * b[off+k-1] in the manner performed
+ * by System.arraycopy. The
+ * value k is added into streamPos
+ * and k is returned.
+ *
+ * This read method cannot block.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the destination array b
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1 if there is no more data because the end of
+ * the stream has been reached.
+ * @exception NullPointerException If b is null.
+ * @exception IndexOutOfBoundsException If off is negative,
+ * len is negative, or len is greater than
+ * b.length - off
+ */
+ @Override
+ public synchronized int read(byte b[], int off, int len) {
+ flushBits();
+ if (b == null) {
+ throw new NullPointerException();
+ } else if (off < 0 || len < 0 || len > b.length - off) {
+ throw new IndexOutOfBoundsException();
+ }
+ if (streamPos >= count) {
+ return -1;
+ }
+ if (streamPos + len > count) {
+ len = (int)(count - streamPos);
+ }
+ if (len <= 0) {
+ return 0;
+ }
+ System.arraycopy(buf, (int)streamPos, b, off, len);
+ streamPos += len;
+ return len;
+ }
+
+ /**
+ * Skips n bytes of input from this input stream. Fewer
+ * bytes might be skipped if the end of the input stream is reached.
+ * The actual number k
+ * of bytes to be skipped is equal to the smaller
+ * of n and count-streamPos.
+ * The value k is added into streamPos
+ * and k is returned.
+ *
+ * @param n the number of bytes to be skipped.
+ * @return the actual number of bytes skipped.
+ */
+ public synchronized long skip(long n) {
+ if (streamPos + n > count) {
+ n = count - streamPos;
+ }
+ if (n < 0) {
+ return 0;
+ }
+ streamPos += n;
+ return n;
+ }
+
+ /**
+ * Returns the number of remaining bytes that can be read (or skipped over)
+ * from this input stream.
+ *
+ * The value returned is count - streamPos,
+ * which is the number of bytes remaining to be read from the input buffer.
+ *
+ * @return the number of remaining bytes that can be read (or skipped
+ * over) from this input stream without blocking.
+ */
+ public synchronized int available() {
+ return (int)(count - streamPos);
+ }
+
+
+
+ /**
+ * Closing a ByteArrayInputStream has no effect. The methods in
+ * this class can be called after the stream has been closed without
+ * generating an IOException.
+ *
+ */
+ @Override
+ public void close() {
+ // does nothing!!
+ }
+
+ @Override
+ public long getStreamPosition() throws IOException {
+ checkClosed();
+ return streamPos-arrayOffset;
+ }
+ @Override
+ public void seek(long pos) throws IOException {
+ checkClosed();
+ flushBits();
+
+ // This test also covers pos < 0
+ if (pos < flushedPos) {
+ throw new IndexOutOfBoundsException("pos < flushedPos!");
+ }
+
+ this.streamPos = pos+arrayOffset;
+ }
+
+ private void flushBits() {
+ bitOffset=0;
+ }
+ @Override
+ public long length() {
+ return count-arrayOffset;
+ }
+}
diff --git a/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamAdapter.java b/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamAdapter.java
new file mode 100644
index 000000000..30fd47813
--- /dev/null
+++ b/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamAdapter.java
@@ -0,0 +1,189 @@
+/*
+ * @(#)ImageInputStreamAdapter.java 1.0 2009-12-17
+ *
+ * Copyright (c) 2009 Werner Randelshofer, Goldau, Switzerland.
+ * All rights reserved.
+ *
+ * You may not use, copy or modify this file, except in compliance with the
+ * license agreement you entered into with Werner Randelshofer.
+ * For details see accompanying license terms.
+ */
+
+package org.monte.media.io;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import javax.imageio.stream.ImageInputStream;
+
+/**
+ * ImageInputStreamAdapter.
+ *
+ * @author Werner Randelshofer
+ * @version 1.0 2009-12-17 Created.
+ */
+public class ImageInputStreamAdapter extends FilterInputStream {
+ private ImageInputStream iis;
+ public ImageInputStreamAdapter(ImageInputStream iis) {
+ super(null);
+ this.iis=iis;
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. The value
+ * byte is returned as an int in the range
+ * 0 to 255. If no byte is available
+ * because the end of the stream has been reached, the value
+ * -1 is returned. This method blocks until input data
+ * is available, the end of the stream is detected, or an exception
+ * is thrown.
+ *
+ * This method
+ * simply performs in.read() and returns the result.
+ *
+ * @return the next byte of data, or -1 if the end of the
+ * stream is reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public int read() throws IOException {
+ return iis.read();
+ }
+
+ /**
+ * Reads up to len bytes of data from this input stream
+ * into an array of bytes. If len is not zero, the method
+ * blocks until some input is available; otherwise, no
+ * bytes are read and 0 is returned.
+ *
+ * This method simply performs in.read(b, off, len)
+ * and returns the result.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset in the destination array b
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1 if there is no more data because the end of
+ * the stream has been reached.
+ * @exception NullPointerException If b is null.
+ * @exception IndexOutOfBoundsException If off is negative,
+ * len is negative, or len is greater than
+ * b.length - off
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ return iis.read(b, off, len);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * This method simply performs in.skip(n).
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ return iis.skipBytes(n);
+ }
+
+ /**
+ * Returns an estimate of the number of bytes that can be read (or
+ * skipped over) from this input stream without blocking by the next
+ * caller of a method for this input stream. The next caller might be
+ * the same thread or another thread. A single read or skip of this
+ * many bytes will not block, but may read or skip fewer bytes.
+ *
+ * This method returns the result of {@link #in in}.available().
+ *
+ * @return an estimate of the number of bytes that can be read (or skipped
+ * over) from this input stream without blocking.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public int available() throws IOException {
+ return (iis.isCached()) ? //
+ (int)Math.min(Integer.MAX_VALUE, iis.length() - iis.getStreamPosition()) :
+ 0;
+ }
+
+ /**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ * This
+ * method simply performs in.close().
+ *
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public void close() throws IOException {
+ iis.close();
+ }
+
+ /**
+ * Marks the current position in this input stream. A subsequent
+ * call to the reset method repositions this stream at
+ * the last marked position so that subsequent reads re-read the same bytes.
+ *
+ * The readlimit argument tells this input stream to
+ * allow that many bytes to be read before the mark position gets
+ * invalidated.
+ *
+ * This method simply performs in.mark(readlimit).
+ *
+ * @param readlimit the maximum limit of bytes that can be read before
+ * the mark position becomes invalid.
+ * @see java.io.FilterInputStream#in
+ * @see java.io.FilterInputStream#reset()
+ */
+ @Override
+ public synchronized void mark(int readlimit) {
+ iis.mark();
+ }
+
+ /**
+ * Repositions this stream to the position at the time the
+ * mark method was last called on this input stream.
+ *
+ * This method
+ * simply performs in.reset().
+ *
+ * Stream marks are intended to be used in
+ * situations where you need to read ahead a little to see what's in
+ * the stream. Often this is most easily done by invoking some
+ * general parser. If the stream is of the type handled by the
+ * parse, it just chugs along happily. If the stream is not of
+ * that type, the parser should toss an exception when it fails.
+ * If this happens within readlimit bytes, it allows the outer
+ * code to reset the stream and try another parser.
+ *
+ * @exception IOException if the stream has not been marked or if the
+ * mark has been invalidated.
+ * @see java.io.FilterInputStream#in
+ * @see java.io.FilterInputStream#mark(int)
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ iis.reset();
+ }
+
+ /**
+ * Tests if this input stream supports the mark
+ * and reset methods.
+ * This method
+ * simply performs in.markSupported().
+ *
+ * @return true if this stream type supports the
+ * mark and reset method;
+ * false otherwise.
+ * @see java.io.FilterInputStream#in
+ * @see java.io.InputStream#mark(int)
+ * @see java.io.InputStream#reset()
+ */
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+}
diff --git a/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamImpl2.java b/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamImpl2.java
new file mode 100644
index 000000000..501224398
--- /dev/null
+++ b/libsrc/cmykjpeg/src/org/monte/media/io/ImageInputStreamImpl2.java
@@ -0,0 +1,65 @@
+/*
+ * @(#)ImageInputStreamImpl2.java
+ *
+ * Copyright (c) 2011 Werner Randelshofer, Goldau, Switzerland.
+ * All rights reserved.
+ *
+ * You may not use, copy or modify this file, except in compliance with the
+ * license agreement you entered into with Werner Randelshofer.
+ * For details see accompanying license terms.
+ */
+package org.monte.media.io;
+
+import java.io.IOException;
+import java.nio.ByteOrder;
+import javax.imageio.stream.ImageInputStreamImpl;
+
+/**
+ * {@code ImageInputStreamImpl2} fixes bugs in ImageInputStreamImpl.
+ *
+ * ImageInputStreamImpl uses read(byte[]) instead of readFully(byte[]) inside of
+ * readShort. This results in corrupt data input if the underlying stream can
+ * not fulfill the read operation in a single step.
+ *
+ * @author Werner Randelshofer
+ * @version $Id: ImageInputStreamImpl2.java 299 2013-01-03 07:40:18Z werner $
+ */
+public abstract class ImageInputStreamImpl2 extends ImageInputStreamImpl {
+ // Length of the buffer used for readFully(type[], int, int)
+ private static final int BYTE_BUF_LENGTH = 8192;
+ /**
+ * Byte buffer used for readFully(type[], int, int). Note that this
+ * array is also used for bulk reads in readShort(), readInt(), etc, so
+ * it should be large enough to hold a primitive value (i.e. >= 8 bytes).
+ * Also note that this array is package protected, so that it can be
+ * used by ImageOutputStreamImpl in a similar manner.
+ */
+ byte[] byteBuf = new byte[BYTE_BUF_LENGTH];
+
+ @Override
+ public short readShort() throws IOException {
+ readFully(byteBuf, 0, 2);
+
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ return (short)
+ (((byteBuf[0] & 0xff) << 8) | ((byteBuf[1] & 0xff) << 0));
+ } else {
+ return (short)
+ (((byteBuf[1] & 0xff) << 8) | ((byteBuf[0] & 0xff) << 0));
+ }
+ }
+ public int readInt() throws IOException {
+ readFully(byteBuf, 0, 4);
+
+ if (byteOrder == ByteOrder.BIG_ENDIAN) {
+ return
+ (((byteBuf[0] & 0xff) << 24) | ((byteBuf[1] & 0xff) << 16) |
+ ((byteBuf[2] & 0xff) << 8) | ((byteBuf[3] & 0xff) << 0));
+ } else {
+ return
+ (((byteBuf[3] & 0xff) << 24) | ((byteBuf[2] & 0xff) << 16) |
+ ((byteBuf[1] & 0xff) << 8) | ((byteBuf[0] & 0xff) << 0));
+ }
+ }
+
+}
diff --git a/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReader.java b/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReader.java
new file mode 100644
index 000000000..206472bc4
--- /dev/null
+++ b/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReader.java
@@ -0,0 +1,823 @@
+/*
+ * @(#)CMYJKJPEGImageReader.java
+ *
+ * Copyright (c) 2010-2011 Werner Randelshofer, Goldau, Switzerland.
+ * All rights reserved.
+ *
+ * You may not use, copy or modify this file, except in compliance with the
+ * license agreement you entered into with Werner Randelshofer.
+ * For details see accompanying license terms.
+ */
+package org.monte.media.jpeg;
+
+import com.sun.imageio.plugins.jpeg.JPEGImageReader;
+import java.util.Iterator;
+import java.util.LinkedList;
+import javax.imageio.metadata.IIOMetadata;
+import org.monte.media.io.ByteArrayImageInputStream;
+import org.monte.media.io.ImageInputStreamAdapter;
+//import com.sun.imageio.plugins.jpeg.JPEGImageReader;
+import java.awt.color.ColorSpace;
+import java.awt.color.ICC_ColorSpace;
+import java.awt.color.ICC_Profile;
+import java.awt.image.*;
+import java.io.*;
+import javax.imageio.*;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.*;
+import static java.lang.Math.*;
+
+/**
+ * Reads a JPEG image with colors in the CMYK color space.
+ *
+ * @author Werner Randelshofer
+ * @version $Id: CMYKJPEGImageReader.java 308 2013-01-06 11:24:06Z werner $
+ */
+public class CMYKJPEGImageReader extends ImageReader {
+
+ private boolean isIgnoreICCProfile = false;
+ private boolean isYCCKInversed = true;
+ private static DirectColorModel RGB = new DirectColorModel(24, 0xff0000, 0xff00, 0xff, 0x0);
+ /**
+ * When we read the header, we read the whole image.
+ */
+ private BufferedImage image;
+
+ public CMYKJPEGImageReader(ImageReaderSpi originatingProvider) {
+ super(originatingProvider);
+ }
+
+ @Override
+ public int getNumImages(boolean allowSearch) throws IOException {
+ return 1;
+ }
+
+ @Override
+ public int getWidth(int imageIndex) throws IOException {
+ readHeader();
+ return image.getWidth();
+ }
+
+ @Override
+ public int getHeight(int imageIndex) throws IOException {
+ readHeader();
+ return image.getHeight();
+ }
+
+ @Override
+ public Iterator
+ * Use this method, if you have already determined that the input stream
+ * contains a CMYK JPEG image.
+ *
+ * @param in An InputStream, preferably an ImageInputStream, in the JPEG
+ * File Interchange Format (JFIF).
+ * @param cmykProfile An ICC_Profile for conversion from the CMYK color
+ * space to the RGB color space. If this parameter is null, a default
+ * profile is used.
+ * @return a BufferedImage containing the decoded image converted into the
+ * RGB color space.
+ * @throws java.io.IOException
+ */
+ public static BufferedImage readRGBImageFromCMYK(InputStream in, ICC_Profile cmykProfile) throws IOException {
+ ImageInputStream inputStream = null;
+ ImageReader reader = createNativeJPEGReader();
+ inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in);
+ reader.setInput(inputStream);
+ Raster raster = reader.readRaster(0, null);
+ BufferedImage image = createRGBImageFromCMYK(raster, cmykProfile);
+ return image;
+ }
+
+ /**
+ * Reads a RGBA JPEG image from the provided InputStream, converting the
+ * colors to RGBA using the provided RGBA ICC_Profile. The image data must
+ * be in the RGBA color space.
+ *
+ * Use this method, if you have already determined that the input stream
+ * contains a RGBA JPEG image.
+ *
+ * @param in An InputStream, preferably an ImageInputStream, in the JPEG
+ * File Interchange Format (JFIF).
+ * @param rgbaProfile An ICC_Profile for conversion from the RGBA color
+ * space to the RGBA color space. If this parameter is null, a default
+ * profile is used.
+ * @return a BufferedImage containing the decoded image converted into the
+ * RGB color space.
+ * @throws java.io.IOException
+ */
+ public static BufferedImage readRGBAImageFromRGBA(InputStream in, ICC_Profile rgbaProfile) throws IOException {
+ ImageInputStream inputStream = null;
+ ImageReader reader = createNativeJPEGReader();
+ inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in);
+ reader.setInput(inputStream);
+ Raster raster = reader.readRaster(0, null);
+ BufferedImage image = createRGBAImageFromRGBA(raster, rgbaProfile);
+ return image;
+ }
+
+ public static BufferedImage readRGBImageFromRGB(InputStream in, ICC_Profile rgbaProfile) throws IOException {
+ ImageInputStream inputStream = null;
+ ImageReader reader = createNativeJPEGReader();
+ inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in);
+ reader.setInput(inputStream);
+ Raster raster = reader.readRaster(0, null);
+ BufferedImage image = createRGBImageFromRGB(raster, rgbaProfile);
+ return image;
+ }
+
+ public static BufferedImage readRGBImageFromYCC(InputStream in, ICC_Profile rgbaProfile) throws IOException {
+ ImageInputStream inputStream = null;
+ ImageReader reader = createNativeJPEGReader();
+ inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in);
+ reader.setInput(inputStream);
+ Raster raster = reader.readRaster(0, null);
+ BufferedImage image = createRGBImageFromYCC(raster, rgbaProfile);
+ return image;
+ }
+
+ /**
+ * Reads a YCCK JPEG image from the provided InputStream, converting the
+ * colors to RGB using the provided CMYK ICC_Profile. The image data must be
+ * in the YCCK color space.
+ *
+ * Use this method, if you have already determined that the input stream
+ * contains a YCCK JPEG image.
+ *
+ * @param in An InputStream, preferably an ImageInputStream, in the JPEG
+ * File Interchange Format (JFIF).
+ * @param cmykProfile An ICC_Profile for conversion from the CMYK color
+ * space to the RGB color space. If this parameter is null, a default
+ * profile is used.
+ * @return a BufferedImage containing the decoded image converted into the
+ * RGB color space.
+ * @throws java.io.IOException
+ */
+ public static BufferedImage readRGBImageFromYCCK(InputStream in, ICC_Profile cmykProfile) throws IOException {
+ ImageInputStream inputStream = null;
+ ImageReader reader = createNativeJPEGReader();
+ inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in);
+ reader.setInput(inputStream);
+ Raster raster = reader.readRaster(0, null);
+ BufferedImage image = createRGBImageFromYCCK(raster, cmykProfile);
+ return image;
+ }
+
+ /**
+ * Reads an inverted-YCCK JPEG image from the provided InputStream,
+ * converting the colors to RGB using the provided CMYK ICC_Profile. The
+ * image data must be in the inverted-YCCK color space.
+ *
+ * Use this method, if you have already determined that the input stream
+ * contains an inverted-YCCK JPEG image.
+ *
+ * @param in An InputStream, preferably an ImageInputStream, in the JPEG
+ * File Interchange Format (JFIF).
+ * @param cmykProfile An ICC_Profile for conversion from the CMYK color
+ * space to the RGB color space. If this parameter is null, a default
+ * profile is used.
+ * @return a BufferedImage containing the decoded image converted into the
+ * RGB color space.
+ * @throws java.io.IOException
+ */
+ public static BufferedImage readRGBImageFromInvertedYCCK(InputStream in, ICC_Profile cmykProfile) throws IOException {
+ ImageInputStream inputStream = null;
+ ImageReader reader = createNativeJPEGReader();
+ inputStream = (in instanceof ImageInputStream) ? (ImageInputStream) in : ImageIO.createImageInputStream(in);
+ reader.setInput(inputStream);
+ Raster raster = reader.readRaster(0, null);
+ raster = convertInvertedYCCKToCMYK(raster);
+ BufferedImage image = createRGBImageFromCMYK(raster, cmykProfile);
+ return image;
+ }
+
+ /**
+ * Creates a buffered image from a raster in the YCCK color space,
+ * converting the colors to RGB using the provided CMYK ICC_Profile.
+ *
+ * @param ycckRaster A raster with (at least) 4 bands of samples.
+ * @param cmykProfile An ICC_Profile for conversion from the CMYK color
+ * space to the RGB color space. If this parameter is null, a default
+ * profile is used.
+ * @return a BufferedImage in the RGB color space.
+ * @throws NullPointerException.
+ */
+ public static BufferedImage createRGBImageFromYCCK(Raster ycckRaster, ICC_Profile cmykProfile) {
+ BufferedImage image;
+ if (cmykProfile != null) {
+ ycckRaster = convertYCCKtoCMYK(ycckRaster);
+ image = createRGBImageFromCMYK(ycckRaster, cmykProfile);
+ } else {
+ int w = ycckRaster.getWidth(), h = ycckRaster.getHeight();
+ int[] rgb = new int[w * h];
+ int[] Y = ycckRaster.getSamples(0, 0, w, h, 0, (int[]) null);
+ int[] Cb = ycckRaster.getSamples(0, 0, w, h, 1, (int[]) null);
+ int[] Cr = ycckRaster.getSamples(0, 0, w, h, 2, (int[]) null);
+ int[] K = ycckRaster.getSamples(0, 0, w, h, 3, (int[]) null);
+
+ float vr, vg, vb;
+ for (int i = 0, imax = Y.length; i < imax; i++) {
+ // FIXME - Use integer arithmetic to improve performance
+ float k = K[i], y = Y[i], cb = Cb[i], cr = Cr[i];
+ vr = y + 1.402f * (cr - 128) - k;
+ vg = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128) - k;
+ vb = y + 1.772f * (cb - 128) - k;
+ rgb[i] = (0xff & (vr < 0.0f ? 0 : vr > 255.0f ? 0xff : (int) (vr + 0.5f))) << 16
+ | (0xff & (vg < 0.0f ? 0 : vg > 255.0f ? 0xff : (int) (vg + 0.5f))) << 8
+ | (0xff & (vb < 0.0f ? 0 : vb > 255.0f ? 0xff : (int) (vb + 0.5f)));
+ }
+
+ Raster rgbRaster = Raster.createPackedRaster(
+ new DataBufferInt(rgb, rgb.length),
+ w, h, w, new int[]{0xff0000, 0xff00, 0xff}, null);
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ ColorModel cm = RGB;//new DirectColorModel(cs, 24, 0xff0000, 0xff00, 0xff, 0x0, false, DataBuffer.TYPE_INT);
+
+ image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null);
+ }
+ return image;
+ }
+
+ /**
+ * Creates a buffered image from a raster in the inverted YCCK color space,
+ * converting the colors to RGB using the provided CMYK ICC_Profile.
+ *
+ * @param ycckRaster A raster with (at least) 4 bands of samples.
+ * @param cmykProfile An ICC_Profile for conversion from the CMYK color
+ * space to the RGB color space. If this parameter is null, a default
+ * profile is used.
+ * @return a BufferedImage in the RGB color space.
+ */
+ public static BufferedImage createRGBImageFromInvertedYCCK(Raster ycckRaster, ICC_Profile cmykProfile) {
+ BufferedImage image;
+ if (cmykProfile != null) {
+ ycckRaster = convertInvertedYCCKToCMYK(ycckRaster);
+ image = createRGBImageFromCMYK(ycckRaster, cmykProfile);
+ } else {
+ int w = ycckRaster.getWidth(), h = ycckRaster.getHeight();
+ int[] rgb = new int[w * h];
+
+ PixelInterleavedSampleModel pix;
+ // if (Adobe_APP14 and transform==2) then YCCK else CMYK
+ int[] Y = ycckRaster.getSamples(0, 0, w, h, 0, (int[]) null);
+ int[] Cb = ycckRaster.getSamples(0, 0, w, h, 1, (int[]) null);
+ int[] Cr = ycckRaster.getSamples(0, 0, w, h, 2, (int[]) null);
+ int[] K = ycckRaster.getSamples(0, 0, w, h, 3, (int[]) null);
+ float vr, vg, vb;
+ for (int i = 0, imax = Y.length; i < imax; i++) {
+ // FIXME - Use integer arithmetic to improve performance
+ float k = 255 - K[i], y = 255 - Y[i], cb = 255 - Cb[i], cr = 255 - Cr[i];
+ vr = y + 1.402f * (cr - 128) - k;
+ vg = y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128) - k;
+ vb = y + 1.772f * (cb - 128) - k;
+ rgb[i] = (0xff & (vr < 0.0f ? 0 : vr > 255.0f ? 0xff : (int) (vr + 0.5f))) << 16
+ | (0xff & (vg < 0.0f ? 0 : vg > 255.0f ? 0xff : (int) (vg + 0.5f))) << 8
+ | (0xff & (vb < 0.0f ? 0 : vb > 255.0f ? 0xff : (int) (vb + 0.5f)));
+ }
+
+ Raster rgbRaster = Raster.createPackedRaster(
+ new DataBufferInt(rgb, rgb.length),
+ w, h, w, new int[]{0xff0000, 0xff00, 0xff}, null);
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ ColorModel cm = RGB;//new DirectColorModel(cs, 24, 0xff0000, 0xff00, 0xff, 0x0, false, DataBuffer.TYPE_INT);
+
+ image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null);
+ }
+ return image;
+ }
+
+ /**
+ * Creates a buffered image from a raster in the CMYK color space,
+ * converting the colors to RGB using the provided CMYK ICC_Profile.
+ *
+ * As seen from a comment made by 'phelps' at
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4799903
+ *
+ * @param cmykRaster A raster with (at least) 4 bands of samples.
+ * @param cmykProfile An ICC_Profile for conversion from the CMYK color
+ * space to the RGB color space. If this parameter is null, a default
+ * profile is used.
+ * @return a BufferedImage in the RGB color space.
+ */
+ public static BufferedImage createRGBImageFromCMYK(Raster cmykRaster, ICC_Profile cmykProfile) {
+ BufferedImage image;
+ int w = cmykRaster.getWidth();
+ int h = cmykRaster.getHeight();
+
+ if (cmykProfile != null) {
+ ColorSpace cmykCS = new ICC_ColorSpace(cmykProfile);
+ image = new BufferedImage(w, h,
+ BufferedImage.TYPE_INT_RGB);
+ WritableRaster rgbRaster = image.getRaster();
+ ColorSpace rgbCS = image.getColorModel().getColorSpace();
+ ColorConvertOp cmykToRgb = new ColorConvertOp(cmykCS, rgbCS, null);
+ cmykToRgb.filter(cmykRaster, rgbRaster);
+ } else {
+
+ int[] rgb = new int[w * h];
+
+ int[] C = cmykRaster.getSamples(0, 0, w, h, 0, (int[]) null);
+ int[] M = cmykRaster.getSamples(0, 0, w, h, 1, (int[]) null);
+ int[] Y = cmykRaster.getSamples(0, 0, w, h, 2, (int[]) null);
+ int[] K = cmykRaster.getSamples(0, 0, w, h, 3, (int[]) null);
+
+ for (int i = 0, imax = C.length; i < imax; i++) {
+ int k = K[i];
+ rgb[i] = (255 - min(255, C[i] + k)) << 16
+ | (255 - min(255, M[i] + k)) << 8
+ | (255 - min(255, Y[i] + k));
+ }
+
+ Raster rgbRaster = Raster.createPackedRaster(
+ new DataBufferInt(rgb, rgb.length),
+ w, h, w, new int[]{0xff0000, 0xff00, 0xff}, null);
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ ColorModel cm = RGB;//new DirectColorModel(cs, 24, 0xff0000, 0xff00, 0xff, 0x0, false, DataBuffer.TYPE_INT);
+ image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null);
+ }
+ return image;
+ }
+
+ /**
+ * Creates a buffered image from a raster in the RGBA color space,
+ * converting the colors to RGB using the provided CMYK ICC_Profile.
+ *
+ * As seen from a comment made by 'phelps' at
+ * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4799903
+ *
+ * @param rgbaRaster A raster with (at least) 4 bands of samples.
+ * @param rgbaProfile An ICC_Profile for conversion from the CMYK color
+ * space to the RGB color space. If this parameter is null, a default
+ * profile is used.
+ * @return a BufferedImage in the RGB color space.
+ */
+ public static BufferedImage createRGBAImageFromRGBA(Raster rgbaRaster, ICC_Profile rgbaProfile) {
+ BufferedImage image;
+ int w = rgbaRaster.getWidth();
+ int h = rgbaRaster.getHeight();
+
+ if (rgbaProfile != null) {
+ ColorSpace rgbaCS = new ICC_ColorSpace(rgbaProfile);
+ image = new BufferedImage(w, h,
+ BufferedImage.TYPE_INT_RGB);
+ WritableRaster rgbRaster = image.getRaster();
+ ColorSpace rgbCS = image.getColorModel().getColorSpace();
+ ColorConvertOp cmykToRgb = new ColorConvertOp(rgbaCS, rgbCS, null);
+ cmykToRgb.filter(rgbaRaster, rgbRaster);
+ } else {
+
+ int[] rgb = new int[w * h];
+
+ int[] R = rgbaRaster.getSamples(0, 0, w, h, 0, (int[]) null);
+ int[] G = rgbaRaster.getSamples(0, 0, w, h, 1, (int[]) null);
+ int[] B = rgbaRaster.getSamples(0, 0, w, h, 2, (int[]) null);
+ int[] A = rgbaRaster.getSamples(0, 0, w, h, 3, (int[]) null);
+
+ for (int i = 0, imax = R.length; i < imax; i++) {
+ rgb[i] = A[i] << 24 | R[i] << 16 | G[i] << 8 | B[i];
+ }
+
+ Raster rgbRaster = Raster.createPackedRaster(
+ new DataBufferInt(rgb, rgb.length),
+ w, h, w, new int[]{0xff0000, 0xff00, 0xff, 0xff000000}, null);
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ ColorModel cm = ColorModel.getRGBdefault();//new DirectColorModel(cs, 32, 0xff0000, 0xff00, 0xff, 0x0ff000000, false, DataBuffer.TYPE_INT);
+ image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null);
+ }
+ return image;
+ }
+
+ public static BufferedImage createRGBImageFromRGB(Raster rgbaRaster, ICC_Profile rgbaProfile) {
+ BufferedImage image;
+ int w = rgbaRaster.getWidth();
+ int h = rgbaRaster.getHeight();
+
+ // ICC_Profile currently not supported
+ rgbaProfile = null;
+ if (rgbaProfile != null) {
+ ColorSpace rgbaCS = new ICC_ColorSpace(rgbaProfile);
+ image = new BufferedImage(w, h,
+ BufferedImage.TYPE_INT_RGB);
+ WritableRaster rgbRaster = image.getRaster();
+ ColorSpace rgbCS = image.getColorModel().getColorSpace();
+ ColorConvertOp cmykToRgb = new ColorConvertOp(rgbaCS, rgbCS, null);
+ cmykToRgb.filter(rgbaRaster, rgbRaster);
+ } else {
+
+ int[] rgb = new int[w * h];
+
+ int[] R = rgbaRaster.getSamples(0, 0, w, h, 0, (int[]) null);
+ int[] G = rgbaRaster.getSamples(0, 0, w, h, 1, (int[]) null);
+ int[] B = rgbaRaster.getSamples(0, 0, w, h, 2, (int[]) null);
+ //int[] A = rgbaRaster.getSamples(0, 0, w, h, 3, (int[]) null);
+
+ for (int i = 0, imax = R.length; i < imax; i++) {
+ rgb[i] = 0xff << 24 | R[i] << 16 | G[i] << 8 | B[i];
+ }
+
+ Raster rgbRaster = Raster.createPackedRaster(
+ new DataBufferInt(rgb, rgb.length),
+ w, h, w, new int[]{0xff0000, 0xff00, 0xff, 0xff000000}, null);
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ ColorModel cm = ColorModel.getRGBdefault();//new DirectColorModel(cs, 32, 0xff0000, 0xff00, 0xff, 0x0ff000000, false, DataBuffer.TYPE_INT);
+ image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null);
+ }
+ return image;
+ }
+
+ public static BufferedImage createRGBImageFromYCC(Raster rgbaRaster, ICC_Profile rgbaProfile) {
+ BufferedImage image;
+ int w = rgbaRaster.getWidth();
+ int h = rgbaRaster.getHeight();
+
+ // ICC_Profile currently not supported
+ rgbaProfile = null;
+ if (rgbaProfile != null) {
+ ColorSpace rgbaCS = new ICC_ColorSpace(rgbaProfile);
+ image = new BufferedImage(w, h,
+ BufferedImage.TYPE_INT_RGB);
+ WritableRaster rgbRaster = image.getRaster();
+ ColorSpace rgbCS = image.getColorModel().getColorSpace();
+ ColorConvertOp cmykToRgb = new ColorConvertOp(rgbaCS, rgbCS, null);
+ cmykToRgb.filter(rgbaRaster, rgbRaster);
+ } else {
+
+ int[] rgb = new int[w * h];
+
+ int[] Y = rgbaRaster.getSamples(0, 0, w, h, 0, (int[]) null);
+ int[] Cb = rgbaRaster.getSamples(0, 0, w, h, 1, (int[]) null);
+ int[] Cr = rgbaRaster.getSamples(0, 0, w, h, 2, (int[]) null);
+ //int[] A = rgbaRaster.getSamples(0, 0, w, h, 3, (int[]) null);
+
+ for (int i = 0, imax = Y.length; i < imax; i++) {
+ int Yi, Cbi, Cri;
+ int R, G, B;
+
+ //RGB can be computed directly from YCbCr (256 levels) as follows:
+ //R = Y + 1.402 (Cr-128)
+ //G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
+ //B = Y + 1.772 (Cb-128)
+ Yi = Y[i];
+ Cbi = Cb[i];
+ Cri = Cr[i];
+ R = (1000 * Yi + 1402 * (Cri - 128)) / 1000;
+ G = (100000 * Yi - 34414 * (Cbi - 128) - 71414 * (Cri - 128)) / 100000;
+ B = (1000 * Yi + 1772 * (Cbi - 128)) / 1000;
+
+ R = min(255, max(0, R));
+ G = min(255, max(0, G));
+ B = min(255, max(0, B));
+
+ rgb[i] = 0xff << 24 | R << 16 | G << 8 | B;
+ }
+
+ Raster rgbRaster = Raster.createPackedRaster(
+ new DataBufferInt(rgb, rgb.length),
+ w, h, w, new int[]{0xff0000, 0xff00, 0xff, 0xff000000}, null);
+ ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
+ ColorModel cm = ColorModel.getRGBdefault();//new DirectColorModel(cs, 32, 0xff0000, 0xff00, 0xff, 0x0ff000000, false, DataBuffer.TYPE_INT);
+ image = new BufferedImage(cm, (WritableRaster) rgbRaster, true, null);
+ }
+ return image;
+ }
+ /**
+ * Define tables for YCC->RGB color space conversion.
+ */
+ private final static int SCALEBITS = 16;
+ private final static int MAXJSAMPLE = 255;
+ private final static int CENTERJSAMPLE = 128;
+ private final static int ONE_HALF = 1 << (SCALEBITS - 1);
+ private final static int[] Cr_r_tab = new int[MAXJSAMPLE + 1];
+ private final static int[] Cb_b_tab = new int[MAXJSAMPLE + 1];
+ private final static int[] Cr_g_tab = new int[MAXJSAMPLE + 1];
+ private final static int[] Cb_g_tab = new int[MAXJSAMPLE + 1];
+
+ /*
+ * Initialize tables for YCC->RGB colorspace conversion.
+ */
+ private static synchronized void buildYCCtoRGBtable() {
+ if (Cr_r_tab[0] == 0) {
+ for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
+ /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */
+ /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */
+ /* Cr=>R value is nearest int to 1.40200 * x */
+ Cr_r_tab[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
+ /* Cb=>B value is nearest int to 1.77200 * x */
+ Cb_b_tab[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
+ /* Cr=>G value is scaled-up -0.71414 * x */
+ Cr_g_tab[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
+ /* Cb=>G value is scaled-up -0.34414 * x */
+ /* We also add in ONE_HALF so that need not do it in inner loop */
+ Cb_g_tab[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
+ }
+ }
+ }
+
+ /*
+ * Adobe-style YCCK->CMYK conversion.
+ * We convert YCbCr to R=1-C, G=1-M, and B=1-Y using the same
+ * conversion as above, while passing K (black) unchanged.
+ * We assume build_ycc_rgb_table has been called.
+ */
+ private static Raster convertInvertedYCCKToCMYK(Raster ycckRaster) {
+ buildYCCtoRGBtable();
+
+ int w = ycckRaster.getWidth(), h = ycckRaster.getHeight();
+ int[] ycckY = ycckRaster.getSamples(0, 0, w, h, 0, (int[]) null);
+ int[] ycckCb = ycckRaster.getSamples(0, 0, w, h, 1, (int[]) null);
+ int[] ycckCr = ycckRaster.getSamples(0, 0, w, h, 2, (int[]) null);
+ int[] ycckK = ycckRaster.getSamples(0, 0, w, h, 3, (int[]) null);
+ int[] cmyk = new int[ycckY.length];
+
+ for (int i = 0; i < ycckY.length; i++) {
+ int y = 255 - ycckY[i];
+ int cb = 255 - ycckCb[i];
+ int cr = 255 - ycckCr[i];
+ int cmykC, cmykM, cmykY;
+ // Range-limiting is essential due to noise introduced by DCT losses.
+ cmykC = MAXJSAMPLE - (y + Cr_r_tab[cr]); // red
+ cmykM = MAXJSAMPLE - (y + // green
+ (Cb_g_tab[cb] + Cr_g_tab[cr]
+ >> SCALEBITS));
+ cmykY = MAXJSAMPLE - (y + Cb_b_tab[cb]); // blue
+ /* K passes through unchanged */
+ cmyk[i] = (cmykC < 0 ? 0 : (cmykC > 255) ? 255 : cmykC) << 24
+ | (cmykM < 0 ? 0 : (cmykM > 255) ? 255 : cmykM) << 16
+ | (cmykY < 0 ? 0 : (cmykY > 255) ? 255 : cmykY) << 8
+ | 255 - ycckK[i];
+ }
+
+ Raster cmykRaster = Raster.createPackedRaster(
+ new DataBufferInt(cmyk, cmyk.length),
+ w, h, w, new int[]{0xff000000, 0xff0000, 0xff00, 0xff}, null);
+ return cmykRaster;
+
+ }
+
+ private static Raster convertYCCKtoCMYK(Raster ycckRaster) {
+ buildYCCtoRGBtable();
+
+ int w = ycckRaster.getWidth(), h = ycckRaster.getHeight();
+ int[] ycckY = ycckRaster.getSamples(0, 0, w, h, 0, (int[]) null);
+ int[] ycckCb = ycckRaster.getSamples(0, 0, w, h, 1, (int[]) null);
+ int[] ycckCr = ycckRaster.getSamples(0, 0, w, h, 2, (int[]) null);
+ int[] ycckK = ycckRaster.getSamples(0, 0, w, h, 3, (int[]) null);
+
+ int[] cmyk = new int[ycckY.length];
+
+ for (int i = 0; i < ycckY.length; i++) {
+ int y = ycckY[i];
+ int cb = ycckCb[i];
+ int cr = ycckCr[i];
+ int cmykC, cmykM, cmykY;
+ // Range-limiting is essential due to noise introduced by DCT losses.
+ cmykC = MAXJSAMPLE - (y + Cr_r_tab[cr]); // red
+ cmykM = MAXJSAMPLE - (y + // green
+ (Cb_g_tab[cb] + Cr_g_tab[cr]
+ >> SCALEBITS));
+ cmykY = MAXJSAMPLE - (y + Cb_b_tab[cb]); // blue
+ /* K passes through unchanged */
+ cmyk[i] = (cmykC < 0 ? 0 : (cmykC > 255) ? 255 : cmykC) << 24
+ | (cmykM < 0 ? 0 : (cmykM > 255) ? 255 : cmykM) << 16
+ | (cmykY < 0 ? 0 : (cmykY > 255) ? 255 : cmykY) << 8
+ | ycckK[i];
+ }
+
+ return Raster.createPackedRaster(
+ new DataBufferInt(cmyk, cmyk.length),
+ w, h, w, new int[]{0xff000000, 0xff0000, 0xff00, 0xff}, null);
+ }
+
+ /**
+ * Reads a JPEG image from the provided InputStream. The image data must be
+ * in the YUV or the Gray color space.
+ *
+ * Use this method, if you have already determined that the input stream
+ * contains a YCC or Gray JPEG image.
+ *
+ * @param in An InputStream, preferably an ImageInputStream, in the JPEG
+ * File Interchange Format (JFIF).
+ * @return a BufferedImage containing the decoded image converted into the
+ * RGB color space.
+ * @throws java.io.IOException
+ */
+ public static BufferedImage readImageFromYCCorGray(ImageInputStream in) throws IOException {
+ ImageReader r = createNativeJPEGReader();
+ r.setInput(in);
+ BufferedImage img = r.read(0);
+ return img;
+ }
+}
diff --git a/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReaderSpi.java b/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReaderSpi.java
new file mode 100644
index 000000000..e15280fdb
--- /dev/null
+++ b/libsrc/cmykjpeg/src/org/monte/media/jpeg/CMYKJPEGImageReaderSpi.java
@@ -0,0 +1,77 @@
+/*
+ * @(#)CMYKJPEGImageReaderSpi.java 1.2 2011-02-17
+ *
+ * Copyright (c) 2010-2011 Werner Randelshofer, Goldau, Switzerland.
+ * All rights reserved.
+ *
+ * You may not use, copy or modify this file, except in compliance with the
+ * license agreement you entered into with Werner Randelshofer.
+ * For details see accompanying license terms.
+ */
+package org.monte.media.jpeg;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Locale;
+import javax.imageio.ImageReader;
+import javax.imageio.spi.ImageReaderSpi;
+import javax.imageio.stream.ImageInputStream;
+
+
+/**
+ * A reader for JPEG images in the CMYK color space.
+ *
+ * @author Werner Randelshofer
+ * @version 1.2 2011-02-17 Removes support for JMF.
+ *
+ * This InputStream uses two special marker values which do not exist
+ * in the JFIF stream:
+ *
+ * The junk data at the beginning of the file can be accessed by calling the
+ * read-methods immediately after opening the stream. Call nextSegment()
+ * immediately after opening the stream if you are not interested into this
+ * junk data.
+ *
+ * Junk data at the end of the file is delivered as part of the EOI_MARKER segment.
+ * Finish reading after encountering the EOI_MARKER segment if you are not interested
+ * in this junk data.
+ *
+ *
+ * References:
+ * Pennebaker, W., Mitchell, J. (1993).
+ * This method
+ * simply performs
+ * This method simply performs
+ * This method
+ * simply performs
+ * The
+ * This method simply performs
+ * This method
+ * simply performs
+ * Stream marks are intended to be used in
+ * situations where you need to read ahead a little to see what's in
+ * the stream. Often this is most easily done by invoking some
+ * general parser. If the stream is of the type handled by the
+ * parse, it just chugs along happily. If the stream is not of
+ * that type, the parser should toss an exception when it fails.
+ * If this happens within readlimit b, it allows the outer
+ * code to reset the stream and try another parser.
+ *
+ * @exception IOException if the stream has not been marked or if the
+ * mark has been invalidated.
+ * @see java.io.FilterInputStream#in
+ * @see java.io.FilterInputStream#mark(int)
+ */
+ @Override
+ public synchronized void reset() throws IOException {
+ throw new IOException("Reset not supported");
+ }
+
+ /**
+ * Tests if this input stream supports the
1.0 2010-07-23 Created.
+ */
+public class CMYKJPEGImageReaderSpi extends ImageReaderSpi {
+
+ public CMYKJPEGImageReaderSpi() {
+ super("Werner Randelshofer",//vendor name
+ "1.0",//version
+ new String[]{"JPEG","JPG"},//names
+ new String[]{"jpg"},//suffixes,
+ new String[]{"image/jpg"},// MIMETypes,
+ "org.monte.media.jpeg.CMYKJPEGImageReader",// readerClassName,
+ new Class[]{ImageInputStream.class,InputStream.class,byte[].class},// inputTypes,
+ null,// writerSpiNames,
+ false,// supportsStandardStreamMetadataFormat,
+ null,// nativeStreamMetadataFormatName,
+ null,// nativeStreamMetadataFormatClassName,
+ null,// extraStreamMetadataFormatNames,
+ null,// extraStreamMetadataFormatClassNames,
+ false,// supportsStandardImageMetadataFormat,
+ null,// nativeImageMetadataFormatName,
+ null,// nativeImageMetadataFormatClassName,
+ null,// extraImageMetadataFormatNames,
+ null// extraImageMetadataFormatClassNames
+ );
+ }
+
+ @Override
+ public boolean canDecodeInput(Object source) throws IOException {
+ if (source instanceof ImageInputStream) {
+ ImageInputStream in = (ImageInputStream) source;
+ in.mark();
+ // Check if file starts with a JFIF SOI magic (0xffd8=-40)
+ if (in.readShort() != -40) {
+ in.reset();
+ return false;
+ }
+ in.reset();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public ImageReader createReaderInstance(Object extension) throws IOException {
+ return new CMYKJPEGImageReader(this);
+ }
+
+ @Override
+ public String getDescription(Locale locale) {
+ return "CMYK JPEG Image Reader";
+ }
+}
diff --git a/libsrc/cmykjpeg/src/org/monte/media/jpeg/Generic CMYK Profile.icc b/libsrc/cmykjpeg/src/org/monte/media/jpeg/Generic CMYK Profile.icc
new file mode 100644
index 000000000..458eb098c
Binary files /dev/null and b/libsrc/cmykjpeg/src/org/monte/media/jpeg/Generic CMYK Profile.icc differ
diff --git a/libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java b/libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java
new file mode 100644
index 000000000..ebb0519c4
--- /dev/null
+++ b/libsrc/cmykjpeg/src/org/monte/media/jpeg/JFIFInputStream.java
@@ -0,0 +1,521 @@
+/*
+ * @(#)JFIFInputStream.java
+ *
+ * Copyright (c) 2008-2012 Werner Randelshofer, Goldau, Switzerland.
+ * All rights reserved.
+ *
+ * You may not use, copy or modify this file, except in compliance with the
+ * license agreement you entered into with Werner Randelshofer.
+ * For details see accompanying license terms.
+ */
+package org.monte.media.jpeg;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * JFIFInputStream.
+ *
+ *
+ *
+ * JPEG File Interchange Format Version 1.02
+ * http://www.jpeg.org/public/jfif.pdf
+ *
+ * JPEG Still Image Data Compression Standard.
+ * Chapmann & Hall, New York.
+ * ISBN 0-442-01272-1
+ *
+ *
+ * @author Werner Randelshofer, Hausmatt 10, CH-6405 Goldau
+ * @version $Id: JFIFInputStream.java 299 2013-01-03 07:40:18Z werner $
+ */
+public class JFIFInputStream extends FilterInputStream {
+
+ /**
+ * This hash set holds the Id's of markers which stand alone,
+ * respectively do no have a data segment.
+ */
+ private final HashSetint in the range
+ * 0 to 255. If no byte is available
+ * because the end of the stream has been reached, the value
+ * -1 is returned. This method blocks until input data
+ * is available, the end of the stream is detected, or an exception
+ * is thrown.
+ * in.read() and returns the result.
+ *
+ * @return the next byte of data, or -1 if the end of the
+ * stream is reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public int read() throws IOException {
+ if (markerFound) {
+ return -1;
+ }
+
+ int b;
+ if (isStuffed0xff) {
+ isStuffed0xff = false;
+ b = 0xff;
+ } else {
+ b = read0();
+ }
+
+ if (segment.isEntropyCoded()) {
+ if (b == 0xff) {
+ b = read0();
+ if (b == 0x00) {
+ // found a stuffed 0xff byte
+ return 0xff;
+ } else if (b == 0xff) {
+ // found an invalid sequence of two 0xff bytes
+ isStuffed0xff = true;
+ return 0xff;
+ }
+ markerFound = true;
+ marker = 0xff00 | b;
+ return -1;
+ }
+ }
+ return b;
+ }
+
+ /**
+ * Reads up to len b of data from this input stream
+ * into an array of b. This method blocks until some input is
+ * available.
+ * in.read(b, off, len)
+ * and returns the result.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off the start offset of the data.
+ * @param len the maximum number of b read.
+ * @return the total number of b read into the buffer, or
+ * -1 if there is no more data because the end of
+ * the stream has been reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public int read(byte b[], int off, int len) throws IOException {
+ if (markerFound) {
+ return -1;
+ }
+
+ int count = 0;
+ if (segment.isEntropyCoded()) {
+ for (; count < len; count++) {
+ int data = read();
+ if (data == -1) {
+ if (count==0) return -1;
+ break;
+ }
+
+ b[off + count] = (byte) data;
+ }
+ } else {
+ long available = segment.length - offset + segment.offset;
+ if (available <= 0) {
+ return -1;
+ }
+ if (available < len) {
+ len = (int) available;
+ }
+ count = in.read(b, off, len);
+ if (count != -1) {
+ offset += count;
+ }
+ }
+ return count;
+ }
+
+ /** Fully skips the specified number of bytes. */
+ public final void skipFully(long n) throws IOException {
+ long total = 0;
+ long cur = 0;
+
+ while ((total < n) && ((cur = (int) in.skip(n - total)) > 0)) {
+ total += cur;
+ }
+ offset+=total;
+ if (total < n) {
+ throw new EOFException();
+ }
+ }
+
+ /**
+ * Skips over and discards n b of data from the
+ * input stream. The skip method may, for a variety of
+ * reasons, end up skipping over some smaller number of b,
+ * possibly 0. The actual number of b skipped is
+ * returned.
+ * in.skip(n).
+ *
+ * @param n the number of b to be skipped.
+ * @return the actual number of b skipped.
+ * @exception IOException if an I/O error occurs.
+ */
+ @Override
+ public long skip(long n) throws IOException {
+ if (markerFound) {
+ return -1;
+ }
+
+ long count = 0;
+ if (segment.isEntropyCoded()) {
+ for (; count < n; count++) {
+ int data = read();
+ if (data == -1) {
+ break;
+ }
+ }
+ } else {
+ long available = segment.length - offset + segment.offset;
+ if (available < n) {
+ n = (int) available;
+ }
+ count = in.skip(n);
+ if (count != -1) {
+ offset += count;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Marks the current position in this input stream. A subsequent
+ * call to the reset method repositions this stream at
+ * the last marked position so that subsequent reads re-read the same b.
+ * readlimit argument tells this input stream to
+ * allow that many b to be read before the mark position gets
+ * invalidated.
+ * in.mark(readlimit).
+ *
+ * @param readlimit the maximum limit of b that can be read before
+ * the mark position becomes invalid.
+ * @see java.io.FilterInputStream#in
+ * @see java.io.FilterInputStream#reset()
+ */
+ @Override
+ public synchronized void mark(int readlimit) {
+ // do nothing, since we don't support marking
+ }
+
+ /**
+ * Repositions this stream to the position at the time the
+ * mark method was last called on this input stream.
+ * in.reset().
+ * mark
+ * and reset methods.
+ * This method
+ * simply performs in.markSupported().
+ *
+ * @return true if this stream type supports the
+ * mark and reset method;
+ * false otherwise.
+ * @see java.io.FilterInputStream#in
+ * @see java.io.InputStream#mark(int)
+ * @see java.io.InputStream#reset()
+ */
+ @Override
+ public boolean markSupported() {
+ return false;
+ }
+}
diff --git a/libsrc/ffdec_lib/nbproject/project.xml b/libsrc/ffdec_lib/nbproject/project.xml
index 634fd0434..029f090fb 100644
--- a/libsrc/ffdec_lib/nbproject/project.xml
+++ b/libsrc/ffdec_lib/nbproject/project.xml
@@ -236,7 +236,7 @@ auxiliary.show.customizer.message=