/*
* Copyright 2016 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.gradientpaints;
import pixelitor.tools.UserDrag;
import java.awt.Color;
import java.awt.MultipleGradientPaint;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import static java.awt.MultipleGradientPaint.CycleMethod.REFLECT;
import static java.awt.MultipleGradientPaint.CycleMethod.REPEAT;
/**
* A Paint that creates an "angle gradient"
*/
public class AngleGradientPaint implements Paint {
private final UserDrag userDrag;
private final Color startColor;
private final Color endColor;
private final MultipleGradientPaint.CycleMethod cycleMethod;
private static final int AA_RES = 4; // the resolution of AA supersampling
private static final int AA_RES2 = AA_RES * AA_RES;
public AngleGradientPaint(UserDrag userDrag, Color startColor, Color endColor, MultipleGradientPaint.CycleMethod cycleMethod) {
this.userDrag = userDrag;
this.startColor = startColor;
this.endColor = endColor;
this.cycleMethod = cycleMethod;
}
@Override
public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
int numComponents = cm.getNumComponents();
if (numComponents == 1) {
return new GrayAngleGradientPaintContext(userDrag, startColor, endColor, cm, cycleMethod);
}
return new AngleGradientPaintContext(userDrag, startColor, endColor, cm, cycleMethod);
}
@Override
public int getTransparency() {
int a1 = startColor.getAlpha();
int a2 = endColor.getAlpha();
return (((a1 & a2) == 0xFF) ? OPAQUE : TRANSLUCENT);
}
private static class AngleGradientPaintContext implements PaintContext {
protected final UserDrag userDrag;
protected final MultipleGradientPaint.CycleMethod cycleMethod;
private final int startAlpha;
private final int startRed;
private final int startGreen;
private final int startBlue;
private final int endAlpha;
private final int endRed;
private final int endGreen;
private final int endBlue;
protected final ColorModel cm;
protected final double drawAngle;
private AngleGradientPaintContext(UserDrag userDrag, Color startColor, Color endColor, ColorModel cm, MultipleGradientPaint.CycleMethod cycleMethod) {
this.userDrag = userDrag;
this.cycleMethod = cycleMethod;
startAlpha = startColor.getAlpha();
startRed = startColor.getRed();
startGreen = startColor.getGreen();
startBlue = startColor.getBlue();
endAlpha = endColor.getAlpha();
endRed = endColor.getRed();
endGreen = endColor.getGreen();
endBlue = endColor.getBlue();
this.cm = cm;
drawAngle = userDrag.getDrawAngle();
}
@Override
public void dispose() {
}
@Override
public ColorModel getColorModel() {
return cm;
}
// Warning: gray subclass has exact copy of the algorithm
@Override
public Raster getRaster(int startX, int startY, int width, int height) {
WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
int[] rasterData = new int[width * height * 4];
for (int j = 0; j < height; j++) {
int y = startY + j;
for (int i = 0; i < width; i++) {
int base = (j * width + i) * 4;
int x = startX + i;
double interpolationValue = getInterpolationValue(x, y);
boolean needsAA = false;
if (cycleMethod != REFLECT) {
double distance = userDrag.taxiCabMetric(x, y);
double threshold = 0.2 / distance;
needsAA = interpolationValue > (1.0 - threshold) || interpolationValue < threshold;
}
if (needsAA) {
int a = 0;
int r = 0;
int g = 0;
int b = 0;
for (int m = 0; m < AA_RES; m++) {
double yy = y + 1.0 / AA_RES * m - 0.5;
for (int n = 0; n < AA_RES; n++) {
double xx = x + 1.0 / AA_RES * n - 0.5;
double interpolationValueAA = getInterpolationValue(xx, yy);
a += (int) (startAlpha + interpolationValueAA * (endAlpha - startAlpha));
r += (int) (startRed + interpolationValueAA * (endRed - startRed));
g += (int) (startGreen + interpolationValueAA * (endGreen - startGreen));
b += (int) (startBlue + interpolationValueAA * (endBlue - startBlue));
}
}
a /= AA_RES2;
r /= AA_RES2;
g /= AA_RES2;
b /= AA_RES2;
rasterData[base] = r;
rasterData[base + 1] = g;
rasterData[base + 2] = b;
rasterData[base + 3] = a;
} else { // no AA
int a = (int) (startAlpha + interpolationValue * (endAlpha - startAlpha));
int r = (int) (startRed + interpolationValue * (endRed - startRed));
int g = (int) (startGreen + interpolationValue * (endGreen - startGreen));
int b = (int) (startBlue + interpolationValue * (endBlue - startBlue));
rasterData[base] = r;
rasterData[base + 1] = g;
rasterData[base + 2] = b;
rasterData[base + 3] = a;
}
}
}
raster.setPixels(0, 0, width, height, rasterData);
return raster;
}
public double getInterpolationValue(double x, double y) {
double relativeAngle = userDrag.getAngleFromStartTo(x, y) - drawAngle;
// relativeAngle is now between -2*PI and 2*PI, and the -2*PI..0 range is the same as 0..2*PI
double interpolationValue = (relativeAngle / (Math.PI * 2)) + 1.0; // between 0..2
interpolationValue %= 1.0f; // between 0..1
if (cycleMethod == REFLECT) {
if (interpolationValue < 0.5) {
interpolationValue = 2.0f * interpolationValue;
} else {
interpolationValue = 2.0f * (1 - interpolationValue);
}
} else if (cycleMethod == REPEAT) {
if (interpolationValue < 0.5) {
interpolationValue = 2.0f * interpolationValue;
} else {
interpolationValue = 2.0f * (interpolationValue - 0.5);
}
}
return interpolationValue;
}
}
private static class GrayAngleGradientPaintContext extends AngleGradientPaintContext {
private final int startGray;
private final int endGray;
private GrayAngleGradientPaintContext(UserDrag userDrag, Color startColor, Color endColor, ColorModel cm, MultipleGradientPaint.CycleMethod cycleMethod) {
super(userDrag, startColor, endColor, cm, cycleMethod);
startGray = startColor.getRed();
endGray = endColor.getRed();
}
@Override
public Raster getRaster(int startX, int startY, int width, int height) {
WritableRaster raster = cm.createCompatibleWritableRaster(width, height);
int[] rasterData = new int[width * height];
for (int j = 0; j < height; j++) {
int y = startY + j;
for (int i = 0; i < width; i++) {
int base = (j * width + i);
int x = startX + i;
double interpolationValue = getInterpolationValue(x, y);
boolean needsAA = false;
if (cycleMethod != REFLECT) {
double distance = userDrag.taxiCabMetric(x, y);
double threshold = 0.2 / distance;
needsAA = interpolationValue > (1.0 - threshold) || interpolationValue < threshold;
}
if (needsAA) {
int g = 0;
for (int m = 0; m < AA_RES; m++) {
double yy = y + 1.0 / AA_RES * m - 0.5;
for (int n = 0; n < AA_RES; n++) {
double xx = x + 1.0 / AA_RES * n - 0.5;
double interpolationValueAA = getInterpolationValue(xx, yy);
g += (int) (startGray + interpolationValueAA * (endGray - startGray));
}
}
g /= AA_RES2;
rasterData[base] = g;
} else { // no AA
int g = (int) (startGray + interpolationValue * (endGray - startGray));
rasterData[base] = g;
}
}
}
raster.setPixels(0, 0, width, height, rasterData);
return raster;
}
}
}