/*
* 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.ImageUtils;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.util.EnumMap;
import java.util.Map;
import static java.awt.RenderingHints.KEY_INTERPOLATION;
import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
/**
* A dabs brush based on images
*/
public class ImageDabsBrush extends DabsBrush {
private static final Map<ImageBrushType, BufferedImage> templateImages = new EnumMap<>(ImageBrushType.class);
private final BufferedImage templateImage;
private BufferedImage coloredBrushImage;
private BufferedImage finalScaledImage;
private Color lastColor;
public ImageDabsBrush(int radius, ImageBrushType imageBrushType, double spacingRatio, AngleSettings angleSettings) {
super(radius, new RadiusRatioSpacing(spacingRatio), angleSettings, false);
// for each brush type multiple brush instances are created because of the symmetry
// however the template image can be shared between them
templateImage = templateImages.computeIfAbsent(imageBrushType,
ImageBrushType::createBWBrushImage);
}
@Override
void setupBrushStamp(double x, double y) {
assert diameter > 0 : "zero diameter in " + getClass().getName();
Color c = targetG.getColor();
if (!c.equals(lastColor)) {
colorizeBrushImage(c);
lastColor = c;
resizeBrushImage(diameter, true);
} else {
resizeBrushImage(diameter, false);
}
}
/**
* This method assumes that the color of coloredBrushImage is OK
*/
private void resizeBrushImage(float newSize, boolean force) {
if (!force) {
if (finalScaledImage != null && finalScaledImage.getWidth() == newSize) {
return;
}
}
if (finalScaledImage != null) {
finalScaledImage.flush();
}
int newSizeInt = (int) newSize;
assert newSizeInt > 0 : "newSize = " + newSize;
finalScaledImage = new BufferedImage(newSizeInt, newSizeInt, TYPE_INT_ARGB);
Graphics2D g = finalScaledImage.createGraphics();
g.drawImage(coloredBrushImage, 0, 0, newSizeInt, newSizeInt, null);
g.dispose();
}
/**
* Creates a colorized brush image from the template image according to the foreground color
*
* @param color
*/
private void colorizeBrushImage(Color color) {
coloredBrushImage = new BufferedImage(templateImage.getWidth(), templateImage.getHeight(), TYPE_INT_ARGB);
int[] srcPixels = ImageUtils.getPixelsAsArray(templateImage);
int[] destPixels = ImageUtils.getPixelsAsArray(coloredBrushImage);
int destRed = color.getRed();
int destGreen = color.getGreen();
int destBlue = color.getBlue();
for (int i = 0; i < destPixels.length; i++) {
int srcRGB = srcPixels[i];
//int a = (srcRGB >>> 24) & 0xFF;
int srcRed = (srcRGB >>> 16) & 0xFF;
int srcGreen = (srcRGB >>> 8) & 0xFF;
int srcBlue = (srcRGB) & 0xFF;
int srcAverage = (srcRed + srcGreen + srcBlue) / 3;
destPixels[i] = (0xFF - srcAverage) << 24 | destRed << 16 | destGreen << 8 | destBlue;
}
}
@Override
public void putDab(double x, double y, double theta) {
if (!settings.isAngleAware() || theta == 0) {
targetG.drawImage(finalScaledImage, (int) x - radius, (int) y - radius, null);
} else {
AffineTransform oldTransform = targetG.getTransform();
targetG.rotate(theta, x, y);
targetG.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
targetG.drawImage(finalScaledImage, (int) x - radius, (int) y - radius, null);
targetG.setTransform(oldTransform);
}
updateComp((int) x, (int) y);
}
}