/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
/*
**********************************************************************
**********************************************************************
**********************************************************************
*** COPYRIGHT (c) Eastman Kodak Company, 1997 ***
*** As an unpublished work pursuant to Title 17 of the United ***
*** States Code. All rights reserved. ***
**********************************************************************
**********************************************************************
**********************************************************************/
package java.awt.image;
import java.awt.
Point;
import java.awt.
Graphics2D;
import java.awt.color.*;
import sun.java2d.cmm.
ColorTransform;
import sun.java2d.cmm.
CMSManager;
import sun.java2d.cmm.
ProfileDeferralMgr;
import sun.java2d.cmm.
PCMM;
import java.awt.geom.
Rectangle2D;
import java.awt.geom.
Point2D;
import java.awt.
RenderingHints;
/**
* This class performs a pixel-by-pixel color conversion of the data in
* the source image. The resulting color values are scaled to the precision
* of the destination image. Color conversion can be specified
* via an array of ColorSpace objects or an array of ICC_Profile objects.
* <p>
* If the source is a BufferedImage with premultiplied alpha, the
* color components are divided by the alpha component before color conversion.
* If the destination is a BufferedImage with premultiplied alpha, the
* color components are multiplied by the alpha component after conversion.
* Rasters are treated as having no alpha channel, i.e. all bands are
* color bands.
* <p>
* If a RenderingHints object is specified in the constructor, the
* color rendering hint and the dithering hint may be used to control
* color conversion.
* <p>
* Note that Source and Destination may be the same object.
* @see java.awt.RenderingHints#KEY_COLOR_RENDERING
* @see java.awt.RenderingHints#KEY_DITHERING
*/
public class
ColorConvertOp implements
BufferedImageOp,
RasterOp {
ICC_Profile[]
profileList;
ColorSpace[]
CSList;
ColorTransform thisTransform,
thisRasterTransform;
ICC_Profile thisSrcProfile,
thisDestProfile;
RenderingHints hints;
boolean
gotProfiles;
float[]
srcMinVals,
srcMaxVals,
dstMinVals,
dstMaxVals;
/* the class initializer */
static {
if (
ProfileDeferralMgr.
deferring) {
ProfileDeferralMgr.
activateProfiles();
}
}
/**
* Constructs a new ColorConvertOp which will convert
* from a source color space to a destination color space.
* The RenderingHints argument may be null.
* This Op can be used only with BufferedImages, and will convert
* directly from the ColorSpace of the source image to that of the
* destination. The destination argument of the filter method
* cannot be specified as null.
* @param hints the <code>RenderingHints</code> object used to control
* the color conversion, or <code>null</code>
*/
public
ColorConvertOp (
RenderingHints hints)
{
profileList = new
ICC_Profile [0]; /* 0 length list */
this.
hints =
hints;
}
/**
* Constructs a new ColorConvertOp from a ColorSpace object.
* The RenderingHints argument may be null. This
* Op can be used only with BufferedImages, and is primarily useful
* when the {@link #filter(BufferedImage, BufferedImage) filter}
* method is invoked with a destination argument of null.
* In that case, the ColorSpace defines the destination color space
* for the destination created by the filter method. Otherwise, the
* ColorSpace defines an intermediate space to which the source is
* converted before being converted to the destination space.
* @param cspace defines the destination <code>ColorSpace</code> or an
* intermediate <code>ColorSpace</code>
* @param hints the <code>RenderingHints</code> object used to control
* the color conversion, or <code>null</code>
* @throws NullPointerException if cspace is null
*/
public
ColorConvertOp (
ColorSpace cspace,
RenderingHints hints)
{
if (
cspace == null) {
throw new
NullPointerException("ColorSpace cannot be null");
}
if (
cspace instanceof
ICC_ColorSpace) {
profileList = new
ICC_Profile [1]; /* 1 profile in the list */
profileList [0] = ((
ICC_ColorSpace)
cspace).
getProfile();
}
else {
CSList = new
ColorSpace[1]; /* non-ICC case: 1 ColorSpace in list */
CSList[0] =
cspace;
}
this.
hints =
hints;
}
/**
* Constructs a new ColorConvertOp from two ColorSpace objects.
* The RenderingHints argument may be null.
* This Op is primarily useful for calling the filter method on
* Rasters, in which case the two ColorSpaces define the operation
* to be performed on the Rasters. In that case, the number of bands
* in the source Raster must match the number of components in
* srcCspace, and the number of bands in the destination Raster
* must match the number of components in dstCspace. For BufferedImages,
* the two ColorSpaces define intermediate spaces through which the
* source is converted before being converted to the destination space.
* @param srcCspace the source <code>ColorSpace</code>
* @param dstCspace the destination <code>ColorSpace</code>
* @param hints the <code>RenderingHints</code> object used to control
* the color conversion, or <code>null</code>
* @throws NullPointerException if either srcCspace or dstCspace is null
*/
public
ColorConvertOp(
ColorSpace srcCspace,
ColorSpace dstCspace,
RenderingHints hints)
{
if ((
srcCspace == null) || (
dstCspace == null)) {
throw new
NullPointerException("ColorSpaces cannot be null");
}
if ((
srcCspace instanceof
ICC_ColorSpace) &&
(
dstCspace instanceof
ICC_ColorSpace)) {
profileList = new
ICC_Profile [2]; /* 2 profiles in the list */
profileList [0] = ((
ICC_ColorSpace)
srcCspace).
getProfile();
profileList [1] = ((
ICC_ColorSpace)
dstCspace).
getProfile();
getMinMaxValsFromColorSpaces(
srcCspace,
dstCspace);
} else {
/* non-ICC case: 2 ColorSpaces in list */
CSList = new
ColorSpace[2];
CSList[0] =
srcCspace;
CSList[1] =
dstCspace;
}
this.
hints =
hints;
}
/**
* Constructs a new ColorConvertOp from an array of ICC_Profiles.
* The RenderingHints argument may be null.
* The sequence of profiles may include profiles that represent color
* spaces, profiles that represent effects, etc. If the whole sequence
* does not represent a well-defined color conversion, an exception is
* thrown.
* <p>For BufferedImages, if the ColorSpace
* of the source BufferedImage does not match the requirements of the
* first profile in the array,
* the first conversion is to an appropriate ColorSpace.
* If the requirements of the last profile in the array are not met
* by the ColorSpace of the destination BufferedImage,
* the last conversion is to the destination's ColorSpace.
* <p>For Rasters, the number of bands in the source Raster must match
* the requirements of the first profile in the array, and the
* number of bands in the destination Raster must match the requirements
* of the last profile in the array. The array must have at least two
* elements or calling the filter method for Rasters will throw an
* IllegalArgumentException.
* @param profiles the array of <code>ICC_Profile</code> objects
* @param hints the <code>RenderingHints</code> object used to control
* the color conversion, or <code>null</code>
* @exception IllegalArgumentException when the profile sequence does not
* specify a well-defined color conversion
* @exception NullPointerException if profiles is null
*/
public
ColorConvertOp (
ICC_Profile[]
profiles,
RenderingHints hints)
{
if (
profiles == null) {
throw new
NullPointerException("Profiles cannot be null");
}
gotProfiles = true;
profileList = new
ICC_Profile[
profiles.length];
for (int
i1 = 0;
i1 <
profiles.length;
i1++) {
profileList[
i1] =
profiles[
i1];
}
this.
hints =
hints;
}
/**
* Returns the array of ICC_Profiles used to construct this ColorConvertOp.
* Returns null if the ColorConvertOp was not constructed from such an
* array.
* @return the array of <code>ICC_Profile</code> objects of this
* <code>ColorConvertOp</code>, or <code>null</code> if this
* <code>ColorConvertOp</code> was not constructed with an
* array of <code>ICC_Profile</code> objects.
*/
public final
ICC_Profile[]
getICC_Profiles() {
if (
gotProfiles) {
ICC_Profile[]
profiles = new
ICC_Profile[
profileList.length];
for (int
i1 = 0;
i1 <
profileList.length;
i1++) {
profiles[
i1] =
profileList[
i1];
}
return
profiles;
}
return null;
}
/**
* ColorConverts the source BufferedImage.
* If the destination image is null,
* a BufferedImage will be created with an appropriate ColorModel.
* @param src the source <code>BufferedImage</code> to be converted
* @param dest the destination <code>BufferedImage</code>,
* or <code>null</code>
* @return <code>dest</code> color converted from <code>src</code>
* or a new, converted <code>BufferedImage</code>
* if <code>dest</code> is <code>null</code>
* @exception IllegalArgumentException if dest is null and this op was
* constructed using the constructor which takes only a
* RenderingHints argument, since the operation is ill defined.
*/
public final
BufferedImage filter(
BufferedImage src,
BufferedImage dest) {
ColorSpace srcColorSpace,
destColorSpace;
BufferedImage savdest = null;
if (
src.
getColorModel() instanceof
IndexColorModel) {
IndexColorModel icm = (
IndexColorModel)
src.
getColorModel();
src =
icm.
convertToIntDiscrete(
src.
getRaster(), true);
}
srcColorSpace =
src.
getColorModel().
getColorSpace();
if (
dest != null) {
if (
dest.
getColorModel() instanceof
IndexColorModel) {
savdest =
dest;
dest = null;
destColorSpace = null;
} else {
destColorSpace =
dest.
getColorModel().
getColorSpace();
}
} else {
destColorSpace = null;
}
if ((
CSList != null) ||
(!(
srcColorSpace instanceof
ICC_ColorSpace)) ||
((
dest != null) &&
(!(
destColorSpace instanceof
ICC_ColorSpace)))) {
/* non-ICC case */
dest =
nonICCBIFilter(
src,
srcColorSpace,
dest,
destColorSpace);
} else {
dest =
ICCBIFilter(
src,
srcColorSpace,
dest,
destColorSpace);
}
if (
savdest != null) {
Graphics2D big =
savdest.
createGraphics();
try {
big.
drawImage(
dest, 0, 0, null);
} finally {
big.
dispose();
}
return
savdest;
} else {
return
dest;
}
}
private final
BufferedImage ICCBIFilter(
BufferedImage src,
ColorSpace srcColorSpace,
BufferedImage dest,
ColorSpace destColorSpace) {
int
nProfiles =
profileList.length;
ICC_Profile srcProfile = null,
destProfile = null;
srcProfile = ((
ICC_ColorSpace)
srcColorSpace).
getProfile();
if (
dest == null) { /* last profile in the list defines
the output color space */
if (
nProfiles == 0) {
throw new
IllegalArgumentException(
"Destination ColorSpace is undefined");
}
destProfile =
profileList [
nProfiles - 1];
dest =
createCompatibleDestImage(
src, null);
}
else {
if (
src.
getHeight() !=
dest.
getHeight() ||
src.
getWidth() !=
dest.
getWidth()) {
throw new
IllegalArgumentException(
"Width or height of BufferedImages do not match");
}
destProfile = ((
ICC_ColorSpace)
destColorSpace).
getProfile();
}
/* Checking if all profiles in the transform sequence are the same.
* If so, performing just copying the data.
*/
if (
srcProfile ==
destProfile) {
boolean
noTrans = true;
for (int
i = 0;
i <
nProfiles;
i++) {
if (
srcProfile !=
profileList[
i]) {
noTrans = false;
break;
}
}
if (
noTrans) {
Graphics2D g =
dest.
createGraphics();
try {
g.
drawImage(
src, 0, 0, null);
} finally {
g.
dispose();
}
return
dest;
}
}
/* make a new transform if needed */
if ((
thisTransform == null) || (
thisSrcProfile !=
srcProfile) ||
(
thisDestProfile !=
destProfile) ) {
updateBITransform(
srcProfile,
destProfile);
}
/* color convert the image */
thisTransform.
colorConvert(
src,
dest);
return
dest;
}
private void
updateBITransform(
ICC_Profile srcProfile,
ICC_Profile destProfile) {
ICC_Profile[]
theProfiles;
int
i1,
nProfiles,
nTransforms,
whichTrans,
renderState;
ColorTransform[]
theTransforms;
boolean
useSrc = false,
useDest = false;
nProfiles =
profileList.length;
nTransforms =
nProfiles;
if ((
nProfiles == 0) || (
srcProfile !=
profileList[0])) {
nTransforms += 1;
useSrc = true;
}
if ((
nProfiles == 0) || (
destProfile !=
profileList[
nProfiles - 1]) ||
(
nTransforms < 2)) {
nTransforms += 1;
useDest = true;
}
/* make the profile list */
theProfiles = new
ICC_Profile[
nTransforms]; /* the list of profiles
for this Op */
int
idx = 0;
if (
useSrc) {
/* insert source as first profile */
theProfiles[
idx++] =
srcProfile;
}
for (
i1 = 0;
i1 <
nProfiles;
i1++) {
/* insert profiles defined in this Op */
theProfiles[
idx++] =
profileList [
i1];
}
if (
useDest) {
/* insert dest as last profile */
theProfiles[
idx] =
destProfile;
}
/* make the transform list */
theTransforms = new
ColorTransform [
nTransforms];
/* initialize transform get loop */
if (
theProfiles[0].
getProfileClass() ==
ICC_Profile.
CLASS_OUTPUT) {
/* if first profile is a printer
render as colorimetric */
renderState =
ICC_Profile.
icRelativeColorimetric;
}
else {
renderState =
ICC_Profile.
icPerceptual; /* render any other
class perceptually */
}
whichTrans =
ColorTransform.
In;
PCMM mdl =
CMSManager.
getModule();
/* get the transforms from each profile */
for (
i1 = 0;
i1 <
nTransforms;
i1++) {
if (
i1 ==
nTransforms -1) { /* last profile? */
whichTrans =
ColorTransform.
Out; /* get output transform */
}
else { /* check for abstract profile */
if ((
whichTrans ==
ColorTransform.
Simulation) &&
(
theProfiles[
i1].
getProfileClass () ==
ICC_Profile.
CLASS_ABSTRACT)) {
renderState =
ICC_Profile.
icPerceptual;
whichTrans =
ColorTransform.
In;
}
}
theTransforms[
i1] =
mdl.
createTransform (
theProfiles[
i1],
renderState,
whichTrans);
/* get this profile's rendering intent to select transform
from next profile */
renderState =
getRenderingIntent(
theProfiles[
i1]);
/* "middle" profiles use simulation transform */
whichTrans =
ColorTransform.
Simulation;
}
/* make the net transform */
thisTransform =
mdl.
createTransform(
theTransforms);
/* update corresponding source and dest profiles */
thisSrcProfile =
srcProfile;
thisDestProfile =
destProfile;
}
/**
* ColorConverts the image data in the source Raster.
* If the destination Raster is null, a new Raster will be created.
* The number of bands in the source and destination Rasters must
* meet the requirements explained above. The constructor used to
* create this ColorConvertOp must have provided enough information
* to define both source and destination color spaces. See above.
* Otherwise, an exception is thrown.
* @param src the source <code>Raster</code> to be converted
* @param dest the destination <code>WritableRaster</code>,
* or <code>null</code>
* @return <code>dest</code> color converted from <code>src</code>
* or a new, converted <code>WritableRaster</code>
* if <code>dest</code> is <code>null</code>
* @exception IllegalArgumentException if the number of source or
* destination bands is incorrect, the source or destination
* color spaces are undefined, or this op was constructed
* with one of the constructors that applies only to
* operations on BufferedImages.
*/
public final
WritableRaster filter (
Raster src,
WritableRaster dest) {
if (
CSList != null) {
/* non-ICC case */
return
nonICCRasterFilter(
src,
dest);
}
int
nProfiles =
profileList.length;
if (
nProfiles < 2) {
throw new
IllegalArgumentException(
"Source or Destination ColorSpace is undefined");
}
if (
src.
getNumBands() !=
profileList[0].
getNumComponents()) {
throw new
IllegalArgumentException(
"Numbers of source Raster bands and source color space " +
"components do not match");
}
if (
dest == null) {
dest =
createCompatibleDestRaster(
src);
}
else {
if (
src.
getHeight() !=
dest.
getHeight() ||
src.
getWidth() !=
dest.
getWidth()) {
throw new
IllegalArgumentException(
"Width or height of Rasters do not match");
}
if (
dest.
getNumBands() !=
profileList[
nProfiles-1].
getNumComponents()) {
throw new
IllegalArgumentException(
"Numbers of destination Raster bands and destination " +
"color space components do not match");
}
}
/* make a new transform if needed */
if (
thisRasterTransform == null) {
int
i1,
whichTrans,
renderState;
ColorTransform[]
theTransforms;
/* make the transform list */
theTransforms = new
ColorTransform [
nProfiles];
/* initialize transform get loop */
if (
profileList[0].
getProfileClass() ==
ICC_Profile.
CLASS_OUTPUT) {
/* if first profile is a printer
render as colorimetric */
renderState =
ICC_Profile.
icRelativeColorimetric;
}
else {
renderState =
ICC_Profile.
icPerceptual; /* render any other
class perceptually */
}
whichTrans =
ColorTransform.
In;
PCMM mdl =
CMSManager.
getModule();
/* get the transforms from each profile */
for (
i1 = 0;
i1 <
nProfiles;
i1++) {
if (
i1 ==
nProfiles -1) { /* last profile? */
whichTrans =
ColorTransform.
Out; /* get output transform */
}
else { /* check for abstract profile */
if ((
whichTrans ==
ColorTransform.
Simulation) &&
(
profileList[
i1].
getProfileClass () ==
ICC_Profile.
CLASS_ABSTRACT)) {
renderState =
ICC_Profile.
icPerceptual;
whichTrans =
ColorTransform.
In;
}
}
theTransforms[
i1] =
mdl.
createTransform (
profileList[
i1],
renderState,
whichTrans);
/* get this profile's rendering intent to select transform
from next profile */
renderState =
getRenderingIntent(
profileList[
i1]);
/* "middle" profiles use simulation transform */
whichTrans =
ColorTransform.
Simulation;
}
/* make the net transform */
thisRasterTransform =
mdl.
createTransform(
theTransforms);
}
int
srcTransferType =
src.
getTransferType();
int
dstTransferType =
dest.
getTransferType();
if ((
srcTransferType ==
DataBuffer.
TYPE_FLOAT) ||
(
srcTransferType ==
DataBuffer.
TYPE_DOUBLE) ||
(
dstTransferType ==
DataBuffer.
TYPE_FLOAT) ||
(
dstTransferType ==
DataBuffer.
TYPE_DOUBLE)) {
if (
srcMinVals == null) {
getMinMaxValsFromProfiles(
profileList[0],
profileList[
nProfiles-1]);
}
/* color convert the raster */
thisRasterTransform.
colorConvert(
src,
dest,
srcMinVals,
srcMaxVals,
dstMinVals,
dstMaxVals);
} else {
/* color convert the raster */
thisRasterTransform.
colorConvert(
src,
dest);
}
return
dest;
}
/**
* Returns the bounding box of the destination, given this source.
* Note that this will be the same as the the bounding box of the
* source.
* @param src the source <code>BufferedImage</code>
* @return a <code>Rectangle2D</code> that is the bounding box
* of the destination, given the specified <code>src</code>
*/
public final
Rectangle2D getBounds2D (
BufferedImage src) {
return
getBounds2D(
src.
getRaster());
}
/**
* Returns the bounding box of the destination, given this source.
* Note that this will be the same as the the bounding box of the
* source.
* @param src the source <code>Raster</code>
* @return a <code>Rectangle2D</code> that is the bounding box
* of the destination, given the specified <code>src</code>
*/
public final
Rectangle2D getBounds2D (
Raster src) {
/* return new Rectangle (src.getXOffset(),
src.getYOffset(),
src.getWidth(), src.getHeight()); */
return
src.
getBounds();
}
/**
* Creates a zeroed destination image with the correct size and number of
* bands, given this source.
* @param src Source image for the filter operation.
* @param destCM ColorModel of the destination. If null, an
* appropriate ColorModel will be used.
* @return a <code>BufferedImage</code> with the correct size and
* number of bands from the specified <code>src</code>.
* @throws IllegalArgumentException if <code>destCM</code> is
* <code>null</code> and this <code>ColorConvertOp</code> was
* created without any <code>ICC_Profile</code> or
* <code>ColorSpace</code> defined for the destination
*/
public
BufferedImage createCompatibleDestImage (
BufferedImage src,
ColorModel destCM) {
ColorSpace cs = null;;
if (
destCM == null) {
if (
CSList == null) {
/* ICC case */
int
nProfiles =
profileList.length;
if (
nProfiles == 0) {
throw new
IllegalArgumentException(
"Destination ColorSpace is undefined");
}
ICC_Profile destProfile =
profileList[
nProfiles - 1];
cs = new
ICC_ColorSpace(
destProfile);
} else {
/* non-ICC case */
int
nSpaces =
CSList.length;
cs =
CSList[
nSpaces - 1];
}
}
return
createCompatibleDestImage(
src,
destCM,
cs);
}
private
BufferedImage createCompatibleDestImage(
BufferedImage src,
ColorModel destCM,
ColorSpace destCS) {
BufferedImage image;
if (
destCM == null) {
ColorModel srcCM =
src.
getColorModel();
int
nbands =
destCS.
getNumComponents();
boolean
hasAlpha =
srcCM.
hasAlpha();
if (
hasAlpha) {
nbands += 1;
}
int[]
nbits = new int[
nbands];
for (int
i = 0;
i <
nbands;
i++) {
nbits[
i] = 8;
}
destCM = new
ComponentColorModel(
destCS,
nbits,
hasAlpha,
srcCM.
isAlphaPremultiplied(),
srcCM.
getTransparency(),
DataBuffer.
TYPE_BYTE);
}
int
w =
src.
getWidth();
int
h =
src.
getHeight();
image = new
BufferedImage(
destCM,
destCM.
createCompatibleWritableRaster(
w,
h),
destCM.
isAlphaPremultiplied(), null);
return
image;
}
/**
* Creates a zeroed destination Raster with the correct size and number of
* bands, given this source.
* @param src the specified <code>Raster</code>
* @return a <code>WritableRaster</code> with the correct size and number
* of bands from the specified <code>src</code>
* @throws IllegalArgumentException if this <code>ColorConvertOp</code>
* was created without sufficient information to define the
* <code>dst</code> and <code>src</code> color spaces
*/
public
WritableRaster createCompatibleDestRaster (
Raster src) {
int
ncomponents;
if (
CSList != null) {
/* non-ICC case */
if (
CSList.length != 2) {
throw new
IllegalArgumentException(
"Destination ColorSpace is undefined");
}
ncomponents =
CSList[1].
getNumComponents();
} else {
/* ICC case */
int
nProfiles =
profileList.length;
if (
nProfiles < 2) {
throw new
IllegalArgumentException(
"Destination ColorSpace is undefined");
}
ncomponents =
profileList[
nProfiles-1].
getNumComponents();
}
WritableRaster dest =
Raster.
createInterleavedRaster(
DataBuffer.
TYPE_BYTE,
src.
getWidth(),
src.
getHeight(),
ncomponents,
new
Point(
src.
getMinX(),
src.
getMinY()));
return
dest;
}
/**
* Returns the location of the destination point given a
* point in the source. If <code>dstPt</code> is non-null,
* it will be used to hold the return value. Note that
* for this class, the destination point will be the same
* as the source point.
* @param srcPt the specified source <code>Point2D</code>
* @param dstPt the destination <code>Point2D</code>
* @return <code>dstPt</code> after setting its location to be
* the same as <code>srcPt</code>
*/
public final
Point2D getPoint2D (
Point2D srcPt,
Point2D dstPt) {
if (
dstPt == null) {
dstPt = new
Point2D.
Float();
}
dstPt.
setLocation(
srcPt.
getX(),
srcPt.
getY());
return
dstPt;
}
/**
* Returns the RenderingIntent from the specified ICC Profile.
*/
private int
getRenderingIntent (
ICC_Profile profile) {
byte[]
header =
profile.
getData(
ICC_Profile.
icSigHead);
int
index =
ICC_Profile.
icHdrRenderingIntent;
/* According to ICC spec, only the least-significant 16 bits shall be
* used to encode the rendering intent. The most significant 16 bits
* shall be set to zero. Thus, we are ignoring two most significant
* bytes here.
*
* See http://www.color.org/ICC1v42_2006-05.pdf, section 7.2.15.
*/
return ((
header[
index+2] & 0xff) << 8) |
(
header[
index+3] & 0xff);
}
/**
* Returns the rendering hints used by this op.
* @return the <code>RenderingHints</code> object of this
* <code>ColorConvertOp</code>
*/
public final
RenderingHints getRenderingHints() {
return
hints;
}
private final
BufferedImage nonICCBIFilter(
BufferedImage src,
ColorSpace srcColorSpace,
BufferedImage dst,
ColorSpace dstColorSpace) {
int
w =
src.
getWidth();
int
h =
src.
getHeight();
ICC_ColorSpace ciespace =
(
ICC_ColorSpace)
ColorSpace.
getInstance(
ColorSpace.
CS_CIEXYZ);
if (
dst == null) {
dst =
createCompatibleDestImage(
src, null);
dstColorSpace =
dst.
getColorModel().
getColorSpace();
} else {
if ((
h !=
dst.
getHeight()) || (
w !=
dst.
getWidth())) {
throw new
IllegalArgumentException(
"Width or height of BufferedImages do not match");
}
}
Raster srcRas =
src.
getRaster();
WritableRaster dstRas =
dst.
getRaster();
ColorModel srcCM =
src.
getColorModel();
ColorModel dstCM =
dst.
getColorModel();
int
srcNumComp =
srcCM.
getNumColorComponents();
int
dstNumComp =
dstCM.
getNumColorComponents();
boolean
dstHasAlpha =
dstCM.
hasAlpha();
boolean
needSrcAlpha =
srcCM.
hasAlpha() &&
dstHasAlpha;
ColorSpace[]
list;
if ((
CSList == null) && (
profileList.length != 0)) {
/* possible non-ICC src, some profiles, possible non-ICC dst */
boolean
nonICCSrc,
nonICCDst;
ICC_Profile srcProfile,
dstProfile;
if (!(
srcColorSpace instanceof
ICC_ColorSpace)) {
nonICCSrc = true;
srcProfile =
ciespace.
getProfile();
} else {
nonICCSrc = false;
srcProfile = ((
ICC_ColorSpace)
srcColorSpace).
getProfile();
}
if (!(
dstColorSpace instanceof
ICC_ColorSpace)) {
nonICCDst = true;
dstProfile =
ciespace.
getProfile();
} else {
nonICCDst = false;
dstProfile = ((
ICC_ColorSpace)
dstColorSpace).
getProfile();
}
/* make a new transform if needed */
if ((
thisTransform == null) || (
thisSrcProfile !=
srcProfile) ||
(
thisDestProfile !=
dstProfile) ) {
updateBITransform(
srcProfile,
dstProfile);
}
// process per scanline
float
maxNum = 65535.0f; // use 16-bit precision in CMM
ColorSpace cs;
int
iccSrcNumComp;
if (
nonICCSrc) {
cs =
ciespace;
iccSrcNumComp = 3;
} else {
cs =
srcColorSpace;
iccSrcNumComp =
srcNumComp;
}
float[]
srcMinVal = new float[
iccSrcNumComp];
float[]
srcInvDiffMinMax = new float[
iccSrcNumComp];
for (int
i = 0;
i <
srcNumComp;
i++) {
srcMinVal[
i] =
cs.
getMinValue(
i);
srcInvDiffMinMax[
i] =
maxNum / (
cs.
getMaxValue(
i) -
srcMinVal[
i]);
}
int
iccDstNumComp;
if (
nonICCDst) {
cs =
ciespace;
iccDstNumComp = 3;
} else {
cs =
dstColorSpace;
iccDstNumComp =
dstNumComp;
}
float[]
dstMinVal = new float[
iccDstNumComp];
float[]
dstDiffMinMax = new float[
iccDstNumComp];
for (int
i = 0;
i <
dstNumComp;
i++) {
dstMinVal[
i] =
cs.
getMinValue(
i);
dstDiffMinMax[
i] = (
cs.
getMaxValue(
i) -
dstMinVal[
i]) /
maxNum;
}
float[]
dstColor;
if (
dstHasAlpha) {
int
size = ((
dstNumComp + 1) > 3) ? (
dstNumComp + 1) : 3;
dstColor = new float[
size];
} else {
int
size = (
dstNumComp > 3) ?
dstNumComp : 3;
dstColor = new float[
size];
}
short[]
srcLine = new short[
w *
iccSrcNumComp];
short[]
dstLine = new short[
w *
iccDstNumComp];
Object pixel;
float[]
color;
float[]
alpha = null;
if (
needSrcAlpha) {
alpha = new float[
w];
}
int
idx;
// process each scanline
for (int
y = 0;
y <
h;
y++) {
// convert src scanline
pixel = null;
color = null;
idx = 0;
for (int
x = 0;
x <
w;
x++) {
pixel =
srcRas.
getDataElements(
x,
y,
pixel);
color =
srcCM.
getNormalizedComponents(
pixel,
color, 0);
if (
needSrcAlpha) {
alpha[
x] =
color[
srcNumComp];
}
if (
nonICCSrc) {
color =
srcColorSpace.
toCIEXYZ(
color);
}
for (int
i = 0;
i <
iccSrcNumComp;
i++) {
srcLine[
idx++] = (short)
((
color[
i] -
srcMinVal[
i]) *
srcInvDiffMinMax[
i] +
0.5f);
}
}
// color convert srcLine to dstLine
thisTransform.
colorConvert(
srcLine,
dstLine);
// convert dst scanline
pixel = null;
idx = 0;
for (int
x = 0;
x <
w;
x++) {
for (int
i = 0;
i <
iccDstNumComp;
i++) {
dstColor[
i] = ((float) (
dstLine[
idx++] & 0xffff)) *
dstDiffMinMax[
i] +
dstMinVal[
i];
}
if (
nonICCDst) {
color =
srcColorSpace.
fromCIEXYZ(
dstColor);
for (int
i = 0;
i <
dstNumComp;
i++) {
dstColor[
i] =
color[
i];
}
}
if (
needSrcAlpha) {
dstColor[
dstNumComp] =
alpha[
x];
} else if (
dstHasAlpha) {
dstColor[
dstNumComp] = 1.0f;
}
pixel =
dstCM.
getDataElements(
dstColor, 0,
pixel);
dstRas.
setDataElements(
x,
y,
pixel);
}
}
} else {
/* possible non-ICC src, possible CSList, possible non-ICC dst */
// process per pixel
int
numCS;
if (
CSList == null) {
numCS = 0;
} else {
numCS =
CSList.length;
}
float[]
dstColor;
if (
dstHasAlpha) {
dstColor = new float[
dstNumComp + 1];
} else {
dstColor = new float[
dstNumComp];
}
Object spixel = null;
Object dpixel = null;
float[]
color = null;
float[]
tmpColor;
// process each pixel
for (int
y = 0;
y <
h;
y++) {
for (int
x = 0;
x <
w;
x++) {
spixel =
srcRas.
getDataElements(
x,
y,
spixel);
color =
srcCM.
getNormalizedComponents(
spixel,
color, 0);
tmpColor =
srcColorSpace.
toCIEXYZ(
color);
for (int
i = 0;
i <
numCS;
i++) {
tmpColor =
CSList[
i].
fromCIEXYZ(
tmpColor);
tmpColor =
CSList[
i].
toCIEXYZ(
tmpColor);
}
tmpColor =
dstColorSpace.
fromCIEXYZ(
tmpColor);
for (int
i = 0;
i <
dstNumComp;
i++) {
dstColor[
i] =
tmpColor[
i];
}
if (
needSrcAlpha) {
dstColor[
dstNumComp] =
color[
srcNumComp];
} else if (
dstHasAlpha) {
dstColor[
dstNumComp] = 1.0f;
}
dpixel =
dstCM.
getDataElements(
dstColor, 0,
dpixel);
dstRas.
setDataElements(
x,
y,
dpixel);
}
}
}
return
dst;
}
/* color convert a Raster - handles byte, ushort, int, short, float,
or double transferTypes */
private final
WritableRaster nonICCRasterFilter(
Raster src,
WritableRaster dst) {
if (
CSList.length != 2) {
throw new
IllegalArgumentException(
"Destination ColorSpace is undefined");
}
if (
src.
getNumBands() !=
CSList[0].
getNumComponents()) {
throw new
IllegalArgumentException(
"Numbers of source Raster bands and source color space " +
"components do not match");
}
if (
dst == null) {
dst =
createCompatibleDestRaster(
src);
} else {
if (
src.
getHeight() !=
dst.
getHeight() ||
src.
getWidth() !=
dst.
getWidth()) {
throw new
IllegalArgumentException(
"Width or height of Rasters do not match");
}
if (
dst.
getNumBands() !=
CSList[1].
getNumComponents()) {
throw new
IllegalArgumentException(
"Numbers of destination Raster bands and destination " +
"color space components do not match");
}
}
if (
srcMinVals == null) {
getMinMaxValsFromColorSpaces(
CSList[0],
CSList[1]);
}
SampleModel srcSM =
src.
getSampleModel();
SampleModel dstSM =
dst.
getSampleModel();
boolean
srcIsFloat,
dstIsFloat;
int
srcTransferType =
src.
getTransferType();
int
dstTransferType =
dst.
getTransferType();
if ((
srcTransferType ==
DataBuffer.
TYPE_FLOAT) ||
(
srcTransferType ==
DataBuffer.
TYPE_DOUBLE)) {
srcIsFloat = true;
} else {
srcIsFloat = false;
}
if ((
dstTransferType ==
DataBuffer.
TYPE_FLOAT) ||
(
dstTransferType ==
DataBuffer.
TYPE_DOUBLE)) {
dstIsFloat = true;
} else {
dstIsFloat = false;
}
int
w =
src.
getWidth();
int
h =
src.
getHeight();
int
srcNumBands =
src.
getNumBands();
int
dstNumBands =
dst.
getNumBands();
float[]
srcScaleFactor = null;
float[]
dstScaleFactor = null;
if (!
srcIsFloat) {
srcScaleFactor = new float[
srcNumBands];
for (int
i = 0;
i <
srcNumBands;
i++) {
if (
srcTransferType ==
DataBuffer.
TYPE_SHORT) {
srcScaleFactor[
i] = (
srcMaxVals[
i] -
srcMinVals[
i]) /
32767.0f;
} else {
srcScaleFactor[
i] = (
srcMaxVals[
i] -
srcMinVals[
i]) /
((float) ((1 <<
srcSM.
getSampleSize(
i)) - 1));
}
}
}
if (!
dstIsFloat) {
dstScaleFactor = new float[
dstNumBands];
for (int
i = 0;
i <
dstNumBands;
i++) {
if (
dstTransferType ==
DataBuffer.
TYPE_SHORT) {
dstScaleFactor[
i] = 32767.0f /
(
dstMaxVals[
i] -
dstMinVals[
i]);
} else {
dstScaleFactor[
i] =
((float) ((1 <<
dstSM.
getSampleSize(
i)) - 1)) /
(
dstMaxVals[
i] -
dstMinVals[
i]);
}
}
}
int
ys =
src.
getMinY();
int
yd =
dst.
getMinY();
int
xs,
xd;
float
sample;
float[]
color = new float[
srcNumBands];
float[]
tmpColor;
ColorSpace srcColorSpace =
CSList[0];
ColorSpace dstColorSpace =
CSList[1];
// process each pixel
for (int
y = 0;
y <
h;
y++,
ys++,
yd++) {
// get src scanline
xs =
src.
getMinX();
xd =
dst.
getMinX();
for (int
x = 0;
x <
w;
x++,
xs++,
xd++) {
for (int
i = 0;
i <
srcNumBands;
i++) {
sample =
src.
getSampleFloat(
xs,
ys,
i);
if (!
srcIsFloat) {
sample =
sample *
srcScaleFactor[
i] +
srcMinVals[
i];
}
color[
i] =
sample;
}
tmpColor =
srcColorSpace.
toCIEXYZ(
color);
tmpColor =
dstColorSpace.
fromCIEXYZ(
tmpColor);
for (int
i = 0;
i <
dstNumBands;
i++) {
sample =
tmpColor[
i];
if (!
dstIsFloat) {
sample = (
sample -
dstMinVals[
i]) *
dstScaleFactor[
i];
}
dst.
setSample(
xd,
yd,
i,
sample);
}
}
}
return
dst;
}
private void
getMinMaxValsFromProfiles(
ICC_Profile srcProfile,
ICC_Profile dstProfile) {
int
type =
srcProfile.
getColorSpaceType();
int
nc =
srcProfile.
getNumComponents();
srcMinVals = new float[
nc];
srcMaxVals = new float[
nc];
setMinMax(
type,
nc,
srcMinVals,
srcMaxVals);
type =
dstProfile.
getColorSpaceType();
nc =
dstProfile.
getNumComponents();
dstMinVals = new float[
nc];
dstMaxVals = new float[
nc];
setMinMax(
type,
nc,
dstMinVals,
dstMaxVals);
}
private void
setMinMax(int
type, int
nc, float[]
minVals, float[]
maxVals) {
if (
type ==
ColorSpace.
TYPE_Lab) {
minVals[0] = 0.0f; // L
maxVals[0] = 100.0f;
minVals[1] = -128.0f; // a
maxVals[1] = 127.0f;
minVals[2] = -128.0f; // b
maxVals[2] = 127.0f;
} else if (
type ==
ColorSpace.
TYPE_XYZ) {
minVals[0] =
minVals[1] =
minVals[2] = 0.0f; // X, Y, Z
maxVals[0] =
maxVals[1] =
maxVals[2] = 1.0f + (32767.0f/ 32768.0f);
} else {
for (int
i = 0;
i <
nc;
i++) {
minVals[
i] = 0.0f;
maxVals[
i] = 1.0f;
}
}
}
private void
getMinMaxValsFromColorSpaces(
ColorSpace srcCspace,
ColorSpace dstCspace) {
int
nc =
srcCspace.
getNumComponents();
srcMinVals = new float[
nc];
srcMaxVals = new float[
nc];
for (int
i = 0;
i <
nc;
i++) {
srcMinVals[
i] =
srcCspace.
getMinValue(
i);
srcMaxVals[
i] =
srcCspace.
getMaxValue(
i);
}
nc =
dstCspace.
getNumComponents();
dstMinVals = new float[
nc];
dstMaxVals = new float[
nc];
for (int
i = 0;
i <
nc;
i++) {
dstMinVals[
i] =
dstCspace.
getMinValue(
i);
dstMaxVals[
i] =
dstCspace.
getMaxValue(
i);
}
}
}