Source for java.awt.image.MultiPixelPackedSampleModel

   1: /* Copyright (C) 2004, 2006,  Free Software Foundation
   2: 
   3: This file is part of GNU Classpath.
   4: 
   5: GNU Classpath is free software; you can redistribute it and/or modify
   6: it under the terms of the GNU General Public License as published by
   7: the Free Software Foundation; either version 2, or (at your option)
   8: any later version.
   9: 
  10: GNU Classpath is distributed in the hope that it will be useful, but
  11: WITHOUT ANY WARRANTY; without even the implied warranty of
  12: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13: General Public License for more details.
  14: 
  15: You should have received a copy of the GNU General Public License
  16: along with GNU Classpath; see the file COPYING.  If not, write to the
  17: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  18: 02110-1301 USA.
  19: 
  20: Linking this library statically or dynamically with other modules is
  21: making a combined work based on this library.  Thus, the terms and
  22: conditions of the GNU General Public License cover the whole
  23: combination.
  24: 
  25: As a special exception, the copyright holders of this library give you
  26: permission to link this library with independent modules to produce an
  27: executable, regardless of the license terms of these independent
  28: modules, and to copy and distribute the resulting executable under
  29: terms of your choice, provided that you also meet, for each linked
  30: independent module, the terms and conditions of the license of that
  31: module.  An independent module is a module which is not derived from
  32: or based on this library.  If you modify this library, you may extend
  33: this exception to your version of the library, but you are not
  34: obligated to do so.  If you do not wish to do so, delete this
  35: exception statement from your version. */
  36: 
  37: package java.awt.image;
  38: 
  39: import gnu.java.awt.Buffers;
  40: 
  41: /**
  42:  * MultiPixelPackedSampleModel provides a single band model that supports
  43:  * multiple pixels in a single unit.  Pixels have 2^n bits and 2^k pixels fit
  44:  * per data element.
  45:  *
  46:  * @author Jerry Quinn (jlquinn@optonline.net)
  47:  */
  48: public class MultiPixelPackedSampleModel extends SampleModel
  49: {
  50:   private int scanlineStride;
  51:   private int[] bitMasks;
  52:   private int[] bitOffsets;
  53:   private int[] sampleSize;
  54:   private int dataBitOffset;
  55:   private int elemBits;
  56:   private int numberOfBits;
  57:   private int numElems;
  58: 
  59:   /**
  60:    * Creates a new <code>MultiPixelPackedSampleModel</code> with the specified
  61:    * data type, which should be one of:
  62:    * <ul>
  63:    *   <li>{@link DataBuffer#TYPE_BYTE};</li>
  64:    *   <li>{@link DataBuffer#TYPE_USHORT};</li>
  65:    *   <li>{@link DataBuffer#TYPE_INT};</li>
  66:    * </ul>
  67:    * 
  68:    * @param dataType  the data type.
  69:    * @param w  the width (in pixels).
  70:    * @param h  the height (in pixels).
  71:    * @param numberOfBits  the number of bits per pixel (must be a power of 2).
  72:    */
  73:   public MultiPixelPackedSampleModel(int dataType, int w, int h,
  74:                      int numberOfBits)
  75:   {
  76:     this(dataType, w, h, numberOfBits, 0, 0);
  77:   }
  78: 
  79:   /**
  80:    * Creates a new <code>MultiPixelPackedSampleModel</code> with the specified
  81:    * data type, which should be one of:
  82:    * <ul>
  83:    *   <li>{@link DataBuffer#TYPE_BYTE};</li>
  84:    *   <li>{@link DataBuffer#TYPE_USHORT};</li>
  85:    *   <li>{@link DataBuffer#TYPE_INT};</li>
  86:    * </ul>
  87:    * 
  88:    * @param dataType  the data type.
  89:    * @param w  the width (in pixels).
  90:    * @param h  the height (in pixels).
  91:    * @param numberOfBits  the number of bits per pixel (must be a power of 2).
  92:    * @param scanlineStride  the number of data elements from a pixel on one 
  93:    *     row to the corresponding pixel in the next row.
  94:    * @param dataBitOffset  the offset to the first data bit.
  95:    */
  96:   public MultiPixelPackedSampleModel(int dataType, int w, int h,
  97:                      int numberOfBits, int scanlineStride,
  98:                      int dataBitOffset)
  99:   {
 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: }