001    /* Copyright (C) 2004, 2006,  Free Software Foundation
002    
003    This file is part of GNU Classpath.
004    
005    GNU Classpath is free software; you can redistribute it and/or modify
006    it under the terms of the GNU General Public License as published by
007    the Free Software Foundation; either version 2, or (at your option)
008    any later version.
009    
010    GNU Classpath is distributed in the hope that it will be useful, but
011    WITHOUT ANY WARRANTY; without even the implied warranty of
012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013    General Public License for more details.
014    
015    You should have received a copy of the GNU General Public License
016    along with GNU Classpath; see the file COPYING.  If not, write to the
017    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
018    02110-1301 USA.
019    
020    Linking this library statically or dynamically with other modules is
021    making a combined work based on this library.  Thus, the terms and
022    conditions of the GNU General Public License cover the whole
023    combination.
024    
025    As a special exception, the copyright holders of this library give you
026    permission to link this library with independent modules to produce an
027    executable, regardless of the license terms of these independent
028    modules, and to copy and distribute the resulting executable under
029    terms of your choice, provided that you also meet, for each linked
030    independent module, the terms and conditions of the license of that
031    module.  An independent module is a module which is not derived from
032    or based on this library.  If you modify this library, you may extend
033    this exception to your version of the library, but you are not
034    obligated to do so.  If you do not wish to do so, delete this
035    exception statement from your version. */
036    
037    package java.awt.image;
038    
039    import gnu.java.awt.Buffers;
040    import 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     */
049    public 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    }