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.color.ICC_Profile;
25import java.awt.image.DataBuffer;
26import java.awt.image.Raster;
27import java.awt.image.SampleModel;
28import java.awt.image.WritableRaster;
29
30/**
31 * This class provides functionality for scaling color data when
32 * ranges of the source and destination color values differs.
33 */
34public class ColorScaler {
35    private static final float MAX_SHORT = 0xFFFF;
36    private static final float MAX_SIGNED_SHORT = 0x7FFF;
37
38    private static final float MAX_XYZ = 1f + (32767f/32768f);
39
40    // Cached values for scaling color data
41    private float[] channelMinValues = null;
42    private float[] channelMulipliers = null; // for scale
43    private float[] invChannelMulipliers = null; // for unscale
44
45    int nColorChannels = 0;
46
47    // For scaling rasters, false if transfer type is double or float
48    boolean isTTypeIntegral = false;
49
50    /**
51     * Loads scaling data for raster. Note, if profile pf is null,
52     * for non-integral data types multipliers are not initialized.
53     * @param r - raster
54     * @param pf - profile which helps to determine the ranges of the color data
55     */
56    public void loadScalingData(Raster r, ICC_Profile pf) {
57        boolean isSrcTTypeIntegral =
58            r.getTransferType() != DataBuffer.TYPE_FLOAT &&
59            r.getTransferType() != DataBuffer.TYPE_DOUBLE;
60        if (isSrcTTypeIntegral)
61            loadScalingData(r.getSampleModel());
62        else if (pf != null)
63            loadScalingData(pf);
64    }
65
66    /**
67     * Use this method only for integral transfer types.
68     * Extracts min/max values from the sample model
69     * @param sm - sample model
70     */
71    public void loadScalingData(SampleModel sm) {
72        // Supposing integral transfer type
73        isTTypeIntegral = true;
74
75        nColorChannels = sm.getNumBands();
76
77        channelMinValues = new float[nColorChannels];
78        channelMulipliers = new float[nColorChannels];
79        invChannelMulipliers = new float[nColorChannels];
80
81        boolean isSignedShort =
82            (sm.getTransferType() == DataBuffer.TYPE_SHORT);
83
84        float maxVal;
85        for (int i=0; i<nColorChannels; i++) {
86            channelMinValues[i] = 0;
87            if (isSignedShort) {
88                channelMulipliers[i] = MAX_SHORT / MAX_SIGNED_SHORT;
89                invChannelMulipliers[i] = MAX_SIGNED_SHORT / MAX_SHORT;
90            } else {
91                maxVal = ((1 << sm.getSampleSize(i)) - 1);
92                channelMulipliers[i] = MAX_SHORT / maxVal;
93                invChannelMulipliers[i] = maxVal / MAX_SHORT;
94            }
95        }
96    }
97
98    /**
99     * Use this method only for double of float transfer types.
100     * Extracts scaling data from the color space signature
101     * and other tags, stored in the profile
102     * @param pf - ICC profile
103     */
104    public void loadScalingData(ICC_Profile pf) {
105        // Supposing double or float transfer type
106        isTTypeIntegral = false;
107
108        nColorChannels = pf.getNumComponents();
109
110        // Get min/max values directly from the profile
111        // Very much like fillMinMaxValues in ICC_ColorSpace
112        float maxValues[] = new float[nColorChannels];
113        float minValues[] = new float[nColorChannels];
114
115        switch (pf.getColorSpaceType()) {
116            case ColorSpace.TYPE_XYZ:
117                minValues[0] = 0;
118                minValues[1] = 0;
119                minValues[2] = 0;
120                maxValues[0] = MAX_XYZ;
121                maxValues[1] = MAX_XYZ;
122                maxValues[2] = MAX_XYZ;
123                break;
124            case ColorSpace.TYPE_Lab:
125                minValues[0] = 0;
126                minValues[1] = -128;
127                minValues[2] = -128;
128                maxValues[0] = 100;
129                maxValues[1] = 127;
130                maxValues[2] = 127;
131                break;
132            default:
133                for (int i=0; i<nColorChannels; i++) {
134                    minValues[i] = 0;
135                    maxValues[i] = 1;
136                }
137        }
138
139        channelMinValues = minValues;
140        channelMulipliers = new float[nColorChannels];
141        invChannelMulipliers = new float[nColorChannels];
142
143        for (int i = 0; i < nColorChannels; i++) {
144            channelMulipliers[i] =
145                MAX_SHORT / (maxValues[i] - channelMinValues[i]);
146
147            invChannelMulipliers[i] =
148                (maxValues[i] - channelMinValues[i]) / MAX_SHORT;
149        }
150    }
151
152    /**
153     * Extracts scaling data from the color space
154     * @param cs - color space
155     */
156    public void loadScalingData(ColorSpace cs) {
157        nColorChannels = cs.getNumComponents();
158
159        channelMinValues = new float[nColorChannels];
160        channelMulipliers = new float[nColorChannels];
161        invChannelMulipliers = new float[nColorChannels];
162
163        for (int i = 0; i < nColorChannels; i++) {
164            channelMinValues[i] = cs.getMinValue(i);
165
166            channelMulipliers[i] =
167                MAX_SHORT / (cs.getMaxValue(i) - channelMinValues[i]);
168
169            invChannelMulipliers[i] =
170                (cs.getMaxValue(i) - channelMinValues[i]) / MAX_SHORT;
171        }
172    }
173
174    /**
175     * Scales and normalizes the whole raster and returns the result
176     * in the float array
177     * @param r - source raster
178     * @return scaled and normalized raster data
179     */
180    public float[][] scaleNormalize(Raster r) {
181        int width = r.getWidth();
182        int height = r.getHeight();
183        float result[][] = new float[width*height][nColorChannels];
184        float normMultipliers[] = new float[nColorChannels];
185
186        int pos = 0;
187        if (isTTypeIntegral) {
188            // Change max value from MAX_SHORT to 1f
189            for (int i=0; i<nColorChannels; i++) {
190                normMultipliers[i] = channelMulipliers[i] / MAX_SHORT;
191            }
192
193            int sample;
194            for (int row=r.getMinX(); row<width; row++) {
195                for (int col=r.getMinY(); col<height; col++) {
196                    for (int chan = 0; chan < nColorChannels; chan++) {
197                        sample = r.getSample(row, col, chan);
198                        result[pos][chan] = (sample * normMultipliers[chan]);
199                    }
200                    pos++;
201                }
202            }
203        } else { // Just get the samples...
204            for (int row=r.getMinX(); row<width; row++) {
205                for (int col=r.getMinY(); col<height; col++) {
206                    for (int chan = 0; chan < nColorChannels; chan++) {
207                        result[pos][chan] = r.getSampleFloat(row, col, chan);
208                    }
209                    pos++;
210                }
211            }
212        }
213        return result;
214    }
215
216    /**
217     * Unscale the whole float array and put the result
218     * in the raster
219     * @param r - destination raster
220     * @param data - input pixels
221     */
222    public void unscaleNormalized(WritableRaster r, float data[][]) {
223        int width = r.getWidth();
224        int height = r.getHeight();
225        float normMultipliers[] = new float[nColorChannels];
226
227        int pos = 0;
228        if (isTTypeIntegral) {
229            // Change max value from MAX_SHORT to 1f
230            for (int i=0; i<nColorChannels; i++) {
231                normMultipliers[i] = invChannelMulipliers[i] * MAX_SHORT;
232            }
233
234            int sample;
235            for (int row=r.getMinX(); row<width; row++) {
236                for (int col=r.getMinY(); col<height; col++) {
237                    for (int chan = 0; chan < nColorChannels; chan++) {
238                        sample = (int) (data[pos][chan] * normMultipliers[chan] + 0.5f);
239                        r.setSample(row, col, chan, sample);
240                    }
241                    pos++;
242                }
243            }
244        } else { // Just set the samples...
245            for (int row=r.getMinX(); row<width; row++) {
246                for (int col=r.getMinY(); col<height; col++) {
247                    for (int chan = 0; chan < nColorChannels; chan++) {
248                        r.setSample(row, col, chan, data[pos][chan]);
249                    }
250                    pos++;
251                }
252            }
253        }
254    }
255
256    /**
257     * Scales the whole raster to short and returns the result
258     * in the array
259     * @param r - source raster
260     * @return scaled and normalized raster data
261     */
262    public short[] scale(Raster r) {
263        int width = r.getWidth();
264        int height = r.getHeight();
265        short result[] = new short[width*height*nColorChannels];
266
267        int pos = 0;
268        if (isTTypeIntegral) {
269            int sample;
270            for (int row=r.getMinX(); row<width; row++) {
271                for (int col=r.getMinY(); col<height; col++) {
272                    for (int chan = 0; chan < nColorChannels; chan++) {
273                        sample = r.getSample(row, col, chan);
274                        result[pos++] =
275                            (short) (sample * channelMulipliers[chan] + 0.5f);
276                    }
277                }
278            }
279        } else {
280            float sample;
281            for (int row=r.getMinX(); row<width; row++) {
282                for (int col=r.getMinY(); col<height; col++) {
283                    for (int chan = 0; chan < nColorChannels; chan++) {
284                        sample = r.getSampleFloat(row, col, chan);
285                        result[pos++] = (short) ((sample - channelMinValues[chan])
286                            * channelMulipliers[chan] + 0.5f);
287                    }
288                }
289            }
290        }
291        return result;
292    }
293
294    /**
295     * Unscales the whole data array and puts obtained values to the raster
296     * @param data - input data
297     * @param wr - destination raster
298     */
299    public void unscale(short[] data, WritableRaster wr) {
300        int width = wr.getWidth();
301        int height = wr.getHeight();
302
303        int pos = 0;
304        if (isTTypeIntegral) {
305            int sample;
306            for (int row=wr.getMinX(); row<width; row++) {
307                for (int col=wr.getMinY(); col<height; col++) {
308                    for (int chan = 0; chan < nColorChannels; chan++) {
309                         sample = (int) ((data[pos++] & 0xFFFF) *
310                                invChannelMulipliers[chan] + 0.5f);
311                         wr.setSample(row, col, chan, sample);
312                    }
313                }
314            }
315        } else {
316            float sample;
317            for (int row=wr.getMinX(); row<width; row++) {
318                for (int col=wr.getMinY(); col<height; col++) {
319                    for (int chan = 0; chan < nColorChannels; chan++) {
320                         sample = (data[pos++] & 0xFFFF) *
321                            invChannelMulipliers[chan] + channelMinValues[chan];
322                         wr.setSample(row, col, chan, sample);
323                    }
324                }
325            }
326        }
327    }
328
329    /**
330     * Scales one pixel and puts obtained values to the chanData
331     * @param pixelData - input pixel
332     * @param chanData - output buffer
333     * @param chanDataOffset - output buffer offset
334     */
335    public void scale(float[] pixelData, short[] chanData, int chanDataOffset) {
336        for (int chan = 0; chan < nColorChannels; chan++) {
337            chanData[chanDataOffset + chan] =
338                    (short) ((pixelData[chan] - channelMinValues[chan]) *
339                        channelMulipliers[chan] + 0.5f);
340        }
341    }
342
343    /**
344     * Unscales one pixel and puts obtained values to the pixelData
345     * @param pixelData - output pixel
346     * @param chanData - input buffer
347     * @param chanDataOffset - input buffer offset
348     */
349    public void unscale(float[] pixelData, short[] chanData, int chanDataOffset) {
350        for (int chan = 0; chan < nColorChannels; chan++) {
351            pixelData[chan] = (chanData[chanDataOffset + chan] & 0xFFFF)
352                * invChannelMulipliers[chan] + channelMinValues[chan];
353        }
354    }
355}