replicant-frameworks_native/awt/java/awt/image/ColorConvertOp.java

711 lines
25 KiB
Java

/*
* 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
* @version $Revision$
*/
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;
/**
* The ColorConvertOp class converts the pixels of the data in the source image
* with the specified ColorSpace objects or an array of ICC_Profile objects. The
* result pixels are scaled to the precision of the destination image.
*
* @since Android 1.0
*/
public class ColorConvertOp implements BufferedImageOp, RasterOp {
// Unused but required by interfaces
/**
* The rendering hints.
*/
RenderingHints renderingHints;
// Sequence consisting of ColorSpace and ICC_Profile elements
/**
* The conversion sequence.
*/
Object conversionSequence[] = new ICC_Profile[0]; // To eliminate checks for
// null
// Not null if ColorConvertOp is constructed from the array of ICC profiles
/**
* The mid profiles.
*/
private ICC_Profile midProfiles[];
/**
* The cc.
*/
private final ColorConverter cc = new ColorConverter();
/**
* The t creator.
*/
private final ICC_TransfomCreator tCreator = new ICC_TransfomCreator();
/**
* The is icc.
*/
private boolean isICC = true;
// Cached ICC_Transform
/**
* The Class ICC_TransfomCreator.
*/
private class ICC_TransfomCreator {
/**
* The transform.
*/
private ICC_Transform transform;
/**
* The max components.
*/
private int maxComponents;
/**
* For the full ICC case.
*
* @param src
* the src.
* @param dst
* the dst.
* @param convSeq
* the conv seq.
* @return the transform.
*/
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
* the src.
* @param dst
* the dst.
* @return the sequence.
*/
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();
}
}
/**
* Instantiates a new ColorConvertOp object using two specified ColorSpace
* objects.
*
* @param srcCS
* the source ColorSpace.
* @param dstCS
* the destination ColorSpace.
* @param hints
* the RenderingHints object used for the color conversion, or
* null.
*/
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;
}
}
/**
* Instantiates a new ColorConvertOp object from the specified ICC_Profile
* objects.
*
* @param profiles
* the array of ICC_Profile objects.
* @param hints
* the RenderingHints object used for the color conversion, or
* null.
*/
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];
}
}
/**
* Instantiates a new ColorConvertOp object using the specified ColorSpace
* object.
*
* @param cs
* the destination ColorSpace or an intermediate ColorSpace.
* @param hints
* the RenderingHints object used for the color conversion, or
* null.
*/
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;
}
}
/**
* Instantiates a new ColorConvertOp object which converts from a source
* color space to a destination color space.
*
* @param hints
* the RenderingHints object used for the color conversion, or
* null.
*/
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, destCM.createCompatibleWritableRaster(src.getWidth(), src
.getHeight()), destCM.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;
}
/**
* Apply sequence.
*
* @param sequence
* the sequence.
* @param tmpData
* the tmp data.
* @param srcCS
* the src cs.
* @param dstCS
* the dst cs.
*/
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();
}
/**
* Gets an array of ICC_Profiles objects which constructs this
* ColorConvertOp object or returns null if this ColorConvertOp is not
* constructed from array of ICC_Profiles.
*
* @return an array of ICC_Profiles objects which constructs this
* ColorConvertOp object or returns null if this ColorConvertOp is
* not constructed from array of ICC_Profiles.
*/
public final ICC_Profile[] getICC_Profiles() {
if (midProfiles != null) {
return midProfiles;
}
return null;
}
public final RenderingHints getRenderingHints() {
return renderingHints;
}
}