001/* ComponentColorModel.java --
002   Copyright (C) 2000, 2002, 2004  Free Software Foundation
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.awt.image;
040
041import gnu.java.awt.Buffers;
042
043import java.awt.Point;
044import java.awt.color.ColorSpace;
045import java.util.Arrays;
046
047public class ComponentColorModel extends ColorModel
048{
049  // Find sum of all elements of the array.
050  private static int sum(int[] values)
051  {
052    int sum = 0;
053    for (int i=0; i<values.length; i++)
054      sum += values[i];
055    return sum;
056  }
057  
058  // Create an appropriate array of bits, given a colorspace (ie, number of
059  // bands), size of the storage data type, and presence of an alpha band.
060  private static int[] findBits(ColorSpace colorSpace, int transferType,
061                                boolean hasAlpha)
062  {
063    int[] bits;
064    if (hasAlpha)
065      bits = new int[colorSpace.getNumComponents()+1];
066    else
067      bits = new int[colorSpace.getNumComponents()];
068
069    Arrays.fill(bits, DataBuffer.getDataTypeSize(transferType));
070    
071    return bits;
072  }
073
074  public ComponentColorModel(ColorSpace colorSpace, int[] bits,
075                             boolean hasAlpha,
076                             boolean isAlphaPremultiplied,
077                             int transparency, int transferType)
078  {
079    super(sum(bits), bits, colorSpace, hasAlpha, isAlphaPremultiplied,
080          transparency, transferType);
081  }
082
083  /**
084   * Construct a new ComponentColorModel.
085   * 
086   * This constructor makes all bits of each sample significant, so for a
087   * transferType of DataBuffer.BYTE, the bits per sample is 8, etc.  If
088   * both hasAlpha and isAlphaPremultiplied are true, color samples are
089   * assumed to be premultiplied by the alpha component.  Transparency may be
090   * one of OPAQUE, BITMASK, or TRANSLUCENT. 
091   * 
092   * @param colorSpace The colorspace for this color model.
093   * @param hasAlpha True if there is an alpha component.
094   * @param isAlphaPremultiplied True if colors are already multiplied by
095   * alpha.
096   * @param transparency The type of alpha values.
097   * @param transferType Data type of pixel sample values.
098   * @since 1.4
099   */
100  public ComponentColorModel(ColorSpace colorSpace,
101                             boolean hasAlpha,
102                             boolean isAlphaPremultiplied,
103                             int transparency, int transferType)
104  {     
105    this(colorSpace, findBits(colorSpace, transferType, hasAlpha), hasAlpha,
106         isAlphaPremultiplied, transparency, transferType);
107  }
108
109  public int getRed(int pixel)
110  {
111    if (getNumComponents()>1) throw new IllegalArgumentException();
112    return (int) getRGBFloat(pixel)[0];
113  }
114
115  public int getGreen(int pixel)
116  {
117    if (getNumComponents()>1) throw new IllegalArgumentException();
118    return (int) getRGBFloat(pixel)[0];
119  }
120  
121  public int getBlue(int pixel)
122  {
123    if (getNumComponents()>1) throw new IllegalArgumentException();
124    return (int) getRGBFloat(pixel)[0];
125  }
126
127  public int getAlpha(int pixel)
128  {
129    if (getNumComponents()>1) throw new IllegalArgumentException();
130    int shift = 8 - getComponentSize(getNumColorComponents());
131    if (shift >= 0) return pixel << shift;
132    return pixel >> (-shift);
133  }
134   
135  public int getRGB(int pixel)
136  {
137    float[] rgb = getRGBFloat(pixel);
138    int ret = getRGB(rgb);
139    if (hasAlpha()) ret |= getAlpha(pixel) << 24;
140    return ret;
141  }
142
143
144  /* Note, it's OK to pass a to large array to toRGB(). Extra
145     elements are ignored. */
146  
147  private float[] getRGBFloat(int pixel)
148  {
149    float[] data = { pixel };
150    return cspace.toRGB(data);
151  }
152
153  private float[] getRGBFloat(Object inData)
154  {
155    DataBuffer buffer =
156    Buffers.createBufferFromData(transferType, inData,
157                                 getNumComponents());
158    int colors = getNumColorComponents();
159    float[] data = new float[colors];
160    
161    // FIXME: unpremultiply data that is premultiplied
162    for (int i=0; i<colors; i++)
163      {
164        float maxValue = (1<<getComponentSize(i))-1;
165        data[i] = buffer.getElemFloat(i)/maxValue; 
166      }
167    float[] rgb = cspace.toRGB(data);
168    return rgb;
169  }
170  
171  public int getRed(Object inData)
172  {
173    return (int) getRGBFloat(inData)[0]*255;
174  }
175
176  public int getGreen(Object inData)
177  {
178    return (int) getRGBFloat(inData)[1]*255;
179  }
180
181  public int getBlue(Object inData)
182  {
183    return (int) getRGBFloat(inData)[2]*255;
184  }
185
186  public int getAlpha(Object inData)
187  {
188    DataBuffer buffer =
189      Buffers.createBufferFromData(transferType, inData,
190                                   getNumComponents());
191    int shift = 8 - getComponentSize(getNumColorComponents());
192    int alpha = buffer.getElem(getNumColorComponents());
193    if (shift >= 0) return alpha << shift;
194    return alpha >> (-shift);
195  }
196
197  private int getRGB(float[] rgb)
198  {
199    /* NOTE: We could cast to byte instead of int here. This would
200       avoid bits spilling over from one bit field to
201       another. But, if we assume that floats are in the [0.0,
202       1.0] range, this will never happen anyway. */
203    
204    /* Remember to multiply BEFORE casting to int, otherwise, decimal
205       point data will be lost. */
206    int ret =
207      (((int) (rgb[0]*255F)) << 16) |
208      (((int) (rgb[1]*255F)) <<  8) |
209      (((int) (rgb[2]*255F)) <<  0);
210    return ret;
211  }
212
213  /**
214   * @param inData pixel data of transferType, as returned by the
215   * getDataElements method in SampleModel.
216   */
217  public int getRGB(Object inData)
218  {
219    float[] rgb = getRGBFloat(inData);
220    int ret = getRGB(rgb);
221    if (hasAlpha()) ret |= getAlpha(inData) << 24;
222    return ret;
223  }
224
225  public Object getDataElements(int rgb, Object pixel)
226  {
227    // Convert rgb to [0.0, 1.0] sRGB values.
228    float[] rgbFloats = {
229      ((rgb >> 16)&0xff)/255.0F,
230      ((rgb >>  8)&0xff)/255.0F,
231      ((rgb >>  0)&0xff)/255.0F
232    };
233
234    // Convert from rgb to color space components.
235    float[] data = cspace.fromRGB(rgbFloats);
236    DataBuffer buffer = Buffers.createBuffer(transferType, pixel,
237                                             getNumComponents());
238    int numColors = getNumColorComponents();
239    
240    if (hasAlpha())
241      {
242        float alpha = ((rgb >> 24)&0xff)/255.0F;
243        
244        /* If color model has alpha and should be premultiplied, multiply
245           color space components with alpha value. */
246        if (isAlphaPremultiplied()) {
247          for (int i=0; i<numColors; i++)
248            data[i] *= alpha;
249        }
250        // Scale the alpha sample to the correct number of bits.
251        alpha *= (1<<(bits[numColors]-1));
252        // Arrange the alpha sample in the output array.
253        buffer.setElemFloat(numColors, alpha);
254      }
255    for (int i=0; i<numColors; i++)
256      {
257        // Scale the color samples to the correct number of bits.
258        float value = data[i]*(1<<(bits[i]-1));
259        // Arrange the color samples in the output array.
260        buffer.setElemFloat(i, value);
261      }
262    return Buffers.getData(buffer);
263  }
264
265  public int[] getComponents(int pixel, int[] components, int offset)
266  {
267    if (getNumComponents()>1) throw new IllegalArgumentException();
268    if (components == null)
269    components = new int[getNumComponents() + offset];
270    components[offset] = pixel;
271    return components;
272  }
273
274  public int[] getComponents(Object pixel, int[] components, int offset)
275  {
276    DataBuffer buffer = Buffers.createBuffer(transferType, pixel,
277                                             getNumComponents());
278    int numComponents = getNumComponents();
279
280    if (components == null)
281      components = new int[numComponents + offset];
282
283    for (int i=0; i<numComponents; i++)
284      components[offset++] = buffer.getElem(i);
285
286    return components;
287  }
288
289  public int getDataElement(int[] components, int offset)
290  {
291    if (getNumComponents()>1) throw new IllegalArgumentException();
292    return components[offset];
293  }
294
295  public Object getDataElements(int[] components, int offset, Object obj)
296  {
297    DataBuffer buffer = Buffers.createBuffer(transferType, obj,
298                                             getNumComponents());
299    int numComponents = getNumComponents();
300
301    for (int i=0; i<numComponents; i++)
302      buffer.setElem(i, components[offset++]);
303
304    return Buffers.getData(buffer);
305  }
306
307  public ColorModel coerceData(WritableRaster raster,
308                               boolean isAlphaPremultiplied) {
309    if (this.isAlphaPremultiplied == isAlphaPremultiplied || !hasAlpha())
310      return this;
311
312    /* TODO: provide better implementation based on the
313       assumptions we can make due to the specific type of the
314       color model. */
315    coerceDataWorker(raster, isAlphaPremultiplied);
316    
317    return new ComponentColorModel(cspace, hasAlpha, isAlphaPremultiplied,
318                                   transparency, transferType);
319  }
320
321  public boolean isCompatibleRaster(Raster raster)
322  {
323    return super.isCompatibleRaster(raster);
324    // FIXME: Should we test something more here? (Why override?)
325  }
326
327  public WritableRaster createCompatibleWritableRaster(int w, int h)
328  {
329    SampleModel sm = createCompatibleSampleModel(w, h);
330    Point origin = new Point(0, 0);
331    return Raster.createWritableRaster(sm, origin);
332  }
333
334
335  /**
336   * Creates a <code>SampleModel</code> whose arrangement of pixel
337   * data is compatible to this <code>ColorModel</code>.
338   *
339   * @param w the number of pixels in the horizontal direction.
340   * @param h the number of pixels in the vertical direction.
341   */
342  public SampleModel createCompatibleSampleModel(int w, int h)
343  {
344    int pixelStride, scanlineStride;
345    int[] bandOffsets;
346
347    pixelStride = getNumComponents();
348    scanlineStride = pixelStride * w;
349
350    /* We might be able to re-use the same bandOffsets array among
351     * multiple calls to this method. However, this optimization does
352     * not seem worthwile because setting up descriptive data
353     * structures (such as SampleModels) is neglectible in comparision
354     * to shuffling around masses of pixel data.
355     */
356    bandOffsets = new int[pixelStride];
357    for (int i = 0; i < pixelStride; i++)
358      bandOffsets[i] = i;
359
360    /* FIXME: Think about whether it would make sense to return the
361     * possibly more efficient PixelInterleavedSampleModel for other
362     * transferTypes as well. It seems unlikely that this would break
363     * any user applications, so the Mauve tests on this method
364     * might be too restrictive.
365     */
366    switch (transferType)
367      {
368      case DataBuffer.TYPE_BYTE:
369      case DataBuffer.TYPE_USHORT:
370        return new PixelInterleavedSampleModel(transferType, w, h,
371                                               pixelStride,
372                                               scanlineStride,
373                                               bandOffsets);
374
375      default:
376        return new ComponentSampleModel(transferType, w, h,
377                                        pixelStride,
378                                        scanlineStride,
379                                        bandOffsets);
380      }
381  }
382
383
384  public boolean isCompatibleSampleModel(SampleModel sm)
385  {
386    return 
387      (sm instanceof ComponentSampleModel) &&
388      super.isCompatibleSampleModel(sm);
389  }
390
391  public WritableRaster getAlphaRaster(WritableRaster raster)
392  {
393    if (!hasAlpha()) return null;
394    
395    SampleModel sm = raster.getSampleModel();
396    int[] alphaBand = { sm.getNumBands() - 1 };
397    SampleModel alphaModel = sm.createSubsetSampleModel(alphaBand);
398    DataBuffer buffer = raster.getDataBuffer();
399    Point origin = new Point(0, 0);
400    return Raster.createWritableRaster(alphaModel, buffer, origin);
401  }
402    
403  public boolean equals(Object obj)
404  {
405    if (!(obj instanceof ComponentColorModel)) return false;
406    return super.equals(obj);
407  }
408}