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