/*
* 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.tools;
import pixelitor.Canvas;
import pixelitor.Composition;
import pixelitor.gui.BlendingModePanel;
import pixelitor.gui.ImageComponent;
import pixelitor.layers.Drawable;
import pixelitor.layers.LayerMask;
import pixelitor.layers.TmpDrawingLayer;
import pixelitor.menus.view.ZoomLevel;
import pixelitor.utils.debug.DebugNode;
import javax.swing.*;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.MultipleGradientPaint;
import java.awt.Paint;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import static java.awt.MultipleGradientPaint.CycleMethod.NO_CYCLE;
import static java.awt.MultipleGradientPaint.CycleMethod.REFLECT;
import static java.awt.MultipleGradientPaint.CycleMethod.REPEAT;
import static java.awt.RenderingHints.KEY_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
import static pixelitor.Composition.ImageChangeActions.FULL;
/**
* The gradient tool
*/
public class GradientTool extends Tool {
private boolean thereWasDragging = false;
private static final String NO_CYCLE_AS_STRING = "No Cycle";
private static final String REFLECT_AS_STRING = "Reflect";
private static final String REPEAT_AS_STRING = "Repeat";
public static final String[] CYCLE_METHODS = {
NO_CYCLE_AS_STRING,
REFLECT_AS_STRING,
REPEAT_AS_STRING};
private JComboBox<GradientColorType> colorTypeSelector;
private JComboBox<GradientType> typeSelector;
private JComboBox<String> cycleMethodSelector;
private JCheckBox invertCheckBox;
private BlendingModePanel blendingModePanel;
GradientTool() {
super('g', "Gradient", "gradient_tool_icon.png", "click and drag to draw a gradient, Shift-drag to constrain the direction.",
Cursor.getDefaultCursor(), true, true, true, ClipStrategy.IMAGE_ONLY);
}
@Override
public void initSettingsPanel() {
typeSelector = new JComboBox<>(GradientType.values());
settingsPanel.addWithLabel("Type: ", typeSelector, "gradientTypeSelector");
// cycle methods cannot be put directly in the JComboBox, because they would be all uppercase
cycleMethodSelector = new JComboBox<>(CYCLE_METHODS);
settingsPanel.addWithLabel("Cycling: ", cycleMethodSelector, "gradientCycleMethodSelector");
settingsPanel.addSeparator();
colorTypeSelector = new JComboBox<>(GradientColorType.values());
settingsPanel.addWithLabel("Color: ", colorTypeSelector, "gradientColorTypeSelector");
invertCheckBox = new JCheckBox();
settingsPanel.addWithLabel("Invert: ", invertCheckBox, "gradientInvert");
settingsPanel.addSeparator();
blendingModePanel = new BlendingModePanel(true);
settingsPanel.add(blendingModePanel);
}
@Override
public void mousePressed(MouseEvent e, ImageComponent ic) {
}
@Override
public void mouseDragged(MouseEvent e, ImageComponent ic) {
thereWasDragging = true; // the gradient will be drawn only when the mouse is released
ic.repaint();
}
@Override
public void mouseReleased(MouseEvent e, ImageComponent ic) {
if (thereWasDragging) {
Composition comp = ic.getComp();
saveFullImageForUndo(comp);
drawGradient(comp.getActiveDrawable(),
getType(),
getGradientColorType(),
getCycleType(),
blendingModePanel.getComposite(),
userDrag,
invertCheckBox.isSelected()
);
thereWasDragging = false;
comp.imageChanged(FULL);
}
}
private MultipleGradientPaint.CycleMethod getCycleType() {
return getCycleMethodFromString((String) cycleMethodSelector.getSelectedItem());
}
private GradientColorType getGradientColorType() {
return (GradientColorType) colorTypeSelector.getSelectedItem();
}
private GradientType getType() {
return (GradientType) typeSelector.getSelectedItem();
}
@Override
public boolean dispatchMouseClicked(MouseEvent e, ImageComponent ic) {
if (super.dispatchMouseClicked(e, ic)) {
return true;
}
thereWasDragging = false;
return false;
}
public static void drawGradient(Drawable dr, GradientType gradientType, GradientColorType colorType, MultipleGradientPaint.CycleMethod cycleMethod, Composite composite, UserDrag userDrag, boolean invert) {
if (userDrag.isClick()) {
return;
}
Graphics2D g;
int width;
int height;
if (dr instanceof LayerMask) {
BufferedImage subImage = dr.getCanvasSizedSubImage();
g = subImage.createGraphics();
width = subImage.getWidth();
height = subImage.getHeight();
} else {
TmpDrawingLayer tmpDrawingLayer = dr.createTmpDrawingLayer(composite);
g = tmpDrawingLayer.getGraphics();
width = tmpDrawingLayer.getWidth();
height = tmpDrawingLayer.getHeight();
}
dr.getComp().applySelectionClipping(g, null);
// repeated gradients are still jaggy
g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
Color startColor = colorType.getStartColor(invert);
Color endColor = colorType.getEndColor(invert);
assert startColor != null;
assert endColor != null;
Color[] colors = {startColor, endColor};
Paint gradient = gradientType.getGradient(userDrag, colors, cycleMethod);
g.setPaint(gradient);
g.fillRect(0, 0, width, height);
g.dispose();
dr.mergeTmpDrawingLayerDown();
dr.updateIconImage();
}
@Override
public void paintOverImage(Graphics2D g2, Canvas canvas, ImageComponent ic, AffineTransform unscaledTransform) {
if (thereWasDragging) {
g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
ZoomLevel zoomLevel = ic.getZoomLevel();
g2.setColor(Color.BLACK);
g2.setStroke(zoomLevel.getOuterGeometryStroke());
userDrag.drawLine(g2);
g2.setColor(Color.WHITE);
g2.setStroke(zoomLevel.getInnerGeometryStroke());
userDrag.drawLine(g2);
}
}
private static MultipleGradientPaint.CycleMethod getCycleMethodFromString(String s) {
switch (s) {
case NO_CYCLE_AS_STRING:
return NO_CYCLE;
case REFLECT_AS_STRING:
return REFLECT;
case REPEAT_AS_STRING:
return REPEAT;
}
throw new IllegalStateException("should not get here");
}
public void setupMaskDrawing(boolean editMask) {
if (editMask) {
blendingModePanel.setEnabled(false);
} else {
blendingModePanel.setEnabled(true);
}
}
@Override
public DebugNode getDebugNode() {
DebugNode node = super.getDebugNode();
node.addStringChild("Type", getType().toString());
node.addStringChild("Cycling", getCycleType().toString());
node.addQuotedStringChild("Color", getGradientColorType().toString());
node.addBooleanChild("Invert", invertCheckBox.isSelected());
node.addFloatChild("Opacity", blendingModePanel.getOpacity());
node.addQuotedStringChild("Blending Mode", blendingModePanel.getBlendingMode().toString());
return node;
}
}