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