001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2003, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.gui.swing.image;
018:
019: // J2SE dependencies
020: import javax.swing.JLabel;
021: import javax.swing.JComponent;
022: import javax.swing.BorderFactory;
023: import javax.swing.border.Border;
024: import java.awt.GridBagConstraints;
025: import java.awt.GridBagLayout;
026: import java.awt.Component;
027: import java.awt.Color;
028:
029: // JAI dependencies
030: import javax.media.jai.KernelJAI;
031: import javax.media.jai.operator.GradientMagnitudeDescriptor; // For Javadoc
032:
033: // Seagis dependencies
034: import org.geotools.resources.Utilities;
035: import org.geotools.resources.SwingUtilities;
036: import org.geotools.resources.i18n.Vocabulary;
037: import org.geotools.resources.i18n.VocabularyKeys;
038:
039: /**
040: * A widget for editing the horizontal and vertical kernels for a
041: * {@linkplain GradientMagnitudeDescriptor gradient magnitude} operation.
042: * This widget combine two {@link KernelEditor} side-by-side: one for the
043: * horizontal component and one for the vertical component.
044: *
045: * <p> </p>
046: * <p align="center"><img src="doc-files/GradientKernelEditor.png"></p>
047: * <p> </p>
048: *
049: * @since 2.3
050: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/image/GradientKernelEditor.java $
051: * @version $Id: GradientKernelEditor.java 20883 2006-08-07 13:48:09Z jgarnett $
052: * @author Martin Desruisseaux
053: *
054: * @see KernelEditor
055: * @see GradientMagnitudeDescriptor
056: * @see org.geotools.coverage.processing.operation.GradientMagnitude
057: */
058: public class GradientKernelEditor extends JComponent {
059: /**
060: * Square root of 2.
061: */
062: private static final float SQRT2 = 1.4142135623730950488016887242097f;
063:
064: /**
065: * Horizontal gradient mask according Prewitt (also know as smoothed).
066: */
067: public static final KernelJAI PREWITT_HORIZONTAL = new KernelJAI(3,
068: 3, new float[] { -1, 0, 1, -1, 0, 1, -1, 0, 1, });
069:
070: /**
071: * Vertical gradient mask according Prewitt (also know as smoothed).
072: */
073: public static final KernelJAI PREWITT_VERTICAL = new KernelJAI(3,
074: 3, new float[] { -1, -1, -1, 0, 0, 0, 1, 1, 1, });
075:
076: /**
077: * Horizontal gradient mask (isotropic).
078: */
079: public static final KernelJAI ISOTROPIC_HORIZONTAL = new KernelJAI(
080: 3, 3, new float[] { -1, 0, 1, -SQRT2, 0, SQRT2, -1, 0, 1, });
081:
082: /**
083: * Vertical gradient mask (isotropic).
084: */
085: public static final KernelJAI ISOTROPIC_VERTICAL = new KernelJAI(3,
086: 3, new float[] { -1, -SQRT2, -1, 0, 0, 0, 1, SQRT2, 1, });
087:
088: /**
089: * Horizontal gradient mask according Sobel.
090: */
091: public static final KernelJAI SOBEL_HORIZONTAL = KernelJAI.GRADIENT_MASK_SOBEL_HORIZONTAL;
092:
093: /**
094: * Vertical gradient mask according Sobel.
095: */
096: public static final KernelJAI SOBEL_VERTICAL = KernelJAI.GRADIENT_MASK_SOBEL_VERTICAL;
097: /*
098: * NOTE: Sobel masks were interchanged in KernelJAI prior and up to JAI 1.1.2-rc.
099: * See for example J.J. Simpson (1990) in Remote sensing environment, 33:17-33.
100: * This bug was fixed in JAI 1.1.2 final.
101: */
102:
103: /**
104: * Horizontal gradient mask according Kirsch.
105: */
106: public static final KernelJAI KIRSCH_HORIZONTAL = new KernelJAI(3,
107: 3, new float[] { -3, -3, 5, -3, 0, 5, -3, -3, 5, });
108:
109: /**
110: * Vertical gradient mask according Kirsch.
111: *
112: * @todo Why positives numbers are on the first row? This is the opposite
113: * of other vertical gradient masks. Need to verify in J.J. Simpson (1990).
114: */
115: public static final KernelJAI KIRSCH_VERTICAL = new KernelJAI(3, 3,
116: new float[] { 5, 5, 5, -3, 0, -3, -3, -3, -3, });
117:
118: /**
119: * The horizontal kernel editor.
120: */
121: private final KernelEditor kernelH = new Editor(true);
122:
123: /**
124: * The vertical kernel editor.
125: */
126: private final KernelEditor kernelV = new Editor(false);
127:
128: /**
129: * Constructs a new editor for gradient kernels.
130: */
131: public GradientKernelEditor() {
132: setLayout(new GridBagLayout());
133: final Vocabulary resources = Vocabulary
134: .getResources(getDefaultLocale());
135: final Border border = BorderFactory.createCompoundBorder(
136: BorderFactory.createLoweredBevelBorder(), BorderFactory
137: .createEmptyBorder(3, 3, 3, 3));
138: kernelH.setBorder(border);
139: kernelV.setBorder(border);
140: final JLabel labelH, labelV;
141: labelH = new JLabel(resources
142: .getString(VocabularyKeys.HORIZONTAL_COMPONENT),
143: JLabel.CENTER);
144: labelV = new JLabel(resources
145: .getString(VocabularyKeys.VERTICAL_COMPONENT),
146: JLabel.CENTER);
147: final GridBagConstraints c = new GridBagConstraints();
148:
149: c.insets.top = 6;
150: c.gridy = 0;
151: c.weightx = 1;
152: c.gridwidth = 1;
153: c.gridheight = 1;
154: c.fill = c.BOTH;
155: c.gridx = 0;
156: add(labelH, c);
157: c.gridx = 1;
158: add(labelV, c);
159: c.gridy = 1;
160: c.weighty = 1;
161: c.insets.bottom = 6;
162: c.gridx = 0;
163: c.insets.left = 6;
164: c.insets.right = 3;
165: add(kernelH, c);
166: c.gridx = 1;
167: c.insets.left = 3;
168: c.insets.right = 6;
169: add(kernelV, c);
170: }
171:
172: /**
173: * Adds a set of predefined kernels. This convenience method invokes {@link
174: * KernelEditor#addDefaultKernels()} on both {@linkplain #getHorizontalEditor
175: * horizontal} and {@linkplain #getVerticalEditor vertical} kernel editors.
176: * The default implementation for those editors will add a set of Sobel kernels.
177: */
178: public void addDefaultKernels() {
179: kernelH.addDefaultKernels();
180: kernelV.addDefaultKernels();
181: }
182:
183: /**
184: * Returns the horizontal kernel editor.
185: */
186: public KernelEditor getHorizontalEditor() {
187: return kernelH;
188: }
189:
190: /**
191: * Returns the vertical kernel editor.
192: */
193: public KernelEditor getVerticalEditor() {
194: return kernelV;
195: }
196:
197: /**
198: * A kernel editor for horizontal or vertical gradient kernel.
199: *
200: * @version $Id: GradientKernelEditor.java 20883 2006-08-07 13:48:09Z jgarnett $
201: * @author Martin Desruisseaux
202: */
203: private static final class Editor extends KernelEditor {
204: /**
205: * {@code true} if this editor is for the horizontal component,
206: * or {@code false} for the vertical component.
207: */
208: private final boolean horizontal;
209:
210: /**
211: * Construct a new kernel editor for the specified component.
212: */
213: public Editor(final boolean horizontal) {
214: super ();
215: this .horizontal = horizontal;
216: }
217:
218: /**
219: * Add a set of predefined kernels.
220: */
221: public void addDefaultKernels() {
222: final String GRADIENT_MASKS = getResources().getString(
223: VocabularyKeys.GRADIENT_MASKS);
224: final KernelJAI prewitt, isotropic, kirsch, sobel;
225: if (horizontal) {
226: prewitt = PREWITT_HORIZONTAL;
227: isotropic = ISOTROPIC_HORIZONTAL;
228: kirsch = KIRSCH_HORIZONTAL;
229: sobel = SOBEL_HORIZONTAL;
230: } else {
231: prewitt = PREWITT_VERTICAL;
232: isotropic = ISOTROPIC_VERTICAL;
233: kirsch = KIRSCH_VERTICAL;
234: sobel = SOBEL_VERTICAL;
235: }
236: addKernel(GRADIENT_MASKS, "Prewitt", prewitt);
237: addKernel(GRADIENT_MASKS, "Isotropic", isotropic);
238: addKernel(GRADIENT_MASKS, "Kirsch", kirsch);
239: addKernel(GRADIENT_MASKS, "Sobel 3\u00D73", sobel);
240: final StringBuffer buffer = new StringBuffer("Sobel ");
241: final int base = buffer.length();
242: for (int i = 5; i <= 15; i += 2) {
243: buffer.setLength(base);
244: buffer.append(i).append('\u00D7').append(i);
245: addKernel(GRADIENT_MASKS, buffer.toString(), getSobel(
246: i, horizontal));
247: }
248: setKernel(sobel);
249: }
250:
251: /**
252: * Retourne une extension de l'opérateur de Sobel. Pour chaque élément dont la position
253: * par rapport à l'élément central est (x,y), on calcule la composante horizontale avec
254: * le cosinus de l'angle divisé par la distance. On peut l'écrire comme suit:
255: *
256: * <blockquote><pre>
257: * cos(atan(y/x)) / sqrt(x²+y²)
258: * </pre></blockquote>
259: *
260: * En utilisant l'identité 1/cos² = (1+tan²), on peut réécrire l'équation comme suit:
261: *
262: * <blockquote><pre>
263: * x / (x²+y²)
264: * </pre></blockquote>
265: *
266: * @param size Taille de la matrice. Doit être un nombre positif et impair.
267: * @param horizontal {@code true} pour l'opérateur horizontal,
268: * or {@code false} pour l'opérateur vertical.
269: */
270: private static KernelJAI getSobel(final int size,
271: final boolean horizontal) {
272: final int key = size / 2;
273: final float[] data = new float[size * size];
274: for (int y = key; y >= 0; y--) {
275: int row1 = (key - y) * size + key;
276: int row2 = (key + y) * size + key;
277: final int y2 = y * y;
278: for (int x = key; x != 0; x--) {
279: final int x2 = x * x;
280: final float v = (float) (2.0 * x / (x2 + y2));
281: if (horizontal) {
282: data[row1 - x] = data[row2 - x] = -v;
283: data[row1 + x] = data[row2 + x] = +v;
284: } else {
285: // Swap x and y.
286: row1 = (key - x) * size + key;
287: row2 = (key + x) * size + key;
288: data[row1 - y] = data[row1 + y] = -v;
289: data[row2 - y] = data[row2 + y] = +v;
290: }
291: }
292: }
293: return new KernelJAI(size, size, key, key, data);
294: }
295: }
296:
297: /**
298: * Shows a dialog box requesting input from the user. If {@code owner} is contained into a
299: * {@link javax.swing.JDesktopPane}, the dialog box will appears as an internal frame. This
300: * method can be invoked from any thread (may or may not be the <cite>Swing</cite> thread).
301: *
302: * @param owner The parent component for the dialog box, or {@code null} if there is no parent.
303: * @param title The dialog box title.
304: * @return {@code true} if user pressed the "Ok" button, or {@code false} otherwise
305: * (e.g. pressing "Cancel" or closing the dialog box from the title bar).
306: */
307: public boolean showDialog(final Component owner, final String title) {
308: return SwingUtilities.showOptionDialog(owner, this , title);
309: }
310:
311: /**
312: * Show the dialog box. This method is provided only as an easy
313: * way to test the dialog appearance from the command line.
314: */
315: public static void main(final String[] args) {
316: final GradientKernelEditor editor = new GradientKernelEditor();
317: editor.addDefaultKernels();
318: editor.showDialog(null, Utilities.getShortClassName(editor));
319: }
320: }
|