001/* Copyright (C) 2004, 2006,  Free Software Foundation
002
003This file is part of GNU Classpath.
004
005GNU Classpath is free software; you can redistribute it and/or modify
006it under the terms of the GNU General Public License as published by
007the Free Software Foundation; either version 2, or (at your option)
008any later version.
009
010GNU Classpath is distributed in the hope that it will be useful, but
011WITHOUT ANY WARRANTY; without even the implied warranty of
012MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013General Public License for more details.
014
015You should have received a copy of the GNU General Public License
016along with GNU Classpath; see the file COPYING.  If not, write to the
017Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01802110-1301 USA.
019
020Linking this library statically or dynamically with other modules is
021making a combined work based on this library.  Thus, the terms and
022conditions of the GNU General Public License cover the whole
023combination.
024
025As a special exception, the copyright holders of this library give you
026permission to link this library with independent modules to produce an
027executable, regardless of the license terms of these independent
028modules, and to copy and distribute the resulting executable under
029terms of your choice, provided that you also meet, for each linked
030independent module, the terms and conditions of the license of that
031module.  An independent module is a module which is not derived from
032or based on this library.  If you modify this library, you may extend
033this exception to your version of the library, but you are not
034obligated to do so.  If you do not wish to do so, delete this
035exception statement from your version. */
036
037package java.awt.image;
038
039import gnu.java.awt.Buffers;
040import gnu.java.lang.CPStringBuilder;
041
042/**
043 * MultiPixelPackedSampleModel provides a single band model that supports
044 * multiple pixels in a single unit.  Pixels have 2^n bits and 2^k pixels fit
045 * per data element.
046 *
047 * @author Jerry Quinn (jlquinn@optonline.net)
048 */
049public class MultiPixelPackedSampleModel extends SampleModel
050{
051  private int scanlineStride;
052  private int[] bitMasks;
053  private int[] bitOffsets;
054  private int[] sampleSize;
055  private int dataBitOffset;
056  private int elemBits;
057  private int numberOfBits;
058  private int numElems;
059
060  /**
061   * Creates a new <code>MultiPixelPackedSampleModel</code> with the specified
062   * data type, which should be one of:
063   * <ul>
064   *   <li>{@link DataBuffer#TYPE_BYTE};</li>
065   *   <li>{@link DataBuffer#TYPE_USHORT};</li>
066   *   <li>{@link DataBuffer#TYPE_INT};</li>
067   * </ul>
068   *
069   * @param dataType  the data type.
070   * @param w  the width (in pixels).
071   * @param h  the height (in pixels).
072   * @param numberOfBits  the number of bits per pixel (must be a power of 2).
073   */
074  public MultiPixelPackedSampleModel(int dataType, int w, int h,
075                                     int numberOfBits)
076  {
077    this(dataType, w, h, numberOfBits, 0, 0);
078  }
079
080  /**
081   * Creates a new <code>MultiPixelPackedSampleModel</code> with the specified
082   * data type, which should be one of:
083   * <ul>
084   *   <li>{@link DataBuffer#TYPE_BYTE};</li>
085   *   <li>{@link DataBuffer#TYPE_USHORT};</li>
086   *   <li>{@link DataBuffer#TYPE_INT};</li>
087   * </ul>
088   *
089   * @param dataType  the data type.
090   * @param w  the width (in pixels).
091   * @param h  the height (in pixels).
092   * @param numberOfBits  the number of bits per pixel (must be a power of 2).
093   * @param scanlineStride  the number of data elements from a pixel on one
094   *     row to the corresponding pixel in the next row.
095   * @param dataBitOffset  the offset to the first data bit.
096   */
097  public MultiPixelPackedSampleModel(int dataType, int w, int h,
098                                     int numberOfBits, int scanlineStride,
099                                     int dataBitOffset)
100  {
101    super(dataType, w, h, 1);
102
103    switch (dataType)
104      {
105      case DataBuffer.TYPE_BYTE:
106        elemBits = 8;
107        break;
108      case DataBuffer.TYPE_USHORT:
109        elemBits = 16;
110        break;
111      case DataBuffer.TYPE_INT:
112        elemBits = 32;
113        break;
114      default:
115        throw new IllegalArgumentException("MultiPixelPackedSampleModel"
116                                           + " unsupported dataType");
117      }
118
119    this.dataBitOffset = dataBitOffset;
120
121    this.numberOfBits = numberOfBits;
122    if (numberOfBits > elemBits)
123      throw new RasterFormatException("MultiPixelPackedSampleModel pixel size"
124                                      + " larger than dataType");
125    switch (numberOfBits)
126      {
127      case 1: case 2: case 4: case 8: case 16: case 32: break;
128      default:
129        throw new RasterFormatException("MultiPixelPackedSampleModel pixel"
130                                        + " size not 2^n bits");
131      }
132    numElems = elemBits / numberOfBits;
133
134    // Compute scan line large enough for w pixels.
135    if (scanlineStride == 0)
136      scanlineStride = ((dataBitOffset + w * numberOfBits) - 1) / elemBits + 1;
137    this.scanlineStride = scanlineStride;
138
139
140    sampleSize = new int[1];
141    sampleSize[0] = numberOfBits;
142
143    bitMasks = new int[numElems];
144    bitOffsets = new int[numElems];
145    for (int i=0; i < numElems; i++)
146      {
147        bitOffsets[numElems - i- 1] = numberOfBits * i;
148        bitMasks[numElems - i - 1] = ((1 << numberOfBits) - 1) <<
149            bitOffsets[numElems - i - 1];
150      }
151  }
152
153  /**
154   * Creates a new <code>MultiPixelPackedSample</code> model with the same
155   * data type and bits per pixel as this model, but with the specified
156   * dimensions.
157   *
158   * @param w  the width (in pixels).
159   * @param h  the height (in pixels).
160   *
161   * @return The new sample model.
162   */
163  public SampleModel createCompatibleSampleModel(int w, int h)
164  {
165    /* FIXME: We can avoid recalculation of bit offsets and sample
166       sizes here by passing these from the current instance to a
167       special private constructor. */
168    return new MultiPixelPackedSampleModel(dataType, w, h, numberOfBits);
169  }
170
171  /**
172   * Creates a DataBuffer for holding pixel data in the format and
173   * layout described by this SampleModel. The returned buffer will
174   * consist of one single bank.
175   *
176   * @return A new data buffer.
177   */
178  public DataBuffer createDataBuffer()
179  {
180    int size = scanlineStride * height;
181    if (dataBitOffset > 0)
182      size += (dataBitOffset - 1) / elemBits + 1;
183    return Buffers.createBuffer(getDataType(), size);
184  }
185
186  /**
187   * Returns the number of data elements required to transfer a pixel in the
188   * get/setDataElements() methods.
189   *
190   * @return <code>1</code>.
191   */
192  public int getNumDataElements()
193  {
194    return 1;
195  }
196
197  /**
198   * Returns an array containing the size (in bits) of the samples in each
199   * band.  The <code>MultiPixelPackedSampleModel</code> class supports only
200   * one band, so this method returns an array with length <code>1</code>.
201   *
202   * @return An array containing the size (in bits) of the samples in band zero.
203   *
204   * @see #getSampleSize(int)
205   */
206  public int[] getSampleSize()
207  {
208    return (int[]) sampleSize.clone();
209  }
210
211  /**
212   * Returns the size of the samples in the specified band.  Note that the
213   * <code>MultiPixelPackedSampleModel</code> supports only one band -- this
214   * method ignored the <code>band</code> argument, and always returns the size
215   * of band zero.
216   *
217   * @param band  the band (this parameter is ignored).
218   *
219   * @return The size of the samples in band zero.
220   *
221   * @see #getSampleSize()
222   */
223  public int getSampleSize(int band)
224  {
225    return sampleSize[0];
226  }
227
228  /**
229   * Returns the index in the data buffer that stores the pixel at (x, y).
230   *
231   * @param x  the x-coordinate.
232   * @param y  the y-coordinate.
233   *
234   * @return The index in the data buffer that stores the pixel at (x, y).
235   *
236   * @see #getBitOffset(int)
237   */
238  public int getOffset(int x, int y)
239  {
240    return scanlineStride * y + ((dataBitOffset + x * numberOfBits) / elemBits);
241  }
242
243  /**
244   * The bit offset (within an element in the data buffer) of the pixels with
245   * the specified x-coordinate.
246   *
247   * @param x  the x-coordinate.
248   *
249   * @return The bit offset.
250   */
251  public int getBitOffset(int x)
252  {
253    return (dataBitOffset + x * numberOfBits) % elemBits;
254  }
255
256  /**
257   * Returns the offset to the first data bit.
258   *
259   * @return The offset to the first data bit.
260   */
261  public int getDataBitOffset()
262  {
263    return dataBitOffset;
264  }
265
266  /**
267   * Returns the number of data elements from a pixel in one row to the
268   * corresponding pixel in the next row.
269   *
270   * @return The scanline stride.
271   */
272  public int getScanlineStride()
273  {
274    return scanlineStride;
275  }
276
277  /**
278   * Returns the number of bits per pixel.
279   *
280   * @return The number of bits per pixel.
281   */
282  public int getPixelBitStride()
283  {
284    return numberOfBits;
285  }
286
287  /**
288   * Returns the transfer type, which is one of the following (depending on
289   * the number of bits per sample for this model):
290   * <ul>
291   *   <li>{@link DataBuffer#TYPE_BYTE};</li>
292   *   <li>{@link DataBuffer#TYPE_USHORT};</li>
293   *   <li>{@link DataBuffer#TYPE_INT};</li>
294   * </ul>
295   *
296   * @return The transfer type.
297   */
298  public int getTransferType()
299  {
300    if (numberOfBits <= DataBuffer.getDataTypeSize(DataBuffer.TYPE_BYTE))
301      return DataBuffer.TYPE_BYTE;
302    else if (numberOfBits <= DataBuffer.getDataTypeSize(DataBuffer.TYPE_USHORT))
303      return DataBuffer.TYPE_USHORT;
304    return DataBuffer.TYPE_INT;
305  }
306
307  /**
308   * Normally this method returns a sample model for accessing a subset of
309   * bands of image data, but since <code>MultiPixelPackedSampleModel</code>
310   * only supports a single band, this overridden implementation just returns
311   * a new instance of <code>MultiPixelPackedSampleModel</code>, with the same
312   * attributes as this instance.
313   *
314   * @param bands  the bands to include in the subset (this is ignored, except
315   *     that if it is non-<code>null</code> a check is made to ensure that the
316   *     array length is equal to <code>1</code>).
317   *
318   * @throws RasterFormatException if <code>bands</code> is not
319   *     <code>null</code> and <code>bands.length != 1</code>.
320   */
321  public SampleModel createSubsetSampleModel(int[] bands)
322  {
323    if (bands != null && bands.length != 1)
324      throw new RasterFormatException("MultiPixelPackedSampleModel only"
325          + " supports one band");
326    return new MultiPixelPackedSampleModel(dataType, width, height,
327        numberOfBits, scanlineStride, dataBitOffset);
328  }
329
330  /**
331   * Extract one pixel and return in an array of transfer type.
332   *
333   * Extracts the pixel at x, y from data and stores into the 0th index of the
334   * array obj, since there is only one band.  If obj is null, a new array of
335   * getTransferType() is created.
336   *
337   * @param x The x-coordinate of the pixel rectangle to store in
338   *     <code>obj</code>.
339   * @param y The y-coordinate of the pixel rectangle to store in
340   *     <code>obj</code>.
341   * @param obj The primitive array to store the pixels into or null to force
342   *     creation.
343   * @param data The DataBuffer that is the source of the pixel data.
344   * @return The primitive array containing the pixel data.
345   * @see java.awt.image.SampleModel#getDataElements(int, int, Object,
346   *     DataBuffer)
347   */
348  public Object getDataElements(int x, int y, Object obj, DataBuffer data)
349  {
350    int pixel = getSample(x, y, 0, data);
351    switch (getTransferType())
352      {
353        case DataBuffer.TYPE_BYTE:
354          if (obj == null)
355            obj = new byte[1];
356          ((byte[]) obj)[0] = (byte) pixel;
357          return obj;
358        case DataBuffer.TYPE_USHORT:
359          if (obj == null)
360            obj = new short[1];
361          ((short[]) obj)[0] = (short) pixel;
362          return obj;
363        case DataBuffer.TYPE_INT:
364          if (obj == null)
365            obj = new int[1];
366          ((int[]) obj)[0] = pixel;
367          return obj;
368        default:
369          // Seems like the only sensible thing to do.
370          throw new ClassCastException();
371      }
372  }
373
374  /**
375   * Returns an array (of length 1) containing the sample for the pixel at
376   * (x, y) in the specified data buffer.  If <code>iArray</code> is not
377   * <code>null</code>, it will be populated with the sample value and
378   * returned as the result of this function (this avoids allocating a new
379   * array instance).
380   *
381   * @param x  the x-coordinate of the pixel.
382   * @param y  the y-coordinate of the pixel.
383   * @param iArray  an array to populate with the sample values and return as
384   *     the result (if <code>null</code>, a new array will be allocated).
385   * @param data  the data buffer (<code>null</code> not permitted).
386   *
387   * @return An array containing the pixel sample value.
388   *
389   * @throws NullPointerException if <code>data</code> is <code>null</code>.
390   */
391  public int[] getPixel(int x, int y, int[] iArray, DataBuffer data)
392  {
393    if (iArray == null)
394      iArray = new int[1];
395    iArray[0] = getSample(x, y, 0, data);
396    return iArray;
397  }
398
399  /**
400   * Returns the sample value for the pixel at (x, y) in the specified data
401   * buffer.
402   *
403   * @param x  the x-coordinate of the pixel.
404   * @param y  the y-coordinate of the pixel.
405   * @param b  the band (in the range <code>0</code> to
406   *     <code>getNumBands() - 1</code>).
407   * @param data  the data buffer (<code>null</code> not permitted).
408   *
409   * @return The sample value.
410   *
411   * @throws NullPointerException if <code>data</code> is <code>null</code>.
412   */
413  public int getSample(int x, int y, int b, DataBuffer data)
414  {
415    int pos =
416      ((dataBitOffset + x * numberOfBits) % elemBits) / numberOfBits;
417    int offset = getOffset(x, y);
418    int samples = data.getElem(offset);
419    return (samples & bitMasks[pos]) >>> bitOffsets[pos];
420  }
421
422  /**
423   * Set the pixel at x, y to the value in the first element of the primitive
424   * array obj.
425   *
426   * @param x The x-coordinate of the data elements in <code>obj</code>.
427   * @param y The y-coordinate of the data elements in <code>obj</code>.
428   * @param obj The primitive array containing the data elements to set.
429   * @param data The DataBuffer to store the data elements into.
430   */
431  public void setDataElements(int x, int y, Object obj, DataBuffer data)
432  {
433    int transferType = getTransferType();
434    try
435      {
436        switch (transferType)
437          {
438            case DataBuffer.TYPE_BYTE:
439              {
440                byte[] in = (byte[]) obj;
441                setSample(x, y, 0, in[0] & 0xFF, data);
442                return;
443              }
444            case DataBuffer.TYPE_USHORT:
445              {
446                short[] in = (short[]) obj;
447                setSample(x, y, 0, in[0] & 0xFFFF, data);
448                return;
449              }
450            case DataBuffer.TYPE_INT:
451              {
452                int[] in = (int[]) obj;
453                setSample(x, y, 0, in[0], data);
454                return;
455              }
456            default:
457              throw new ClassCastException("Unsupported data type");
458          }
459      }
460    catch (ArrayIndexOutOfBoundsException aioobe)
461      {
462        String msg = "While writing data elements" +
463          ", x=" + x + ", y=" + y +
464          ", width=" + width + ", height=" + height +
465          ", scanlineStride=" + scanlineStride +
466          ", offset=" + getOffset(x, y) +
467          ", data.getSize()=" + data.getSize() +
468          ", data.getOffset()=" + data.getOffset() +
469          ": " + aioobe;
470        throw new ArrayIndexOutOfBoundsException(msg);
471      }
472  }
473
474  /**
475   * Sets the sample value for the pixel at (x, y) in the specified data
476   * buffer to the specified value.
477   *
478   * @param x  the x-coordinate of the pixel.
479   * @param y  the y-coordinate of the pixel.
480   * @param iArray  the sample value (<code>null</code> not permitted).
481   * @param data  the data buffer (<code>null</code> not permitted).
482   *
483   * @throws NullPointerException if either <code>iArray</code> or
484   *     <code>data</code> is <code>null</code>.
485   *
486   * @see #setSample(int, int, int, int, DataBuffer)
487   */
488  public void setPixel(int x, int y, int[] iArray, DataBuffer data)
489  {
490    setSample(x, y, 0, iArray[0], data);
491  }
492
493  /**
494   * Sets the sample value for a band for the pixel at (x, y) in the
495   * specified data buffer.
496   *
497   * @param x  the x-coordinate of the pixel.
498   * @param y  the y-coordinate of the pixel.
499   * @param b  the band (in the range <code>0</code> to
500   *     <code>getNumBands() - 1</code>).
501   * @param s  the sample value.
502   * @param data  the data buffer (<code>null</code> not permitted).
503   *
504   * @throws NullPointerException if <code>data</code> is <code>null</code>.
505   */
506  public void setSample(int x, int y, int b, int s, DataBuffer data)
507  {
508    int bitpos =
509      ((dataBitOffset + x * numberOfBits) % elemBits) / numberOfBits;
510    int offset = getOffset(x, y);
511
512    s = s << bitOffsets[bitpos];
513    s = s & bitMasks[bitpos];
514
515    int sample = data.getElem(offset);
516    sample |= s;
517    data.setElem(offset, sample);
518  }
519
520  /**
521   * Tests this sample model for equality with an arbitrary object.  This
522   * method returns <code>true</code> if and only if:
523   * <ul>
524   *   <li><code>obj</code> is not <code>null</code>;
525   *   <li><code>obj</code> is an instance of
526   *       <code>MultiPixelPackedSampleModel</code>;
527   *   <li>both models have the same:
528   *     <ul>
529   *       <li><code>dataType</code>;
530   *       <li><code>width</code>;
531   *       <li><code>height</code>;
532   *       <li><code>numberOfBits</code>;
533   *       <li><code>scanlineStride</code>;
534   *       <li><code>dataBitOffsets</code>.
535   *     </ul>
536   *   </li>
537   * </ul>
538   *
539   * @param obj  the object (<code>null</code> permitted)
540   *
541   * @return <code>true</code> if this model is equal to <code>obj</code>, and
542   *     <code>false</code> otherwise.
543   */
544  public boolean equals(Object obj)
545  {
546    if (this == obj)
547      return true;
548    if (! (obj instanceof MultiPixelPackedSampleModel))
549      return false;
550    MultiPixelPackedSampleModel that = (MultiPixelPackedSampleModel) obj;
551    if (this.dataType != that.dataType)
552      return false;
553    if (this.width != that.width)
554      return false;
555    if (this.height != that.height)
556      return false;
557    if (this.numberOfBits != that.numberOfBits)
558      return false;
559    if (this.scanlineStride != that.scanlineStride)
560      return false;
561    if (this.dataBitOffset != that.dataBitOffset)
562      return false;
563    return true;
564  }
565
566  /**
567   * Returns a hash code for this <code>MultiPixelPackedSampleModel</code>.
568   *
569   * @return A hash code.
570   */
571  public int hashCode()
572  {
573    // this hash code won't match Sun's, but that shouldn't matter...
574    int result = 193;
575    result = 37 * result + dataType;
576    result = 37 * result + width;
577    result = 37 * result + height;
578    result = 37 * result + numberOfBits;
579    result = 37 * result + scanlineStride;
580    result = 37 * result + dataBitOffset;
581    return result;
582  }
583
584  /**
585   * Creates a String with some information about this SampleModel.
586   * @return A String describing this SampleModel.
587   * @see java.lang.Object#toString()
588   */
589  public String toString()
590  {
591    CPStringBuilder result = new CPStringBuilder();
592    result.append(getClass().getName());
593    result.append("[");
594    result.append("scanlineStride=").append(scanlineStride);
595    for(int i=0; i < bitMasks.length; i+=1)
596    {
597      result.append(", mask[").append(i).append("]=0x").append(Integer.toHexString(bitMasks[i]));
598    }
599
600    result.append("]");
601    return result.toString();
602  }
603}