/* * 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.brushes; import pixelitor.utils.debug.DebugNode; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; /** * The brush used by the Clone Tool */ public class CloneBrush extends CopyBrush { private double srcX; private double srcY; private double dx; private double dy; private boolean aligned = true; private boolean firstCloningStart = true; private double scaleX; private double scaleY; private double rotate; public CloneBrush(int radius, CopyBrushType type) { super(radius, type, new RadiusRatioSpacing(0.25)); } public void setSource(BufferedImage sourceImage, double srcX, double srcY) { this.sourceImage = sourceImage; this.srcX = srcX; this.srcY = srcY; firstCloningStart = true; } // marks the point where the cloning was started public void setCloningDestPoint(double destX, double destY) { boolean reinitializeDistance = false; // aligned = forces the source point to follow the mouse, even after a stroke is completed // unaligned = the cloning distance is reinitialized for each stroke if (!aligned || firstCloningStart) { reinitializeDistance = true; } firstCloningStart = false; if (reinitializeDistance) { this.dx = -srcX + destX + radius; this.dy = -srcY + destY + radius; } } @Override void setupBrushStamp(double x, double y) { Graphics2D g = brushImage.createGraphics(); type.beforeDrawImage(g); // Concatenated transformations have a last-specified-first-applied order, // so start with the last transformation, that works when there is no scaling/rotating AffineTransform transform = AffineTransform.getTranslateInstance( (dx - x), (dy - y)); if (scaleX != 1.0 || scaleY != 1.0 || rotate != 0.0) { g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); // first we need to scale/rotate the image around the source point transform.translate(srcX, srcY); transform.scale(scaleX, scaleY); transform.rotate(rotate); transform.translate(-srcX, -srcY); } g.drawImage(sourceImage, transform, null); type.afterDrawImage(g); g.dispose(); super.debugImage(); } @Override public void putDab(double x, double y, double theta) { AffineTransform transform = AffineTransform.getTranslateInstance( x - radius, y - radius ); targetG.drawImage(brushImage, transform, null); updateComp((int) x, (int) y); } public void setAligned(boolean aligned) { this.aligned = aligned; } public void setScale(double scaleX, double scaleY) { this.scaleX = scaleX; this.scaleY = scaleY; } public void setRotate(double rotate) { this.rotate = rotate; } public boolean isAligned() { return aligned; } @Override public DebugNode getDebugNode() { DebugNode node = super.getDebugNode(); node.addDoubleChild("srcX", srcX); node.addDoubleChild("srcY", srcY); node.addDoubleChild("dx", dx); node.addDoubleChild("dy", dy); node.addDoubleChild("scaleX", scaleX); node.addDoubleChild("scaleY", scaleY); node.addDoubleChild("rotate", rotate); node.addBooleanChild("aligned", aligned); node.addBooleanChild("firstCloningStart", firstCloningStart); return node; } }