KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > awt > image > RescaleOp


1 /*
2  * @(#)RescaleOp.java 1.43 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.awt.image;
9
10 import java.awt.color.ColorSpace JavaDoc;
11 import java.awt.geom.Rectangle2D JavaDoc;
12 import java.awt.Rectangle JavaDoc;
13 import java.awt.geom.Point2D JavaDoc;
14 import java.awt.RenderingHints JavaDoc;
15 import sun.awt.image.ImagingLib;
16
17 /**
18  * This class performs a pixel-by-pixel rescaling of the data in the
19  * source image by multiplying the sample values for each pixel by a scale
20  * factor and then adding an offset. The scaled sample values are clipped
21  * to the minimum/maximum representable in the destination image.
22  * <p>
23  * The pseudo code for the rescaling operation is as follows:
24  * <pre>
25  *for each pixel from Source object {
26  * for each band/component of the pixel {
27  * dstElement = (srcElement*scaleFactor) + offset
28  * }
29  *}
30  * </pre>
31  * <p>
32  * For Rasters, rescaling operates on bands. The number of
33  * sets of scaling constants may be one, in which case the same constants
34  * are applied to all bands, or it must equal the number of Source
35  * Raster bands.
36  * <p>
37  * For BufferedImages, rescaling operates on color and alpha components.
38  * The number of sets of scaling constants may be one, in which case the
39  * same constants are applied to all color (but not alpha) components.
40  * Otherwise, the number of sets of scaling constants may
41  * equal the number of Source color components, in which case no
42  * rescaling of the alpha component (if present) is performed.
43  * If neither of these cases apply, the number of sets of scaling constants
44  * must equal the number of Source color components plus alpha components,
45  * in which case all color and alpha components are rescaled.
46  * <p>
47  * BufferedImage sources with premultiplied alpha data are treated in the same
48  * manner as non-premultiplied images for purposes of rescaling. That is,
49  * the rescaling is done per band on the raw data of the BufferedImage source
50  * without regard to whether the data is premultiplied. If a color conversion
51  * is required to the destination ColorModel, the premultiplied state of
52  * both source and destination will be taken into account for this step.
53  * <p>
54  * Images with an IndexColorModel cannot be rescaled.
55  * <p>
56  * If a RenderingHints object is specified in the constructor, the
57  * color rendering hint and the dithering hint may be used when color
58  * conversion is required.
59  * <p>
60  * Note that in-place operation is allowed (i.e. the source and destination can
61  * be the same object).
62  * @version 10 Feb 1997
63  * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
64  * @see java.awt.RenderingHints#KEY_DITHERING
65  */

