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 }