Skip to content

Commit

Permalink
SVG export in "Cubes Pattern"
Browse files Browse the repository at this point in the history
  • Loading branch information
lbalazscs committed Jan 17, 2025
1 parent 51fa034 commit 64118ab
Show file tree
Hide file tree
Showing 15 changed files with 389 additions and 225 deletions.
12 changes: 6 additions & 6 deletions src/main/java/pixelitor/filters/CircleWeave.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024 Laszlo Balazs-Csiki and Contributors
* Copyright 2025 Laszlo Balazs-Csiki and Contributors
*
* This file is part of Pixelitor. Pixelitor is free software: you
* can redistribute it and/or modify it under the terms of the GNU
Expand Down Expand Up @@ -60,7 +60,7 @@ protected Path2D createCurve(int width, int height) {

int m = mParam.getValue();
int type = typeParam.getValue();
boolean nonlin = hasNonlinDistort();
boolean nonlin = transform.hasNonlinDistort();

return switch (type) {
case TYPE_MYSTIC_ROSE -> createMysticRose(points, nonlin);
Expand Down Expand Up @@ -88,8 +88,8 @@ private static Path2D createMysticRose(Point2D[] points, boolean nonlin) {

private Path2D createCircles(Point2D[] points, boolean nonlin, int width, int height) {
double radius = getRadius(width, height) / 2.0;
double cx = width * center.getRelativeX();
double cy = height * center.getRelativeY();
double cx = transform.getCx(width);
double cy = transform.getCy(height);
Point2D imageCenter = new Point2D.Double(cx, cy);

Path2D path = new Path2D.Double();
Expand Down Expand Up @@ -120,8 +120,8 @@ private static Path2D createTimesTable(int m, Point2D[] points, boolean nonlin)
private Point2D[] calcPoints(int width, int height, int numPoints) {
Point2D[] points = new Point2D[numPoints];
double r = getRadius(width, height);
double cx = width * center.getRelativeX();
double cy = height * center.getRelativeY();
double cx = transform.getCx(width);
double cy = transform.getCy(height);

double angleIncrement = 2 * Math.PI / numPoints;
for (int i = 0; i < points.length; i++) {
Expand Down
192 changes: 106 additions & 86 deletions src/main/java/pixelitor/filters/Cubes.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,29 @@

package pixelitor.filters;

import pixelitor.filters.gui.*;
import pixelitor.Canvas;
import pixelitor.Views;
import pixelitor.filters.gui.ColorParam;
import pixelitor.filters.gui.FilterButtonModel;
import pixelitor.filters.gui.IntChoiceParam;
import pixelitor.filters.gui.IntChoiceParam.Item;
import pixelitor.filters.gui.RangeParam;
import pixelitor.filters.util.ShapeWithColor;
import pixelitor.io.FileIO;
import pixelitor.utils.Distortion;
import pixelitor.utils.ImageUtils;
import pixelitor.utils.NonlinTransform;
import pixelitor.utils.Transform;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.Serial;
import java.util.ArrayList;
import java.util.List;

import static java.awt.Color.BLACK;
import static java.awt.Color.GRAY;
Expand All @@ -39,7 +48,6 @@
import static java.awt.RenderingHints.KEY_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
import static pixelitor.filters.gui.TransparencyPolicy.USER_ONLY_TRANSPARENCY;
import static pixelitor.utils.NonlinTransform.NONE;

/**
* The Render/Geometry/Cubes Pattern filter.
Expand All @@ -63,7 +71,6 @@ public class Cubes extends ParametrizedFilter {
new Item("Corner Cut 3", TYPE_CORNER_CUT3),
new Item("Interlocking", TYPE_INTERLOCKING),
});
// private final RangeParam hollowDepthParam = new RangeParam("Hollow Depth (%)", 1, 50, 99);

private final RangeParam sizeParam = new RangeParam("Size", 5, 20, 200);
private final ColorParam topColorParam = new ColorParam("Top Color", WHITE, USER_ONLY_TRANSPARENCY);
Expand All @@ -72,91 +79,91 @@ public class Cubes extends ParametrizedFilter {
private final RangeParam edgeWidthParam = new RangeParam("Edge Width", 0, 0, 10);
private final ColorParam edgeColorParam = new ColorParam("Edge Color", BLACK, USER_ONLY_TRANSPARENCY);

private final GroupedRangeParam scale = new GroupedRangeParam("Scale (%)", 1, 100, 500);
private final AngleParam rotate = new AngleParam("Rotate", 0);
private final EnumParam<NonlinTransform> distortType = NonlinTransform.asParam();
private final RangeParam distortAmount = NonlinTransform.createAmountParam();
private final Transform transform = new Transform();

public Cubes() {
super(false);

distortType.setupEnableOtherIf(distortAmount, NonlinTransform::hasAmount);
edgeWidthParam.setupEnableOtherIfNotZero(edgeColorParam);
// typeParam.setupEnableOtherIf(hollowDepthParam, selectedType ->
// selectedType.valueIs(TYPE_HOLLOWED));

setParams(
typeParam,
sizeParam,
// hollowDepthParam,
topColorParam,
leftColorParam,
rightColorParam,
edgeWidthParam,
edgeColorParam,
new DialogParam("Transform",
distortType, distortAmount, rotate, scale)
);
transform.createDialogParam()
).withAction(FilterButtonModel.createExportSvg(this::exportSVG));
}

@Override
public BufferedImage transform(BufferedImage src, BufferedImage dest) {
dest = ImageUtils.createImageWithSameCM(src);

int width = dest.getWidth();
int height = dest.getHeight();

Graphics2D g = dest.createGraphics();
g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);

double sx = scale.getPercentage(0);
double sy = scale.getPercentage(0);
double angle = rotate.getValueInRadians();
double cx = width / 2.0;
double cy = height / 2.0;
if (sx != 1 || sy != 1) {
g.translate(cx, cy);
g.scale(sx, sy);
g.translate(-cx, -cy);
}
if (angle != 0) {
g.rotate(angle, cx, cy);
AffineTransform at = transform.calcAffineTransform(width, height);
if (at != null) {
g.transform(at);
}

NonlinTransform distortion = distortType.getSelected();
double amount = distortAmount.getValueAsDouble();
Point2D pivotPoint = new Point2D.Double(cx, cy);

float edgeWidth = edgeWidthParam.getValueAsFloat();
boolean renderEdges = edgeWidth > 0;
Color edgeColor = null;
if (renderEdges) {
if (edgeWidth > 0) {
g.setStroke(new BasicStroke(edgeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
edgeColor = edgeColorParam.getColor();
}

List<ShapeWithColor> shapes = createDistortedShapes(width, height);

for (ShapeWithColor shapeWithColor : shapes) {
g.setColor(shapeWithColor.color());
g.fill(shapeWithColor.shape());

if (edgeWidth > 0) {
g.setColor(edgeColorParam.getColor());
g.draw(shapeWithColor.shape());
}
}

g.dispose();
return dest;
}

private List<ShapeWithColor> createDistortedShapes(int width, int height) {
List<ShapeWithColor> shapes = createShapes(width, height);

if (transform.hasNonlinDistort()) {
Distortion distortion = transform.createDistortion(width, height);
shapes = shapes.stream()
.map(sc -> sc.distort(distortion))
.toList();
}
return shapes;
}

private List<ShapeWithColor> createShapes(int width, int height) {
List<ShapeWithColor> shapes = new ArrayList<>();

Color topColor = topColorParam.getColor();
Color rightColor = rightColorParam.getColor();
Color leftColor = leftColorParam.getColor();

int type = typeParam.getSelected().getValue();
boolean interlocking = type == TYPE_INTERLOCKING;
// double hollowDepth = hollowDepthParam.getPercentage();

double size = sizeParam.getValueAsDouble();

double longer = size * Math.cos(Math.PI / 6);
double shorter = size * Math.sin(Math.PI / 6);

double horizontalSpacing = interlocking ? 3 * longer : 2 * longer;
double verticalSpacing = size + shorter;
if (interlocking) {
verticalSpacing = size * 0.75;
}
int numCubesX = (int) (width / horizontalSpacing) + 2;
double verticalSpacing = interlocking ? size * 0.75 : (size + shorter);

int numCubesY = (int) (height / verticalSpacing)
+ (interlocking ? 3 : 2);
int numCubesX = (int) (width / horizontalSpacing) + 2;
int numCubesY = (int) (height / verticalSpacing) + (interlocking ? 3 : 2);

int numCornerCuts = switch (type) {
case TYPE_BASIC, TYPE_INTERLOCKING -> 0;
Expand All @@ -166,52 +173,62 @@ public BufferedImage transform(BufferedImage src, BufferedImage dest) {
default -> throw new IllegalStateException("Unexpected value: " + type);
};

double verOffset = interlocking ? -size / 2 : 0;
double moveHorOffset = transform.getHorOffset(width);
double moveVerOffset = transform.getVerOffset(height);

double verOffset = moveVerOffset + (interlocking ? -size / 2 : 0);
for (int row = 0; row < numCubesY; row++) {
double horOffset = row % 2 == 0 ? 0 : longer;
double horOffset = moveHorOffset + (row % 2 == 0 ? 0 : longer);
if (interlocking) {
horOffset *= 1.5;
}

for (int col = 0; col < numCubesX; col++) {
// base point (shared vertex)
double baseX = horOffset + col * horizontalSpacing;
double baseY = verOffset + row * verticalSpacing;

Path2D topFace = createTop(baseX, baseY, longer, shorter, interlocking, size);
renderShape(topFace, g, distortion, pivotPoint, amount, width, height, topColor, edgeColor);

Path2D rightFace = createRight(baseX, baseY, longer, shorter, interlocking, size);
renderShape(rightFace, g, distortion, pivotPoint, amount, width, height, rightColor, edgeColor);
addCubeShapes(shapes, baseX, baseY, longer, shorter, interlocking, size,
topColor, rightColor, leftColor, numCornerCuts);
}
}

Path2D leftFace = createLeft(baseX, baseY, longer, shorter, interlocking, size);
renderShape(leftFace, g, distortion, pivotPoint, amount, width, height, leftColor, edgeColor);
return shapes;
}

for (int cut = 0; cut < numCornerCuts; cut++) {
double cutRatio = 1.0 - (double) (cut + 1) / (numCornerCuts + 1);
private void addCubeShapes(List<ShapeWithColor> shapes,
double baseX, double baseY,
double longer, double shorter,
boolean interlocking, double size,
Color topColor, Color rightColor, Color leftColor,
int numCornerCuts) {

AffineTransform carvedCube = new AffineTransform();
carvedCube.translate(baseX, baseY);
if (cut % 2 == 0) {
carvedCube.rotate(Math.PI);
}
carvedCube.scale(cutRatio, cutRatio);
carvedCube.translate(-baseX, -baseY);
Path2D topFace = createTop(baseX, baseY, longer, shorter, interlocking, size);
Path2D rightFace = createRight(baseX, baseY, longer, shorter, interlocking, size);
Path2D leftFace = createLeft(baseX, baseY, longer, shorter, interlocking, size);

Shape miniTopFace = carvedCube.createTransformedShape(topFace);
renderShape(miniTopFace, g, distortion, pivotPoint, amount, width, height, topColor, edgeColor);
shapes.add(new ShapeWithColor(topFace, topColor));
shapes.add(new ShapeWithColor(rightFace, rightColor));
shapes.add(new ShapeWithColor(leftFace, leftColor));

Shape miniLeftFace = carvedCube.createTransformedShape(leftFace);
renderShape(miniLeftFace, g, distortion, pivotPoint, amount, width, height, leftColor, edgeColor);
for (int cut = 0; cut < numCornerCuts; cut++) {
double cutRatio = 1.0 - (double) (cut + 1) / (numCornerCuts + 1);

Shape miniRightFace = carvedCube.createTransformedShape(rightFace);
renderShape(miniRightFace, g, distortion, pivotPoint, amount, width, height, rightColor, edgeColor);
}
// Shapes.fillCircle(baseX, baseY, 5, Color.RED, g);
AffineTransform carvedCube = new AffineTransform();
carvedCube.translate(baseX, baseY);
if (cut % 2 == 0) {
carvedCube.rotate(Math.PI);
}
}
carvedCube.scale(cutRatio, cutRatio);
carvedCube.translate(-baseX, -baseY);

g.dispose();
return dest;
Shape miniTopFace = carvedCube.createTransformedShape(topFace);
Shape miniRightFace = carvedCube.createTransformedShape(rightFace);
Shape miniLeftFace = carvedCube.createTransformedShape(leftFace);

shapes.add(new ShapeWithColor(miniTopFace, topColor));
shapes.add(new ShapeWithColor(miniRightFace, rightColor));
shapes.add(new ShapeWithColor(miniLeftFace, leftColor));
}
}

private static Path2D createTop(double baseX, double baseY, double longer, double shorter, boolean interlocking, double size) {
Expand Down Expand Up @@ -262,17 +279,20 @@ private static Path2D createLeft(double baseX, double baseY, double longer, doub
return left;
}

private static void renderShape(Shape shape, Graphics2D g, NonlinTransform distortion, Point2D pivotPoint, double amount, int width, int height, Color color, Color edgeColor) {
if (distortion != NONE) {
shape = distortion.transform(shape, pivotPoint, amount, width, height);
}
g.setColor(color);
g.fill(shape);

if (edgeColor != null) {
g.setColor(edgeColor);
g.draw(shape);
private void exportSVG() {
Canvas canvas = Views.getActiveComp().getCanvas();
int width = canvas.getWidth();
int height = canvas.getHeight();
List<ShapeWithColor> shapes = createDistortedShapes(width, height);
AffineTransform at = transform.calcAffineTransform(width, height);
if (at != null) {
shapes = shapes.stream()
.map(s -> s.transform(at))
.toList();
}
String svgContent = ShapeWithColor.createSvgContent(shapes, canvas, null,
edgeWidthParam.getValue(), edgeColorParam.getColor());
FileIO.saveSVG(svgContent, "cubes.svg");
}

@Override
Expand Down
Loading

0 comments on commit 64118ab

Please sign in to comment.