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