1 /* 2 * $RCSfile: KernelJAI.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:57:11 $ 10 * $State: Exp $ 11 */ 12 package com.lightcrafts.mediax.jai; 13 import java.awt.image.Kernel; 14 import java.io.Serializable; 15 import com.lightcrafts.mediax.jai.JaiI18N; 16 17 /** 18 * A kernel representing a matrix with a key position, 19 * used by operators such as <code> Convolve </code>. 20 * 21 * <p> A <code>KernelJAI</code> is characterized by its width, height, and 22 * origin, or key element. The key element is the element which is placed 23 * over the current source pixel to perform convolution or error diffusion. 24 * 25 * <p>A kernel K is separable it the outer product of two one-dimensional 26 * vectors. It can speed up computation. One can construct a kernel 27 * from two one-dimensional vectors. 28 * 29 * <>The symmetry can be useful (such as computation speedup). Currently 30 * the protected instance variables isHorizonallySymmetric 31 * and isVerticallySymmetric are set to false. 32 * 33 * 34 * @see com.lightcrafts.mediax.jai.operator.ConvolveDescriptor 35 * @see com.lightcrafts.mediax.jai.operator.OrderedDitherDescriptor 36 * @see com.lightcrafts.mediax.jai.operator.ErrorDiffusionDescriptor 37 */ 38 public class KernelJAI extends Object implements Serializable { 39 40 /** 41 * Floyd and Steinberg error filter (1975). 42 * <pre> 43 * (1/16 x) [ * 7 ] 44 * [ 3 5 1 ] 45 * </pre> 46 */ 47 public static final KernelJAI ERROR_FILTER_FLOYD_STEINBERG = 48 new KernelJAI(3, 2, 1, 0, 49 new float[] {0.0F/16.0F, 0.0F/16.0F, 7.0F/16.0F, 50 3.0F/16.0F, 5.0F/16.0F, 1.0F/16.0F}); 51 52 /** 53 * Jarvis, Judice, and Ninke error filter (1976). 54 * <pre> 55 * [ * 7 5 ] 56 * (1/48 x) [ 3 5 7 5 3 ] 57 * [ 1 3 5 3 1 ] 58 * </pre> 59 */ 60 public static final KernelJAI ERROR_FILTER_JARVIS = 61 new KernelJAI(5, 3, 2, 0, 62 new float[] {0.0F, 0.0F, 0.0F, 7.0F/48.0F, 5.0F/48.0F, 63 3.0F/48.0F, 5.0F/48.0F, 7.0F/48.0F, 5.0F/48.0F, 3.0F/48.0F, 64 1.0F/48.0F, 3.0F/48.0F, 5.0F/48.0F, 3.0F/48.0F, 1.0F/48.0F}); 65 66 /** 67 * Stucki error filter (1981). 68 * <pre> 69 * [ * 7 5 ] 70 * (1/42 x) [ 2 4 8 4 2 ] 71 * [ 1 2 4 2 1 ] 72 * </pre> 73 */ 74 public static final KernelJAI ERROR_FILTER_STUCKI = 75 new KernelJAI(5, 3, 2, 0, 76 new float[] {0.0F, 0.0F, 0.0F, 7.0F/42.0F, 5.0F/42.0F, 77 2.0F/42.0F, 4.0F/42.0F, 8.0F/42.0F, 4.0F/42.0F, 2.0F/42.0F, 78 1.0F/42.0F, 2.0F/42.0F, 4.0F/42.0F, 2.0F/42.0F, 1.0F/42.0F}); 79 80 /** 81 * 4x4x1 mask useful for dithering 8-bit grayscale images to 1-bit images. 82 */ 83 public static final KernelJAI[] DITHER_MASK_441 = new KernelJAI[] { 84 new KernelJAI(4, 4, 1, 1, 85 new float[] {0.9375F, 0.4375F, 0.8125F, 0.3125F, 86 0.1875F, 0.6875F, 0.0625F, 0.5625F, 87 0.7500F, 0.2500F, 0.8750F, 0.3750F, 88 0.0000F, 0.5000F, 0.1250F, 0.6250F}) 89 }; 90 91 /** 92 * 4x4x3 mask useful for dithering 24-bit color images to 8-bit 93 * pseudocolor images. 94 */ 95 public static final KernelJAI[] DITHER_MASK_443 = new KernelJAI[] { 96 new KernelJAI(4, 4, 1, 1, 97 new float[] {0.0000F, 0.5000F, 0.1250F, 0.6250F, 98 0.7500F, 0.2500F, 0.8750F, 0.3750F, 99 0.1875F, 0.6875F, 0.0625F, 0.5625F, 100 0.9375F, 0.4375F, 0.8125F, 0.3125F}), 101 new KernelJAI(4, 4, 1, 1, 102 new float[] {0.6250F, 0.1250F, 0.5000F, 0.0000F, 103 0.3750F, 0.8750F, 0.2500F, 0.7500F, 104 0.5625F, 0.0625F, 0.6875F, 0.1875F, 105 0.3125F, 0.8125F, 0.4375F, 0.9375F}), 106 new KernelJAI(4, 4, 1, 1, 107 new float[] {0.9375F, 0.4375F, 0.8125F, 0.3125F, 108 0.1875F, 0.6875F, 0.0625F, 0.5625F, 109 0.7500F, 0.2500F, 0.8750F, 0.3750F, 110 0.0000F, 0.5000F, 0.1250F, 0.6250F}) 111 }; 112 113 /** 114 * Gradient Mask for SOBEL_VERTICAL. 115 */ 116 public static final KernelJAI GRADIENT_MASK_SOBEL_VERTICAL = 117 new KernelJAI(3, 3, 1, 1, 118 new float[] {-1, -2 , -1, 119 0, 0, 0, 120 1, 2, 1}); 121 122 /** 123 * Gradient Mask for SOBEL_HORIZONTAL. 124 */ 125 public static final KernelJAI GRADIENT_MASK_SOBEL_HORIZONTAL = 126 new KernelJAI(3, 3, 1, 1, 127 new float[] {-1, 0, 1, 128 -2, 0, 2, 129 -1, 0, 1}); 130 131 /** The width of the kernel. */ 132 protected int width; 133 134 /** The height of the kernel. */ 135 protected int height; 136 137 /** The X coordinate of the key element. */ 138 protected int xOrigin; 139 140 /** The Y coordinate of the key element. */ 141 protected int yOrigin; 142 143 /** The kernel data in row-major format. */ 144 protected float[] data = null; 145 146 /** The horizontal data for a separable kernel */ 147 protected float[] dataH = null; 148 149 /** The vertical data for a separable kernel */ 150 protected float[] dataV = null; 151 152 /** True if the kernel is separable. */ 153 protected boolean isSeparable = false; 154 155 /** True if the kernel has horizontal (Y axis) symmetry. */ 156 protected boolean isHorizontallySymmetric = false; 157 158 /** True if the kernel has vertical (X axis) symmetry. */ 159 protected boolean isVerticallySymmetric = false; 160 161 /** Variable to cache a copy of the rotated kernel */ 162 protected KernelJAI rotatedKernel = null; 163 164 checkSeparable()165 private synchronized void checkSeparable() { 166 // Define a local constant for single precision floating 167 // point tolerance. 168 float floatZeroTol = (float)1.0E-5; 169 170 if (isSeparable) { return; } // already separable 171 if (width <= 1 || height <= 1) { return; } 172 // 1D kernel is non-separable unless constructed to explicitly so 173 // (either dataH or dataV will be a 1x1. 174 175 // else: 176 // Check to see if given kernel can be factored into separable kernels 177 // previous approach: if data[0]==0, then not separable; 178 // new approach: find the largest element (and its row number) first then 179 // check to see if rows are multiples of that row 180 // Normalize is also important: separable kernel implimentation has 181 // hash table look ups... and expecting things in range 182 183 float maxData = 0.0F; 184 int imax = 0, jmax = 0; 185 186 for (int k=0; k < this.data.length; k++){ 187 float tmp = Math.abs(this.data[k]); 188 if (tmp > maxData){ 189 imax = k; 190 maxData = tmp; 191 } 192 } 193 194 195 // check for 0 kernel 196 // a case that should not happen in meaningful convolution 197 if (maxData < floatZeroTol/(float)data.length){ 198 isSeparable = false; 199 return; 200 } 201 202 float tmpRow[] = new float[width]; 203 float fac = 1.0F / data[imax]; 204 205 // position of the max data element in the kernel matrix 206 jmax = imax%width; 207 imax = imax/width; 208 209 210 for(int j = 0; j < width; j++){ 211 tmpRow[j] = data[imax*width + j] * fac; 212 } 213 214 // 215 // Rank 1 checking: every row should be a multiple of tmpRow 216 // if separable (a rank one kernel matrix) 217 for(int i = 0, i0 = 0; i < height; i++, i0 += width) { 218 for(int j = 0; j < width; j++ ) { 219 float tmp = Math.abs(data[i0+jmax]*tmpRow[j]-data[i0+j]); 220 if (tmp > floatZeroTol) { 221 isSeparable = false; 222 return; 223 } 224 } 225 } 226 227 228 dataH = tmpRow; 229 dataV = new float[height]; 230 for (int i = 0; i < height; i++) { 231 dataV[i] = data[jmax + i * width]; 232 } 233 isSeparable = true; 234 235 // normalizing - so that dataH and dataV add up to 1 236 // in some cases, it may not be possible for both if 237 // the original kernel does not add up to 1. 238 // Row adds up to 1 as 1st choice. 239 // If both dataH and dataV add up small, 240 // no normalization is done. 241 // NOTE: non-positive kernels, normalization may be skipped 242 float sumH = 0.0F, sumV =0.0F; 243 for (int j = 0; j < width; j++) { sumH += dataH[j]; } 244 for (int j = 0; j < height; j++) { sumV += dataV[j]; } 245 246 if (Math.abs(sumH)>= Math.abs(sumV) && Math.abs(sumH) > floatZeroTol){ 247 fac = 1.0F/sumH; 248 for (int j = 0; j < width; j++) { dataH[j] *= fac; } 249 for (int j = 0; j < height; j++) { dataV[j] *= sumH; } 250 }else if (Math.abs(sumH)< Math.abs(sumV) && 251 Math.abs(sumV) > floatZeroTol){ 252 fac = 1.0F/sumV; 253 for (int j = 0; j < width; j++) { dataH[j] *= sumV; } 254 for (int j = 0; j < height; j++) { dataV[j] *= fac; } 255 } 256 } 257 258 259 classifyKernel()260 private void classifyKernel() { 261 if (isSeparable == false) { 262 checkSeparable(); 263 } 264 isHorizontallySymmetric = false; 265 isVerticallySymmetric = false; 266 } 267 268 /** 269 * Constructs a KernelJAI with the given parameters. The data 270 * array is copied. 271 * 272 * @param width the width of the kernel. 273 * @param height the height of the kernel. 274 * @param xOrigin the X coordinate of the key kernel element. 275 * @param yOrigin the Y coordinate of the key kernel element. 276 * @param data the float data in row-major format. 277 * 278 * @throws IllegalArgumentException if data is null. 279 * @throws IllegalArgumentException if width is not a positive number. 280 * @throws IllegalArgumentException if height is not a positive number. 281 * @throws IllegalArgumentException if kernel data array does not have 282 * width * height number of elements. 283 * @classifies as non-separable if width or height is 1. 284 */ KernelJAI(int width, int height, int xOrigin, int yOrigin, float[] data)285 public KernelJAI(int width, 286 int height, 287 int xOrigin, 288 int yOrigin, 289 float[] data) { 290 291 if ( data == null ) { 292 throw new IllegalArgumentException(JaiI18N.getString("Generic0")); 293 } 294 295 this.width = width; 296 this.height = height; 297 this.xOrigin = xOrigin; 298 this.yOrigin = yOrigin; 299 this.data = (float[])data.clone(); 300 if (width <= 0) { 301 throw new IllegalArgumentException(JaiI18N.getString("KernelJAI0")); 302 } 303 if (height <= 0) { 304 throw new IllegalArgumentException(JaiI18N.getString("KernelJAI1")); 305 } 306 if (width*height != data.length) { 307 throw new IllegalArgumentException(JaiI18N.getString("KernelJAI2")); 308 } 309 classifyKernel(); 310 } 311 312 /** 313 * Constructs a separable KernelJAI from two float arrays. 314 * The data arrays are copied. 315 * 316 * A Separable kernel K = dataH * dataV^T, the outer product of two 317 * one dimensional vectors dataH and dataV. It can often speed up 318 * compution. 319 * 320 * @param width the width of the kernel. 321 * @param height the height of the kernel. 322 * @param xOrigin the X coordinate of the key kernel element. 323 * @param yOrigin the Y coordinate of the key kernel element. 324 * @param dataH the float data for the horizontal direction. 325 * @param dataV the float data for the vertical direction. 326 * 327 * @throws IllegalArgumentException if dataH is null. 328 * @throws IllegalArgumentException if dataV is null. 329 * @throws IllegalArgumentException if width is not a positive number. 330 * @throws IllegalArgumentException if height is not a positive number. 331 * @throws IllegalArgumentException if dataH does not have width elements. 332 * @throws IllegalArgumentException if dataV does not have height elements. 333 * @must use the other constructor when dataH or dataV is null 334 */ KernelJAI(int width, int height, int xOrigin, int yOrigin, float[] dataH, float[] dataV)335 public KernelJAI(int width, 336 int height, 337 int xOrigin, 338 int yOrigin, 339 float[] dataH, 340 float[] dataV) { 341 342 if ( dataH == null || dataV == null ) { 343 throw new IllegalArgumentException(JaiI18N.getString("Generic0")); 344 } 345 346 if (width <= 0) { 347 throw new IllegalArgumentException(JaiI18N.getString("KernelJAI0")); 348 } 349 350 if (height <= 0) { 351 throw new IllegalArgumentException(JaiI18N.getString("KernelJAI1")); 352 } 353 354 if (width != dataH.length) { 355 throw new IllegalArgumentException(JaiI18N.getString("KernelJAI3")); 356 } 357 358 if (height != dataV.length) { 359 throw new IllegalArgumentException(JaiI18N.getString("KernelJAI4")); 360 } 361 362 this.width = width; 363 this.height = height; 364 this.xOrigin = xOrigin; 365 this.yOrigin = yOrigin; 366 this.dataH = (float[])dataH.clone(); 367 this.dataV = (float[])dataV.clone(); 368 this.data = new float[dataH.length*dataV.length]; 369 370 int rowOffset = 0; 371 for (int i = 0; i < dataV.length; i++) { 372 float vValue = dataV[i]; 373 for (int j = 0; j < dataH.length; j++) { 374 data[rowOffset+j] = vValue*dataH[j]; 375 } 376 rowOffset += dataH.length; 377 } 378 isSeparable = true; 379 classifyKernel(); 380 } 381 382 /** 383 * Constructs a kernel with the given parameters. The data 384 * array is copied. The key element is set to 385 * (trunc(width/2), trunc(height/2)). 386 * 387 * @param width the width of the kernel. 388 * @param height the height of the kernel. 389 * @param data the float data in row-major format. 390 * 391 * @throws IllegalArgumentException if data is null. 392 * @throws IllegalArgumentException if width is not a positive number. 393 * @throws IllegalArgumentException if height is not a positive number. 394 * @throws IllegalArgumentException if data does not have 395 * width * height number of elements. 396 */ KernelJAI(int width, int height, float[] data)397 public KernelJAI(int width, int height, float[] data) { 398 this(width, height, width/2, height/2, data); 399 } 400 401 /** 402 * Constructs a KernelJAI from a java.awt.image.Kernel 403 * object. 404 * 405 * @throws NullPointerException if k is null. 406 */ KernelJAI(Kernel k)407 public KernelJAI(Kernel k) { 408 // XXX - NullPointerException (inconsistent style) 409 this(k.getWidth(), k.getHeight(), 410 k.getXOrigin(), k.getYOrigin(), k.getKernelData(null)); 411 } 412 413 /** Returns the width of the kernel. */ getWidth()414 public int getWidth() { 415 return width; 416 } 417 418 /** Returns the height of the kernel. */ getHeight()419 public int getHeight() { 420 return height; 421 } 422 423 /** Returns the X coordinate of the key kernel element. */ getXOrigin()424 public int getXOrigin() { 425 return xOrigin; 426 } 427 428 /** Returns the Y coordinate of the key kernel element. */ getYOrigin()429 public int getYOrigin() { 430 return yOrigin; 431 } 432 433 /** Returns a copy of the kernel data in row-major format. */ getKernelData()434 public float[] getKernelData() { 435 return (float[])data.clone(); 436 } 437 438 /** 439 * Returns the horizontal portion of the kernel if the 440 * kernel is separable, or <code>null</code> otherwise. The kernel may 441 * be tested for separability by calling <code>isSeparable()</code>. 442 */ getHorizontalKernelData()443 public float[] getHorizontalKernelData() { 444 if (dataH == null) { 445 return null; 446 } 447 return (float[])dataH.clone(); 448 } 449 450 /** 451 * Returns the vertical portion of the kernel if the 452 * kernel is separable, or <code>null</code> otherwise. The kernel may 453 * be tested for separability by calling <code>isSeparable()</code>. 454 */ getVerticalKernelData()455 public float[] getVerticalKernelData() { 456 if (dataV == null) { 457 return null; 458 } 459 return (float[])dataV.clone(); 460 } 461 462 /** 463 * Returns a given element of the kernel. 464 * 465 * @throws ArrayIndexOutOfBoundsException if either xIndex or yIndex is 466 * an invalid index. 467 */ getElement(int xIndex, int yIndex)468 public float getElement(int xIndex, int yIndex) { 469 if (!isSeparable) { 470 return data[yIndex*width + xIndex]; 471 } else { 472 return dataH[xIndex]*dataV[yIndex]; 473 } 474 } 475 476 /** 477 * Returns true if the kernel is separable. 478 */ isSeparable()479 public boolean isSeparable() { 480 return isSeparable; 481 } 482 483 /** Returns true if the kernel has horizontal (Y axis) symmetry. */ isHorizontallySymmetric()484 public boolean isHorizontallySymmetric() { 485 return isHorizontallySymmetric; 486 } 487 488 /** Returns true if the kernel has vertical (X axis) symmetry. */ isVerticallySymmetric()489 public boolean isVerticallySymmetric() { 490 return isVerticallySymmetric; 491 } 492 493 /** 494 * Returns the number of pixels required to the left of the key element. 495 */ getLeftPadding()496 public int getLeftPadding() { 497 return xOrigin; 498 } 499 500 /** 501 * Returns the number of pixels required to the right of the key element. 502 */ getRightPadding()503 public int getRightPadding() { 504 return width - xOrigin - 1; 505 } 506 507 /** 508 * Returns the number of pixels required above the key element. 509 */ getTopPadding()510 public int getTopPadding() { 511 return yOrigin; 512 } 513 514 /** 515 * Returns the number of pixels required below the key element. 516 */ getBottomPadding()517 public int getBottomPadding() { 518 return height - yOrigin - 1; 519 } 520 521 /** 522 * Returns a 180 degree rotated version of the kernel. This is 523 * needed by most convolve operations to get the correct results. 524 * 525 * @return the rotated kernel. 526 */ getRotatedKernel()527 public KernelJAI getRotatedKernel() { 528 if (rotatedKernel == null) { 529 if ( this.isSeparable){ 530 float rotDataH[] = new float[this.width]; 531 float rotDataV[] = new float[this.height]; 532 for (int i = 0; i < this.width; i++) { 533 rotDataH[i] = this.dataH[width-1-i]; 534 } 535 for (int i = 0; i < this.height; i++) { 536 rotDataV[i] = this.dataV[height-1-i]; 537 } 538 rotatedKernel = 539 new KernelJAI(width, 540 height, 541 width-1-xOrigin, 542 height-1-yOrigin, 543 rotDataH, 544 rotDataV); 545 }else{ 546 int length = data.length; 547 float newData[] = new float[data.length]; 548 for (int i = 0; i < length; i++) { 549 newData[i] = data[length-1-i]; 550 } 551 rotatedKernel = 552 new KernelJAI(width, 553 height, 554 width-1-xOrigin, 555 height-1-yOrigin, 556 newData); 557 } 558 } 559 return rotatedKernel; 560 } 561 } 562