1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17/**
18 * @author Oleg V. Khaschansky
19 * @version $Revision$
20 */
21package org.apache.harmony.awt.gl.color;
22
23import java.awt.color.ColorSpace;
24import java.awt.image.BufferedImage;
25import java.awt.image.ColorModel;
26import java.awt.image.Raster;
27import java.awt.image.WritableRaster;
28
29/**
30 * This class combines ColorScaler, ICC_Transform and NativeImageFormat functionality
31 * in the workflows for different types of input/output pixel data.
32 */
33public class ColorConverter {
34    private ColorScaler scaler = new ColorScaler();
35
36    public void loadScalingData(ColorSpace cs) {
37        scaler.loadScalingData(cs);
38    }
39
40    /**
41     * Translates pixels, stored in source buffered image and writes the data
42     * to the destination image.
43     * @param t - ICC transform
44     * @param src - source image
45     * @param dst - destination image
46     */
47    public void translateColor(ICC_Transform t,
48            BufferedImage src, BufferedImage dst) {
49      NativeImageFormat srcIF = NativeImageFormat.createNativeImageFormat(src);
50      NativeImageFormat dstIF = NativeImageFormat.createNativeImageFormat(dst);
51
52      if (srcIF != null && dstIF != null) {
53          t.translateColors(srcIF, dstIF);
54          return;
55      }
56
57        srcIF = createImageFormat(src);
58        dstIF = createImageFormat(dst);
59
60        short srcChanData[] = (short[]) srcIF.getChannelData();
61        short dstChanData[] = (short[]) dstIF.getChannelData();
62
63        ColorModel srcCM = src.getColorModel();
64        int nColorChannels = srcCM.getNumColorComponents();
65        scaler.loadScalingData(srcCM.getColorSpace()); // input scaling data
66        ColorModel dstCM = dst.getColorModel();
67
68        // Prepare array for alpha channel
69        float alpha[] = null;
70        boolean saveAlpha = srcCM.hasAlpha() && dstCM.hasAlpha();
71        if (saveAlpha) {
72            alpha = new float[src.getWidth()*src.getHeight()];
73        }
74
75        WritableRaster wr = src.getRaster();
76        int srcDataPos = 0, alphaPos = 0;
77        float normalizedVal[];
78        for (int row=0, nRows = srcIF.getNumRows(); row<nRows; row++) {
79            for (int col=0, nCols = srcIF.getNumCols(); col<nCols; col++) {
80                normalizedVal = srcCM.getNormalizedComponents(
81                    wr.getDataElements(col, row, null),
82                    null, 0);
83                // Save alpha channel
84                if (saveAlpha) {
85                    // We need nColorChannels'th element cause it's nChannels - 1
86                    alpha[alphaPos++] = normalizedVal[nColorChannels];
87                }
88                scaler.scale(normalizedVal, srcChanData, srcDataPos);
89                srcDataPos += nColorChannels;
90            }
91        }
92
93        t.translateColors(srcIF, dstIF);
94
95        nColorChannels = dstCM.getNumColorComponents();
96        boolean fillAlpha = dstCM.hasAlpha();
97        scaler.loadScalingData(dstCM.getColorSpace()); // output scaling data
98        float dstPixel[] = new float[dstCM.getNumComponents()];
99        int dstDataPos = 0;
100        alphaPos = 0;
101        wr = dst.getRaster();
102
103        for (int row=0, nRows = dstIF.getNumRows(); row<nRows; row++) {
104            for (int col=0, nCols = dstIF.getNumCols(); col<nCols; col++) {
105                scaler.unscale(dstPixel, dstChanData, dstDataPos);
106                dstDataPos += nColorChannels;
107                if (fillAlpha) {
108                    if (saveAlpha) {
109                        dstPixel[nColorChannels] = alpha[alphaPos++];
110                    } else {
111                        dstPixel[nColorChannels] = 1f;
112                    }
113                }
114                wr.setDataElements(col, row,
115                        dstCM.getDataElements(dstPixel, 0 , null));
116            }
117        }
118    }
119
120    /**
121     * Translates pixels, stored in the float data buffer.
122     * Each pixel occupies separate array. Input pixels passed in the buffer
123     * are replaced by output pixels and then the buffer is returned
124     * @param t - ICC transform
125     * @param buffer - data buffer
126     * @param srcCS - source color space
127     * @param dstCS - destination color space
128     * @param nPixels - number of pixels
129     * @return translated pixels
130     */
131    public float[][] translateColor(ICC_Transform t,
132            float buffer[][],
133            ColorSpace srcCS,
134            ColorSpace dstCS,
135            int nPixels) {
136        // Scale source data
137        if (srcCS != null) { // if it is null use old scaling data
138            scaler.loadScalingData(srcCS);
139        }
140        int nSrcChannels = t.getNumInputChannels();
141        short srcShortData[] = new short[nPixels*nSrcChannels];
142        for (int i=0, srcDataPos = 0; i<nPixels; i++) {
143            scaler.scale(buffer[i], srcShortData, srcDataPos);
144            srcDataPos += nSrcChannels;
145        }
146
147        // Apply transform
148        short dstShortData[] = this.translateColor(t, srcShortData, null);
149
150        int nDstChannels = t.getNumOutputChannels();
151        int bufferSize = buffer[0].length;
152        if (bufferSize < nDstChannels + 1) { // Re-allocate buffer if needed
153            for (int i=0; i<nPixels; i++) {
154                // One extra element reserved for alpha
155                buffer[i] = new float[nDstChannels + 1];
156            }
157        }
158
159        // Unscale destination data
160        if (dstCS != null) { // if it is null use old scaling data
161            scaler.loadScalingData(dstCS);
162        }
163        for (int i=0, dstDataPos = 0; i<nPixels; i++) {
164            scaler.unscale(buffer[i], dstShortData, dstDataPos);
165            dstDataPos += nDstChannels;
166        }
167
168        return buffer;
169    }
170
171    /**
172     * Translates pixels stored in a raster.
173     * All data types are supported
174     * @param t - ICC transform
175     * @param src - source pixels
176     * @param dst - destination pixels
177     */
178   public void translateColor(ICC_Transform t, Raster src, WritableRaster dst) {
179        try{
180            NativeImageFormat srcFmt = NativeImageFormat.createNativeImageFormat(src);
181            NativeImageFormat dstFmt = NativeImageFormat.createNativeImageFormat(dst);
182
183          if (srcFmt != null && dstFmt != null) {
184              t.translateColors(srcFmt, dstFmt);
185              return;
186          }
187        } catch (IllegalArgumentException e) {
188      }
189
190        // Go ahead and rescale the source image
191        scaler.loadScalingData(src, t.getSrc());
192        short srcData[] = scaler.scale(src);
193
194        short dstData[] = translateColor(t, srcData, null);
195
196        scaler.loadScalingData(dst, t.getDst());
197        scaler.unscale(dstData, dst);
198   }
199
200    /**
201     * Translates pixels stored in an array of shorts.
202     * Samples are stored one-by-one, i.e. array structure is like following: RGBRGBRGB...
203     * The number of pixels is (size of the array) / (number of components).
204     * @param t - ICC transform
205     * @param src - source pixels
206     * @param dst - destination pixels
207     * @return destination pixels, stored in the array, passed in dst
208     */
209    public short[] translateColor(ICC_Transform t, short src[], short dst[]) {
210        NativeImageFormat srcFmt = createImageFormat(t, src, 0, true);
211        NativeImageFormat dstFmt = createImageFormat(t, dst, srcFmt.getNumCols(), false);
212
213        t.translateColors(srcFmt, dstFmt);
214
215        return (short[]) dstFmt.getChannelData();
216    }
217
218
219    /**
220     * Creates NativeImageFormat from buffered image.
221     * @param bi - buffered image
222     * @return created NativeImageFormat
223     */
224    private NativeImageFormat createImageFormat(BufferedImage bi) {
225        int nRows = bi.getHeight();
226        int nCols = bi.getWidth();
227        int nComps = bi.getColorModel().getNumColorComponents();
228        short imgData[] = new short[nRows*nCols*nComps];
229        return new NativeImageFormat(
230                imgData, nComps, nRows, nCols);
231    }
232
233    /**
234     * Creates one-row NativeImageFormat, using either nCols if it is positive,
235     * or arr.length to determine the number of pixels
236     *
237     * @param t - transform
238     * @param arr - short array or null if nCols is positive
239     * @param nCols - number of pixels in the array or 0 if array is not null
240     * @param in - is it an input or output array
241     * @return one-row NativeImageFormat
242     */
243    private NativeImageFormat createImageFormat(
244            ICC_Transform t, short arr[], int nCols, boolean in
245    ) {
246        int nComponents = in ? t.getNumInputChannels() : t.getNumOutputChannels();
247
248        if (arr == null || arr.length < nCols*nComponents) {
249            arr = new short[nCols*nComponents];
250        }
251
252        if (nCols == 0)
253            nCols = arr.length / nComponents;
254
255        return new NativeImageFormat(arr, nComponents, 1, nCols);
256    }
257}
258