Skip to content

Commit 0a930f1

Browse files
committed
small improvements
1 parent 75fea20 commit 0a930f1

15 files changed

Lines changed: 128 additions & 105 deletions

File tree

src/main/java/pixelitor/filters/Filter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public String getName() {
109109
* Returns true if this filter can process grayscale (TYPE_BYTE_GRAY) images.
110110
*/
111111
public boolean supportsGray() {
112-
// override to return false if the filter only works with RGB images
112+
// override to return false if the filter only works with ARGB images
113113
return true;
114114
}
115115

src/main/java/pixelitor/filters/Stripes.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import java.awt.Graphics2D;
3131
import java.awt.Shape;
3232
import java.awt.geom.AffineTransform;
33-
import java.awt.geom.GeneralPath;
33+
import java.awt.geom.Path2D;
3434
import java.awt.geom.Rectangle2D;
3535
import java.awt.image.BufferedImage;
3636
import java.io.Serial;
@@ -58,6 +58,9 @@ public class Stripes extends ParametrizedFilter {
5858
@Serial
5959
private static final long serialVersionUID = 1L;
6060

61+
// an approximation of a sine-like arc using a cubic Bezier curve
62+
private static final double BEZIER_CONTROL_FACTOR = 0.552284749831;
63+
6164
private final IntChoiceParam type = new IntChoiceParam("Type", new Item[]{
6265
new Item("Straight", TYPE_STRAIGHT),
6366
new Item("Chevron", TYPE_CHEVRON),
@@ -174,7 +177,7 @@ private List<ShapeWithColor> createChevronShapes(int width, int height) {
174177

175178
// create a prototype centerline path for the chevron
176179
// it needs to be long enough to span the diagonal, so we add a buffer
177-
GeneralPath centerline = new GeneralPath();
180+
Path2D centerline = new Path2D.Double();
178181
double startX = -diagonal / 2.0 - wavelength;
179182
double endX = diagonal / 2.0 + wavelength;
180183
double currentX = startX;
@@ -189,7 +192,6 @@ private List<ShapeWithColor> createChevronShapes(int width, int height) {
189192
goUp = !goUp;
190193
}
191194

192-
// use BasicStroke to create a thick shape from the centerline
193195
Shape protoChevron = createStrokedShape(centerline, thickness);
194196

195197
return generateStripes(width, height, period, protoChevron);
@@ -212,9 +214,7 @@ private List<ShapeWithColor> createCurvedShapes(int width, int height) {
212214
double diagonal = Math.sqrt(width * width + height * height);
213215

214216
// create a prototype centerline path for the wave using Bezier curves
215-
GeneralPath centerline = new GeneralPath();
216-
// this constant provides a good approximation of a sine-like arc using a cubic Bezier curve
217-
final double BEZIER_CONTROL_FACTOR = 0.552284749831;
217+
Path2D centerline = new Path2D.Double();
218218

219219
double halfWavelength = wavelength / 2.0;
220220
double controlXOffset = halfWavelength * BEZIER_CONTROL_FACTOR;
@@ -247,9 +247,10 @@ private List<ShapeWithColor> createCurvedShapes(int width, int height) {
247247
return generateStripes(width, height, period, protoWave);
248248
}
249249

250-
private static Shape createStrokedShape(GeneralPath centerPath, int thickness) {
251-
// use BasicStroke to create a thick shape from the centerPath
252-
BasicStroke stroke = new BasicStroke(thickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);
250+
// Creates a thick shape from a centerline.
251+
private static Shape createStrokedShape(Path2D centerPath, int thickness) {
252+
// use a large miter limit to ensure that Chevron always has proper spikes
253+
BasicStroke stroke = new BasicStroke(thickness, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 100.0f);
253254
return stroke.createStrokedShape(centerPath);
254255
}
255256

src/main/java/pixelitor/filters/jhlabsproxies/JHDotsHalftone.java

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public class JHDotsHalftone extends ParametrizedFilter {
5353
private static final int GRID_TRIANGLE = 0;
5454
private static final int GRID_SQUARE = 1;
5555

56+
private static final double SQRT_2 = 1.4142135623730951;
57+
private static final double SQRT_3 = 1.7320508075688772;
58+
5659
private final RangeParam dotRadius = new RangeParam("Dot Radius", 1, 10, 100);
5760
private final IntChoiceParam shapeParam = new IntChoiceParam("Dot Shape", new Item[]{
5861
new Item("Circle", SHAPE_CIRCLE),
@@ -109,27 +112,35 @@ public BufferedImage transform(BufferedImage src, BufferedImage dest) {
109112
return filter.filter(src, dest);
110113
}
111114

115+
/**
116+
* Creates a mask image from the clustered dot matrix.
117+
*/
112118
private BufferedImage createMaskImage(BufferedImage src) {
113-
int matrixSize = 2 * dotRadius.getValue();
114-
int[][] matrix = genClusteredDotMatrix(
115-
matrixSize, 0.5, shapeParam.getValue());
116-
BufferedImage maskImage = ImageUtils.createImageWithSameCM(src, matrixSize, matrixSize);
119+
int maskSize = 2 * dotRadius.getValue();
120+
int[][] matrix = genClusteredDotMatrix(maskSize, shapeParam.getValue());
121+
BufferedImage maskImage = ImageUtils.createImageWithSameCM(src, maskSize, maskSize);
117122

118123
int[] maskPixels = ImageUtils.getPixels(maskImage);
119-
for (int y = 0; y < matrixSize; y++) {
120-
for (int x = 0; x < matrixSize; x++) {
124+
for (int y = 0; y < maskSize; y++) {
125+
for (int x = 0; x < maskSize; x++) {
121126
int threshold = matrix[x][y];
122-
maskPixels[x + y * matrixSize] = 0xFF_00_00_00 | threshold << 16 | threshold << 8 | threshold;
127+
maskPixels[x + y * maskSize] = 0xFF_00_00_00 | threshold << 16 | threshold << 8 | threshold;
123128
}
124129
}
125130

126131
return maskImage;
127132
}
128133

129-
private static int[][] genClusteredDotMatrix(int matrixSize, double dotSize, int shape) {
134+
/**
135+
* Creates a square matrix where dots are clustered together
136+
* to represent different intensity thresholds (priority orders).
137+
* As brightness increases, dots will appear in that order,
138+
* expanding outward, and growing into recognizable shapes.
139+
*/
140+
private static int[][] genClusteredDotMatrix(int matrixSize, int shape) {
130141
assert matrixSize % 2 == 0 : "matrixSize = " + matrixSize;
131-
assert dotSize > 0 && dotSize <= 1 : "dotSize = " + dotSize;
132142

143+
// stores a pixel's coordinates and its distance from the center
133144
record MPoint(int x, int y, double dist) {
134145
}
135146

@@ -149,54 +160,62 @@ record MPoint(int x, int y, double dist) {
149160
// sort the points by the distance to the center of the shape
150161
points.sort(Comparator.comparingDouble(p -> p.dist));
151162

152-
// assign threshold values
153-
for (int i = 0; i < matrixSize * matrixSize; i++) {
163+
// assign 0-255 threshold values
164+
int total = matrixSize * matrixSize;
165+
for (int i = 0; i < total; i++) {
154166
MPoint p = points.get(i);
155-
matrix[p.x][p.y] = (int) Math.round((double) i / (matrixSize * matrixSize) * 255);
167+
matrix[p.x][p.y] = (int) Math.round((double) i / total * 255);
156168
}
157169

158170
return matrix;
159171
}
160172

161173
private static double distanceToCenter(int shape, double dx, double dy) {
162174
return switch (shape) {
163-
case SHAPE_CIRCLE -> Math.sqrt(dx * dx + dy * dy);
164-
case SHAPE_SQUARE -> Math.max(Math.abs(dx), Math.abs(dy)); // Manhattan distance
165-
case SHAPE_DIAMOND -> Math.abs(dx) + Math.abs(dy); // Manhattan distance
175+
case SHAPE_CIRCLE -> Math.hypot(dx, dy);
176+
case SHAPE_SQUARE -> Math.max(Math.abs(dx), Math.abs(dy));
177+
case SHAPE_DIAMOND -> Math.abs(dx) + Math.abs(dy);
166178
case SHAPE_CROSS -> Math.min(Math.abs(dx), Math.abs(dy));
167179
case SHAPE_X -> {
168-
double distanceToFirstDiagonal = Math.abs(dx - dy) / Math.sqrt(2); // Distance to y = x
169-
double distanceToSecondDiagonal = Math.abs(dx + dy) / Math.sqrt(2); // Distance to y = -x
180+
double distanceToFirstDiagonal = Math.abs(dx - dy) / SQRT_2; // distance to y = x
181+
double distanceToSecondDiagonal = Math.abs(dx + dy) / SQRT_2; // distance to y = -x
170182
yield Math.min(distanceToFirstDiagonal, distanceToSecondDiagonal);
171183
}
172-
case SHAPE_TRIANGLE -> {
173-
// Equilateral triangle pointing upwards
174-
double a = 2.0 / Math.sqrt(3); // Scaling factor for unit triangle
175-
double dist1 = (Math.sqrt(3) * dx - dy) / 2;
176-
double dist2 = (-Math.sqrt(3) * dx - dy) / 2;
184+
case SHAPE_TRIANGLE -> { // equilateral triangle pointing upwards
185+
// distances to the triangle’s edges
186+
double dist1 = (SQRT_3 * dx - dy) / 2;
187+
double dist2 = (-SQRT_3 * dx - dy) / 2;
177188
double dist3 = dy;
178189

179-
yield Math.max(Math.max(dist1, dist2), dist3) / a;
190+
double scale = 2.0 / SQRT_3; // scaling factor for unit triangle
191+
yield Math.max(Math.max(dist1, dist2), dist3) / scale;
180192
}
181193
case SHAPE_HEXAGON -> {
182-
// Distance to center of a regular hexagon
183-
double q = (Math.sqrt(3) / 3 * dx - 1.0 / 3 * dy);
194+
// uses axial coordinates to calculate distance from the center of a regular hexagon
195+
double q = (SQRT_3 / 3 * dx - 1.0 / 3 * dy);
184196
double r = (2.0 / 3 * dy);
185197
double s = -q - r;
186198
yield (Math.abs(q) + Math.abs(r) + Math.abs(s)) / 2;
187199
}
188200
case SHAPE_OCTAGON -> {
189201
double absX = Math.abs(dx);
190202
double absY = Math.abs(dy);
191-
yield Math.max(absX, absY) + (Math.sqrt(2) - 1) * Math.min(absX, absY);
203+
yield Math.max(absX, absY) + (SQRT_2 - 1) * Math.min(absX, absY);
192204
}
193205
case SHAPE_STAR -> {
194-
double angle = Math.atan2(dy, dx) + 3 * Math.PI / 2;
195-
double radius = Math.sqrt(dx * dx + dy * dy);
206+
// uses polar coordinates to create a 5-pointed star
207+
double angle = Math.atan2(dy, dx) + 3 * Math.PI / 2; // orient the star to point up
208+
double radius = Math.hypot(dx, dy);
209+
// modulates the radius based on the angle to form the star's points
196210
double modifiedRadius = radius * (1 + 0.25 * Math.cos(5 * angle));
197211
yield modifiedRadius;
198212
}
199213
default -> throw new IllegalArgumentException("Invalid shape: " + shape);
200214
};
201215
}
216+
217+
@Override
218+
public boolean supportsGray() {
219+
return false;
220+
}
202221
}

src/main/java/pixelitor/filters/painters/TransformedRectangle.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 Laszlo Balazs-Csiki and Contributors
2+
* Copyright 2025 Laszlo Balazs-Csiki and Contributors
33
*
44
* This file is part of Pixelitor. Pixelitor is free software: you
55
* can redistribute it and/or modify it under the terms of the GNU
@@ -25,7 +25,7 @@
2525
import java.awt.Rectangle;
2626
import java.awt.Shape;
2727
import java.awt.geom.AffineTransform;
28-
import java.awt.geom.GeneralPath;
28+
import java.awt.geom.Path2D;
2929
import java.awt.geom.Point2D;
3030

3131
import static java.lang.Math.max;
@@ -56,7 +56,7 @@ public class TransformedRectangle implements Debuggable {
5656
private double bottomLeftY;
5757

5858
// Cached shape and bounding box of the transformed rectangle
59-
private GeneralPath cachedShape;
59+
private Path2D cachedShape;
6060
private Rectangle cachedBox;
6161

6262
public TransformedRectangle(Rectangle r,
@@ -137,7 +137,7 @@ public Shape asShape() {
137137
return cachedShape;
138138
}
139139

140-
cachedShape = new GeneralPath();
140+
cachedShape = new Path2D.Double();
141141
cachedShape.moveTo(topLeftX, topLeftY);
142142
cachedShape.lineTo(topRightX, topRightY);
143143
cachedShape.lineTo(bottomRightX, bottomRightY);

src/main/java/pixelitor/filters/painters/TransformedTextPainter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ private Shape calcUntransformedTextShape(Graphics2D g2) {
697697
boolean hasUnderline = UNDERLINE_ON.equals(attributes.get(UNDERLINE));
698698
boolean hasStrikeThrough = STRIKETHROUGH_ON.equals(attributes.get(STRIKETHROUGH));
699699

700-
GeneralPath fullShape = new GeneralPath();
700+
Path2D fullShape = new Path2D.Float();
701701

702702
float currentY = effectsPadding + metrics.getAscent();
703703

@@ -754,7 +754,7 @@ private Shape getLineShape(String line, FontRenderContext frc, FontMetrics metri
754754
return glyphsOutline;
755755
}
756756

757-
// uses Area instead of GeneralPath.append to ensure that
757+
// uses Area instead of Path2D.append to ensure that
758758
// self-intersecting paths don't create unfilled holes
759759
// and effects are painted as if the underline/strikethrough
760760
// was part of the font

src/main/java/pixelitor/selection/SelectionType.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import java.awt.Shape;
3131
import java.awt.geom.Area;
3232
import java.awt.geom.Ellipse2D;
33-
import java.awt.geom.GeneralPath;
33+
import java.awt.geom.Path2D;
3434
import java.awt.geom.Rectangle2D;
3535
import java.awt.image.BufferedImage;
3636
import java.util.Stack;
@@ -67,13 +67,13 @@ public Shape createShapeFromEvent(PMouseEvent event, Shape oldShape) {
6767
}, LASSO("Freehand") {
6868
@Override
6969
public Shape createShapeFromDrag(Drag drag, Shape oldShape) {
70-
if (oldShape instanceof GeneralPath gp) {
70+
if (oldShape instanceof Path2D path) {
7171
// extend the existing path
72-
gp.lineTo(drag.getEndX(), drag.getEndY());
73-
return gp;
72+
path.lineTo(drag.getEndX(), drag.getEndY());
73+
return path;
7474
} else {
7575
// start a new path
76-
GeneralPath p = new GeneralPath();
76+
Path2D p = new Path2D.Double();
7777
p.moveTo(drag.getStartX(), drag.getStartY());
7878
p.lineTo(drag.getEndX(), drag.getEndY());
7979
return p;
@@ -92,13 +92,13 @@ public Shape createShapeFromDrag(Drag drag, Shape oldShape) {
9292

9393
@Override
9494
public Shape createShapeFromEvent(PMouseEvent pe, Shape oldShape) {
95-
if (oldShape instanceof GeneralPath gp) {
95+
if (oldShape instanceof Path2D path) {
9696
// extend the existing path
97-
gp.lineTo(pe.getImX(), pe.getImY());
98-
return gp;
97+
path.lineTo(pe.getImX(), pe.getImY());
98+
return path;
9999
} else {
100100
// start a new path
101-
GeneralPath p = new GeneralPath();
101+
Path2D p = new Path2D.Double();
102102
p.moveTo(pe.getImX(), pe.getImY());
103103
// first point only defines the start, no line yet
104104
return p;

src/main/java/pixelitor/tools/pen/Path.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import java.awt.Rectangle;
3535
import java.awt.Shape;
3636
import java.awt.geom.AffineTransform;
37-
import java.awt.geom.GeneralPath;
3837
import java.awt.geom.Path2D;
3938
import java.awt.geom.PathIterator;
4039
import java.io.IOException;
@@ -135,15 +134,15 @@ public DraggablePoint findHandleAt(double x, double y, boolean altDown) {
135134
}
136135

137136
public Path2D toImageSpaceShape() {
138-
GeneralPath path = new GeneralPath();
137+
Path2D path = new Path2D.Double();
139138
for (SubPath subPath : subPaths) {
140139
subPath.addToImageSpaceShape(path);
141140
}
142141
return path;
143142
}
144143

145144
public Shape toComponentSpaceShape() {
146-
GeneralPath path = new GeneralPath();
145+
Path2D path = new Path2D.Double();
147146
for (SubPath subPath : subPaths) {
148147
subPath.addToComponentSpaceShape(path);
149148
}

0 commit comments

Comments
 (0)