66 public class RescaleOp implements BufferedImageOp JavaDoc, RasterOp JavaDoc {
67     float[] scaleFactors;
68     float[] offsets;
69     int length = 0;
70     RenderingHints JavaDoc hints;
71
72     private int srcNbits;
73     private int dstNbits;
74
75     
76     /**
77      * Constructs a new RescaleOp with the desired scale factors
78      * and offsets. The length of the scaleFactor and offset arrays
79      * must meet the restrictions stated in the class comments above.
80      * The RenderingHints argument may be null.
81      * @param scaleFactors the specified scale factors
82      * @param offsets the specified offsets
83      * @param hints the specified <code>RenderingHints</code>, or
84      * <code>null</code>
85      */

86     public RescaleOp (float[] scaleFactors, float[] offsets,
87                       RenderingHints JavaDoc hints) {
88         length = scaleFactors.length;
89         if (length > offsets.length) length = offsets.length;
90
91         this.scaleFactors = new float[length];
92         this.offsets = new float[length];
93         for (int i=0; i < length; i++) {
94             this.scaleFactors[i] = scaleFactors[i];
95             this.offsets[i] = offsets[i];
96         }
97         this.hints = hints;
98     }
99
100     /**
101      * Constructs a new RescaleOp with the desired scale factor
102      * and offset. The scaleFactor and offset will be applied to
103      * all bands in a source Raster and to all color (but not alpha)
104      * components in a BufferedImage.
105      * The RenderingHints argument may be null.
106      * @param scaleFactor the specified scale factor
107      * @param offset the specified offset
108      * @param hints the specified <code>RenderingHints</code>, or
109      * <code>null</code>
110      */

111     public RescaleOp (float scaleFactor, float offset, RenderingHints JavaDoc hints) {
112         length = 1;
113         this.scaleFactors = new float[1];
114         this.offsets = new float[1];
115         this.scaleFactors[0] = scaleFactor;
116         this.offsets[0] = offset;
117         this.hints = hints;
118     }
119
120     /**
121      * Returns the scale factors in the given array. The array is also
122      * returned for convenience. If scaleFactors is null, a new array
123      * will be allocated.
124      * @param scaleFactors the array to contain the scale factors of
125      * this <code>RescaleOp</code>
126      * @return the scale factors of this <code>RescaleOp</code>.
127      */

128     final public float[] getScaleFactors (float scaleFactors[]) {
129         if (scaleFactors == null) {
130             return (float[]) this.scaleFactors.clone();
131         }
132         System.arraycopy (this.scaleFactors, 0, scaleFactors, 0,
133                           Math.min(this.scaleFactors.length,
134                                    scaleFactors.length));
135         return scaleFactors;
136     }
137
138     /**
139      * Returns the offsets in the given array. The array is also returned
140      * for convenience. If offsets is null, a new array
141      * will be allocated.
142      * @param offsets the array to contain the offsets of
143      * this <code>RescaleOp</code>
144      * @return the offsets of this <code>RescaleOp</code>.
145      */

146     final public float[] getOffsets(float offsets[]) {
147         if (offsets == null) {
148             return (float[]) this.offsets.clone();
149         }
150
151         System.arraycopy (this.offsets, 0, offsets, 0,
152                           Math.min(this.offsets.length, offsets.length));
153         return offsets;
154     }
155
156     /**
157      * Returns the number of scaling factors and offsets used in this
158      * RescaleOp.
159      * @return the number of scaling factors and offsets of this
160      * <code>RescaleOp</code>.
161      */

162     final public int getNumFactors() {
163         return length;
164     }
165      
166
167     /**
168      * Creates a ByteLookupTable to implement the rescale.
169      * The table may have either a SHORT or BYTE input.
170      * @param nElems Number of elements the table is to have.
171      * This will generally be 256 for byte and
172      * 65536 for short.
173      */

174     private ByteLookupTable JavaDoc createByteLut(float scale[],
175                                           float off[],
176                                           int nBands,
177                                           int nElems) {
178  
179         byte[][] lutData = new byte[scale.length][nElems];
180  
181         for (int band=0; band<scale.length; band++) {
182             float bandScale = scale[band];
183             float bandOff = off[band];
184             byte[] bandLutData = lutData[band];
185             for (int i=0; i<nElems; i++) {
186                 int val = (int)(i*bandScale + bandOff);
187                 if ((val & 0xffffff00) != 0) {
188                     if (val < 0) {
189                         val = 0;
190                     } else {
191                         val = 255;
192                     }
193                 }
194                 bandLutData[i] = (byte)val;
195             }
196
197         }
198  
199         return new ByteLookupTable JavaDoc(0, lutData);
200     }
201  
202     /**
203      * Creates a ShortLookupTable to implement the rescale.
204      * The table may have either a SHORT or BYTE input.
205      * @param nElems Number of elements the table is to have.
206      * This will generally be 256 for byte and
207      * 65536 for short.
208      */

209     private ShortLookupTable JavaDoc createShortLut(float scale[],
210                                             float off[],
211                                             int nBands,
212                                             int nElems) {
213  
214         short[][] lutData = new short[scale.length][nElems];
215  
216         for (int band=0; band<scale.length; band++) {
217             float bandScale = scale[band];
218             float bandOff = off[band];
219             short[] bandLutData = lutData[band];
220             for (int i=0; i<nElems; i++) {
221                 int val = (int)(i*bandScale + bandOff);
222                 if ((val & 0xffff0000) != 0) {
223                     if (val < 0) {
224                         val = 0;
225                     } else {
226                         val = 65535;
227                     }
228                 }
229                 bandLutData[i] = (short)val;
230             }
231         }
232  
233         return new ShortLookupTable JavaDoc(0, lutData);
234     }
235  
236  
237     /**
238      * Determines if the rescale can be performed as a lookup.
239      * The dst must be a byte or short type.
240      * The src must be less than 16 bits.
241      * All source band sizes must be the same and all dst band sizes
242      * must be the same.
243      */

244     private boolean canUseLookup(Raster JavaDoc src, Raster JavaDoc dst) {
245
246         //
247
// Check that the src datatype is either a BYTE or SHORT
248
//
249
int datatype = src.getDataBuffer().getDataType();
250         if(datatype != DataBuffer.TYPE_BYTE &&
251            datatype != DataBuffer.TYPE_USHORT) {
252             return false;
253         }
254  
255         //
256
// Check dst sample sizes. All must be 8 or 16 bits.
257
//
258
SampleModel JavaDoc dstSM = dst.getSampleModel();
259         dstNbits = dstSM.getSampleSize(0);
260
261         if (!(dstNbits == 8 || dstNbits == 16)) {
262             return false;
263         }
264         for (int i=1; i<src.getNumBands(); i++) {
265             int bandSize = dstSM.getSampleSize(i);
266             if (bandSize != dstNbits) {
267                 return false;
268             }
269         }
270  
271         //
272
// Check src sample sizes. All must be the same size
273
//
274
SampleModel JavaDoc srcSM = src.getSampleModel();
275         srcNbits = srcSM.getSampleSize(0);
276         if (srcNbits > 16) {
277             return false;
278         }
279         for (int i=1; i<src.getNumBands(); i++) {
280             int bandSize = srcSM.getSampleSize(i);
281             if (bandSize != srcNbits) {
282                 return false;
283             }
284         }
285  
286         return true;
287     }
288  
289     /**
290      * Rescales the source BufferedImage.
291      * If the color model in the source image is not the same as that
292      * in the destination image, the pixels will be converted
293      * in the destination. If the destination image is null,
294      * a BufferedImage will be created with the source ColorModel.
295      * An IllegalArgumentException may be thrown if the number of
296      * scaling factors/offsets in this object does not meet the
297      * restrictions stated in the class comments above, or if the
298      * source image has an IndexColorModel.
299      * @param src the <code>BufferedImage</code> to be filtered
300      * @param dst the destination for the filtering operation
301      * or <code>null</code>
302      * @return the filtered <code>BufferedImage</code>.
303      * @throws IllegalArgumentException if the <code>ColorModel</code>
304      * of <code>src</code> is an <code>IndexColorModel</code>,
305      * or if the number of scaling factors and offsets in this
306      * <code>RescaleOp</code> do not meet the requirements
307      * stated in the class comments.
308      */

309     public final BufferedImage JavaDoc filter (BufferedImage JavaDoc src, BufferedImage JavaDoc dst) {
310         ColorModel JavaDoc srcCM = src.getColorModel();
311         ColorModel JavaDoc dstCM;
312         int numBands = srcCM.getNumColorComponents();
313
314         
315         if (srcCM instanceof IndexColorModel JavaDoc) {
316             throw new
317                 IllegalArgumentException JavaDoc("Rescaling cannot be "+
318                                          "performed on an indexed image");
319         }
320         if (length != 1 && length != numBands &&
321             length != srcCM.getNumComponents())
322         {
323             throw new IllegalArgumentException JavaDoc("Number of scaling constants "+
324                                                "does not equal the number of"+
325                                                " of color or color/alpha "+
326                                                " components");
327         }
328         
329         boolean needToConvert = false;
330
331         // Include alpha
332
if (length > numBands && srcCM.hasAlpha()) {
333             length = numBands+1;
334         }
335         
336         int width = src.getWidth();
337         int height = src.getHeight();
338
339         if (dst == null) {
340             dst = createCompatibleDestImage(src, null);
341             dstCM = srcCM;
342         }
343         else {
344             if (width != dst.getWidth()) {
345                 throw new
346                     IllegalArgumentException JavaDoc("Src width ("+width+
347                                              ") not equal to dst width ("+
348                                              dst.getWidth()+")");
349             }
350             if (height != dst.getHeight()) {
351                 throw new
352                     IllegalArgumentException JavaDoc("Src height ("+height+
353                                              ") not equal to dst height ("+
354                                              dst.getHeight()+")");
355             }
356
357             dstCM = dst.getColorModel();
358             if(srcCM.getColorSpace().getType() !=
359                dstCM.getColorSpace().getType()) {
360                 needToConvert = true;
361                 dst = createCompatibleDestImage(src, null);
362             }
363
364         }
365         
366         BufferedImage JavaDoc origDst = dst;
367
368         //
369
// Try to use a native BI rescale operation first
370
//
371
if (ImagingLib.filter(this, src, dst) == null) {
372             //
373
// Native BI rescale failed - convert to rasters
374
//
375
WritableRaster JavaDoc srcRaster = src.getRaster();
376             WritableRaster JavaDoc dstRaster = dst.getRaster();
377
378             if (srcCM.hasAlpha()) {
379                 if (numBands-1 == length || length == 1) {
380                     int minx = srcRaster.getMinX();
381                     int miny = srcRaster.getMinY();
382                     int[] bands = new int[numBands-1];
383                     for (int i=0; i < numBands-1; i++) {
384                         bands[i] = i;
385                     }
386                     srcRaster =
387                         srcRaster.createWritableChild(minx, miny,
388                                                       srcRaster.getWidth(),
389                                                       srcRaster.getHeight(),
390                                                       minx, miny,
391                                                       bands);
392                 }
393             }
394             if (dstCM.hasAlpha()) {
395                 int dstNumBands = dstRaster.getNumBands();
396                 if (dstNumBands-1 == length || length == 1) {
397                     int minx = dstRaster.getMinX();
398                     int miny = dstRaster.getMinY();
399                     int[] bands = new int[numBands-1];
400                     for (int i=0; i < numBands-1; i++) {
401                         bands[i] = i;
402                     }
403                     dstRaster =
404                         dstRaster.createWritableChild(minx, miny,
405                                                       dstRaster.getWidth(),
406                                                       dstRaster.getHeight(),
407                                                       minx, miny,
408                                                       bands);
409                 }
410             }
411
412             //
413
// Call the raster filter method
414
//
415
filter(srcRaster, dstRaster);
416             
417         }
418         
419         if (needToConvert) {
420             // ColorModels are not the same
421
ColorConvertOp JavaDoc ccop = new ColorConvertOp JavaDoc(hints);
422             ccop.filter(dst, origDst);
423         }
424
425         return origDst;
426     }
427
428     /**
429      * Rescales the pixel data in the source Raster.
430      * If the destination Raster is null, a new Raster will be created.
431      * The source and destination must have the same number of bands.
432      * Otherwise, an IllegalArgumentException is thrown.
433      * Note that the number of scaling factors/offsets in this object must
434      * meet the restrictions stated in the class comments above.
435      * Otherwise, an IllegalArgumentException is thrown.
436      * @param src the <code>Raster</code> to be filtered
437      * @param dst the destination for the filtering operation
438      * or <code>null</code>
439      * @return the filtered <code>WritableRaster</code>.
440      * @throws IllegalArgumentException if <code>src</code> and
441      * <code>dst</code> do not have the same number of bands,
442      * or if the number of scaling factors and offsets in this
443      * <code>RescaleOp</code> do not meet the requirements
444      * stated in the class comments.
445      */

446     public final WritableRaster JavaDoc filter (Raster JavaDoc src, WritableRaster JavaDoc dst) {
447         int numBands = src.getNumBands();
448         int width = src.getWidth();
449         int height = src.getHeight();
450         int[] srcPix = null;
451         int step = 0;
452         int tidx = 0;
453
454         // Create a new destination Raster, if needed
455
if (dst == null) {
456             dst = createCompatibleDestRaster(src);
457         }
458         else if (height != dst.getHeight() || width != dst.getWidth()) {
459             throw new
460                IllegalArgumentException JavaDoc("Width or height of Rasters do not "+
461                                         "match");
462         }
463         else if (numBands != dst.getNumBands()) {
464             // Make sure that the number of bands are equal
465
throw new IllegalArgumentException JavaDoc("Number of bands in src "
466                             + numBands
467                             + " does not equal number of bands in dest "
468                             + dst.getNumBands());
469         }
470         // Make sure that the arrays match
471
// Make sure that the low/high/constant arrays match
472
if (length != 1 && length != src.getNumBands()) {
473             throw new IllegalArgumentException JavaDoc("Number of scaling constants "+
474                                                "does not equal the number of"+
475                                                " of bands in the src raster");
476         }
477
478         
479         //
480
// Try for a native raster rescale first
481
//
482
if (ImagingLib.filter(this, src, dst) != null) {
483             return dst;
484         }
485
486         //
487
// Native raster rescale failed.
488
// Try to see if a lookup operation can be used
489
//
490
if (canUseLookup(src, dst)) {
491             int srcNgray = (1 << srcNbits);
492             int dstNgray = (1 << dstNbits);
493
494             if (dstNgray == 256) {
495                 ByteLookupTable JavaDoc lut = createByteLut(scaleFactors, offsets,
496                                                     numBands, srcNgray);
497                 LookupOp JavaDoc op = new LookupOp JavaDoc(lut, hints);
498                 op.filter(src, dst);
499             } else {
500                 ShortLookupTable JavaDoc lut = createShortLut(scaleFactors, offsets,
501                                                       numBands, srcNgray);
502                 LookupOp JavaDoc op = new LookupOp JavaDoc(lut, hints);
503                 op.filter(src, dst);
504             }
505         } else {
506             //
507
// Fall back to the slow code
508
//
509
if (length > 1) {
510                 step = 1;
511             }
512
513             int sminX = src.getMinX();
514             int sY = src.getMinY();
515             int dminX = dst.getMinX();
516             int dY = dst.getMinY();
517             int sX;
518             int dX;
519
520             //
521
// Determine bits per band to determine maxval for clamps.
522
// The min is assumed to be zero.
523
// REMIND: This must change if we ever support signed data types.
524
//
525
int nbits;
526             int dstMax[] = new int[numBands];
527             int dstMask[] = new int[numBands];
528             SampleModel JavaDoc dstSM = dst.getSampleModel();
529             for (int z=0; z<numBands; z++) {
530                 nbits = dstSM.getSampleSize(z);
531                 dstMax[z] = (1 << nbits) - 1;
532                 dstMask[z] = ~(dstMax[z]);
533             }
534
535             int val;
536             for (int y=0; y < height; y++, sY++, dY++) {
537                 dX = dminX;
538                 sX = sminX;
539                 for (int x = 0; x < width; x++, sX++, dX++) {
540                     // Get data for all bands at this x,y position
541
srcPix = src.getPixel(sX, sY, srcPix);
542                     tidx = 0;
543                     for (int z=0; z<numBands; z++, tidx += step) {
544                         val = (int)(srcPix[z]*scaleFactors[tidx]
545                                           + offsets[tidx]);
546                         // Clamp
547
if ((val & dstMask[z]) != 0) {
548                             if (val < 0) {
549                                 val = 0;
550                             } else {
551                                 val = dstMax[z];
552                             }
553                         }
554                         srcPix[z] = val;
555
556                     }
557
558                     // Put it back for all bands
559
dst.setPixel(dX, dY, srcPix);
560                 }
561             }
562         }
563         return dst;
564     }
565
566     /**
567      * Returns the bounding box of the rescaled destination image. Since
568      * this is not a geometric operation, the bounding box does not
569      * change.
570      */

571     public final Rectangle2D JavaDoc getBounds2D (BufferedImage JavaDoc src) {
572          return getBounds2D(src.getRaster());
573     }
574
575     /**
576      * Returns the bounding box of the rescaled destination Raster. Since
577      * this is not a geometric operation, the bounding box does not
578      * change.
579      * @param src the rescaled destination <code>Raster</code>
580      * @return the bounds of the specified <code>Raster</code>.
581      */

582     public final Rectangle2D JavaDoc getBounds2D (Raster JavaDoc src) {
583     return src.getBounds();
584     }
585
586     /**
587      * Creates a zeroed destination image with the correct size and number of
588      * bands.
589      * @param src Source image for the filter operation.
590      * @param destCM ColorModel of the destination. If null, the
591      * ColorModel of the source will be used.
592      * @return the zeroed-destination image.
593      */

594     public BufferedImage JavaDoc createCompatibleDestImage (BufferedImage JavaDoc src,
595                                                     ColorModel JavaDoc destCM) {
596         BufferedImage JavaDoc image;
597         if (destCM == null) {
598             ColorModel JavaDoc cm = src.getColorModel();
599             image = new BufferedImage JavaDoc(cm,
600                                       src.getRaster().createCompatibleWritableRaster(),
601                                       cm.isAlphaPremultiplied(),
602                                       null);
603         }
604         else {
605             int w = src.getWidth();
606             int h = src.getHeight();
607             image = new BufferedImage JavaDoc (destCM,
608                                    destCM.createCompatibleWritableRaster(w, h),
609                                    destCM.isAlphaPremultiplied(), null);
610         }
611
612         return image;
613     }
614     
615     /**
616      * Creates a zeroed-destination <code>Raster</code> with the correct
617      * size and number of bands, given this source.
618      * @param src the source <code>Raster</code>
619      * @return the zeroed-destination <code>Raster</code>.
620      */

621     public WritableRaster JavaDoc createCompatibleDestRaster (Raster JavaDoc src) {
622         return src.createCompatibleWritableRaster(src.getWidth(), src.getHeight());
623     }
624     
625     /**
626      * Returns the location of the destination point given a
627      * point in the source. If dstPt is non-null, it will
628      * be used to hold the return value. Since this is not a geometric
629      * operation, the srcPt will equal the dstPt.
630      * @param srcPt a point in the source image
631      * @param dstPt the destination point or <code>null</code>
632      * @return the location of the destination point.
633      */

634     public final Point2D JavaDoc getPoint2D (Point2D JavaDoc srcPt, Point2D JavaDoc dstPt) {
635         if (dstPt == null) {
636             dstPt = new Point2D.Float JavaDoc();
637         }
638     dstPt.setLocation(srcPt.getX(), srcPt.getY());
639         return dstPt;
640     }
641     
642     /**
643      * Returns the rendering hints for this op.
644      * @return the rendering hints of this <code>RescaleOp</code>.
645      */

646     public final RenderingHints JavaDoc getRenderingHints() {
647         return hints;
648     }
649 }
650
Popular Tags