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