ch.randelshofer.media.jpeg.CMYKJPEGImageReader Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* @(#)MJPGImageReader.java 1.1 2010-11-16
*
* Copyright (c) 2010 Werner Randelshofer, Immensee, 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 ch.randelshofer.media.jpeg;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.BufferedImage;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.PixelInterleavedSampleModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.LinkedList;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import ch.randelshofer.io.ByteArrayImageInputStream;
import ch.randelshofer.io.ImageInputStreamAdapter;
/**
* Reads a JPEG image with colors in the CMYK color space.
*
* @author Werner Randelshofer
* @version 1.1 2010-11-16 Fixes handling of JPEG images in the RGBA color space
* (in prior versions, these were wrongly handled as CMYK images).
*
1.0 2010-07-23 Created.
*/
public class CMYKJPEGImageReader extends ImageReader {
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(CMYKJPEGImageReaderSpi originatingProvider) {
super(originatingProvider);
}
@Override
public int getNumImages(boolean allowSearch) throws IOException {
return 1;
}
@Override
public int getWidth(int imageIndex) throws IOException {
readHeader(null);
return image.getWidth();
}
@Override
public int getHeight(int imageIndex) throws IOException {
readHeader(null);
return image.getHeight();
}
@Override
public Iterator getImageTypes(int imageIndex) throws IOException {
readHeader(null);
LinkedList l = new LinkedList();
l.add(new ImageTypeSpecifier(RGB, RGB.createCompatibleSampleModel(image.getWidth(), image.getHeight())));
return l.iterator();
}
@Override
public IIOMetadata getStreamMetadata() throws IOException {
return null;
}
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
return null;
}
@Override
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
if (imageIndex > 0) {
throw new IndexOutOfBoundsException();
}
readHeader(param);
return image;
}
/**
* Reads the PGM header. Does nothing if the header has already been loaded.
*/
private void readHeader(ImageReadParam param) throws IOException {
if (image == null) {
ImageInputStream iis = null;
Object in = getInput();
if (in instanceof byte[]) {
iis = new ByteArrayImageInputStream((byte[]) in);
} else if (in instanceof ImageInputStream) {
iis = (ImageInputStream) in;
} else if (in instanceof InputStream) {
iis = new MemoryCacheImageInputStream((InputStream) in);
} else {
throw new IOException("Can't handle input of type " + in);
}
// use CMYKJpegImageReadParam if available, else fallback to
// default.
if (param != null && param instanceof CMYKJPEGImageReadParam) {
setYCCKInversed(((CMYKJPEGImageReadParam) param).getUseInvertedYCCKColor());
}
image = read(iis, isYCCKInversed);
}
}
/**
* @return the YCCKInversed property.
*/
public boolean isYCCKInversed() {
return isYCCKInversed;
}
/**
* @param newValue the new value
*/
public void setYCCKInversed(boolean newValue) {
this.isYCCKInversed = newValue;
}
public static BufferedImage read(ImageInputStream in, boolean inverseYCCKColors) throws IOException {
// Extract metadata from the JFIF stream.
// --------------------------------------
// In particular, we are interested into the following fields:
int samplePrecision = 0;
int numberOfLines = 0;
int numberOfSamplesPerLine = 0;
int numberOfComponentsInFrame = 0;
int app14AdobeColorTransform = 0;
ByteArrayOutputStream app2ICCProfile = new ByteArrayOutputStream();
// Browse for marker segments, and extract data from those
// which are of interest.
JFIFInputStream fifi = new JFIFInputStream(new ImageInputStreamAdapter(in));
for (JFIFInputStream.Segment seg = fifi.getNextSegment(); seg != null; seg = fifi.getNextSegment()) {
if (0xffc0 <= seg.marker && seg.marker <= 0xffc3
|| 0xffc5 <= seg.marker && seg.marker <= 0xffc7
|| 0xffc9 <= seg.marker && seg.marker <= 0xffcb
|| 0xffcd <= seg.marker && seg.marker <= 0xffcf) {
// SOF0 - SOF15: Start of Frame Header marker segment
DataInputStream dis = new DataInputStream(fifi);
samplePrecision = dis.readUnsignedByte();
numberOfLines = dis.readUnsignedShort();
numberOfSamplesPerLine = dis.readUnsignedShort();
numberOfComponentsInFrame = dis.readUnsignedByte();
// ...the rest of SOF header is not important to us.
// In fact, by encounterint a SOF header, we have reached
// the end of the metadata section we are interested in.
// Thus we can abort here.
break;
} else if (seg.marker == 0xffe2) {
// APP2: Application-specific marker segment
if (seg.length >= 26) {
DataInputStream dis = new DataInputStream(fifi);
// Check for 12-bytes containing the null-terminated string: "ICC_PROFILE".
if (dis.readLong() == 0x4943435f50524f46L && dis.readInt() == 0x494c4500) {
// Skip 2 bytes
dis.skipBytes(2);
// Read Adobe ICC_PROFILE int buffer. The profile is split up over
// multiple APP2 marker segments.
byte[] b = new byte[512];
for (int count = dis.read(b); count != -1; count = dis.read(b)) {
app2ICCProfile.write(b, 0, count);
}
}
}
} else if (seg.marker == 0xffee) {
// APP14: Application-specific marker segment
// [email protected] changed the condition to >=, it was ==
if (seg.length >= 12) {
DataInputStream dis = new DataInputStream(fifi);
// Check for 6-bytes containing the null-terminated string: "Adobe".
if (dis.readInt() == 0x41646f62L && dis.readUnsignedShort() == 0x6500) {
int version = dis.readUnsignedByte();
int app14Flags0 = dis.readUnsignedShort();
int app14Flags1 = dis.readUnsignedShort();
app14AdobeColorTransform = dis.readUnsignedByte();
}
}
}
}
//fifi.close();
// Read the image data
BufferedImage img = null;
if (numberOfComponentsInFrame != 4) {
// Read image with YUV color encoding.
in.seek(0);
img = readImageFromYUVorGray(in);
} else if (numberOfComponentsInFrame == 4) {
// Try to instantiate an ICC_Profile from the app2ICCProfile
ICC_Profile profile = null;
if (app2ICCProfile.size() > 0) {
try {
profile = ICC_Profile.getInstance(new ByteArrayInputStream(app2ICCProfile.toByteArray()));
} catch (Throwable ex) {
// icc profile is corrupt
ex.printStackTrace();
}
}
// In case of failure, use a Generic CMYK profile
if (profile == null) {
profile = ICC_Profile.getInstance(JPEGImageIO.class.getResourceAsStream("Generic CMYK Profile.icc"));
}
switch (app14AdobeColorTransform) {
case 0:
default:
// Read image with RGBA color encoding.
in.seek(0);
img = readRGBAImageFromRGBA(new ImageInputStreamAdapter(in), profile);
break;
case 1:
throw new IOException("YCbCr not supported");
case 2:
// Read image with inverted YCCK color encoding.
// FIXME - How do we determine from the JFIF file whether
// YCCK colors are inverted?
in.seek(0);
if (inverseYCCKColors) {
img = readRGBImageFromInvertedYCCK(new ImageInputStreamAdapter(in), profile);
} else {
img = readRGBImageFromYCCK(new ImageInputStreamAdapter(in), profile);
}
break;
}
}
return img;
}
private static ImageReader createNativeJPEGReader() throws IIOException {
/*
* Adapted for use in an OSGi framework which may or may not
* provide access to the Sun JPEGImageReader.
* We assume the class is available from the system class loader
* and if not, we throw an IIOException for not being able to read...
*/
ClassLoader loader = ClassLoader.getSystemClassLoader();
if (loader == null) {
loader = CMYKJPEGImageReader.class.getClassLoader();
if (loader.getParent() != null) {
loader = loader.getParent();
}
}
try {
@SuppressWarnings("unchecked")
Class jpegImageReaderClass = (Class) loader.loadClass("com.sun.imageio.plugins.jpeg.JPEGImageReader");
Constructor c = jpegImageReaderClass.getConstructor(ImageReaderSpi.class);
return c.newInstance(new Object[] { null });
} catch (Exception e) {
throw new IIOException("Cannot create Sun JPEGImageReader backend",
e);
}
}
/**
* Reads a CMYK JPEG image from the provided InputStream, converting the
* colors to RGB using the provided CMYK ICC_Profile. The image data must
* be in the CMYK color space.
*
* 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();
try {
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;
} finally {
reader.dispose();
}
}
/**
* 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();
try {
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;
} finally {
reader.dispose();
}
}
/**
* 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();
try {
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;
} finally {
reader.dispose();
}
}
/**
* 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();
try {
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;
} finally {
reader.dispose();
}
}
/**
* 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++) {
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 = 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++) {
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 = 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 - Math.min(255, C[i] + k)) << 16
| (255 - Math.min(255, M[i] + k)) << 8
| (255 - Math.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 = 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();
// 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] = 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 = 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 colorspace 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 YUV 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 readImageFromYUVorGray(ImageInputStream in) throws IOException {
ImageReader r = createNativeJPEGReader();
try {
r.setInput(in);
BufferedImage img = r.read(0);
return img;
} finally {
r.dispose();
}
}
}