Skip to content

Commit

Permalink
More options in the "Concentric Shapes" filter
Browse files Browse the repository at this point in the history
  • Loading branch information
lbalazscs committed Sep 1, 2023
1 parent b84f89c commit 3c27c39
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 16 deletions.
79 changes: 65 additions & 14 deletions src/main/java/pixelitor/filters/ConcentricShapes.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

package pixelitor.filters;

import org.jdesktop.swingx.geom.Star2D;
import pixelitor.Canvas;
import pixelitor.Views;
import pixelitor.colors.Colors;
Expand All @@ -26,7 +27,9 @@
import pixelitor.utils.ImageUtils;
import pixelitor.utils.Shapes;

import java.awt.*;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.image.BufferedImage;
import java.io.Serial;
import java.util.ArrayList;
Expand All @@ -48,40 +51,80 @@ public class ConcentricShapes extends ParametrizedFilter {
private static final long serialVersionUID = 1L;

enum ConcentricShapeType {
CIRCLES("Circles") {
CIRCLES("Circles", false, 1.0, 1.0) {
@Override
Shape createShape(double cx, double cy, double r, int n) {
return Shapes.createCircle(cx, cy, r);
}
}, POLYGONS("Polygons") {
}, POLYGONS("Polygons", true, 1.0, 1.0) {
@Override
Shape createShape(double cx, double cy, double r, int n) {
return Shapes.createCircumscribedPolygon(n, cx, cy, r);
}
// the number of rings multiplier corresponds to the max/min radius ratio
}, STARS("Stars", true, 2.0, 2.0) {
@Override
Shape createShape(double cx, double cy, double r, int n) {
return new Star2D(cx, cy, r / 2.0, r, n);
}
}, BATS("Bats", false, 2.0, 2.0) {
@Override
Shape createShape(double cx, double cy, double r, int n) {
return Shapes.createBat(cx - r, cy - r, 2 * r, 2 * r);
}
}, HEARTS("Hearts", false, 1.0, 1.5) {
@Override
Shape createShape(double cx, double cy, double r, int n) {
return Shapes.createHeart(cx - r, cy - r, 2 * r, 2 * r);
}
}, FLOWERS("Flowers", true, 1.0, 1.0) {
@Override
Shape createShape(double cx, double cy, double r, int n) {
return Shapes.createFlower(n, cx, cy, r);
}
};

private final String guiName;
private final boolean hasSides;
private final double distMultiplier;
private final double ringsMultiplier;

ConcentricShapeType(String guiName) {
ConcentricShapeType(String guiName, boolean hasSides, double distMultiplier, double ringsMultiplier) {
this.guiName = guiName;
this.hasSides = hasSides;
this.distMultiplier = distMultiplier;
this.ringsMultiplier = ringsMultiplier;
}

abstract Shape createShape(double cx, double cy, double r, int n);

public boolean hasSides() {
return hasSides;
}

public double getDistMultiplier() {
return distMultiplier;
}

public double getRingsMultiplier() {
return ringsMultiplier;
}

@Override
public String toString() {
return guiName;
}
}

private final EnumParam<ConcentricShapeType> type = new EnumParam<>("Type", ConcentricShapeType.class);
private final RangeParam polySides = new RangeParam("Polygon Sides", 3, 4, 25);
private final RangeParam sidesParam = new RangeParam("Sides", 3, 7, 25);
private final ImagePositionParam center = new ImagePositionParam("Center");
private final RangeParam distanceParam = new RangeParam("Distance", 1, 20, 100);
private final ColorListParam colorsParam = new ColorListParam("Colors",
2, 2, WHITE, BLACK, Colors.CW_RED, Colors.CW_GREEN, Colors.CW_BLUE,
Colors.CW_ORANGE, Colors.CW_TEAL, Colors.CW_VIOLET);
private final RangeParam randomnessParam = new RangeParam("Randomness", 0, 0, 100);
private final AngleParam rotateParam = new AngleParam("Rotate", 0);

private record ColoredShape(Color color, Shape shape) {
}
Expand All @@ -90,14 +133,16 @@ public ConcentricShapes() {
super(false);

FilterButtonModel reseedAction = paramSet.createReseedAction("", "Reseed Randomness");
type.setupEnableOtherIf(polySides, selectedType -> selectedType == ConcentricShapeType.POLYGONS);
type.setupEnableOtherIf(sidesParam, ConcentricShapeType::hasSides);
type.setupEnableOtherIf(rotateParam, t -> t != ConcentricShapeType.CIRCLES);

setParams(
type,
polySides,
sidesParam,
center,
distanceParam,
colorsParam,
rotateParam,
randomnessParam.withAction(reseedAction)
).withAction(new FilterButtonModel("Export SVG...", this::exportSVG,
null, "Export the current image to an SVG file",
Expand All @@ -113,17 +158,17 @@ public BufferedImage doTransform(BufferedImage src, BufferedImage dest) {
g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);

Random random = paramSet.getLastSeedRandom();
List<ColoredShape> coloredShapes = createShapes(dest.getWidth(), dest.getHeight(), random);
for (ColoredShape coloredShape : coloredShapes) {
g.setColor(coloredShape.color());
g.fill(coloredShape.shape());
List<ColoredShape> shapes = createShapes(dest.getWidth(), dest.getHeight(), random, rotateParam.getValueInRadians());
for (ColoredShape shape : shapes) {
g.setColor(shape.color());
g.fill(shape.shape());
}

g.dispose();
return dest;
}

private List<ColoredShape> createShapes(int width, int height, Random rng) {
private List<ColoredShape> createShapes(int width, int height, Random rng, double angle) {
double cx = width * center.getRelativeX();
double cy = height * center.getRelativeY();

Expand All @@ -137,17 +182,23 @@ private List<ColoredShape> createShapes(int width, int height, Random rng) {
double distance = distanceParam.getValueAsDouble();
int numRings = 1 + (int) (maxDist / distance);

distance *= shapeType.getDistMultiplier();
numRings *= shapeType.getRingsMultiplier();

Color[] colors = colorsParam.getColors();
int numColors = colors.length;
List<ColoredShape> retVal = new ArrayList<>(numRings);

for (int ring = numRings; ring > 0; ring--) {
double r = ring * distance;
Color color = colorsParam.getColor((ring - 1) % numColors);
Shape shape = shapeType.createShape(cx, cy, r, polySides.getValue());
Shape shape = shapeType.createShape(cx, cy, r, sidesParam.getValue());
if (randomness > 0) {
shape = Shapes.randomize(shape, rng, rndMultiplier * r);
}
if (angle != 0) {
shape = Shapes.rotate(shape, angle, cx, cy);
}
retVal.add(new ColoredShape(color, shape));
}
return retVal;
Expand All @@ -159,7 +210,7 @@ private void exportSVG() {
content.append("\n");

Canvas canvas = Views.getActiveComp().getCanvas();
List<ColoredShape> coloredShapes = createShapes(canvas.getWidth(), canvas.getHeight(), paramSet.getLastSeedRandom());
List<ColoredShape> coloredShapes = createShapes(canvas.getWidth(), canvas.getHeight(), paramSet.getLastSeedRandom(), rotateParam.getValueInRadians());
for (ColoredShape coloredShape : coloredShapes) {
String svgPath = Shapes.toSVGPath(coloredShape.shape());
String svgColor = Colors.toHTMLHex(coloredShape.color(), false);
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/pixelitor/filters/gui/ColorListParamGUI.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public ColorListParamGUI(ColorListParam model, Color[] candidateColors, int minN
add(colorsPanel);

numColorsModel.setAdjustmentListener(() ->
changeNumVisibleSwatches(numColorsModel.getValue()));
numColorsChanged(numColorsModel.getValue()));

setBorder(createTitledBorder(model.getName()));
}
Expand All @@ -92,6 +92,11 @@ private ColorSwatch createAndAddColorSwatch(Color color, int index) {
return swatch;
}

private void numColorsChanged(int newNum) {
changeNumVisibleSwatches(newNum);
updateModelColors();
}

private void changeNumVisibleSwatches(int newNum) {
if (newNum == numVisibleSwatches) {
return;
Expand All @@ -114,6 +119,9 @@ private void changeNumVisibleSwatches(int newNum) {
}
}
numVisibleSwatches = newNum;
}

private void updateModelColors() {
Color[] newColors = new Color[numVisibleSwatches];
for (int i = 0; i < numVisibleSwatches; i++) {
newColors[i] = swatches.get(i).getForeground();
Expand All @@ -140,6 +148,8 @@ public void updateGUI() {
Color[] colors = model.getColors();
int numColors = colors.length;

changeNumVisibleSwatches(numColors);

for (int i = 0; i < numColors; i++) {
Color color = colors[i];
ColorSwatch swatch = swatches.get(i);
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/pixelitor/filters/gui/UserPreset.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,14 @@ public String getName() {
public String get(String key) {
String value = content.get(key);
if (value == null) {
// oct 2021: temporary hack for compatible color list upgrade of starburst
if ("Ray Colors".equals(key)) {
// oct 2021: temporary hack for compatible color list upgrade of starburst
value = content.get("Ray Color");
} else if ("Sides".equals(key)) {
// sept 2023: 4.3.0 => 4.3.1 migration in Concentric Shapes
value = content.get("Polygon Sides");
}

if (GUIMode.isDevelopment()) {
System.out.println("UserPreset::get: no value found for the key " + key);
}
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/pixelitor/utils/Shapes.java
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,37 @@ public static Shape createCircumscribedPolygon(int n, double cx, double cy, doub
return path;
}

public static Shape createFlower(int n, double cx, double cy, double radius) {
double angleIncrement = Math.PI * 2 / n;
double halfAngleIncrement = angleIncrement / 2;
double angle = -Math.PI / 2;
double distantRadius = radius / Math.cos(halfAngleIncrement);

Path2D path = new Path2D.Double();
for (int i = 0; i < n; i++) {
path.moveTo(cx, cy);

double p2X = cx + radius * Math.cos(angle);
double p2Y = cy + radius * Math.sin(angle);

double startAngle = angle - halfAngleIncrement;
double cp2X = cx + distantRadius * Math.cos(startAngle);
double cp2Y = cy + distantRadius * Math.sin(startAngle);

// p2 is at half point between cp2 and cp3
double cp3X = p2X + (p2X - cp2X);
double cp3Y = p2Y + (p2Y - cp2Y);

path.curveTo(cx, cy, cp2X, cp2Y, p2X, p2Y);
path.curveTo(cp3X, cp3Y, cx, cy, cx, cy);

angle += angleIncrement;
path.closePath();
}

return path;
}

public static Shape createHexagon(double cx, double cy, double radius) {
double cos60 = 0.5;
double sin60 = 0.86602540378443864676372317075294;
Expand Down Expand Up @@ -906,6 +937,7 @@ public static Shape createRabbit(double x, double y, double width, double height
epY = y + 0.9044799f * height;
path.curveTo(cp1X, cp1Y, cp2X, cp2Y, epX, epY);

path.closePath();
return path;
}

Expand Down Expand Up @@ -1178,6 +1210,7 @@ public static Shape createBat(double x, double y, double width, double height) {
epY = y + 0.8711912f * height;
path.curveTo(cp1X, cp1Y, cp2X, cp2Y, epX, epY);

path.closePath();
return path;
}

Expand Down Expand Up @@ -1418,6 +1451,7 @@ public static Shape createHeart(double x, double y, double width, double height)
cp1XLeft, cp1Y,
centerX, bottomY);

path.closePath();
return path;
}

Expand Down Expand Up @@ -1666,6 +1700,7 @@ public static Shape createKiwi(double x, double y, double width, double height)
epY = y + 0.4113421f * height;
path.curveTo(cp1X, cp1Y, cp2X, cp2Y, epX, epY);

path.closePath();
return path;
}

Expand Down

0 comments on commit 3c27c39

Please sign in to comment.