/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Oleg V. Khaschansky
*/
package java.awt.image;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import org.apache.harmony.awt.gl.color.ColorConverter;
import org.apache.harmony.awt.gl.color.ColorScaler;
import org.apache.harmony.awt.gl.color.ICC_Transform;
import org.apache.harmony.awt.internal.nls.Messages;
public class ColorConvertOp implements BufferedImageOp, RasterOp {
// Unused but required by interfaces
RenderingHints renderingHints;
// Sequence consisting of ColorSpace and ICC_Profile elements
Object conversionSequence[] = new ICC_Profile[0]; // To eliminate checks for null
// Not null if ColorConvertOp is constructed from the array of ICC profiles
private ICC_Profile midProfiles[];
private final ColorConverter cc = new ColorConverter();
private final ICC_TransfomCreator tCreator = new ICC_TransfomCreator();
private boolean isICC = true;
// Cached ICC_Transform
private class ICC_TransfomCreator {
private ICC_Transform transform;
private int maxComponents;
/**
* For the full ICC case
* @param src
* @param dst
* @param convSeq
* @return
*/
public ICC_Transform getTransform(ICC_Profile src, ICC_Profile dst, ICC_Profile convSeq[]) {
if (transform != null &&
src == transform.getSrc() &&
dst == transform.getDst()) {
return transform;
}
int length = convSeq.length;
int srcFlg = 0, dstFlg = 0;
if (length == 0 || src != convSeq[0]) {
if (src != null) {
srcFlg = 1; // need src profile
}
}
if (length == 0 || dst != convSeq[length-1]) {
if (dst != null) {
dstFlg = 1; // need dst profile
}
}
ICC_Profile profiles[];
int nProfiles = length + srcFlg + dstFlg;
if (nProfiles == length) {
profiles = convSeq;
} else {
profiles = new ICC_Profile[nProfiles];
int pos = 0;
if (srcFlg != 0) {
profiles[pos++] = src;
}
for (int i=0; i<length; i++) {
profiles[pos++] = convSeq[i];
}
if (dstFlg != 0) {
profiles[pos++] = dst;
}
}
return transform = new ICC_Transform(profiles);
}
/**
* Used only when there are non-ICC color spaces.
* Returns sequence of non-ICC color spaces and ICC transforms
* made from src, dst and conversionSequence.
* @param src
* @param dst
* @return
*/
public Object[] getSequence(Object src, Object dst) {
ArrayList<Object> profiles = new ArrayList<Object>(10);
ArrayList<Object> sequence = new ArrayList<Object>(10);
// We need this profile anyway
ICC_Profile xyzProfile = ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
Object conversionFirst = null, conversionLast = null;
int conversionLength = conversionSequence.length;
if (conversionLength > 0) {
conversionFirst = conversionSequence[0];
conversionLast = conversionSequence[conversionLength-1];
}
boolean iccSequenceStarted = false;
if (src != conversionFirst && src != null) {
if (src instanceof ICC_Profile) {
profiles.add(src);
iccSequenceStarted = true;
} else {
profiles.add(xyzProfile);
sequence.add(src); // Add non-ICC color space to the sequence
}
} else {
profiles.add(xyzProfile);
}
for (int i=0; i<conversionLength; i++) {
if (conversionSequence[i] instanceof ICC_Profile) {
profiles.add(conversionSequence[i]);
iccSequenceStarted = true;
} else if (iccSequenceStarted) {
profiles.add(xyzProfile);
// Eliminate same profiles if there are any
// (e.g. xyzProfile may occur several times)
Object prev = profiles.get(0);
for (int k=1; k<profiles.size(); k++) {
if (prev == profiles.get(k)) {
k--;
profiles.remove(k);
}
prev = profiles.get(k);
}
// If only one profile left we skip the transform -
// it can be only CIEXYZ
if (profiles.size() > 1) {
sequence.add(new ICC_Transform(profiles.toArray(new ICC_Profile[0])));
// Add non-ICC color space to the sequence
sequence.add(conversionSequence[i]);
}
profiles.clear();
profiles.add(xyzProfile);
iccSequenceStarted = false; // Sequence of ICC profiles is processed
} else { // Add non-ICC color space to the sequence
sequence.add(conversionSequence[i]);
}
}
if (dst != conversionLast && dst != null) { // Add last profile if needed
if (dst instanceof ICC_Profile) {
profiles.add(dst);
iccSequenceStarted = true;
} else if (iccSequenceStarted) {
profiles.add(xyzProfile);
} else {
sequence.add(dst); // Add last non-ICC color space to the sequence
}
}
if (iccSequenceStarted) { // Make last transform if needed
sequence.add(new ICC_Transform(profiles.toArray(new ICC_Profile[0])));
if (dst != null && !(dst instanceof ICC_Profile)) {
sequence.add(dst); // Add last non-ICC color space to the
// sequence
}
}
// Calculate max number of components
// This number will be used for memory allocation
maxComponents = 0;
Object o;
for (int i=0, size = sequence.size(); i<size; i++) {
o = sequence.get(i);
if (o instanceof ICC_Transform) {
ICC_Transform t = (ICC_Transform) o;
maxComponents =
(maxComponents > t.getNumInputChannels() + 1) ?
maxComponents :
t.getNumInputChannels() + 1;
maxComponents =
(maxComponents > t.getNumOutputChannels() + 1) ?
maxComponents :
t.getNumOutputChannels() + 1;
} else {
ColorSpace cs = (ColorSpace) o;
maxComponents =
(maxComponents > cs.getNumComponents() + 1) ?
maxComponents :
cs.getNumComponents() + 1;
}
}
return sequence.toArray();
}
}
public ColorConvertOp(ColorSpace srcCS, ColorSpace dstCS, RenderingHints hints) {
if (srcCS == null || dstCS == null) {
throw new NullPointerException(Messages.getString("awt.25B")); //$NON-NLS-1$
}
renderingHints = hints;
boolean srcICC = srcCS instanceof ICC_ColorSpace;
boolean dstICC = dstCS instanceof ICC_ColorSpace;
if (srcICC && dstICC) {
conversionSequence = new ICC_Profile[2];
} else {
conversionSequence = new Object[2];
isICC = false;
}
if (srcICC) {
conversionSequence[0] = ((ICC_ColorSpace) srcCS).getProfile();
} else {
conversionSequence[0] = srcCS;
}
if (dstICC) {
conversionSequence[1] = ((ICC_ColorSpace) dstCS).getProfile();
} else {
conversionSequence[1] = dstCS;
}
}
public ColorConvertOp(ICC_Profile profiles[], RenderingHints hints) {
if (profiles == null) {
throw new NullPointerException(Messages.getString("awt.25C")); //$NON-NLS-1$
}
renderingHints = hints;
// This array is not used in the program logic, so don't need to copy it
// Store it only to return back
midProfiles = profiles;
conversionSequence = new ICC_Profile[midProfiles.length];
// Add profiles to the conversion sequence
for (int i=0, length=midProfiles.length; i<length; i++) {
conversionSequence[i] = midProfiles[i];
}
}
public ColorConvertOp(ColorSpace cs, RenderingHints hints) {
if (cs == null) {
throw new NullPointerException(Messages.getString("awt.25B")); //$NON-NLS-1$
}
renderingHints = hints;
if (cs instanceof ICC_ColorSpace) {
conversionSequence = new ICC_Profile[1];
conversionSequence[0] = ((ICC_ColorSpace) cs).getProfile();
} else {
conversionSequence = new Object[1];
conversionSequence[0] = cs;
isICC = false;
}
}
public ColorConvertOp(RenderingHints hints) {
renderingHints = hints;
}
public final WritableRaster filter(Raster src, WritableRaster dst) {
if (conversionSequence.length < 2) {
throw new IllegalArgumentException(Messages.getString("awt.25D")); //$NON-NLS-1$
}
ICC_Profile srcPf = null, dstPf = null; // unused if isICC is false
int nSrcColorComps, nDstColorComps;
Object first = conversionSequence[0];
Object last = conversionSequence[conversionSequence.length - 1];
// Get the number of input/output color components
if (isICC) {
srcPf = (ICC_Profile) first;
dstPf = (ICC_Profile) last;
nSrcColorComps = srcPf.getNumComponents();
nDstColorComps = dstPf.getNumComponents();
} else {
if (first instanceof ICC_Profile) {
srcPf = (ICC_Profile) first;
nSrcColorComps = srcPf.getNumComponents();
} else {
nSrcColorComps = ((ColorSpace) first).getNumComponents();
}
if (last instanceof ICC_Profile) {
dstPf = (ICC_Profile) last;
nDstColorComps = dstPf.getNumComponents();
} else {
nDstColorComps = ((ColorSpace) last).getNumComponents();
}
}
// Check that source and destination rasters are compatible with
// transforms and with each other
if (src.getNumBands() != nSrcColorComps) {
// awt.25E=Incorrect number of source raster bands. Should be equal
// to the number of color components of source colorspace.
throw new IllegalArgumentException(Messages.getString("awt.25E")); //$NON-NLS-1$
}
if (dst != null) { // Check destination raster
if (dst.getNumBands() !=
nDstColorComps) {
// awt.25F=Incorrect number of destination raster bands. Should
// be equal to the number of color components of destination
// colorspace.
throw new IllegalArgumentException(Messages.getString("awt.25F")); //$NON-NLS-1$
}
if (src.getWidth() != dst.getWidth() ||
src.getHeight() != dst.getHeight()) {
throw new IllegalArgumentException(Messages.getString("awt.260")); //$NON-NLS-1$
}
} else {
dst = createCompatibleDestRaster(src);
}
if (isICC) {
// Create transform
ICC_Transform t = tCreator.getTransform(srcPf, dstPf,
(ICC_Profile[])conversionSequence);
cc.translateColor(t, src, dst);
} else {
Object[] sequence = tCreator.getSequence(null, null);
// Get data from the source raster
ColorScaler scaler = new ColorScaler();
scaler.loadScalingData(src, null);
float tmpData[][] = scaler.scaleNormalize(src);
// Get source and destination color spaces
ColorSpace srcCS = (srcPf == null) ?
(ColorSpace) first:
new ICC_ColorSpace(srcPf);
ColorSpace dstCS = (dstPf == null) ?
(ColorSpace) last:
new ICC_ColorSpace(dstPf);
applySequence(sequence, tmpData, srcCS, dstCS);
scaler.loadScalingData(dst, null);
scaler.unscaleNormalized(dst, tmpData);
}
return dst;
}
public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel destCM) {
// If destination color model is passed only one line needed
if (destCM != null) {
return new BufferedImage(destCM,
destCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()),
destCM.isAlphaPremultiplied(),
null);
}
int nSpaces = conversionSequence.length;
if (nSpaces < 1) {
throw new IllegalArgumentException(Messages.getString("awt.261")); //$NON-NLS-1$
}
// Get destination color space
Object destination = conversionSequence[nSpaces-1];
ColorSpace dstCS =
(destination instanceof ColorSpace) ?
(ColorSpace) destination :
new ICC_ColorSpace((ICC_Profile) destination);
ColorModel srcCM = src.getColorModel();
ColorModel dstCM = new ComponentColorModel(dstCS,
srcCM.hasAlpha(),
srcCM.isAlphaPremultiplied(),
srcCM.getTransparency(),
srcCM.getTransferType());
return new BufferedImage(dstCM,
dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()),
dstCM.isAlphaPremultiplied(),
null);
}
public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
if (dst == null && conversionSequence.length < 1) {
throw new IllegalArgumentException(Messages.getString("awt.262")); //$NON-NLS-1$
}
ColorModel srcCM = src.getColorModel();
// First handle index color model
if (srcCM instanceof IndexColorModel) {
src = ((IndexColorModel) srcCM).convertToIntDiscrete(src.getRaster(), false);
}
ColorSpace srcCS = srcCM.getColorSpace();
BufferedImage res;
boolean isDstIndex = false;
if (dst != null) {
if (src.getWidth() != dst.getWidth() ||
src.getHeight() != dst.getHeight()) {
throw new IllegalArgumentException(Messages.getString("awt.263")); //$NON-NLS-1$
}
if (dst.getColorModel() instanceof IndexColorModel) {
isDstIndex = true;
res = createCompatibleDestImage(src, null);
} else {
res = dst;
}
} else {
res = createCompatibleDestImage(src, null);
}
ColorModel dstCM = res.getColorModel();
ColorSpace dstCS = dstCM.getColorSpace();
ICC_Profile srcPf = null, dstPf = null;
if (srcCS instanceof ICC_ColorSpace) {
srcPf = ((ICC_ColorSpace)srcCS).getProfile();
}
if (dstCS instanceof ICC_ColorSpace) {
dstPf = ((ICC_ColorSpace)dstCS).getProfile();
}
boolean isFullICC = isICC && srcPf != null && dstPf != null;
if (isFullICC) {
ICC_Transform t =
tCreator.getTransform(srcPf, dstPf, (ICC_Profile[]) conversionSequence);
cc.translateColor(t, src, res);
} else { // Perform non-ICC transform
Object sequence[] = tCreator.getSequence(
srcPf == null ? (Object) srcCS : srcPf,
dstPf == null ? (Object) dstCS : dstPf);
int srcW = src.getWidth();
int srcH = src.getHeight();
int numPixels = srcW*srcH;
// Load all pixel data into array tmpData
float tmpData[][] =
new float[numPixels][tCreator.maxComponents];
for (int row=0, dataPos=0; row<srcW; row++) {
for (int col=0; col<srcH; col++) {
tmpData[dataPos] =
srcCM.getNormalizedComponents(
src.getRaster().getDataElements(row, col, null),
tmpData[dataPos], 0);
dataPos++;
}
}
// Copy alpha channel if needed
float alpha[] = null;
int alphaIdx = srcCM.numComponents - 1;
if (srcCM.hasAlpha() && dstCM.hasAlpha()) {
alpha = new float[numPixels];
for (int i=0; i<numPixels; i++) {
alpha[i] = tmpData[i][alphaIdx];
}
}
// Translate colors
applySequence(sequence, tmpData, srcCS, dstCS);
// Copy alpha if needed
if (dstCM.hasAlpha()) {
alphaIdx = dstCM.numComponents - 1;
if (alpha != null) {
for (int i=0; i<numPixels; i++) {
tmpData[i][alphaIdx] = alpha[i];
}
} else {
for (int i=0; i<numPixels; i++) {
tmpData[i][alphaIdx] = 1f;
}
}
}
// Store data back to the image
for (int row=0, dataPos=0; row<srcW; row++) {
for (int col=0; col<srcH; col++) {
res.getRaster().setDataElements(row, col,
dstCM.getDataElements(tmpData[dataPos++], 0 , null));
}
}
}
if (isDstIndex) { // Convert image into indexed color
Graphics2D g2d = dst.createGraphics();
g2d.drawImage(res, 0, 0, null);
g2d.dispose();
return dst;
}
return res;
}
private void applySequence(
Object sequence[],
float tmpData[][],
ColorSpace srcCS,
ColorSpace dstCS
) {
ColorSpace xyzCS = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
int numPixels = tmpData.length;
// First transform...
if (sequence[0] instanceof ICC_Transform) { // ICC
ICC_Transform t = (ICC_Transform)sequence[0];
cc.translateColor(t, tmpData, srcCS, xyzCS, numPixels);
} else { // non ICC
for (int k=0; k<numPixels; k++) {
tmpData[k] = srcCS.toCIEXYZ(tmpData[k]);
}
cc.loadScalingData(xyzCS); // prepare for scaling XYZ
}
for (Object element : sequence) {
if (element instanceof ICC_Transform) {
ICC_Transform t = (ICC_Transform)element;
cc.translateColor(t, tmpData, null, null, numPixels);
} else {
ColorSpace cs = (ColorSpace) element;
for (int k=0; k<numPixels; k++) {
tmpData[k] = cs.fromCIEXYZ(tmpData[k]);
tmpData[k] = cs.toCIEXYZ(tmpData[k]);
}
}
}
// Last transform...
if (sequence[sequence.length-1] instanceof ICC_Transform) { // ICC
ICC_Transform t = (ICC_Transform)sequence[sequence.length-1];
cc.translateColor(t, tmpData, xyzCS, dstCS, numPixels);
} else { // non ICC
for (int k=0; k<numPixels; k++) {
tmpData[k] = dstCS.fromCIEXYZ(tmpData[k]);
}
}
}
public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
if (dstPt != null) {
dstPt.setLocation(srcPt);
return dstPt;
}
return new Point2D.Float((float) srcPt.getX(), (float) srcPt.getY());
}
public WritableRaster createCompatibleDestRaster(Raster src) {
int nComps = 0;
int nSpaces = conversionSequence.length;
if (nSpaces < 2) {
throw new IllegalArgumentException(Messages.getString("awt.261")); //$NON-NLS-1$
}
Object lastCS = conversionSequence[nSpaces-1];
if (lastCS instanceof ColorSpace) {
nComps = ((ColorSpace) lastCS).getNumComponents();
} else {
nComps = ((ICC_Profile) lastCS).getNumComponents();
}
// Calculate correct data type
int dstDataType = src.getDataBuffer().getDataType();
if (dstDataType != DataBuffer.TYPE_BYTE &&
dstDataType != DataBuffer.TYPE_SHORT) {
dstDataType = DataBuffer.TYPE_SHORT;
}
return Raster.createInterleavedRaster(
dstDataType,
src.getWidth(),
src.getHeight(),
nComps,
new Point(src.getMinX(), src.getMinY())
);
}
public final Rectangle2D getBounds2D(Raster src) {
return src.getBounds();
}
public final Rectangle2D getBounds2D(BufferedImage src) {
return src.getRaster().getBounds();
}
public final ICC_Profile[] getICC_Profiles() {
if (midProfiles != null) {
return midProfiles;
}
return null;
}
public final RenderingHints getRenderingHints() {
return renderingHints;
}
}