/* * Copyright 2017 Laszlo Balazs-Csiki * * This file is part of Pixelitor. Pixelitor is free software: you * can redistribute it and/or modify it under the terms of the GNU * General Public License, version 3 as published by the Free * Software Foundation. * * Pixelitor 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pixelitor. If not, see <http://www.gnu.org/licenses/>. */ package pixelitor.filters.convolve; import org.jdesktop.swingx.combobox.EnumComboBoxModel; import pixelitor.filters.gui.FilterGUIPanel; import pixelitor.layers.Drawable; import pixelitor.utils.Messages; import pixelitor.utils.NotANumberException; import pixelitor.utils.Utils; import javax.swing.*; import java.awt.Component; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; /** * An adjustment panel for customizable convolutions */ public class CustomConvolveAdjustments extends FilterGUIPanel implements ActionListener { private static final int TEXTFIELD_PREFERRED_WIDTH = 70; private JTextField[] textFields; private JPanel textFieldsP; private JButton normalizeButton; private Box presetsBox; private final int size; public CustomConvolveAdjustments(Convolve filter, Drawable dr) { super(filter, dr); setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); size = filter.getSize(); initLeftVerticalBox(filter); initPresetBox(); reset(size); } private void initLeftVerticalBox(Convolve filter) { Box leftVerticalBox = Box.createVerticalBox(); textFieldsP = new JPanel(); textFields = new JTextField[size * size]; for (int i = 0; i < textFields.length; i++) { textFields[i] = new JTextField(); } textFieldsP.setLayout(new GridLayout(size, size)); for (JTextField textField : textFields) { setupTextField(textField); } textFieldsP.setBorder(BorderFactory.createTitledBorder("Kernel")); textFieldsP.setAlignmentX(Component.LEFT_ALIGNMENT); leftVerticalBox.add(textFieldsP); // these two lines must come after adding the textFieldsP to the box Dimension minimumSize = textFieldsP.getMinimumSize(); textFieldsP.setPreferredSize(new Dimension(size * TEXTFIELD_PREFERRED_WIDTH, minimumSize.height)); normalizeButton = new JButton("Normalize (preserve brightness)"); normalizeButton.addActionListener(this); normalizeButton.setAlignmentX(Component.LEFT_ALIGNMENT); leftVerticalBox.add(normalizeButton); JButton tryButton = new JButton("Try"); tryButton.addActionListener(this); leftVerticalBox.add(tryButton); leftVerticalBox.add(Box.createVerticalStrut(20)); JLabel cmLabel = new JLabel("Convolution method:", JLabel.LEFT); cmLabel.setAlignmentX(Component.LEFT_ALIGNMENT); leftVerticalBox.add(cmLabel); EnumComboBoxModel<ConvolveMethod> convolveMethodModel = filter.getConvolveMethodModel(); JComboBox<ConvolveMethod> convolveMethodCB = new JComboBox<>(convolveMethodModel); convolveMethodCB.setAlignmentX(Component.LEFT_ALIGNMENT); leftVerticalBox.add(convolveMethodCB); convolveMethodCB.addActionListener(this); leftVerticalBox.setMaximumSize(leftVerticalBox.getPreferredSize()); leftVerticalBox.setAlignmentY(Component.TOP_ALIGNMENT); add(leftVerticalBox); } private void initPresetBox() { presetsBox = Box.createVerticalBox(); presetsBox.setBorder(BorderFactory.createTitledBorder("Presets")); if (size == 3) { init3x3Presets(); } else if (size == 5) { init5x5Presets(); } else { throw new IllegalStateException("size = " + size); } presetsBox.add(Box.createVerticalStrut(20)); JButton randomizeButton = new JButton("Randomize"); randomizeButton.addActionListener(e -> { setValues(Convolve.getRandomKernelMatrix(size)); actionPerformed(e); }); presetsBox.add(randomizeButton); JButton doNothingButton = new JButton("Do Nothing"); doNothingButton.addActionListener(e -> { reset(size); actionPerformed(e); }); presetsBox.add(doNothingButton); presetsBox.setMaximumSize(presetsBox.getPreferredSize()); presetsBox.setAlignmentY(Component.TOP_ALIGNMENT); add(presetsBox); } private void initPreset(String name, float[] kernel) { JButton button = new JButton(name); button.addActionListener(e -> { setValues(kernel); actionPerformed(e); }); presetsBox.add(button); } private void init5x5Presets() { initPreset("Diamond Blur", new float[]{ 0.0f, 0.0f, 0.077f, 0.0f, 0.0f, 0.0f, 0.077f, 0.077f, 0.077f, 0.0f, 0.077f, 0.077f, 0.077f, 0.077f, 0.077f, 0.0f, 0.077f, 0.077f, 0.077f, 0.0f, 0.0f, 0.0f, 0.077f, 0.0f, 0.0f, }); initPreset("Motion Blur", new float[]{ 0.0f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.0f, 0.2f, 0.0f, 0.0f, 0.0f, 0.0f, }); initPreset("Find Horizontal Edges", new float[]{ 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, }); initPreset("Find Vertical Edges", new float[]{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, -2.0f, 6.0f, -2.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, }); initPreset("Find Diagonal Edges", new float[]{ 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 6.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, }); initPreset("Find Diagonal Edges 2", new float[]{ -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -2.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, }); initPreset("Sharpen", new float[]{ -0.125f, -0.125f, -0.125f, -0.125f, -0.125f, -0.125f, 0.25f, 0.25f, 0.25f, -0.125f, -0.125f, 0.25f, 1.0f, 0.25f, -0.125f, -0.125f, 0.25f, 0.25f, 0.25f, -0.125f, -0.125f, -0.125f, -0.125f, -0.125f, -0.125f, }); } private void init3x3Presets() { initPreset("\"Corner\" Blur", (new float[]{ 0.25f, 0.0f, 0.25f, 0.0f, 0.0f, 0.0f, 0.25f, 0.0f, 0.25f})); initPreset("\"Gaussian\" Blur", new float[]{ 1 / 16.0f, 2 / 16.0f, 1 / 16.0f, 2 / 16.0f, 4 / 16.0f, 2 / 16.0f, 1 / 16.0f, 2 / 16.0f, 1 / 16.0f}); initPreset("Mean Filter", new float[]{ 0.1115f, 0.1115f, 0.1115f, 0.1115f, 0.1115f, 0.1115f, 0.1115f, 0.1115f, 0.1115f}); initPreset("Sharpen", new float[]{ 0, -1, 0, -1, 5, -1, 0, -1, 0}); initPreset("Edge Detection", new float[]{ 0, -1, 0, -1, 4, -1, 0, -1, 0}); initPreset("Edge Detection 2", new float[]{ -1, -1, -1, -1, 8, -1, -1, -1, -1}); initPreset("Horizontal Edge Detection", new float[]{ -1, -1, -1, 0, 0, 0, 1, 1, 1}); initPreset("Vertical Edge Detection", new float[]{ -1, 0, 1, -1, 0, 1, -1, 0, 1}); initPreset("Emboss", new float[]{ -2, -2, 0, -2, 6, 0, 0, 0, 0}); initPreset("Emboss 2", new float[]{ -2, 0, 0, 0, 0, 0, 0, 0, 2}); initPreset("Color Emboss", new float[]{ -1, -1, 0, -1, 1, 1, 0, 1, 1}); } private void reset(int size) { float[] defaultValues = new float[size * size]; for (int i = 0; i < defaultValues.length; i++) { defaultValues[i] = 0.0f; } defaultValues[defaultValues.length / 2] = 1.0f; setValues(defaultValues); } private void setupTextField(JTextField textField) { textFieldsP.add(textField); textField.addActionListener(this); } @Override public void actionPerformed(ActionEvent e) { float sum = 0; float[] values = new float[size * size]; for (int i = 0; i < values.length; i++) { String s = textFields[i].getText(); try { values[i] = Utils.string2float(s); } catch (NotANumberException ex) { Messages.showError("Wrong Number Format", ex.getMessage()); } sum += values[i]; } enableNormalizeButton(sum); if (e.getSource() == normalizeButton) { if (sum != 0.0f) { for (int i = 0; i < values.length; i++) { values[i] /= sum; } setValues(values); } } Convolve kernelFilter = (Convolve) filter; kernelFilter.setKernelMatrix(values); super.executeFilterPreview(); } private void setValues(float[] values) { assert values.length == size * size; float sum = 0; for (int i = 0; i < textFields.length; i++) { JTextField textField = textFields[i]; textField.setText(Utils.float2String(values[i])); sum += values[i]; } enableNormalizeButton(sum); } private void enableNormalizeButton(float sum) { boolean notZero = sum < -0.003f || sum > 0.003f; boolean notOne = sum < 0.997f || sum > 1.003f; normalizeButton.setEnabled(notZero && notOne); } }