/*
* 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.NO_CYCLE;
import static java.awt.MultipleGradientPaint.CycleMethod.REFLECT;
import static java.awt.MultipleGradientPaint.CycleMethod.REPEAT;
/**
* A Paint that creates a "diamond gradient"
*/
public class DiamondGradientPaint 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 DiamondGradientPaint(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 GrayDiamondGradientPaintContext(userDrag, startColor, endColor, cm, cycleMethod);
}
return new DiamondGradientPaintContext(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 DiamondGradientPaintContext 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 float dragRelDX;
protected final float dragRelDY;
protected final double dragDist;
private DiamondGradientPaintContext(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;
dragDist = userDrag.getDistance();
double dragDistSqr = dragDist * dragDist;
dragRelDX = (float) (userDrag.getDX() / dragDistSqr);
dragRelDY = (float) (userDrag.getDY() / dragDistSqr);
}
@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 == REPEAT) {
double threshold = 1.0 / dragDist;
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++) {
float yy = (y + 1.0f / AA_RES * m - 0.5f);
for (int n = 0; n < AA_RES; n++) {
float xx = x + 1.0f / AA_RES * n - 0.5f;
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 dx = x - userDrag.getStartX();
double dy = y - userDrag.getStartY();
double v1 = Math.abs((dx * this.dragRelDX) + (dy * this.dragRelDY));
double v2 = Math.abs((dx * this.dragRelDY) - (dy * this.dragRelDX));
double interpolationValue = v1 + v2;
if (cycleMethod == NO_CYCLE) {
if (interpolationValue > 1.0) {
interpolationValue = 1.0f;
}
} else if (cycleMethod == REFLECT) {
interpolationValue %= 1.0;
if (interpolationValue < 0.5) {
interpolationValue = 2.0f * interpolationValue;
} else {
interpolationValue = 2.0f * (1 - interpolationValue);
}
} else if (cycleMethod == REPEAT) {
interpolationValue %= 1.0;
if (interpolationValue < 0.5) {
interpolationValue = 2.0f * interpolationValue;
} else {
interpolationValue = 2.0f * (interpolationValue - 0.5f);
}
}
return interpolationValue;
}
}
private static class GrayDiamondGradientPaintContext extends DiamondGradientPaintContext {
private final int startGray;
private final int endGray;
private GrayDiamondGradientPaintContext(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 == REPEAT) {
double threshold = 1.0 / dragDist;
needsAA = interpolationValue > (1.0 - threshold) || interpolationValue < threshold;
}
if (needsAA) {
int g = 0;
for (int m = 0; m < AA_RES; m++) {
float yy = (y + 1.0f / AA_RES * m - 0.5f);
for (int n = 0; n < AA_RES; n++) {
float xx = x + 1.0f / AA_RES * n - 0.5f;
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;
}
}
}