/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2003-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing.image;
import java.text.ParseException;
import javax.swing.JLabel;
import javax.swing.JComponent;
import javax.swing.BorderFactory;
import javax.swing.border.Border;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Component;
import javax.media.jai.KernelJAI;
import javax.media.jai.operator.GradientMagnitudeDescriptor;
import org.geotoolkit.gui.swing.Dialog;
import org.geotoolkit.resources.Vocabulary;
import org.geotoolkit.internal.swing.SwingUtilities;
import static java.awt.GridBagConstraints.*;
import static org.apache.sis.math.MathFunctions.SQRT_2;
/**
* A widget for editing the horizontal and vertical kernels for a
* {@linkplain GradientMagnitudeDescriptor gradient magnitude} operation.
* This widget combine two {@link KernelEditor} side-by-side: one for the
* horizontal component and one for the vertical component.
*
* <table cellspacing="24" cellpadding="12" align="center"><tr valign="top"><td>
* <img src="doc-files/GradientKernelEditor.png">
* </td><td width="500" bgcolor="lightblue">
* {@section Demo}
* The image on the left side gives an example of this widget appearance.
* To try this component in your browser, see the
* <a href="http://www.geotoolkit.org/demos/geotk-simples/applet/GradientKernelEditor.html">demonstration applet</a>.
* </td></tr></table>
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @see KernelEditor
* @see GradientMagnitudeDescriptor
* @see org.geotoolkit.coverage.processing.operation.GradientMagnitude
*
* @since 2.3
* @module
*/
@SuppressWarnings("serial")
public class GradientKernelEditor extends JComponent implements Dialog {
/**
* Horizontal gradient mask according Prewitt (also know as smoothed).
*/
public static final KernelJAI PREWITT_HORIZONTAL = new KernelJAI(3,3,new float[] {
-1, 0, 1,
-1, 0, 1,
-1, 0, 1,
});
/**
* Vertical gradient mask according Prewitt (also know as smoothed).
*/
public static final KernelJAI PREWITT_VERTICAL = new KernelJAI(3,3,new float[] {
-1, -1, -1,
0, 0, 0,
1, 1, 1,
});
/**
* Horizontal gradient mask (isotropic).
*/
public static final KernelJAI ISOTROPIC_HORIZONTAL = new KernelJAI(3,3,new float[] {
-1, 0, 1,
(float) -SQRT_2, 0, (float) SQRT_2,
-1, 0, 1,
});
/**
* Vertical gradient mask (isotropic).
*/
public static final KernelJAI ISOTROPIC_VERTICAL = new KernelJAI(3,3,new float[] {
-1, (float) -SQRT_2, -1,
0, 0, 0,
1, (float) SQRT_2, 1,
});
/**
* Horizontal gradient mask according Sobel.
*/
public static final KernelJAI SOBEL_HORIZONTAL = KernelJAI.GRADIENT_MASK_SOBEL_HORIZONTAL;
/**
* Vertical gradient mask according Sobel.
*/
public static final KernelJAI SOBEL_VERTICAL = KernelJAI.GRADIENT_MASK_SOBEL_VERTICAL;
/*
* NOTE: Sobel masks were interchanged in KernelJAI prior and up to JAI 1.1.2-rc.
* See for example J.J. Simpson (1990) in Remote sensing environment, 33:17-33.
* This bug was fixed in JAI 1.1.2 final.
*/
/**
* Horizontal gradient mask according Kirsch.
*/
public static final KernelJAI KIRSCH_HORIZONTAL = new KernelJAI(3,3,new float[] {
-3, -3, 5,
-3, 0, 5,
-3, -3, 5,
});
/**
* Vertical gradient mask according Kirsch.
*
* @todo Why positives numbers are on the first row? This is the opposite
* of other vertical gradient masks. Need to verify in J.J. Simpson (1990).
*/
public static final KernelJAI KIRSCH_VERTICAL = new KernelJAI(3,3,new float[] {
5, 5, 5,
-3, 0, -3,
-3, -3, -3,
});
/**
* The horizontal kernel editor.
*/
private final KernelEditor kernelH = new Editor(true);
/**
* The vertical kernel editor.
*/
private final KernelEditor kernelV = new Editor(false);
/**
* Constructs a new editor for gradient kernels.
*/
public GradientKernelEditor() {
setLayout(new GridBagLayout());
final Vocabulary resources = Vocabulary.getResources(getDefaultLocale());
final Border border = BorderFactory.createCompoundBorder(
BorderFactory.createLoweredBevelBorder(),
BorderFactory.createEmptyBorder(3,3,3,3));
kernelH.setBorder(border);
kernelV.setBorder(border);
final JLabel labelH, labelV;
labelH = new JLabel(resources.getString(Vocabulary.Keys.HorizontalComponent), JLabel.CENTER);
labelV = new JLabel(resources.getString(Vocabulary.Keys.VerticalComponent), JLabel.CENTER);
final GridBagConstraints c = new GridBagConstraints();
c.insets.top = 6;
c.gridy=0; c.weightx=1; c.gridwidth=1; c.gridheight=1; c.fill=BOTH;
c.gridx=0; add(labelH, c);
c.gridx=1; add(labelV, c);
c.gridy=1; c.weighty=1; c.insets.bottom = 6;
c.gridx=0; c.insets.left=6; c.insets.right=3; add(kernelH, c);
c.gridx=1; c.insets.left=3; c.insets.right=6; add(kernelV, c);
}
/**
* Adds a set of predefined kernels. This convenience method invokes {@link
* KernelEditor#addDefaultKernels()} on both {@linkplain #getHorizontalEditor
* horizontal} and {@linkplain #getVerticalEditor vertical} kernel editors.
* The default implementation for those editors will add a set of Sobel kernels.
*/
public void addDefaultKernels() {
kernelH.addDefaultKernels();
kernelV.addDefaultKernels();
}
/**
* Returns the horizontal kernel editor.
*
* @return The horizontal kernel editor.
*/
public KernelEditor getHorizontalEditor() {
return kernelH;
}
/**
* Returns the vertical kernel editor.
*
* @return The vertical kernel editor.
*/
public KernelEditor getVerticalEditor() {
return kernelV;
}
/**
* A kernel editor for horizontal or vertical gradient kernel.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.3
* @module
*/
@SuppressWarnings("serial")
private static final class Editor extends KernelEditor {
/**
* {@code true} if this editor is for the horizontal component,
* or {@code false} for the vertical component.
*/
private final boolean horizontal;
/**
* Construct a new kernel editor for the specified component.
*/
public Editor(final boolean horizontal) {
super();
this.horizontal = horizontal;
}
/**
* Add a set of predefined kernels.
*/
@Override
public void addDefaultKernels() {
final String GRADIENT_MASKS = getResources().getString(Vocabulary.Keys.GradientMasks);
final KernelJAI prewitt, isotropic, kirsch, sobel;
if (horizontal) {
prewitt = PREWITT_HORIZONTAL;
isotropic = ISOTROPIC_HORIZONTAL;
kirsch = KIRSCH_HORIZONTAL;
sobel = SOBEL_HORIZONTAL;
} else {
prewitt = PREWITT_VERTICAL;
isotropic = ISOTROPIC_VERTICAL;
kirsch = KIRSCH_VERTICAL;
sobel = SOBEL_VERTICAL;
}
addKernel(GRADIENT_MASKS, "Prewitt", prewitt);
addKernel(GRADIENT_MASKS, "Isotropic", isotropic);
addKernel(GRADIENT_MASKS, "Kirsch", kirsch);
addKernel(GRADIENT_MASKS, "Sobel 3\u00D73", sobel);
final StringBuffer buffer = new StringBuffer("Sobel ");
final int base = buffer.length();
for (int i=5; i<=15; i+=2) {
buffer.setLength(base);
buffer.append(i).append('\u00D7').append(i);
addKernel(GRADIENT_MASKS, buffer.toString(), getSobel(i, horizontal));
}
setKernel(sobel);
}
/**
* Retourne une extension de l'opérateur de Sobel. Pour chaque élément dont la position
* par rapport à l'élément central est (x,y), on calcule la composante horizontale avec
* le cosinus de l'angle divisé par la distance. On peut l'écrire comme suit:
*
* {@preformat math
* cos(atan(y/x)) / sqrt(x²+y²)
* }
*
* En utilisant l'identité 1/cos² = (1+tan²), on peut réécrire l'équation comme suit:
*
* {@preformat math
* x / (x²+y²)
* }
*
* @param size Taille de la matrice. Doit être un nombre positif et impair.
* @param horizontal {@code true} pour l'opérateur horizontal,
* ou {@code false} pour l'opérateur vertical.
*/
private static KernelJAI getSobel(final int size, final boolean horizontal) {
final int key = size/2;
final float[] data = new float[size*size];
for (int y=key; y>=0; y--) {
int row1 = (key-y)*size + key;
int row2 = (key+y)*size + key;
final int y2 = y*y;
for (int x=key; x!=0; x--) {
final int x2 = x*x;
final float v = (float) (2.0*x / (x2+y2));
if (horizontal) {
data[row1-x] = data[row2-x] = -v;
data[row1+x] = data[row2+x] = +v;
} else {
// Swap x and y.
row1 = (key-x)*size + key;
row2 = (key+x)*size + key;
data[row1-y] = data[row1+y] = -v;
data[row2-y] = data[row2+y] = +v;
}
}
}
return new KernelJAI(size, size, key, key, data);
}
}
/**
* {@inheritDoc}
*
* @since 3.12
*/
@Override
public void commitEdit() throws ParseException {
}
/**
* {@inheritDoc}
*/
@Override
public boolean showDialog(final Component owner, final String title) {
return SwingUtilities.showDialog(owner, this, title);
}
}