1 /*
2  * $RCSfile: RescaleOpImage.java,v $
3  *
4  * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
5  *
6  * Use is subject to license terms.
7  *
8  * $Revision: 1.1 $
9  * $Date: 2005/02/11 04:56:41 $
10  * $State: Exp $
11  */
12 package com.lightcrafts.media.jai.opimage;
13 import java.awt.Rectangle;
14 import java.awt.image.DataBuffer;
15 import java.awt.image.Raster;
16 import java.awt.image.RenderedImage;
17 import java.awt.image.WritableRaster;
18 
19 import com.lightcrafts.mediax.jai.ColormapOpImage;
20 import com.lightcrafts.mediax.jai.ImageLayout;
21 import com.lightcrafts.mediax.jai.RasterAccessor;
22 import com.lightcrafts.mediax.jai.RasterFormatTag;
23 
24 import java.util.Collection;
25 import java.util.LinkedList;
26 import java.util.Map;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.ExecutorService;
29 import java.util.concurrent.Executors;
30 
31 import com.lightcrafts.media.jai.util.ImageUtil;
32 
33 /**
34  * An <code>OpImage</code> implementing the "Rescale" operation.
35  *
36  * <p> The "Rescale" operation maps the pixel values of an image from
37  * one range to another range by multiplying each pixel value by one
38  * of a set of constants and then adding another constant to the
39  * result of the multiplication. The pixel values of the destination
40  * image are defined by the pseudocode:
41  *
42  * <pre>
43  *     for (int h = 0; h < dstHeight; h++) {
44  *         for (int w = 0; w < dstWidth; w++) {
45  *             for (int b = 0; b < dstNumBands; b++) {
46  *                 scale = (scales.length < dstNumBands)?
47  *                 scales[0]:scales[b];
48  *                 offset = (offsets.length < dstNumBands)?
49  *                 offsets[0]:offsets[b];
50  *                 dst[h][w][b] = srcs[h][w][b] * scale + offset;
51  *             }
52  *         }
53  *     }
54  * </pre>
55  *
56  * @see com.lightcrafts.mediax.jai.operator.RescaleDescriptor
57  * @see RescaleCRIF
58  *
59  *
60  * @since EA3
61  */
62 final class RescaleOpImage extends ColormapOpImage {
63 
64     /** The constants to be multiplied, one for each band. */
65     protected double[] constants;
66     protected double[] offsets;
67 
68     private byte[][] byteTable = null;
69 
70     static final int numProc = Runtime.getRuntime().availableProcessors();
71 
initByteTable()72     private synchronized void initByteTable() {
73 
74         if (byteTable != null) {
75             return;
76         }
77 
78         int nbands = constants.length;
79 
80         byteTable = new byte[nbands][256];
81 
82         // Initialize table which implements Rescale and clamp
83         for(int band=0; band<nbands; band++) {
84             byte[] t = byteTable[band];
85             double c = constants[band];
86             double o = offsets[band];
87             for (int i = 0; i < 256; i++) {
88                 t[i] = ImageUtil.clampRoundByte(i * c + o);
89             }
90         }
91     }
92 
93     /**
94      * Constructor.
95      *
96      * @param source     The source image.
97      * @param config     Configurable attributes of the image including
98      *        configuration variables indexed by
99      *        <code>RenderingHints.Key</code>s and image properties indexed
100      *        by <code>String</code>s or <code>CaselessStringKey</code>s.
101      *        This is simply forwarded to the superclass constructor.
102      * @param layout     The destination image layout.
103      * @param constants  The constants to be multiplied, stored as reference.
104      * @param offsets    The offsets to be added, stored as reference.
105      */
RescaleOpImage(RenderedImage source, Map config, ImageLayout layout, double[] constants, double[] offsets)106     public RescaleOpImage(RenderedImage source,
107                           Map config,
108                           ImageLayout layout,
109                           double[] constants,
110                           double[] offsets) {
111         super(source, layout, config, true);
112 
113         int numBands = getSampleModel().getNumBands();
114 
115         if (constants.length < numBands) {
116             this.constants = new double[numBands];
117             for (int i = 0; i < numBands; i++) {
118                 this.constants[i] = constants[0];
119             }
120         } else {
121             this.constants = constants;
122         }
123 
124         if (offsets.length < numBands) {
125             this.offsets   = new double[numBands];
126             for (int i = 0; i < numBands; i++) {
127                 this.offsets[i]   = offsets[0];
128             }
129         } else {
130             this.offsets = offsets;
131         }
132 
133         // Set flag to permit in-place operation.
134         permitInPlaceOperation();
135 
136         // Initialize the colormap if necessary.
137         initializeColormapOperation();
138     }
139 
140     /**
141      * Transform the colormap according to the rescaling parameters.
142      */
transformColormap(byte[][] colormap)143     protected void transformColormap(byte[][] colormap) {
144         for (int b = 0; b < 3; b++) {
145             byte[] map = colormap[b];
146             int mapSize = map.length;
147 
148             float c = (float)(b < constants.length ?
149                               constants[b] : constants[0]);
150             float o = (float)(b < constants.length ?
151                               offsets[b] : offsets[0]);
152 
153             for (int i = 0; i < mapSize; i++) {
154                 map[i] = ImageUtil.clampRoundByte((map[i] & 0xFF) * c + o);
155             }
156         }
157     }
158 
159     /**
160      * Rescales to the pixel values within a specified rectangle.
161      *
162      * @param sources   Cobbled sources, guaranteed to provide all the
163      *                  source data necessary for computing the rectangle.
164      * @param dest      The tile containing the rectangle to be computed.
165      * @param destRect  The rectangle within the tile to be computed.
166      */
computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect)167     protected void computeRect(Raster[] sources,
168                                WritableRaster dest,
169                                Rectangle destRect) {
170         // Retrieve format tags.
171         RasterFormatTag[] formatTags = getFormatTags();
172 
173         Rectangle srcRect = mapDestRect(destRect, 0);
174 
175         RasterAccessor dst = new RasterAccessor(dest, destRect,
176                                                 formatTags[1], getColorModel());
177         RasterAccessor src = new RasterAccessor(sources[0], srcRect,
178                                                 formatTags[0],
179                                                 getSourceImage(0).getColorModel());
180 
181         switch (dst.getDataType()) {
182         case DataBuffer.TYPE_BYTE:
183             computeRectByte(src, dst);
184             break;
185         case DataBuffer.TYPE_USHORT:
186             computeRectUShort(src, dst);
187             break;
188         case DataBuffer.TYPE_SHORT:
189             computeRectShort(src, dst);
190             break;
191         case DataBuffer.TYPE_INT:
192             computeRectInt(src, dst);
193             break;
194         case DataBuffer.TYPE_FLOAT:
195             computeRectFloat(src, dst);
196             break;
197         case DataBuffer.TYPE_DOUBLE:
198             computeRectDouble(src, dst);
199             break;
200         }
201 
202         if (dst.needsClamping()) {
203             /* Further clamp down to underlying raster data type. */
204             dst.clampDataArrays();
205         }
206         dst.copyDataToRaster();
207     }
208 
computeRectByte(RasterAccessor src, RasterAccessor dst)209     private void computeRectByte(RasterAccessor src,
210                                  RasterAccessor dst) {
211         final int dstWidth = dst.getWidth();
212         final int dstHeight = dst.getHeight();
213         final int dstBands = dst.getNumBands();
214 
215         final int dstLineStride = dst.getScanlineStride();
216         final int dstPixelStride = dst.getPixelStride();
217         final int[] dstBandOffsets = dst.getBandOffsets();
218         final byte[][] dstData = dst.getByteDataArrays();
219 
220         final int srcLineStride = src.getScanlineStride();
221         final int srcPixelStride = src.getPixelStride();
222         final int[] srcBandOffsets = src.getBandOffsets();
223         final byte[][] srcData = src.getByteDataArrays();
224 
225         initByteTable();
226 
227         ExecutorService threadPool = Executors.newFixedThreadPool(numProc);
228         Collection<Callable<Void>> processes = new LinkedList<Callable<Void>>();
229         for (int band = 0; band < dstBands; band++) {
230             final byte[] s = srcData[band];
231             final byte[] d = dstData[band];
232 
233             final int dstLineOffset = dstBandOffsets[band];
234             final int srcLineOffset = srcBandOffsets[band];
235 
236             final byte[] clamp = byteTable[band];
237 
238             for (int h = 0; h < dstHeight; h++) {
239                 final int hh = h;
240                 processes.add(new Callable<Void>() {
241                     @Override
242                     public Void call() {
243                         int dstPixelOffset = dstLineOffset + hh * dstLineStride;
244                         int srcPixelOffset = srcLineOffset + hh * srcLineStride;
245 
246                         for (int w = 0; w < dstWidth; w++) {
247                             d[dstPixelOffset] = clamp[s[srcPixelOffset] & 0xFF];
248 
249                             dstPixelOffset += dstPixelStride;
250                             srcPixelOffset += srcPixelStride;
251                         }
252                         return null;
253                     }
254                 });
255             }
256         }
257         try {
258             threadPool.invokeAll(processes);
259         } catch (InterruptedException e) {
260             throw new RuntimeException(e);
261         } finally {
262             threadPool.shutdown();
263         }
264     }
265 
computeRectUShort(RasterAccessor src, RasterAccessor dst)266     private void computeRectUShort(RasterAccessor src,
267                                    RasterAccessor dst) {
268         final int dstWidth = dst.getWidth();
269         final int dstHeight = dst.getHeight();
270         final int dstBands = dst.getNumBands();
271 
272         final int dstLineStride = dst.getScanlineStride();
273         final int dstPixelStride = dst.getPixelStride();
274         final int[] dstBandOffsets = dst.getBandOffsets();
275         final short[][] dstData = dst.getShortDataArrays();
276 
277         final int srcLineStride = src.getScanlineStride();
278         final int srcPixelStride = src.getPixelStride();
279         final int[] srcBandOffsets = src.getBandOffsets();
280         final short[][] srcData = src.getShortDataArrays();
281 
282         ExecutorService threadPool = Executors.newFixedThreadPool(numProc);
283         Collection<Callable<Void>> processes = new LinkedList<Callable<Void>>();
284         for (int band = 0; band < dstBands; band++) {
285             final float c = (float)constants[band];
286             final float o = (float)offsets[band];
287             final short[] s = srcData[band];
288             final short[] d = dstData[band];
289 
290             final int dstLineOffset = dstBandOffsets[band];
291             final int srcLineOffset = srcBandOffsets[band];
292 
293             for (int h = 0; h < dstHeight; h++) {
294                 final int hh = h;
295                 processes.add(new Callable<Void>() {
296                     @Override
297                     public Void call() {
298                         int dstPixelOffset = dstLineOffset + hh * dstLineStride;
299                         int srcPixelOffset = srcLineOffset + hh * srcLineStride;
300 
301                         for (int w = 0; w < dstWidth; w++) {
302                             d[dstPixelOffset] = ImageUtil.clampRoundUShort(
303                                     (s[srcPixelOffset] & 0xFFFF) * c + o);
304 
305                             dstPixelOffset += dstPixelStride;
306                             srcPixelOffset += srcPixelStride;
307                         }
308                     return null;
309                     }
310                 });
311             }
312         }
313         try {
314             threadPool.invokeAll(processes);
315         } catch (InterruptedException e) {
316             throw new RuntimeException(e);
317         } finally {
318             threadPool.shutdown();
319         }
320     }
321 
computeRectShort(RasterAccessor src, RasterAccessor dst)322     private void computeRectShort(RasterAccessor src,
323                                   RasterAccessor dst) {
324         final int dstWidth = dst.getWidth();
325         final int dstHeight = dst.getHeight();
326         final int dstBands = dst.getNumBands();
327 
328         final int dstLineStride = dst.getScanlineStride();
329         final int dstPixelStride = dst.getPixelStride();
330         final int[] dstBandOffsets = dst.getBandOffsets();
331         final short[][] dstData = dst.getShortDataArrays();
332 
333         final int srcLineStride = src.getScanlineStride();
334         final int srcPixelStride = src.getPixelStride();
335         final int[] srcBandOffsets = src.getBandOffsets();
336         final short[][] srcData = src.getShortDataArrays();
337 
338         ExecutorService threadPool = Executors.newFixedThreadPool(numProc);
339         Collection<Callable<Void>> processes = new LinkedList<Callable<Void>>();
340         for (int band = 0; band < dstBands; band++) {
341             final float c = (float)constants[band];
342             final float o = (float)offsets[band];
343             final short[] s = srcData[band];
344             final short[] d = dstData[band];
345 
346             final int dstLineOffset = dstBandOffsets[band];
347             final int srcLineOffset = srcBandOffsets[band];
348 
349             for (int h = 0; h < dstHeight; h++) {
350                 final int hh = h;
351                 processes.add(new Callable<Void>() {
352                     @Override
353                     public Void call() {
354                         int dstPixelOffset = dstLineOffset + hh * dstLineStride;
355                         int srcPixelOffset = srcLineOffset + hh * srcLineStride;
356 
357                         for (int w = 0; w < dstWidth; w++) {
358                             d[dstPixelOffset] = ImageUtil.clampRoundShort(s[srcPixelOffset] * c + o);
359 
360                             dstPixelOffset += dstPixelStride;
361                             srcPixelOffset += srcPixelStride;
362                         }
363                     return null;
364                     }
365                 });
366             }
367         }
368         try {
369             threadPool.invokeAll(processes);
370         } catch (InterruptedException e) {
371             throw new RuntimeException(e);
372         } finally {
373             threadPool.shutdown();
374         }
375     }
376 
computeRectInt(RasterAccessor src, RasterAccessor dst)377     private void computeRectInt(RasterAccessor src,
378                                 RasterAccessor dst) {
379         final int dstWidth = dst.getWidth();
380         final int dstHeight = dst.getHeight();
381         final int dstBands = dst.getNumBands();
382 
383         final int dstLineStride = dst.getScanlineStride();
384         final int dstPixelStride = dst.getPixelStride();
385         final int[] dstBandOffsets = dst.getBandOffsets();
386         final int[][] dstData = dst.getIntDataArrays();
387 
388         final int srcLineStride = src.getScanlineStride();
389         final int srcPixelStride = src.getPixelStride();
390         final int[] srcBandOffsets = src.getBandOffsets();
391         final int[][] srcData = src.getIntDataArrays();
392 
393         ExecutorService threadPool = Executors.newFixedThreadPool(numProc);
394         Collection<Callable<Void>> processes = new LinkedList<Callable<Void>>();
395         for (int b = 0; b < dstBands; b++) {
396             final double c = constants[b];
397             final double o = offsets[b];
398             final int[] s = srcData[b];
399             final int[] d = dstData[b];
400 
401             final int dstLineOffset = dstBandOffsets[b];
402             final int srcLineOffset = srcBandOffsets[b];
403 
404             for (int h = 0; h < dstHeight; h++) {
405                 final int hh = h;
406                 processes.add(new Callable<Void>() {
407                     @Override
408                     public Void call() {
409                         int dstPixelOffset = dstLineOffset + hh * dstLineStride;
410                         int srcPixelOffset = srcLineOffset + hh * srcLineStride;
411 
412                         for (int w = 0; w < dstWidth; w++) {
413                             d[dstPixelOffset] = ImageUtil.clampRoundInt(s[srcPixelOffset] * c + o);
414 
415                             dstPixelOffset += dstPixelStride;
416                             srcPixelOffset += srcPixelStride;
417                         }
418                         return null;
419                     }
420                 });
421             }
422         }
423         try {
424             threadPool.invokeAll(processes);
425         } catch (InterruptedException e) {
426             throw new RuntimeException(e);
427         } finally {
428             threadPool.shutdown();
429         }
430     }
431 
computeRectFloat(RasterAccessor src, RasterAccessor dst)432     private void computeRectFloat(RasterAccessor src,
433                                   RasterAccessor dst) {
434         final int dstWidth = dst.getWidth();
435         final int dstHeight = dst.getHeight();
436         final int dstBands = dst.getNumBands();
437 
438         final int dstLineStride = dst.getScanlineStride();
439         final int dstPixelStride = dst.getPixelStride();
440         final int[] dstBandOffsets = dst.getBandOffsets();
441         final float[][] dstData = dst.getFloatDataArrays();
442 
443         final int srcLineStride = src.getScanlineStride();
444         final int srcPixelStride = src.getPixelStride();
445         final int[] srcBandOffsets = src.getBandOffsets();
446         final float[][] srcData = src.getFloatDataArrays();
447 
448         ExecutorService threadPool = Executors.newFixedThreadPool(numProc);
449         Collection<Callable<Void>> processes = new LinkedList<Callable<Void>>();
450         for (int band = 0; band < dstBands; band++) {
451             final double c = constants[band];
452             final double o = offsets[band];
453             final float[] s = srcData[band];
454             final float[] d = dstData[band];
455 
456             final int dstLineOffset = dstBandOffsets[band];
457             final int srcLineOffset = srcBandOffsets[band];
458 
459             for (int h = 0; h < dstHeight; h++) {
460                 final int hh = h;
461                 processes.add(new Callable<Void>() {
462                     @Override
463                     public Void call() {
464                         int dstPixelOffset = dstLineOffset + hh * dstLineStride;
465                         int srcPixelOffset = srcLineOffset + hh * srcLineStride;
466 
467                         for (int w = 0; w < dstWidth; w++) {
468                             d[dstPixelOffset] = ImageUtil.clampFloat(s[srcPixelOffset] * c + o);
469 
470                             dstPixelOffset += dstPixelStride;
471                             srcPixelOffset += srcPixelStride;
472                         }
473                         return null;
474                     }
475                 });
476             }
477         }
478         try {
479             threadPool.invokeAll(processes);
480         } catch (InterruptedException e) {
481             throw new RuntimeException(e);
482         } finally {
483             threadPool.shutdown();
484         }
485     }
486 
computeRectDouble(RasterAccessor src, RasterAccessor dst)487     private void computeRectDouble(RasterAccessor src,
488                                    RasterAccessor dst) {
489         final int dstWidth = dst.getWidth();
490         final int dstHeight = dst.getHeight();
491         final int dstBands = dst.getNumBands();
492 
493         final int dstLineStride = dst.getScanlineStride();
494         final int dstPixelStride = dst.getPixelStride();
495         final int[] dstBandOffsets = dst.getBandOffsets();
496         final double[][] dstData = dst.getDoubleDataArrays();
497 
498         final int srcLineStride = src.getScanlineStride();
499         final int srcPixelStride = src.getPixelStride();
500         final int[] srcBandOffsets = src.getBandOffsets();
501         final double[][] srcData = src.getDoubleDataArrays();
502 
503         ExecutorService threadPool = Executors.newFixedThreadPool(numProc);
504         Collection<Callable<Void>> processes = new LinkedList<Callable<Void>>();
505         for (int band = 0; band < dstBands; band++) {
506             final double c = constants[band];
507             final double o = offsets[band];
508             final double[] s = srcData[band];
509             final double[] d = dstData[band];
510 
511             final int dstLineOffset = dstBandOffsets[band];
512             final int srcLineOffset = srcBandOffsets[band];
513 
514             for (int h = 0; h < dstHeight; h++) {
515                 final int hh = h;
516                 processes.add(new Callable<Void>() {
517                     @Override
518                     public Void call() {
519                         int dstPixelOffset = dstLineOffset + hh * dstLineStride;
520                         int srcPixelOffset = srcLineOffset + hh * srcLineStride;
521 
522                         for (int w = 0; w < dstWidth; w++) {
523                             d[dstPixelOffset] = s[srcPixelOffset] * c + o;
524 
525                             dstPixelOffset += dstPixelStride;
526                             srcPixelOffset += srcPixelStride;
527                         }
528                     return null;
529                     }
530                 });
531             }
532         }
533         try {
534             threadPool.invokeAll(processes);
535         } catch (InterruptedException e) {
536             throw new RuntimeException(e);
537         } finally {
538             threadPool.shutdown();
539         }
540     }
541 }
542