1 /*
2  * $RCSfile: Convolve3x3OpImage.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:19 $
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 import com.lightcrafts.mediax.jai.AreaOpImage;
19 import com.lightcrafts.mediax.jai.BorderExtender;
20 import com.lightcrafts.mediax.jai.ImageLayout;
21 import com.lightcrafts.mediax.jai.KernelJAI;
22 import com.lightcrafts.mediax.jai.RasterAccessor;
23 import com.lightcrafts.mediax.jai.RasterFormatTag;
24 import java.util.Map;
25 // import com.lightcrafts.media.jai.test.OpImageTester;
26 
27 /**
28  * An OpImage class to perform a 3x3 convolution on a source image.
29  *
30  * <p> This class implements a convolution operation. Convolution is a
31  * spatial operation that computes each output sample by multiplying
32  * elements of a kernel with the samples surrounding a particular
33  * source sample.
34  *
35  * <p> For each destination sample, the kernel is rotated 180 degrees
36  * and its "key element" is placed over the source pixel corresponding
37  * with the destination pixel.  The kernel elements are multiplied
38  * with the source pixels under them, and the resulting products are
39  * summed together to produce the destination sample value.
40  *
41  * <p> Convolution, or any neighborhood operation, leaves a band of
42  * pixels around the edges undefined, i.e., for a 3x3 kernel, only
43  * four kernel elements and four source pixels contribute to the
44  * destination pixel located at (0,0).  Such pixels are not includined
45  * in the destination image.  A BorderOpImage may be used to add an
46  * appropriate border to the source image in order to avoid shrinkage
47  * of the image boundaries.
48  *
49  * <p> The Kernel cannot be bigger in any dimension than the image data.
50  *
51  *
52  * @see KernelJAI
53  */
54 final class Convolve3x3OpImage extends AreaOpImage {
55     /**
56      * The 3x3 kernel with which to do the convolve operation.
57      */
58     protected KernelJAI kernel;
59 
60     float tables[][] = new float[9][256];
61 
62     /**
63      * Creates a Convolve3x3OpImage given a ParameterBlock containing the image
64      * source and a pre-rotated convolution kernel.  The image dimensions
65      * are derived
66      * from the source image.  The tile grid layout, SampleModel, and
67      * ColorModel may optionally be specified by an ImageLayout
68      * object.
69      *
70      * @param source a RenderedImage.
71      * @param extender a BorderExtender, or null.
72      * @param layout an ImageLayout optionally containing the tile grid layout,
73      *        SampleModel, and ColorModel, or null.
74      * @param kernel the pre-rotated convolution KernelJAI.
75      * @param cobbleSources a boolean indicating whether computeRect()
76      *        expects contiguous sources.
77      */
Convolve3x3OpImage(RenderedImage source, BorderExtender extender, Map config, ImageLayout layout, KernelJAI kernel)78     public Convolve3x3OpImage(RenderedImage source,
79                               BorderExtender extender,
80                               Map config,
81                               ImageLayout layout,
82                               KernelJAI kernel) {
83 	super(source,
84               layout,
85               config,
86               true,
87               extender,
88               kernel.getLeftPadding(),
89               kernel.getRightPadding(),
90               kernel.getTopPadding(),
91               kernel.getBottomPadding());
92 
93 	this.kernel = kernel;
94 	if ((kernel.getWidth() != 3) ||
95 	    (kernel.getHeight() != 3) ||
96 	    (kernel.getXOrigin() != 1) ||
97 	    (kernel.getYOrigin() != 1)) {
98             throw new RuntimeException(JaiI18N.getString("Convolve3x3OpImage0"));
99         }
100 
101         if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE) {
102             float kdata[] = kernel.getKernelData();
103             float k0 = kdata[0],
104                   k1 = kdata[1],
105                   k2 = kdata[2],
106                   k3 = kdata[3],
107                   k4 = kdata[4],
108                   k5 = kdata[5],
109                   k6 = kdata[6],
110                   k7 = kdata[7],
111                   k8 = kdata[8];
112 
113             for (int j = 0; j < 256; j++) {
114                 byte b = (byte)j;
115                 float f = (float)j;
116                 tables[0][b+128] = k0*f+0.5f;
117                 tables[1][b+128] = k1*f;
118                 tables[2][b+128] = k2*f;
119                 tables[3][b+128] = k3*f;
120                 tables[4][b+128] = k4*f;
121                 tables[5][b+128] = k5*f;
122                 tables[6][b+128] = k6*f;
123                 tables[7][b+128] = k7*f;
124                 tables[8][b+128] = k8*f;
125             }
126         }
127     }
128 
129     /**
130      * Performs convolution on a specified rectangle. The sources are
131      * cobbled.
132      *
133      * @param sources an array of source Rasters, guaranteed to provide all
134      *                necessary source data for computing the output.
135      * @param dest a WritableRaster tile containing the area to be computed.
136      * @param destRect the rectangle within dest to be processed.
137      */
computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect)138     protected void computeRect(Raster[] sources,
139                                WritableRaster dest,
140                                Rectangle destRect) {
141         // Retrieve format tags.
142         RasterFormatTag[] formatTags = getFormatTags();
143 
144         Raster source = sources[0];
145         Rectangle srcRect = mapDestRect(destRect, 0);
146 
147         RasterAccessor srcAccessor =
148             new RasterAccessor(source,srcRect,
149                                formatTags[0],
150                                getSourceImage(0).getColorModel());
151         RasterAccessor dstAccessor =
152             new RasterAccessor(dest,destRect,
153                                formatTags[1], getColorModel());
154 
155         switch (dstAccessor.getDataType()) {
156         case DataBuffer.TYPE_BYTE:
157             byteLoop(srcAccessor,dstAccessor);
158             break;
159         case DataBuffer.TYPE_SHORT:
160             shortLoop(srcAccessor,dstAccessor);
161             break;
162         case DataBuffer.TYPE_INT:
163             intLoop(srcAccessor,dstAccessor);
164             break;
165         default:
166             String className = this.getClass().getName();
167             throw new RuntimeException(JaiI18N.getString("Convolve3x3OpImage1"));
168         }
169 
170         // If the RasterAccessor object set up a temporary buffer for the
171         // op to write to, tell the RasterAccessor to write that data
172         // to the raster no that we're done with it.
173         if (dstAccessor.isDataCopy()) {
174             dstAccessor.clampDataArrays();
175             dstAccessor.copyDataToRaster();
176         }
177     }
178 
byteLoop(RasterAccessor src, RasterAccessor dst)179     private void byteLoop(RasterAccessor src, RasterAccessor dst) {
180         int dwidth = dst.getWidth();
181         int dheight = dst.getHeight();
182         int dnumBands = dst.getNumBands();
183 
184         // cache these out to avoid an array access per kernel value
185         float t0[] = tables[0],
186               t1[] = tables[1],
187               t2[] = tables[2],
188               t3[] = tables[3],
189               t4[] = tables[4],
190               t5[] = tables[5],
191               t6[] = tables[6],
192               t7[] = tables[7],
193               t8[] = tables[8];
194 
195         float kdata[] = kernel.getKernelData();
196 
197         byte dstDataArrays[][] = dst.getByteDataArrays();
198         int dstBandOffsets[] = dst.getBandOffsets();
199         int dstPixelStride = dst.getPixelStride();
200         int dstScanlineStride = dst.getScanlineStride();
201 
202         byte srcDataArrays[][] = src.getByteDataArrays();
203         int srcBandOffsets[] = src.getBandOffsets();
204         int srcPixelStride = src.getPixelStride();
205         int srcScanlineStride = src.getScanlineStride();
206 
207         // precalcaculate offsets
208         int centerScanlineOffset = srcScanlineStride;
209         int bottomScanlineOffset = srcScanlineStride*2;
210         int middlePixelOffset = dnumBands;
211         int rightPixelOffset = dnumBands*2;
212 
213         for (int k = 0; k < dnumBands; k++)  {
214             byte dstData[] = dstDataArrays[k];
215             byte srcData[] = srcDataArrays[k];
216             int srcScanlineOffset = srcBandOffsets[k];
217             int dstScanlineOffset = dstBandOffsets[k];
218             for (int j = 0; j < dheight; j++)  {
219                 int srcPixelOffset = srcScanlineOffset;
220                 int dstPixelOffset = dstScanlineOffset;
221                 for (int i = 0; i < dwidth; i++)  {
222                     float f =
223                         t0[128+srcData[srcPixelOffset]] +
224                         t1[128+srcData[srcPixelOffset +
225                                        middlePixelOffset]] +
226                         t2[128+srcData[srcPixelOffset +
227                                        rightPixelOffset]] +
228                         t3[128+srcData[srcPixelOffset +
229                                        centerScanlineOffset]]+
230                         t4[128+srcData[srcPixelOffset +
231                                        centerScanlineOffset +
232                                        middlePixelOffset]]+
233                         t5[128+srcData[srcPixelOffset +
234                                        centerScanlineOffset +
235                                        rightPixelOffset]] +
236                         t6[128+srcData[srcPixelOffset +
237                                        bottomScanlineOffset]] +
238                         t7[128+srcData[srcPixelOffset +
239                                        bottomScanlineOffset +
240                                        middlePixelOffset]] +
241                         t8[128+srcData[srcPixelOffset +
242                                        bottomScanlineOffset +
243                                        rightPixelOffset]];
244 
245                     // do the clamp
246                     int val = (int) f;
247                     if (val < 0)  {
248                         val = 0;
249                     } else if (val > 255)  {
250                         val = 255;
251                     }
252                     dstData[dstPixelOffset] = (byte)(val);
253                     srcPixelOffset += srcPixelStride;
254                     dstPixelOffset += dstPixelStride;
255                 }
256                 srcScanlineOffset += srcScanlineStride;
257                 dstScanlineOffset += dstScanlineStride;
258             }
259         }
260     }
261 
shortLoop(RasterAccessor src, RasterAccessor dst)262     private void shortLoop(RasterAccessor src, RasterAccessor dst) {
263         int dwidth = dst.getWidth();
264         int dheight = dst.getHeight();
265         int dnumBands = dst.getNumBands();
266 
267         short dstDataArrays[][] = dst.getShortDataArrays();
268         int dstBandOffsets[] = dst.getBandOffsets();
269         int dstPixelStride = dst.getPixelStride();
270         int dstScanlineStride = dst.getScanlineStride();
271 
272         short srcDataArrays[][] = src.getShortDataArrays();
273         int srcBandOffsets[] = src.getBandOffsets();
274         int srcPixelStride = src.getPixelStride();
275         int srcScanlineStride = src.getScanlineStride();
276 
277         // precalcaculate offsets
278         int centerScanlineOffset = srcScanlineStride;
279         int bottomScanlineOffset = srcScanlineStride*2;
280         int middlePixelOffset = dnumBands;
281         int rightPixelOffset = dnumBands*2;
282 
283         float kdata[] = kernel.getKernelData();
284         float k0 = kdata[0],
285               k1 = kdata[1],
286               k2 = kdata[2],
287               k3 = kdata[3],
288               k4 = kdata[4],
289               k5 = kdata[5],
290               k6 = kdata[6],
291               k7 = kdata[7],
292               k8 = kdata[8];
293 
294         for (int k = 0; k < dnumBands; k++)  {
295             short dstData[] = dstDataArrays[k];
296             short srcData[] = srcDataArrays[k];
297             int srcScanlineOffset = srcBandOffsets[k];
298             int dstScanlineOffset = dstBandOffsets[k];
299             for (int j = 0; j < dheight; j++)  {
300                 int srcPixelOffset = srcScanlineOffset;
301                 int dstPixelOffset = dstScanlineOffset;
302                 for (int i = 0; i < dwidth; i++)  {
303                     float f =
304                         k0*srcData[srcPixelOffset] +
305                         k1*srcData[srcPixelOffset +
306                                    middlePixelOffset] +
307                         k2*srcData[srcPixelOffset +
308                                    rightPixelOffset] +
309                         k3*srcData[srcPixelOffset +
310                                    centerScanlineOffset] +
311                         k4*srcData[srcPixelOffset +
312                                    centerScanlineOffset +
313                                    middlePixelOffset] +
314                         k5*srcData[srcPixelOffset +
315                                    centerScanlineOffset +
316                                    rightPixelOffset] +
317                         k6*srcData[srcPixelOffset +
318                                    bottomScanlineOffset] +
319                         k7*srcData[srcPixelOffset +
320                                    bottomScanlineOffset +
321                                    middlePixelOffset] +
322                         k8*srcData[srcPixelOffset +
323                                    bottomScanlineOffset +
324                                    rightPixelOffset];
325 
326                     int val = (int)f;
327                     if (val < Short.MIN_VALUE) {
328                        val = Short.MIN_VALUE;
329                     } else if (val > Short.MAX_VALUE) {
330                        val = Short.MAX_VALUE;
331                     }
332                     dstData[dstPixelOffset] = (short)val;
333                     srcPixelOffset += srcPixelStride;
334                     dstPixelOffset += dstPixelStride;
335                 }
336                 srcScanlineOffset += srcScanlineStride;
337                 dstScanlineOffset += dstScanlineStride;
338             }
339         }
340     }
341 
intLoop(RasterAccessor src, RasterAccessor dst)342     private void intLoop(RasterAccessor src, RasterAccessor dst) {
343         int dwidth = dst.getWidth();
344         int dheight = dst.getHeight();
345         int dnumBands = dst.getNumBands();
346 
347         int dstDataArrays[][] = dst.getIntDataArrays();
348         int dstBandOffsets[] = dst.getBandOffsets();
349         int dstPixelStride = dst.getPixelStride();
350         int dstScanlineStride = dst.getScanlineStride();
351 
352         int srcDataArrays[][] = src.getIntDataArrays();
353         int srcBandOffsets[] = src.getBandOffsets();
354         int srcPixelStride = src.getPixelStride();
355         int srcScanlineStride = src.getScanlineStride();
356 
357         // precalcaculate offsets
358         int centerScanlineOffset = srcScanlineStride;
359         int bottomScanlineOffset = srcScanlineStride*2;
360         int middlePixelOffset = dnumBands;
361         int rightPixelOffset = dnumBands*2;
362 
363         float kdata[] = kernel.getKernelData();
364         float k0 = kdata[0],
365               k1 = kdata[1],
366               k2 = kdata[2],
367               k3 = kdata[3],
368               k4 = kdata[4],
369               k5 = kdata[5],
370               k6 = kdata[6],
371               k7 = kdata[7],
372               k8 = kdata[8];
373 
374         for (int k = 0; k < dnumBands; k++)  {
375             int dstData[] = dstDataArrays[k];
376             int srcData[] = srcDataArrays[k];
377             int srcScanlineOffset = srcBandOffsets[k];
378             int dstScanlineOffset = dstBandOffsets[k];
379             for (int j = 0; j < dheight; j++)  {
380                 int srcPixelOffset = srcScanlineOffset;
381                 int dstPixelOffset = dstScanlineOffset;
382                 for (int i = 0; i < dwidth; i++)  {
383                     float f =
384                         k0*srcData[srcPixelOffset] +
385                         k1*srcData[srcPixelOffset +
386                                    middlePixelOffset] +
387                         k2*srcData[srcPixelOffset +
388                                    rightPixelOffset] +
389                         k3*srcData[srcPixelOffset +
390                                    centerScanlineOffset] +
391                         k4*srcData[srcPixelOffset +
392                                    centerScanlineOffset +
393                                    middlePixelOffset] +
394                         k5*srcData[srcPixelOffset +
395                                    centerScanlineOffset +
396                                    rightPixelOffset] +
397                         k6*srcData[srcPixelOffset +
398                                    bottomScanlineOffset] +
399                         k7*srcData[srcPixelOffset +
400                                    bottomScanlineOffset +
401                                    middlePixelOffset] +
402                         k8*srcData[srcPixelOffset +
403                                    bottomScanlineOffset +
404                                    rightPixelOffset];
405 
406                     dstData[dstPixelOffset] = (int)f;
407                     srcPixelOffset += srcPixelStride;
408                     dstPixelOffset += dstPixelStride;
409                 }
410                 srcScanlineOffset += srcScanlineStride;
411                 dstScanlineOffset += dstScanlineStride;
412             }
413         }
414     }
415 
416 //     public static OpImage createTestImage(OpImageTester oit) {
417 //         float data[] = {0.05f,0.10f,0.05f,
418 //                         0.10f,0.40f,0.10f,
419 //                         0.05f,0.10f,0.05f};
420 //         KernelJAI kJAI = new KernelJAI(3,3,1,1,data);
421 //         return new Convolve3x3OpImage(oit.getSource(), null, null,
422 //                                       new ImageLayout(oit.getSource()),
423 //                                       kJAI);
424 //     }
425 
426 //     // Calls a method on OpImage that uses introspection, to make this
427 //     // class, discover it's createTestImage() call, call it and then
428 //     // benchmark the performance of the created OpImage chain.
429 //     public static void main(String args[]) {
430 //         String classname = "com.lightcrafts.media.jai.opimage.Convolve3x3OpImage";
431 //         OpImageTester.performDiagnostics(classname,args);
432 //     }
433 }
434