/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.image;
import magick.
ImageType;
import magick.
MagickException;
import magick.
MagickImage;
import magick.
PixelPacket;
import java.awt.*;
import java.awt.color.
ColorSpace;
import java.awt.color.
ICC_ColorSpace;
import java.awt.color.
ICC_Profile;
import java.awt.image.*;
/**
* Utility for converting JMagick {@code MagickImage}s to standard Java
* {@code BufferedImage}s and back.
* <p/>
* <em>NOTE: This class is considered an implementation detail and not part of
* the public API. This class is subject to change without further notice.
* You have been warned. :-)</em>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickUtil.java#4 $
*/
public final class
MagickUtil {
// IMPORTANT NOTE: Disaster happens if any of these constants are used outside this class
// because you then have a dependency on MagickException (this is due to Java class loading
// and initialization magic).
// Do not use outside this class. If the constants need to be shared, move to Magick or ImageUtil.
/** Color Model usesd for bilevel (B/W) */
private static final
IndexColorModel CM_MONOCHROME =
MonochromeColorModel.
getInstance();
/** Color Model usesd for raw ABGR */
private static final
ColorModel CM_COLOR_ALPHA =
new
ComponentColorModel(
ColorSpace.
getInstance(
ColorSpace.
CS_sRGB), new int[] {8, 8, 8, 8},
true, true,
Transparency.
TRANSLUCENT,
DataBuffer.
TYPE_BYTE);
/** Color Model usesd for raw BGR */
private static final
ColorModel CM_COLOR_OPAQUE =
new
ComponentColorModel(
ColorSpace.
getInstance(
ColorSpace.
CS_sRGB), new int[] {8, 8, 8},
false, false,
Transparency.
OPAQUE,
DataBuffer.
TYPE_BYTE);
/** Color Model usesd for raw RGB */
//private static final ColorModel CM_COLOR_RGB = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x0);
/** Color Model usesd for raw GRAY + ALPHA */
private static final
ColorModel CM_GRAY_ALPHA =
new
ComponentColorModel(
ColorSpace.
getInstance(
ColorSpace.
CS_GRAY),
true, true,
Transparency.
TRANSLUCENT,
DataBuffer.
TYPE_BYTE);
/** Color Model usesd for raw GRAY */
private static final
ColorModel CM_GRAY_OPAQUE =
new
ComponentColorModel(
ColorSpace.
getInstance(
ColorSpace.
CS_GRAY),
false, false,
Transparency.
OPAQUE,
DataBuffer.
TYPE_BYTE);
/** Band offsets for raw ABGR */
private static final int[]
BAND_OFF_TRANS = new int[] {3, 2, 1, 0};
/** Band offsets for raw BGR */
private static final int[]
BAND_OFF_OPAQUE = new int[] {2, 1, 0};
/** The point at {@code 0, 0} */
private static final
Point LOCATION_UPPER_LEFT = new
Point(0, 0);
private static final boolean
DEBUG =
Magick.
DEBUG;
// Only static members and methods
private
MagickUtil() {}
/**
* Converts a {@code MagickImage} to a {@code BufferedImage}.
* <p/>
* The conversion depends on {@code pImage}'s {@code ImageType}:
* <dl>
* <dt>{@code ImageType.BilevelType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY}</dd>
*
* <dt>{@code ImageType.GrayscaleType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_GRAY}</dd>
* <dt>{@code ImageType.GrayscaleMatteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_USHORT_GRAY}</dd>
*
* <dt>{@code ImageType.PaletteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
* <dt>{@code ImageType.PaletteMatteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
*
* <dt>{@code ImageType.TrueColorType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_3BYTE_BGR}</dd>
* <dt>{@code ImageType.TrueColorPaletteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_4BYTE_ABGR}</dd>
*
* @param pImage the original {@code MagickImage}
* @return a new {@code BufferedImage}
*
* @throws IllegalArgumentException if {@code pImage} is {@code null}
* or if the {@code ImageType} is not one mentioned above.
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
public static
BufferedImage toBuffered(
MagickImage pImage) throws
MagickException {
if (
pImage == null) {
throw new
IllegalArgumentException("image == null");
}
long
start = 0L;
if (
DEBUG) {
start =
System.
currentTimeMillis();
}
BufferedImage image = null;
try {
switch (
pImage.
getImageType()) {
case
ImageType.
BilevelType:
image =
bilevelToBuffered(
pImage);
break;
case
ImageType.
GrayscaleType:
image =
grayToBuffered(
pImage, false);
break;
case
ImageType.
GrayscaleMatteType:
image =
grayToBuffered(
pImage, true);
break;
case
ImageType.
PaletteType:
image =
paletteToBuffered(
pImage, false);
break;
case
ImageType.
PaletteMatteType:
image =
paletteToBuffered(
pImage, true);
break;
case
ImageType.
TrueColorType:
image =
rgbToBuffered(
pImage, false);
break;
case
ImageType.
TrueColorMatteType:
image =
rgbToBuffered(
pImage, true);
break;
case
ImageType.
ColorSeparationType:
image =
cmykToBuffered(
pImage, false);
break;
case
ImageType.
ColorSeparationMatteType:
image =
cmykToBuffered(
pImage, true);
break;
case
ImageType.
OptimizeType:
default:
throw new
IllegalArgumentException("Unknown JMagick image type: " +
pImage.
getImageType());
}
}
finally {
if (
DEBUG) {
long
time =
System.
currentTimeMillis() -
start;
System.
out.
println("Converted JMagick image type: " +
pImage.
getImageType() + " to BufferedImage: " +
image);
System.
out.
println("Conversion to BufferedImage: " +
time + " ms");
}
}
return
image;
}
/**
* Converts a {@code BufferedImage} to a {@code MagickImage}.
* <p/>
* The conversion depends on {@code pImage}'s {@code ColorModel}:
* <dl>
* <dt>{@code IndexColorModel} with 1 bit b/w</dt>
* <dd>{@code MagickImage} of type {@code ImageType.BilevelType}</dd>
* <dt>{@code IndexColorModel} > 1 bit,</dt>
* <dd>{@code MagickImage} of type {@code ImageType.PaletteType}
* or {@code MagickImage} of type {@code ImageType.PaletteMatteType}
* depending on <tt>ColorModel.getAlpha()</dd>
*
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY}</dt>
* <dd>{@code MagickImage} of type {@code ImageType.GrayscaleType}
* or {@code MagickImage} of type {@code ImageType.GrayscaleMatteType}
* depending on <tt>ColorModel.getAlpha()</dd>
*
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB}</dt>
* <dd>{@code MagickImage} of type {@code ImageType.TrueColorType}
* or {@code MagickImage} of type {@code ImageType.TrueColorPaletteType}</dd>
*
* @param pImage the original {@code BufferedImage}
* @return a new {@code MagickImage}
*
* @throws IllegalArgumentException if {@code pImage} is {@code null}
* or if the {@code ColorModel} is not one mentioned above.
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
public static
MagickImage toMagick(
BufferedImage pImage) throws
MagickException {
if (
pImage == null) {
throw new
IllegalArgumentException("image == null");
}
long
start = 0L;
if (
DEBUG) {
start =
System.
currentTimeMillis();
}
try {
ColorModel cm =
pImage.
getColorModel();
if (
cm instanceof
IndexColorModel) {
// Handles both BilevelType, PaletteType and PaletteMatteType
return
indexedToMagick(
pImage, (
IndexColorModel)
cm,
cm.
hasAlpha());
}
switch (
cm.
getColorSpace().
getType()) {
case
ColorSpace.
TYPE_GRAY:
// Handles GrayType and GrayMatteType
return
grayToMagick(
pImage,
cm.
hasAlpha());
case
ColorSpace.
TYPE_RGB:
// Handles TrueColorType and TrueColorMatteType
return
rgbToMagic(
pImage,
cm.
hasAlpha());
case
ColorSpace.
TYPE_CMY:
case
ColorSpace.
TYPE_CMYK:
case
ColorSpace.
TYPE_HLS:
case
ColorSpace.
TYPE_HSV:
// Other types not supported yet
default:
throw new
IllegalArgumentException("Unknown buffered image type: " +
pImage);
}
}
finally {
if (
DEBUG) {
long
time =
System.
currentTimeMillis() -
start;
System.
out.
println("Conversion to MagickImage: " +
time + " ms");
}
}
}
private static
MagickImage rgbToMagic(
BufferedImage pImage, boolean
pAlpha) throws
MagickException {
MagickImage image = new
MagickImage();
BufferedImage buffered =
ImageUtil.
toBuffered(
pImage,
pAlpha ?
BufferedImage.
TYPE_4BYTE_ABGR :
BufferedImage.
TYPE_3BYTE_BGR);
// Need to get data of sub raster, not the full data array, this is
// just a convenient way
Raster raster;
if (
buffered.
getRaster().
getParent() != null) {
raster =
buffered.
getData(new
Rectangle(
buffered.
getWidth(),
buffered.
getHeight()));
}
else {
raster =
buffered.
getRaster();
}
image.
constituteImage(
buffered.
getWidth(),
buffered.
getHeight(),
pAlpha ? "ABGR" : "BGR",
((
DataBufferByte)
raster.
getDataBuffer()).
getData());
return
image;
}
private static
MagickImage grayToMagick(
BufferedImage pImage, boolean
pAlpha) throws
MagickException {
MagickImage image = new
MagickImage();
// TODO: Make a fix for TYPE_USHORT_GRAY
// The code below does not seem to work (JMagick issues?)...
/*
if (pImage.getType() == BufferedImage.TYPE_USHORT_GRAY) {
short[] data = ((DataBufferUShort) pImage.getRaster().getDataBuffer()).getData();
int[] intData = new int[data.length];
for (int i = 0; i < data.length; i++) {
intData[i] = (data[i] & 0xffff) * 0xffff;
}
image.constituteImage(pImage.getWidth(), pImage.getHeight(), "I", intData);
System.out.println("storageClass: " + image.getStorageClass());
System.out.println("depth: " + image.getDepth());
System.out.println("imageType: " + image.getImageType());
}
else {
*/
BufferedImage buffered =
ImageUtil.
toBuffered(
pImage,
pAlpha ?
BufferedImage.
TYPE_4BYTE_ABGR :
BufferedImage.
TYPE_BYTE_GRAY);
// Need to get data of sub raster, not the full data array, this is
// just a convenient way
Raster raster;
if (
buffered.
getRaster().
getParent() != null) {
raster =
buffered.
getData(new
Rectangle(
buffered.
getWidth(),
buffered.
getHeight()));
}
else {
raster =
buffered.
getRaster();
}
image.
constituteImage(
buffered.
getWidth(),
buffered.
getHeight(),
pAlpha ? "ABGR" : "I", ((
DataBufferByte)
raster.
getDataBuffer()).
getData());
//}
return
image;
}
private static
MagickImage indexedToMagick(
BufferedImage pImage,
IndexColorModel pColorModel, boolean
pAlpha) throws
MagickException {
MagickImage image =
rgbToMagic(
pImage,
pAlpha);
int
mapSize =
pColorModel.
getMapSize();
image.
setNumberColors(
mapSize);
return
image;
}
/*
public static MagickImage toMagick(BufferedImage pImage) throws MagickException {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
final int width = pImage.getWidth();
final int height = pImage.getHeight();
// int ARGB -> byte RGBA conversion
// NOTE: This is ImageMagick Q16 compatible raw RGBA format with 16 bits/sample...
// For a Q8 build, we could probably go with half the space...
// NOTE: This is close to insanity, as it wastes extreme ammounts of memory
final int[] argb = new int[width];
final byte[] raw16 = new byte[width * height * 8];
for (int y = 0; y < height; y++) {
// Fetch one line of ARGB data
pImage.getRGB(0, y, width, 1, argb, 0, width);
for (int x = 0; x < width; x++) {
int pixel = (x + (y * width)) * 8;
raw16[pixel ] = (byte) ((argb[x] >> 16) & 0xff); // R
raw16[pixel + 2] = (byte) ((argb[x] >> 8) & 0xff); // G
raw16[pixel + 4] = (byte) ((argb[x] ) & 0xff); // B
raw16[pixel + 6] = (byte) ((argb[x] >> 24) & 0xff); // A
}
}
// Create magick image
ImageInfo info = new ImageInfo();
info.setMagick("RGBA"); // Raw RGBA samples
info.setSize(width + "x" + height); // String?!?
MagickImage image = new MagickImage(info);
image.setImageAttribute("depth", "8");
// Set pixel data in 16 bit raw RGBA format
image.blobToImage(info, raw16);
return image;
}
*/
/**
* Converts a bi-level {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_BYTE_BINARY}.
*
* @param pImage the original {@code MagickImage}
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static
BufferedImage bilevelToBuffered(
MagickImage pImage) throws
MagickException {
// As there is no way to get the binary representation of the image,
// convert to gray, and the create a binary image from it
BufferedImage temp =
grayToBuffered(
pImage, false);
BufferedImage image = new
BufferedImage(
temp.
getWidth(),
temp.
getHeight(),
BufferedImage.
TYPE_BYTE_BINARY,
CM_MONOCHROME);
ImageUtil.
drawOnto(
image,
temp);
return
image;
}
/**
* Converts a gray {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_USHORT_GRAY} or {@code TYPE_BYTE_GRAY}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static
BufferedImage grayToBuffered(
MagickImage pImage, boolean
pAlpha) throws
MagickException {
Dimension size =
pImage.
getDimension();
int
length =
size.
width *
size.
height;
int
bands =
pAlpha ? 2 : 1;
byte[]
pixels = new byte[
length *
bands];
// TODO: Make a fix for 16 bit TYPE_USHORT_GRAY?!
// Note: The ordering AI or I corresponds to BufferedImage
// TYPE_CUSTOM and TYPE_BYTE_GRAY respectively
pImage.
dispatchImage(0, 0,
size.
width,
size.
height,
pAlpha ? "AI" : "I",
pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new
DataBufferByte(
pixels,
pixels.length);
int[]
bandOffsets =
pAlpha ? new int[] {1, 0} : new int[] {0};
WritableRaster raster =
Raster.
createInterleavedRaster(
buffer,
size.
width,
size.
height,
size.
width *
bands,
bands,
bandOffsets,
LOCATION_UPPER_LEFT);
return new
BufferedImage(
pAlpha ?
CM_GRAY_ALPHA :
CM_GRAY_OPAQUE,
raster,
pAlpha, null);
}
/**
* Converts a palette-based {@code MagickImage} to a
* {@code BufferedImage}, of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of <= 16 colors) or {@code TYPE_BYTE_INDEXED}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static
BufferedImage paletteToBuffered(
MagickImage pImage, boolean
pAlpha) throws
MagickException {
// Create indexcolormodel for the image
IndexColorModel cm;
try {
cm =
createIndexColorModel(
pImage.
getColormap(),
pAlpha);
}
catch (
MagickException e) {
// NOTE: Some MagickImages incorrecly (?) reports to be paletteType,
// but does not have a colormap, this is a workaround.
return
rgbToBuffered(
pImage,
pAlpha);
}
// As there is no way to get the indexes of an indexed image, convert to
// RGB, and the create an indexed image from it
BufferedImage temp =
rgbToBuffered(
pImage,
pAlpha);
BufferedImage image;
if (
cm.
getMapSize() <= 16) {
image = new
BufferedImage(
temp.
getWidth(),
temp.
getHeight(),
BufferedImage.
TYPE_BYTE_BINARY,
cm);
}
else {
image = new
BufferedImage(
temp.
getWidth(),
temp.
getHeight(),
BufferedImage.
TYPE_BYTE_INDEXED,
cm);
}
// Create transparent background for images containing alpha
if (
pAlpha) {
Graphics2D g =
image.
createGraphics();
try {
g.
setComposite(
AlphaComposite.
Clear);
g.
fillRect(0, 0,
temp.
getWidth(),
temp.
getHeight());
}
finally {
g.
dispose();
}
}
// NOTE: This is (surprisingly) much faster than using g2d.drawImage()..
// (Tests shows 20-30ms, vs. 600-700ms on the same image)
BufferedImageOp op = new
CopyDither(
cm);
op.
filter(
temp,
image);
return
image;
}
/**
* Creates an {@code IndexColorModel} from an array of
* {@code PixelPacket}s.
*
* @param pColormap the original colormap as a {@code PixelPacket} array
* @param pAlpha keep alpha channel
*
* @return a new {@code IndexColorModel}
*/
public static
IndexColorModel createIndexColorModel(
PixelPacket[]
pColormap, boolean
pAlpha) {
int[]
colors = new int[
pColormap.length];
// TODO: Verify if this is correct for alpha...?
int
trans =
pAlpha ?
colors.length - 1 : -1;
//for (int i = 0; i < pColormap.length; i++) {
for (int
i =
pColormap.length - 1;
i != 0;
i--) {
PixelPacket color =
pColormap[
i];
if (
pAlpha) {
colors[
i] = (0xff - (
color.
getOpacity() & 0xff)) << 24 |
(
color.
getRed() & 0xff) << 16 |
(
color.
getGreen() & 0xff) << 8 |
(
color.
getBlue() & 0xff);
}
else {
colors[
i] = (
color.
getRed() & 0xff) << 16 |
(
color.
getGreen() & 0xff) << 8 |
(
color.
getBlue() & 0xff);
}
}
return new
InverseColorMapIndexColorModel(8,
colors.length,
colors, 0,
pAlpha,
trans,
DataBuffer.
TYPE_BYTE);
}
/**
* Converts an (A)RGB {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_4BYTE_ABGR} or {@code TYPE_3BYTE_BGR}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static
BufferedImage rgbToBuffered(
MagickImage pImage, boolean
pAlpha) throws
MagickException {
Dimension size =
pImage.
getDimension();
int
length =
size.
width *
size.
height;
int
bands =
pAlpha ? 4 : 3;
byte[]
pixels = new byte[
length *
bands];
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
// Note: The ordering ABGR or BGR corresponds to BufferedImage
// TYPE_4BYTE_ABGR and TYPE_3BYTE_BGR respectively
pImage.
dispatchImage(0, 0,
size.
width,
size.
height,
pAlpha ? "ABGR" : "BGR",
pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new
DataBufferByte(
pixels,
pixels.length);
int[]
bandOffsets =
pAlpha ?
BAND_OFF_TRANS :
BAND_OFF_OPAQUE;
WritableRaster raster =
Raster.
createInterleavedRaster(
buffer,
size.
width,
size.
height,
size.
width *
bands,
bands,
bandOffsets,
LOCATION_UPPER_LEFT);
return new
BufferedImage(
pAlpha ?
CM_COLOR_ALPHA :
CM_COLOR_OPAQUE,
raster,
pAlpha, null);
}
/**
* Converts an {@code MagickImage} to a {@code BufferedImage} which holds an CMYK ICC profile
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static
BufferedImage cmykToBuffered(
MagickImage pImage, boolean
pAlpha) throws
MagickException {
Dimension size =
pImage.
getDimension();
int
length =
size.
width *
size.
height;
// Retreive the ICC profile
ICC_Profile profile =
ICC_Profile.
getInstance(
pImage.
getColorProfile().
getInfo());
ColorSpace cs = new
ICC_ColorSpace(
profile);
int
bands =
cs.
getNumComponents() + (
pAlpha ? 1 : 0);
int[]
bits = new int[
bands];
for (int
i = 0;
i <
bands;
i++) {
bits[
i] = 8;
}
ColorModel cm =
pAlpha ?
new
ComponentColorModel(
cs,
bits, true, true,
Transparency.
TRANSLUCENT,
DataBuffer.
TYPE_BYTE) :
new
ComponentColorModel(
cs,
bits, false, false,
Transparency.
OPAQUE,
DataBuffer.
TYPE_BYTE);
byte[]
pixels = new byte[
length *
bands];
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
// TODO: handle more generic cases if profile is not CMYK
// TODO: Test "ACMYK"
pImage.
dispatchImage(0, 0,
size.
width,
size.
height,
pAlpha ? "ACMYK" : "CMYK",
pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new
DataBufferByte(
pixels,
pixels.length);
// TODO: build array from bands variable, here it just works for CMYK
// The values has not been tested with an alpha picture actually...
int[]
bandOffsets =
pAlpha ? new int[] {0, 1, 2, 3, 4} : new int[] {0, 1, 2, 3};
WritableRaster raster =
Raster.
createInterleavedRaster(
buffer,
size.
width,
size.
height,
size.
width *
bands,
bands,
bandOffsets,
LOCATION_UPPER_LEFT);
return new
BufferedImage(
cm,
raster,
pAlpha, null);
}
}