output = new Output<>("output", Turtle.class, new Turtle());
+
+ public PatternOnPath() {
+ super("PatternOnPath");
+ addVariable(pattern);
+ addVariable(path);
+ addVariable(count);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle sum = new Turtle();
+ Turtle myPattern = pattern.getValue();
+ Turtle myPath = path.getValue();
+ int c = count.getValue().intValue();
+ if(c>0) {
+ TurtlePathWalker walker = new TurtlePathWalker(myPath);
+ double pDistance = walker.getDrawDistance();
+ double step = (pDistance==0) ? 1 : pDistance/(double)c;
+ while(!walker.isDone()) {
+ Point2D p = walker.walk(step);
+ Turtle stamp = new Turtle(myPattern);
+ stamp.translate(p.x,p.y);
+ sum.add(stamp);
+ setComplete((int)(100*walker.getTSum()/pDistance));
+ }
+ }
+ setComplete(100);
+ output.send(sum);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PointOnPath.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PointOnPath.java
new file mode 100644
index 000000000..0d463737c
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PointOnPath.java
@@ -0,0 +1,68 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.convenience.Point2D;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.makelangelo.turtle.TurtlePathWalker;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+
+/**
+ * (px,py) = path(index), where path(0) is the start and path(path.length) is the end.
+ * (nx,ny) = the approximate normal at path(index). This is approximated by finding
+ * normalize(path(index+epsilon) - path(index))
+ * for some very small epsilon, and taking into account the start and end of the path.
+ * If the path is of zero-length then (0,0) will be generated.
+ * path.length can be obtained from LoadTurtle.
+ */
+public class PointOnPath extends Node {
+ private final Input path = new Input<>("path", Turtle.class, new Turtle());
+ private final Input index = new Input<>("index", Number.class, 0);
+ private final Output px = new Output<>("px", Number.class, 0);
+ private final Output py = new Output<>("py", Number.class, 0);
+ private final Output nx = new Output<>("nx", Number.class, 0);
+ private final Output ny = new Output<>("ny", Number.class, 0);
+
+ public PointOnPath() {
+ super("PointOnPath");
+ addVariable(path);
+ addVariable(px);
+ addVariable(py);
+ addVariable(nx);
+ addVariable(ny);
+ }
+
+ private static final double EPSILON=0.00001;
+
+ @Override
+ public void update() {
+ Turtle myPath = path.getValue();
+ double total = myPath.getDrawDistance();
+ double c0 = index.getValue().doubleValue();
+ if(total==0 || c0 <= 0) {
+ px.send(0);
+ px.send(0);
+ nx.send(1);
+ ny.send(0);
+ return;
+ }
+
+ double c1 = c0 + EPSILON;
+ if(c1>total) {
+ c1 = total;
+ c0 = total - EPSILON;
+ }
+ TurtlePathWalker walker = new TurtlePathWalker(myPath);
+ Point2D p0 = walker.walk(c0);
+ Point2D p1 = walker.walk(c1-c0);
+ double dx = p1.x - p0.x;
+ double dy = p1.y - p0.y;
+ Point2D n = new Point2D(dx,dy);
+ n.normalize();
+ px.send(p0.x);
+ px.send(p0.y);
+ nx.send(n.x);
+ ny.send(n.y);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PrintTurtle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PrintTurtle.java
new file mode 100644
index 000000000..5983c71cb
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/PrintTurtle.java
@@ -0,0 +1,243 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.donatello.graphview.GraphViewPanel;
+import com.marginallyclever.makelangelo.turtle.MovementType;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.makelangelo.turtle.TurtleMove;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.nodegraphcore.PrintWithGraphics;
+import com.marginallyclever.nodegraphcore.port.Input;
+
+import java.awt.*;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Print the {@link Turtle}'s path behind the {@link Node}s.
+ * On {@link #update()} pass over the {@link Turtle} once and build a list of polylines for faster rendering.
+ * This is done using a {@link PolylineBuilder} which also optimizes to remove points on nearly straight lines.
+ */
+public class PrintTurtle extends Node implements PrintWithGraphics {
+ //private static final Logger logger = LoggerFactory.getLogger(PrintTurtle.class);
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Input px = new Input<>("X",Number.class,0);
+ private final Input py = new Input<>("Y",Number.class,0);
+ private final Input showTravel = new Input<>("show travel",Boolean.class,false);
+ private final Input travelColor = new Input<>("travel color",Color.class,Color.GREEN);
+ private final Input lineThickness = new Input<>("line thickness",Number.class,1);
+ private final List polylines = new ArrayList<>();
+
+ /**
+ * A poly line to draw.
+ */
+ static class Polyline {
+ private final int[] x;
+ private final int[] y;
+ private final int n;
+ private final Color color;
+
+ public Polyline(int[] x, int[] y, int n, Color color) {
+ if(x.length!=y.length) throw new IllegalArgumentException("x and y must be the same length");
+ if(n=this.x.length) {
+ // grow the buffer if needed.
+ int[] newX = new int[this.x.length*2];
+ int[] newY = new int[this.y.length*2];
+ System.arraycopy(this.x,0,newX,0,n);
+ System.arraycopy(this.y,0,newY,0,n);
+ this.x = newX;
+ this.y = newY;
+ }
+ this.x[n] = x;
+ this.y[n] = y;
+ n++;
+ }
+
+ /**
+ * Build a {@link Polyline} with the given color. This is the same as calling {@link #compile(Color, int)}
+ * with a deviation of 10 degrees.
+ * @param color the color of the line.
+ * @return a {@link Polyline} with the given color.
+ */
+ public Polyline compile(Color color) {
+ return compile(color,10);
+ }
+
+ /**
+ * Build a {@link Polyline} with the given color. Remove any points that form a nearly straight line.
+ * @param color the color of the line.
+ * @param deviationDegrees the maximum deviation in degrees between two lines to be considered a straight line.
+ * @return a {@link Polyline} with the given color.
+ */
+ public Polyline compile(Color color, int deviationDegrees) {
+ if(n<3) {
+ return new Polyline(x.clone(), y.clone(), n, color);
+ }
+
+ // examine the buffers and remove any points that form a nearly straight line.
+ // use a dot product to determine if the angle between the two lines is less than `maxDeviation` degrees.
+ var nx = new int[n];
+ var ny = new int[n];
+ int j=0;
+ nx[j] = x[0];
+ ny[j] = y[0];
+ j++;
+
+ var maxDeviation = Math.toRadians(deviationDegrees);
+ var x0 = x[0];
+ var y0 = y[0];
+ var x1 = x[1];
+ var y1 = y[1];
+ for(int i=2;imaxDeviation) {
+ // otherwise save point 1
+ nx[j] = x1;
+ ny[j] = y1;
+ j++;
+ x0 = x1;
+ y0 = y1;
+ }
+ // move on to the next point.
+ x1 = x2;
+ y1 = y2;
+ }
+ nx[j] = x1;
+ ny[j] = y1;
+ j++;
+ //if(j p.draw(g2));
+ }
+
+ private void generatePolylines(Turtle myTurtle) {
+ int size = myTurtle.history.size();
+ int count = 0;
+
+ setComplete(0);
+
+ // where we're at in the drawing (to check if we're between first & last)
+ boolean showPenUp = showTravel.getValue();
+ TurtleMove previousMove = null;
+
+ Color upColor = travelColor.getValue();
+ Color downColor = new Color(0,0,0);
+ PolylineBuilder builder = new PolylineBuilder();
+ builder.add(0,0);
+ try {
+ for (TurtleMove m : myTurtle.history) {
+ if(m==null) throw new NullPointerException();
+ if(m.type == MovementType.TOOL_CHANGE) {
+ downColor = m.getColor();
+ continue;
+ }
+ if ( previousMove != null) {
+ if( previousMove.type != m.type ) {
+ polylines.add(builder.compile(previousMove.type == MovementType.TRAVEL ? upColor : downColor));
+ builder.clear();
+ builder.add((int) previousMove.x, (int) previousMove.y);
+ }
+ if ((m.type == MovementType.TRAVEL && showPenUp) || m.type == MovementType.DRAW_LINE) {
+ builder.add((int) m.x, (int) m.y);
+ }
+ }
+ previousMove = m;
+ setComplete((int) (100.0 * count++ / size));
+ }
+ if(builder.n>0 && previousMove!=null) {
+ polylines.add(builder.compile(previousMove.type == MovementType.TRAVEL ? upColor : downColor));
+ }
+ }
+ catch(Exception ignored) {}
+ setComplete(100);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/SaveTurtle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/SaveTurtle.java
new file mode 100644
index 000000000..1d02a6c58
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/SaveTurtle.java
@@ -0,0 +1,41 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.plotter.plottersettings.PlotterSettings;
+import com.marginallyclever.makelangelo.plotter.plottersettings.PlotterSettingsManager;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.makelangelo.makeart.io.TurtleFactory;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Save a {@link Turtle} to a file.
+ */
+public class SaveTurtle extends Node {
+
+ private static final Logger logger = LoggerFactory.getLogger(SaveTurtle.class);
+
+ private final Input filename = new Input<>("filename",String.class,null);
+ private final Output turtle = new Output<>("turtle", Turtle.class,new Turtle());
+
+ public SaveTurtle() {
+ super("SaveTurtle");
+ addVariable(filename);
+ addVariable(turtle);
+ }
+
+ @Override
+ public void update() {
+ if(filename.getValue().isEmpty()) return;
+
+ try {
+ PlotterSettings settings = PlotterSettingsManager.buildMakelangelo5();
+ TurtleFactory.save(turtle.getValue(),filename.getValue(),settings);
+ } catch (Exception e) {
+ logger.warn("Failed to update, ignoring", e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/Spiral.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/Spiral.java
new file mode 100644
index 000000000..b0d900877
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/Spiral.java
@@ -0,0 +1,76 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.makelangelo.turtle.TurtlePathWalker;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+
+/**
+ * Warp an existing path into a spiral.
+ */
+public class Spiral extends Node {
+ private final Input source = new Input<>("turtle", Turtle.class, new Turtle());
+ private final Input r0 = new Input<>("r0", Number.class, 1);
+ private final Input dr = new Input<>("dr", Number.class, 1);
+ private final Input stepSize = new Input<>("stepSize", Number.class, 1);
+ private final Output output = new Output<>("output", Turtle.class, new Turtle());
+
+ public Spiral() {
+ super("Spiral");
+ addVariable(source);
+ addVariable(r0);
+ addVariable(dr);
+ addVariable(stepSize);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ try {
+ var r0Value = r0.getValue().doubleValue();
+ var drValue = dr.getValue().doubleValue();
+ var turtle = source.getValue();
+ TurtlePathWalker pathWalker = new TurtlePathWalker(turtle);
+ double dist = turtle.getBounds().width;
+ var pace = Math.max(0.0001, stepSize.getValue().doubleValue());
+
+ double curveSum = 0;
+ double px,py;
+
+ var result = new Turtle();
+ double t = 0.0; // the distance along the curve
+ setComplete(0);
+ while(!pathWalker.isDone()) {
+ // get the next point
+ var p2 = pathWalker.walk(pace);
+ // the x of the original path is the distance forward
+ var dt = p2.x-t;
+ t = p2.x;
+ // radius at this point
+ double rN = r0Value + drValue * (curveSum/(2*Math.PI));
+ // forward direction
+ double nx = Math.cos(curveSum);
+ double ny = Math.sin(curveSum);
+ // position
+ px = rN * nx;
+ py = rN * ny;
+ // dt is the arc length of the curve between the last point and this point
+ // the angle represented by dt and rN is calculated as follows:
+ double dtAngle = dt / rN;
+ curveSum += dtAngle;
+ // the y of the original path is the distance sideways
+ double tx = p2.y * nx;
+ double ty = p2.y * ny;
+
+ result.moveTo(px+tx,py+ty);
+ result.penDown();
+ setComplete((int) (t / dist * 100));
+ }
+ setComplete(100);
+ output.send(result);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TransformTurtle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TransformTurtle.java
new file mode 100644
index 000000000..61416648a
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TransformTurtle.java
@@ -0,0 +1,40 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+
+/**
+ * Transform a {@link Turtle} by scaling, rotating, and translating it.
+ */
+public class TransformTurtle extends Node {
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Input sx = new Input<>("scale x",Number.class,1);
+ private final Input sy = new Input<>("scale y",Number.class,1);
+ private final Input rotate = new Input<>("rotate degrees",Number.class,0);
+ private final Input tx = new Input<>("translate x",Number.class,0);
+ private final Input ty = new Input<>("translate y",Number.class,0);
+ private final Output output = new Output<>("output", Turtle.class,new Turtle());
+
+ public TransformTurtle() {
+ super("TransformTurtle");
+ addVariable(turtle);
+ addVariable(sx);
+ addVariable(sy);
+ addVariable(rotate);
+ addVariable(tx);
+ addVariable(ty);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle input = turtle.getValue();
+ Turtle moved = new Turtle(input);
+ moved.scale(sx.getValue().doubleValue(),sy.getValue().doubleValue());
+ moved.rotate(rotate.getValue().doubleValue());
+ moved.translate(tx.getValue().doubleValue(),ty.getValue().doubleValue());
+ output.send(moved);
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TruchetTiles.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TruchetTiles.java
new file mode 100644
index 000000000..396035ee0
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TruchetTiles.java
@@ -0,0 +1,69 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.makeart.truchet.TruchetTile;
+import com.marginallyclever.makelangelo.makeart.truchet.TruchetTileFactory;
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Create a basic Truchet tile pattern from an image. the intensity of the image decides the tile type.
+ */
+public class TruchetTiles extends Node {
+ Input source = new Input<>("Source", BufferedImage.class, new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB));
+ Input spaceBetweenLines = new Input<>("Spacing", Number.class, 10);
+ Input linesPerTileCount = new Input<>("Qty", Number.class, 10);
+ Output output = new Output<>("Output", Turtle.class, new Turtle());
+
+ public TruchetTiles() {
+ super("TruchetTiles");
+ addVariable(source);
+ addVariable(spaceBetweenLines);
+ addVariable(linesPerTileCount);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ var img = source.getValue();
+ int space = Math.max(1,spaceBetweenLines.getValue().intValue());
+ int lines = Math.max(1,linesPerTileCount.getValue().intValue());
+ int tileSize = space * lines;
+
+ try {
+ var c = img.getColorModel().getNumComponents();
+ var raster = img.getRaster();
+ int []pixels = new int[c];
+
+ Turtle turtle = new Turtle();
+ List ttgList = new ArrayList<>();
+
+ for(int y=0;y128?0:1,turtle,space,lines));
+ }
+ }
+
+ if(!ttgList.isEmpty()) {
+ var i = ttgList.iterator();
+ for(int y=0;y {
+ @Override
+ public Object toJSON(Object object) throws JSONException {
+ JSONObject json = new JSONObject();
+ Turtle turtle = (Turtle)object;
+ // for a complete snapshot, capture all the instance details, too.
+ return json;
+ }
+
+ @Override
+ public Turtle fromJSON(Object object) throws JSONException {
+ JSONObject json = (JSONObject)object;
+ Turtle turtle = new Turtle();
+ // for a complete snapshot, restore all the instance details, too.
+ return turtle;
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToBufferedImage.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToBufferedImage.java
new file mode 100644
index 000000000..3fd12ef71
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToBufferedImage.java
@@ -0,0 +1,69 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.makelangelo.turtle.TurtleMove;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+
+/**
+ * Convert a {@link Turtle} to a {@link BufferedImage}.
+ */
+public class TurtleToBufferedImage extends Node {
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Output output = new Output<>("output", BufferedImage.class, new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB));
+
+ public TurtleToBufferedImage() {
+ super("TurtleToBufferedImage");
+ addVariable(turtle);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle myTurtle = turtle.getValue();
+ if(myTurtle!=null && !myTurtle.history.isEmpty()) {
+ Rectangle2D r = myTurtle.getBounds();
+ int h = (int)Math.ceil(r.getHeight());
+ int w = (int)Math.ceil(r.getWidth());
+ BufferedImage img = new BufferedImage(w,h,BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D)img.createGraphics();
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
+ g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_PURE);
+ g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
+ g.translate(-r.getX(),-r.getY());
+
+ TurtleMove previousMove = null;
+ Color downColor = Color.BLACK;
+
+ for (TurtleMove m : myTurtle.history) {
+ if (m == null) throw new NullPointerException();
+
+ switch (m.type) {
+ case TRAVEL -> {
+ previousMove = m;
+ }
+ case DRAW_LINE -> {
+ if (previousMove != null) {
+ g.setColor(downColor);
+ g.drawLine((int) previousMove.x, (int) previousMove.y, (int) m.x, (int) m.y);
+ }
+ previousMove = m;
+ }
+ case TOOL_CHANGE -> {
+ downColor = m.getColor();
+ g.setStroke(new BasicStroke((int) m.getDiameter()));
+ }
+ }
+ }
+ output.send(img);
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToRectangle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToRectangle.java
new file mode 100644
index 000000000..51bda6431
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/TurtleToRectangle.java
@@ -0,0 +1,33 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.Node;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * Returns the bounding box of the turtle's path.
+ * @author Dan Royer
+ * @since 2022-04-14
+ */
+public class TurtleToRectangle extends Node {
+ private final Input turtle = new Input<>("turtle", Turtle.class,new Turtle());
+ private final Output output = new Output<>("output", Rectangle2D.class, new Rectangle(0,0,0,0));
+
+ public TurtleToRectangle() {
+ super("TurtleToRectangle");
+ addVariable(turtle);
+ addVariable(output);
+ }
+
+ @Override
+ public void update() {
+ Turtle myTurtle = turtle.getValue();
+ if(myTurtle!=null ) {
+ output.send(myTurtle.getBounds());
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Circle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Circle.java
new file mode 100644
index 000000000..26d68998c
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Circle.java
@@ -0,0 +1,41 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Circle extends Node {
+ private static final Logger logger = LoggerFactory.getLogger(Circle.class);
+
+ private final Input radius = new Input<>("radius", Number.class, 50);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+
+ public Circle() {
+ super("Circle");
+ addVariable(radius);
+ addVariable(contents);
+ }
+
+ @Override
+ public void update() {
+ try {
+ Turtle t = new Turtle();
+ double r = radius.getValue().doubleValue()/2.0;
+ double circumference = Math.ceil(Math.PI*r*2.0);
+ t.jumpTo(r,0);
+ for(int i=0;i x0 = new Input<>("x0", Number.class, 0);
+ private final Input y0 = new Input<>("y0", Number.class, 0);
+ private final Input x1 = new Input<>("x1", Number.class, 1);
+ private final Input y1 = new Input<>("y1", Number.class, 0);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+
+ public Line() {
+ super("Line");
+ addVariable(x0);
+ addVariable(y0);
+ addVariable(x1);
+ addVariable(y1);
+ addVariable(contents);
+ }
+
+ @Override
+ public void update() {
+ try {
+ Turtle t = new Turtle();
+ t.jumpTo(x0.getValue().doubleValue(),y0.getValue().doubleValue());
+ t.moveTo(x1.getValue().doubleValue(),y1.getValue().doubleValue());
+ t.penUp();
+ contents.send(t);
+ } catch (Exception e) {
+ logger.warn("Failed to update, ignoring", e);
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/NGon.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/NGon.java
new file mode 100644
index 000000000..d695172a5
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/NGon.java
@@ -0,0 +1,43 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NGon extends Node {
+ private static final Logger logger = LoggerFactory.getLogger(NGon.class);
+
+ private final Input radius = new Input<>("radius", Number.class, 10);
+ private final Input steps = new Input<>("steps", Number.class, 4);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+
+ public NGon() {
+ super("NGon");
+ addVariable(radius);
+ addVariable(steps);
+ addVariable(contents);
+ }
+
+ @Override
+ public void update() {
+ try {
+ Turtle t = new Turtle();
+ double r = radius.getValue().doubleValue();
+ int s = steps.getValue().intValue();
+
+ t.jumpTo(r,0);
+ for(int i=1;i<=s;++i) {
+ double v = ( 2.0*Math.PI*(double)i ) / (double)s;
+ t.moveTo(Math.cos(v), Math.sin(v));
+ }
+ t.penUp();
+ contents.send(t);
+ } catch (Exception e) {
+ logger.warn("Failed to update, ignoring", e);
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Rectangle.java b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Rectangle.java
new file mode 100644
index 000000000..51d9df27a
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/donatelloimpl/nodes/shapes/Rectangle.java
@@ -0,0 +1,42 @@
+package com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes;
+
+import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.nodegraphcore.port.Input;
+import com.marginallyclever.nodegraphcore.port.Output;
+import com.marginallyclever.nodegraphcore.Node;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Rectangle extends Node {
+ private static final Logger logger = LoggerFactory.getLogger(Rectangle.class);
+
+ private final Input w = new Input<>("width", Number.class, 100);
+ private final Input h = new Input<>("height", Number.class, 100);
+ private final Output contents = new Output<>("contents", Turtle.class, new Turtle());
+
+ public Rectangle() {
+ super("Rectangle");
+ addVariable(w);
+ addVariable(h);
+ addVariable(contents);
+ }
+
+ @Override
+ public void update() {
+ try {
+ Turtle t = new Turtle();
+ double ww = w.getValue().doubleValue()/2.0;
+ double hh = h.getValue().doubleValue()/2.0;
+ t.jumpTo(-ww,-hh);
+ t.moveTo( ww,-hh);
+ t.moveTo( ww, hh);
+ t.moveTo(-ww, hh);
+ t.moveTo(-ww,-hh);
+ t.penUp();
+ contents.send(t);
+ } catch (Exception e) {
+ logger.warn("Failed to update, ignoring", e);
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/TransformedImage.java b/src/main/java/com/marginallyclever/makelangelo/makeart/TransformedImage.java
index 4342e6771..e86754e0a 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/TransformedImage.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/TransformedImage.java
@@ -5,7 +5,6 @@
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
-import java.util.Arrays;
/**
* TransformedImage is a {@link BufferedImage}, with a transformation matrix on top.
@@ -43,6 +42,12 @@ public TransformedImage(TransformedImage copy) {
scaleY = copy.scaleY;
}
+ /**
+ * Can the image be sampled at this location?
+ * @param x pre-transform x
+ * @param y pre-transform y
+ * @return true if the image can be sampled at this location
+ */
public boolean canSampleAt(double x, double y) {
int sampleX = getTransformedX(x);
int sampleY = getTransformedY(y);
@@ -91,81 +96,54 @@ public int sample(double cx, double cy, double radius) {
}
/**
- * Sample the image, taking into account fractions of pixels. left must be less than right, bottom must be less than top.
+ * Sample the image, taking into account fractions of pixels.
* @param x0 left
* @param y0 top
* @param x1 right
* @param y1 bottom
- * @return greyscale intensity in this region. [0...255]v
+ * @return greyscale intensity in this region. [0...255]
*/
public int sample(double x0, double y0, double x1, double y1) {
- double sampleValue = 0;
- double weightedSum = 0;
-
- int left = (int)Math.floor(x0);
- int right = (int)Math.ceil (x1);
- int bottom = (int)Math.floor(y0);
- int top = (int)Math.ceil (y1);
-
- // calculate the weight matrix
- int w = Math.max(1,right-left);
- int h = Math.max(1,top-bottom);
- if(w==1 && h==1) {
- if (canSampleAt(left, bottom)) {
- return sample1x1Unchecked(left, bottom);
- } else {
- return 0;
- }
- }
-
- double [] m = new double[w*h];
- Arrays.fill(m, 1);
-
- // bottom edge
- if(bottomy1) {
- double yWeightEnd = top-y1;
- for(int i=0;i right) {
+ int temp = left;
+ left = right;
+ right = temp;
}
- // right edge
- if(right>x1) {
- double xWeightEnd = right-x1;
- for(int i=0;i top) {
+ int temp = bottom;
+ bottom = top;
+ top = temp;
}
-
- int i=0;
+ // find the bounds of the image once, instead of inside the loops.
+ bottom = Math.max(Math.min(bottom, sourceImage.getHeight()), 0);
+ top = Math.max(Math.min(top, sourceImage.getHeight()), 0);
+ left = Math.max(Math.min(left, sourceImage.getWidth()), 0);
+ right = Math.max(Math.min(right, sourceImage.getWidth()), 0);
+
+ // now sample the entire area to average the intensity
+ int count = (top-bottom) * (right-left);
+ // if no hits, return white
+ if(count==0) return 255;
+
+ var raster = sourceImage.getRaster();
+ var componentCount = sourceImage.getColorModel().getNumComponents();
+ var pixel = new double[componentCount];
+ double sampleValue = 0;
for(int y=bottom;y lowpass) {
@@ -104,9 +104,9 @@ public void start(Paper paper, TransformedImage image) {
}
} else {
// every odd line move right to left
- for (x = xRight; x > xLeft; x -= fullStep) {
+ for (x = xRight + halfStep; x > xLeft; x -= fullStep) {
// read a block of the image and find the average intensity in this block
- z = img.sample( x - halfStep, y - halfStep, x + halfStep, y + halfStep);
+ z = img.sample( x, y, halfStep);
// scale the intensity value
double scaleZ = (255.0f - z) / 255.0f;
if (scaleZ > lowpass) {
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_CMYK_Circles.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_CMYK_Circles.java
index a989e2e7e..5f928bccd 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_CMYK_Circles.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_CMYK_Circles.java
@@ -8,6 +8,7 @@
import com.marginallyclever.makelangelo.makeart.turtletool.InfillTurtle;
import com.marginallyclever.makelangelo.makeart.turtletool.RemoveExtraColorChangesFromTurtle;
import com.marginallyclever.makelangelo.paper.Paper;
+import com.marginallyclever.makelangelo.select.SelectBoolean;
import com.marginallyclever.makelangelo.select.SelectReadOnlyText;
import com.marginallyclever.makelangelo.select.SelectSlider;
import com.marginallyclever.makelangelo.turtle.Turtle;
@@ -23,7 +24,8 @@
*/
public class Converter_CMYK_Circles extends ImageConverter {
private static final Logger logger = LoggerFactory.getLogger(Converter_CMYK_Circles.class);
- static protected int maxCircleRadius =5;
+ protected static int maxCircleRadius =5;
+ protected static boolean fillCircles = false;
public Converter_CMYK_Circles() {
super();
@@ -34,6 +36,12 @@ public Converter_CMYK_Circles() {
fireRestart();
});
add(maxCircleSize);
+ SelectBoolean fillCircles = new SelectBoolean("fillCircles",Translator.get("Converter_CMYK_Circles.fillCircles"),this.fillCircles);
+ fillCircles.addSelectListener((evt)->{
+ Converter_CMYK_Circles.fillCircles = (boolean)evt.getNewValue();
+ fireRestart();
+ });
+ add(fillCircles);
add(new SelectReadOnlyText("note",Translator.get("Converter_CMYK_Crosshatch.Note")));
}
@@ -139,10 +147,10 @@ private void circlesAlongLine(double x1, double y1, double x0, double y0, Transf
double b;
for( b = 0; b <= distance; b+= maxCircleRadius*2) {
n = b / distance;
- x = dx * n + P0.x;
+ x = dx * n + P0.x + halfStep;
y = dy * n + P0.y;
- v = img.sample( x - halfStep, y - halfStep, x + halfStep, y + halfStep);
+ v = img.sample( x, y, halfStep);
drawCircle(cx + x, cy + y, maxCircleRadius * ((255.0-v)/255.0));
}
@@ -162,13 +170,15 @@ private void drawCircle(double x,double y,double r) {
}
t.moveTo(x+r,y+0);
- try {
- InfillTurtle filler = new InfillTurtle();
- filler.setPenDiameter(t.getDiameter());
- Turtle t2 = filler.run(t);
- turtle.add(t2);
- } catch(Exception e) {
- // shape was not closed, do nothing.
+ if(fillCircles) {
+ try {
+ InfillTurtle filler = new InfillTurtle();
+ filler.setPenDiameter(t.getDiameter());
+ Turtle t2 = filler.run(t);
+ turtle.add(t2);
+ } catch (Exception e) {
+ // shape was not closed, do nothing.
+ }
}
turtle.add(t);
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_FlowField.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_FlowField.java
index 2c1cd90f8..1e6521ee1 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_FlowField.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_FlowField.java
@@ -10,6 +10,7 @@
import com.marginallyclever.makelangelo.paper.Paper;
import com.marginallyclever.makelangelo.select.*;
import com.marginallyclever.makelangelo.turtle.Turtle;
+import com.marginallyclever.makelangelo.turtle.TurtlePathWalker;
import javax.vecmath.Vector2d;
import java.awt.*;
@@ -58,7 +59,6 @@ public Converter_FlowField() {
add(fieldRandomSeed);
fieldRandomSeed.addSelectListener(evt->{
seed = (int)evt.getNewValue();
- random.setSeed(seed);
fireRestart();
});
@@ -225,10 +225,11 @@ private SampleAt[] calculateSamplesOnce(TransformedImage img, Turtle line) {
double len = line.getDrawDistance();
int numSamples = (int)(len/samplingRate);
SampleAt [] samples = new SampleAt[numSamples];
+ TurtlePathWalker walker = new TurtlePathWalker(line);
- Point2D p = line.interpolate(0.0);
+ Point2D p = walker.walk(0);
for(int i=0;i255) z=255;
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_SpiralPulse.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_SpiralPulse.java
index ce793e348..ac9288230 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_SpiralPulse.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_SpiralPulse.java
@@ -111,7 +111,7 @@ public void start(Paper paper, TransformedImage image) {
fy = Math.sin(f) * r2;
// clip to paper boundaries
if( rect.contains(fx, fy) ) {
- z = img.sample( fx - zigZagSpacing, fy - halfStep, fx + zigZagSpacing, fy + halfStep);
+ z = img.sample( fx, fy, halfStep);
scale_z = (255.0f - z) / 255.0f;
pulse_size = halfStep * scale_z;
nx = (halfStep+pulse_size*n) * fx / r2;
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_TruchetFromImage.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_TruchetFromImage.java
index 11cba82a5..f2ee6399d 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_TruchetFromImage.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_TruchetFromImage.java
@@ -69,7 +69,7 @@ public void start(Paper paper, TransformedImage image) {
for(double y=miny;y128) truchet.tileA(px+x,py+y);
else truchet.tileB(px+x,py+y);
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_Voronoi.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_Voronoi.java
index c224aa792..1cc57cd18 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_Voronoi.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_Voronoi.java
@@ -21,6 +21,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
+import java.util.concurrent.atomic.DoubleAdder;
+import java.util.stream.IntStream;
/**
* Shared methods for Voronoi converters
@@ -103,7 +105,10 @@ public void start(Paper paper, TransformedImage image) {
@Override
public boolean iterate() {
+ turtle.history.clear();
+
iterations++;
+
lock.lock();
try {
double noiseLevel = evolveCells();
@@ -133,10 +138,11 @@ private double evolveCells() {
}
private double adjustCenters(TransformedImage image) {
- double change=0;
GeometryFactory factory = new GeometryFactory();
- for(int i=0;i {
Polygon poly = voronoiDiagram.getHull(i);
PreparedPolygon hull = new PreparedPolygon(poly);
VoronoiCell cell = cells.get(i);
@@ -179,11 +185,12 @@ private double adjustCenters(TransformedImage image) {
double dx = wx - cell.center.x;
double dy = wy - cell.center.y;
cell.change = (dx*dx+dy*dy);
- change += cell.change;
+ change.add(cell.change);
cell.set(wx,wy);
}
- }
- return change;
+ });
+
+ return change.sum();
}
private double getStepSize(double maxy, double miny, double xDiff) {
@@ -235,14 +242,7 @@ private double findRightEdge(PreparedPolygon poly,GeometryFactory factory, doubl
@Override
public void stop() {
super.stop();
- lock.lock();
- try {
- writeOutCells();
- }
- finally {
- lock.unlock();
- }
- fireConversionFinished();
+ writeOutCells();
}
protected void renderEdges(GL2 gl2) {
@@ -280,8 +280,13 @@ public boolean getDrawVoronoi() {
@Override
public void generateOutput() {
- writeOutCells();
-
+ lock.lock();
+ try {
+ writeOutCells();
+ }
+ finally {
+ lock.unlock();
+ }
fireConversionFinished();
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_VoronoiZigZag.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_VoronoiZigZag.java
index 6d4267da3..d629eb2c1 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_VoronoiZigZag.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imageconverter/Converter_VoronoiZigZag.java
@@ -12,6 +12,9 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.IntStream;
/**
* Dithering using a particle system.
@@ -154,19 +157,20 @@ public void flipTests() {
int lpc = getLowpassCutoff();
int size = cells.size();
+ // cannot run in parallel one thread might change the list while another is reading it.
for (int start = 0; start < size - 2 && !isThreadCancelled(); ++start) {
VoronoiCell a = cells.get(ti(start ));
VoronoiCell b = cells.get(ti(start+1));
if(a.weight bestDiff = new AtomicReference<>(0.0);
+ AtomicInteger bestIndex = new AtomicInteger(-1);
- for (int end = start + 2; end < size && !isThreadCancelled(); ++end) {
+ IntStream.range(start + 2, size).parallel().forEach(end -> {
VoronoiCell c = cells.get(ti(end-1));
VoronoiCell d = cells.get(ti(end ));
- if(c.weightcutoff && tries<1000);
if(tries==1000) break; // ran out of points to try?
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterContrastAdjust.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterContrastAdjust.java
index 3d3d6ad03..622ca1890 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterContrastAdjust.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterContrastAdjust.java
@@ -10,6 +10,7 @@
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.stream.IntStream;
/**
* Adjusts the top and bottom of the constrast curve.
@@ -41,17 +42,21 @@ public TransformedImage filter() {
TransformedImage after = new TransformedImage(img);
BufferedImage afterBI = after.getSourceImage();
- for (int y = 0; y < h; ++y) {
- for (int x = 0; x < w; ++x) {
- int color = bi.getRGB(x, y);
- int red = adjust(red32(color));
- int green = adjust(green32(color));
- int blue = adjust(blue32(color));
- int alpha = alpha32(color);
+ var raster = bi.getRaster();
+ var afterRaster = afterBI.getRaster();
+ var count = bi.getColorModel().getNumComponents();
+ // Temporary array to hold pixel components
- afterBI.setRGB(x, y, ImageFilter.encode32bit(red,green,blue,alpha));
+ IntStream.range(0, h).parallel().forEach(y -> {
+ int[] pixel = new int[count];
+ for (int x = 0; x < w; ++x) {
+ raster.getPixel(x, y, pixel);
+ pixel[0] = adjust(pixel[0]);
+ pixel[1] = adjust(pixel[1]);
+ pixel[2] = adjust(pixel[2]);
+ afterRaster.setPixel(x, y, pixel);
}
- }
+ });
return after;
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterDesaturate.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterDesaturate.java
index 05a04d572..3f8adb2e5 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterDesaturate.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterDesaturate.java
@@ -7,6 +7,7 @@
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.stream.IntStream;
/**
* Converts an image to greyscale.
@@ -28,20 +29,32 @@ public TransformedImage filter() {
BufferedImage bi = img.getSourceImage();
TransformedImage after = new TransformedImage(img);
BufferedImage afterBI = after.getSourceImage();
+ var raster = bi.getRaster();
+ var afterRaster = afterBI.getRaster();
- int x, y;
- for (y = 0; y < h; ++y) {
- for (x = 0; x < w; ++x) {
- double pixel = decode32bit(bi.getRGB(x, y));
- //double v2 = sRGBtoLinear(pixel);
- double v2 = toneControl(pixel);
- int rgb = (int) Math.min(255, Math.max(0, v2));
- afterBI.setRGB(x, y, ImageFilter.encode32bit(rgb));
+ var count = bi.getColorModel().getNumComponents();
+ // Temporary array to hold pixel components
+
+ IntStream.range(0, h).parallel().forEach(y -> {
+ int[] pixel = new int[count];
+ for (int x = 0; x < w; ++x) {
+ raster.getPixel(x, y, pixel);
+ double average = (pixel[0]+pixel[1]+pixel[2])/3.0;
+ int toned = (int)toneControl(average);
+ pixel[0] = toned;
+ pixel[1] = toned;
+ pixel[2] = toned;
+ afterRaster.setPixel(x,y,pixel);
}
- }
+ });
return after;
}
+ /**
+ * Convert a single pixel from sRGB to linear.
+ * @param b a number between 0 and 255, inclusive.
+ * @return a number between 0 and 255, inclusive.
+ */
private double sRGBtoLinear(double b) {
b /= 255.0;
if (b <= 0.04045) b /= 12.92;
@@ -50,7 +63,9 @@ private double sRGBtoLinear(double b) {
}
/**
- * accepts and returns a number between 0 and 255, inclusive.
+ * Non-linear tone control. Mostly brightens highlights while leaving midtones and shadows alone.
+ * @param b a number between 0 and 255, inclusive.
+ * @return a number between 0 and 255, inclusive.
*/
private double toneControl(double b) {
b /= 255.0;
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterDifference.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterDifference.java
index 9ad08ec6e..f9864a382 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterDifference.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterDifference.java
@@ -2,7 +2,6 @@
import com.marginallyclever.makelangelo.makeart.TransformedImage;
-import java.awt.*;
import java.awt.image.BufferedImage;
/**
@@ -30,16 +29,22 @@ public TransformedImage filter() {
}
BufferedImage rr = result.getSourceImage();
+ var rasterA = aa.getRaster();
+ var rasterB = bb.getRaster();
+ var rasterR = rr.getRaster();
+ var cm = aa.getColorModel().getNumComponents();
+ // Temporary array to hold pixel components
+ int[] pixelA = new int[cm];
+ int[] pixelB = new int[cm];
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
- Color diff = new Color(aa.getRGB(x, y));
- Color other = new Color(bb.getRGB(x, y));
- var diff2 = new Color(
- modify(diff.getRed() , other.getRed() ),
- modify(diff.getGreen(), other.getGreen()),
- modify(diff.getBlue() , other.getBlue() ) );
- rr.setRGB(x, y, diff2.hashCode());
+ rasterA.getPixel(x, y, pixelA);
+ rasterB.getPixel(x, y, pixelB);
+ pixelA[0] = modify(pixelA[0],pixelB[0]);
+ pixelA[1] = modify(pixelA[1],pixelB[1]);
+ pixelA[2] = modify(pixelA[2],pixelB[2]);
+ rasterR.setPixel(x, y, pixelA);
}
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterInvert.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterInvert.java
index ab3640b84..11d4e1371 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterInvert.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterInvert.java
@@ -1,6 +1,5 @@
package com.marginallyclever.makelangelo.makeart.imagefilter;
-import com.marginallyclever.convenience.ColorRGB;
import com.marginallyclever.convenience.ResizableImagePanel;
import com.marginallyclever.makelangelo.makeart.TransformedImage;
@@ -8,6 +7,7 @@
import java.awt.image.BufferedImage;
import java.io.FileInputStream;
import java.io.IOException;
+import java.util.stream.IntStream;
/**
@@ -27,20 +27,24 @@ public TransformedImage filter() {
BufferedImage src = img.getSourceImage();
int h = src.getHeight();
int w = src.getWidth();
- int x, y;
TransformedImage after = new TransformedImage(img);
BufferedImage afterBI = after.getSourceImage();
-
- for (y = 0; y < h; ++y) {
- for (x = 0; x < w; ++x) {
- ColorRGB color = new ColorRGB(src.getRGB(x, y));
- color.red = 255 - color.red;
- color.green = 255 - color.green;
- color.blue = 255 - color.blue;
- afterBI.setRGB(x, y, color.toInt());
+ var raster = src.getRaster();
+ var afterRaster = afterBI.getRaster();
+ var componentCount = src.getColorModel().getNumComponents();
+ // Temporary array to hold pixel components
+
+ IntStream.range(0, h).parallel().forEach(y -> {
+ int[] pixel = new int[componentCount];
+ for (int x = 0; x < w; ++x) {
+ raster.getPixel(x,y,pixel);
+ pixel[0] = 255 - pixel[0];
+ pixel[1] = 255 - pixel[1];
+ pixel[2] = 255 - pixel[2];
+ afterRaster.setPixel(x, y, pixel);
}
- }
+ });
return after;
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterJumpFlood.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterJumpFlood.java
index fc4b8c3c2..61aeacbbc 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterJumpFlood.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterJumpFlood.java
@@ -8,7 +8,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
-import java.util.Random;
+import java.util.stream.IntStream;
/**
* Converts an image using the jump flood algorithm. On a white surface, black pixels will "spread out" creating
@@ -19,7 +19,6 @@ public class FilterJumpFlood extends ImageFilter {
private final List points = new ArrayList<>();
private int scale;
private final TransformedImage img;
- private static int seed=0;
public FilterJumpFlood(TransformedImage img) {
super();
@@ -40,24 +39,28 @@ public TransformedImage filter() {
public BufferedImage fillImage(BufferedImage image) {
points.clear();
+
+ var h = image.getHeight();
+ var w = image.getWidth();
+
// Scan the image to find the initial points (black pixels)
- for (int x = 0; x < image.getWidth(); x++) {
- for (int y = 0; y < image.getHeight(); y++) {
+ IntStream.range(0, h).parallel().forEach(y -> {
+ for (int x = 0; x < w; x++) {
Color color = new Color(image.getRGB(x, y));
if (color.equals(Color.BLACK)) {
points.add(new Point(x, y));
}
}
- }
+ });
scale = Math.min(image.getWidth(), image.getHeight()) /2;
// Run the algorithm
- for (int x = 0; x < image.getWidth(); x++) {
- for (int y = 0; y < image.getHeight(); y++) {
+ IntStream.range(0, h).parallel().forEach(y -> {
+ for (int x = 0; x < w; x++) {
updatePixel(image,points,x, y);
}
- }
+ });
return image;
}
@@ -79,19 +82,24 @@ private void updatePixel(BufferedImage image,List points,int x, int y) {
public static void main(String[] args) throws IOException {
//*
BufferedImage image = new BufferedImage(400, 500, BufferedImage.TYPE_INT_RGB);
- for (int x = 0; x < image.getWidth(); x++) {
- for (int y = 0; y < image.getHeight(); y++) {
- image.setRGB(x, y, Color.WHITE.getRGB());
- }
- }
+ var g = image.getGraphics();
+ g.setColor(Color.WHITE);
+ g.fillRect(0, 0, image.getWidth(), image.getHeight());
+
// some random black pixels
for(int i=0;i<25;++i) {
- image.setRGB((int)(Math.random()*image.getWidth()), (int)(Math.random()*image.getHeight()), Color.BLACK.getRGB());
+ image.setRGB(
+ (int)(Math.random()*image.getWidth()),
+ (int)(Math.random()*image.getHeight()),
+ Color.BLACK.getRGB());
}
TransformedImage src = new TransformedImage( image );
+ long start = System.currentTimeMillis();
FilterJumpFlood f = new FilterJumpFlood(src);
TransformedImage dest = f.filter();
+ long end = System.currentTimeMillis();
+ System.out.println("FilterJumpFlood took "+(end-start)+"ms");
ResizableImagePanel.showImage(dest.getSourceImage(), "Filter_JumpFlood" );
}
}
\ No newline at end of file
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterLevels.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterLevels.java
index 2f0460283..a8dc56831 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterLevels.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/FilterLevels.java
@@ -11,14 +11,13 @@
import java.io.IOException;
/**
- * Converts an image to N levels.
+ * Converts an image to N levels of grey
* @author Dan Royer
*/
public class FilterLevels extends ImageFilter {
private static final Logger logger = LoggerFactory.getLogger(FilterLevels.class);
private final TransformedImage img;
private final double levels;
- private final int mode = 1;
public FilterLevels(TransformedImage img, int levels) {
super();
@@ -28,113 +27,34 @@ public FilterLevels(TransformedImage img, int levels) {
@Override
public TransformedImage filter() {
- return switch (mode) {
- case 0 -> filterLevels(img);
- case 1 -> filterTone(img);
- case 2 -> filterSimple(img);
- default -> null;
- };
- }
-
- protected TransformedImage filterLevels(TransformedImage img) {
int h = img.getSourceImage().getHeight();
int w = img.getSourceImage().getWidth();
- int x, y, i;
double max_intensity = -1000;
double min_intensity = 1000;
BufferedImage bi = img.getSourceImage();
- for (y = 0; y < h; ++y) {
- for (x = 0; x < w; ++x) {
- i = decode32bit(bi.getRGB(x, y));
- if (max_intensity < i) max_intensity = i;
- if (min_intensity > i) min_intensity = i;
- }
- }
- double intensity_range = max_intensity - min_intensity;
-
- double ilevels = 1;
- if (levels != 0)
- ilevels = 1.0 / levels;
-
- double pixel;
-
TransformedImage after = new TransformedImage(img);
BufferedImage afterBI = after.getSourceImage();
- for (y = 0; y < h; ++y) {
- for (x = 0; x < w; ++x) {
- pixel = decode32bit(bi.getRGB(x, y));
- double a = (pixel - min_intensity) / intensity_range;
- double c = a * levels * ilevels;
- int b = (int) Math.max(Math.min(c * 255.0, 255), 0);
- afterBI.setRGB(x, y, ImageFilter.encode32bit(b));
- }
- }
+ double step = 255.0 / (levels - 1); // Step size for quantization
- return after;
- }
-
- private double sRGBtoLinear(double b) {
- b /= 255.0;
- if (b <= 0.04045) b /= 12.92;
- else b = Math.pow((b + 0.055) / 1.055, 2.4);
- return b * 255.0;
- }
-
- /**
- * accepts and returns a number between 0 and 255, inclusive.
- */
- private double toneControl(double b) {
- b /= 255.0;
- b = 0.017 * Math.exp(3.29 * b) + 0.005 * Math.exp(7.27 * b);
- return Math.min(1, Math.max(0, b)) * 255.0;
- }
-
- public TransformedImage filterTone(TransformedImage img) {
- int h = img.getSourceImage().getHeight();
- int w = img.getSourceImage().getWidth();
-
- BufferedImage bi = img.getSourceImage();
- TransformedImage after = new TransformedImage(img);
- BufferedImage afterBI = after.getSourceImage();
-
- int x, y;
- for (y = 0; y < h; ++y) {
- for (x = 0; x < w; ++x) {
+ for (int y = 0; y < h; ++y) {
+ for (int x = 0; x < w; ++x) {
double pixel = decode32bit(bi.getRGB(x, y));
- //double v2 = sRGBtoLinear(pixel);
- double v2 = toneControl(pixel);
- int rgb = (int) Math.min(255, Math.max(0, v2));
- afterBI.setRGB(x, y, ImageFilter.encode32bit(rgb));
+ double d = (int)Math.round((pixel * (levels-1)) / 255.0);
+ double c = (int) Math.round(d * step);
+ int b = (int) Math.max(Math.min(c, 255), 0);
+ afterBI.setRGB(x, y, ImageFilter.encode32bit(b));
}
}
- return after;
- }
-
- public TransformedImage filterSimple(TransformedImage img) {
- int h = img.getSourceImage().getHeight();
- int w = img.getSourceImage().getWidth();
- BufferedImage bi = img.getSourceImage();
- TransformedImage after = new TransformedImage(img);
- BufferedImage afterBI = after.getSourceImage();
-
- int x, y;
- for (y = 0; y < h; ++y) {
- for (x = 0; x < w; ++x) {
- double pixel = decode32bit(bi.getRGB(x, y));
- int rgb = (int) Math.min(255, Math.max(0, pixel));
- afterBI.setRGB(x, y, ImageFilter.encode32bit(rgb));
- }
- }
return after;
}
public static void main(String[] args) throws IOException {
TransformedImage src = new TransformedImage( ImageIO.read(new FileInputStream("src/test/resources/mandrill.png")) );
- FilterLevels f = new FilterLevels(src,255);
+ FilterLevels f = new FilterLevels(src,8);
ResizableImagePanel.showImage(f.filter().getSourceImage(), "Filter_Greyscale" );
}
}
\ No newline at end of file
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/ImageFilter.java b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/ImageFilter.java
index 62cbab300..a35353b61 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/ImageFilter.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/imagefilter/ImageFilter.java
@@ -9,48 +9,13 @@
* @author dan Royer
*/
public abstract class ImageFilter {
- protected static int red32(int color) {
- return ((color >> 16) & 0xff);
- }
-
- protected static int green32(int color) {
- return ((color >> 8) & 0xff);
- }
-
- protected static int blue32(int color) {
- return ((color) & 0xff);
- }
-
- protected static int alpha32(int color) {
- return ((color >> 24) & 0xff);
- }
-
/**
* @param color RGBA
* @return grayscale value
*/
public static int decode32bit(int color) {
- int r = red32(color);
- int g = green32(color);
- int b = blue32(color);
- int a = alpha32(color);
-
- return average(r, g, b, a / 255.0);
- }
-
- /**
- * @param red 0-255
- * @param green 0-255
- * @param blue 0-255
- * @param alpha 0-255
- * @return RGB color
- */
- public static int encode32bit(int red,int green,int blue,int alpha) {
- red &= 0xff;
- green &= 0xff;
- blue &= 0xff;
- alpha &= 0xff;
- return (alpha << 24) | (red << 16) | (green << 8) | blue;
+ Color c = new Color(color);
+ return (c.getRed() + c.getGreen() + c.getBlue()) / 3;
}
/**
@@ -59,34 +24,8 @@ public static int encode32bit(int red,int green,int blue,int alpha) {
*/
public static int encode32bit(int greyscale) {
greyscale &= 0xff;
- return encode32bit(greyscale,greyscale,greyscale,0xff);
- }
-
- /**
- * @param color RGBA
- * @return grayscale value
- */
- protected static int decodeColor(Color color) {
- int r = color.getRed();
- int g = color.getGreen();
- int b = color.getBlue();
- int a = color.getAlpha();
- return average(r, g, b, a / 255.0);
- }
-
- /**
- * @param r red
- * @param g green
- * @param b blue
- * @param a alpha
- * @return grayscale value
- */
- private static int average(int r, int g, int b, double a) {
- int r2 = (int)(r * a);
- int g2 = (int)(g * a);
- int b2 = (int)(b * a);
-
- return (r2 + g2 + b2) / 3;
+ Color c = new Color(greyscale,greyscale,greyscale);
+ return c.getRGB();
}
/**
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/io/LoadFilePanel.java b/src/main/java/com/marginallyclever/makelangelo/makeart/io/LoadFilePanel.java
index 030732954..55fe3c0b9 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/io/LoadFilePanel.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/io/LoadFilePanel.java
@@ -62,6 +62,10 @@ private void stopExistingImageConverter() {
public boolean onNewFilenameChosen(String filename) {
stopExistingImageConverter();
+ if(filename.startsWith("\"") && filename.endsWith("\"")) {
+ // probably copy/pasted from a windows explorer window, which adds quotes around the filename.
+ filename = filename.substring(1,filename.length()-1);
+ }
selectedFilename.setText(filename);
try {
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/io/TurtleFactory.java b/src/main/java/com/marginallyclever/makelangelo/makeart/io/TurtleFactory.java
index 8a25f9a0d..7283a511d 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/io/TurtleFactory.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/io/TurtleFactory.java
@@ -47,10 +47,12 @@ public static Turtle load(String filename) throws Exception {
if(isValidExtension(filename,loader.getFileNameFilter())) {
try(FileInputStream in = new FileInputStream(filename)) {
return loader.load(in);
+ } catch(Exception e) {
+ throw new Exception("TurtleFactory could not load '" + filename + "'.", e);
}
}
}
- throw new IllegalStateException("TurtleFactory could not load '"+filename+"'.");
+ throw new IllegalStateException("TurtleFactory doesn't recognize the format of '"+filename+"'.");
}
private static boolean isValidExtension(String filename, FileNameExtensionFilter filter) {
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/truchet/TruchetTileFactory.java b/src/main/java/com/marginallyclever/makelangelo/makeart/truchet/TruchetTileFactory.java
index 0fdd94a05..611460cad 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/truchet/TruchetTileFactory.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/truchet/TruchetTileFactory.java
@@ -24,17 +24,25 @@ public static List getNames() {
}));
}
+ /**
+ * spaceBetweenLines * linesPerTileCount = tileSize
+ * @param index the index of the tile to create
+ * @param turtle the turtle to draw with
+ * @param spaceBetweenLines the distance between lines
+ * @param linesPerTileCount the number of lines per tile
+ * @return a new Truchet tile
+ */
public static TruchetTile getTile(int index, Turtle turtle, double spaceBetweenLines, double linesPerTileCount) {
- switch(index) {
- case 0: return new TruchetDiagonalRising(turtle,spaceBetweenLines,linesPerTileCount);
- case 1: return new TruchetDiagonalFalling(turtle,spaceBetweenLines,linesPerTileCount);
- case 2: return new TruchetOrthogonalH(turtle,spaceBetweenLines,linesPerTileCount);
- case 3: return new TruchetOrthogonalV(turtle,spaceBetweenLines,linesPerTileCount);
- case 4: return new TruchetCurvedCurtainL(turtle,spaceBetweenLines,linesPerTileCount);
- case 5: return new TruchetCurvedCurtainR(turtle,spaceBetweenLines,linesPerTileCount);
- case 6: return new TruchetCurvedFanL(turtle,spaceBetweenLines,linesPerTileCount);
- case 7: return new TruchetCurvedFanR(turtle,spaceBetweenLines,linesPerTileCount);
- default: throw new IllegalArgumentException("Unknown Truchet tile index "+index);
- }
+ return switch (index) {
+ case 0 -> new TruchetDiagonalRising(turtle, spaceBetweenLines, linesPerTileCount);
+ case 1 -> new TruchetDiagonalFalling(turtle, spaceBetweenLines, linesPerTileCount);
+ case 2 -> new TruchetOrthogonalH(turtle, spaceBetweenLines, linesPerTileCount);
+ case 3 -> new TruchetOrthogonalV(turtle, spaceBetweenLines, linesPerTileCount);
+ case 4 -> new TruchetCurvedCurtainL(turtle, spaceBetweenLines, linesPerTileCount);
+ case 5 -> new TruchetCurvedCurtainR(turtle, spaceBetweenLines, linesPerTileCount);
+ case 6 -> new TruchetCurvedFanL(turtle, spaceBetweenLines, linesPerTileCount);
+ case 7 -> new TruchetCurvedFanR(turtle, spaceBetweenLines, linesPerTileCount);
+ default -> throw new IllegalArgumentException("Unknown Truchet tile index " + index);
+ };
}
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/Generator_FlowField.java b/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/Generator_FlowField.java
index 6de6a314a..063f85271 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/Generator_FlowField.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/Generator_FlowField.java
@@ -44,7 +44,6 @@ public Generator_FlowField() {
add(selectRandomSeed);
selectRandomSeed.addSelectListener((evt)->{
seed = (int)evt.getNewValue();
- random.setSeed(seed);
generate();
});
@@ -120,6 +119,8 @@ public String getName() {
@Override
public void generate() {
+ random.setSeed(seed);
+ noiseMaker.setSeed(seed);
Turtle turtle = new Turtle();
if (fromEdge) {
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeight.java b/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeight.java
index 93721f0b7..e3ba68672 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeight.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeight.java
@@ -3,7 +3,7 @@
import java.util.Collections;
import java.util.LinkedList;
-class LineWeight {
+public class LineWeight {
public LinkedList segments = new LinkedList<>();
public void flip() {
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeightByImageIntensity.java b/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeightByImageIntensity.java
index 0599fcf09..98df87b3f 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeightByImageIntensity.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeightByImageIntensity.java
@@ -25,7 +25,6 @@ public class LineWeightByImageIntensity extends TurtleGenerator {
private static final Logger logger = LoggerFactory.getLogger(LineWeightByImageIntensity.class);
private final double EPSILON = 0.001;
- private final double CORNER_THRESHOLD = Math.cos(Math.toRadians(15));
/**
* must be greater than zero.
diff --git a/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeightSegment.java b/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeightSegment.java
index e98450820..eff739f51 100644
--- a/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeightSegment.java
+++ b/src/main/java/com/marginallyclever/makelangelo/makeart/turtlegenerator/lineweight/LineWeightSegment.java
@@ -8,7 +8,7 @@
* Many segments make up a {@link LineWeight}.
* @author Dan Royer
*/
-class LineWeightSegment {
+public class LineWeightSegment {
public Point2D start, end;
public int ix, iy; // index for faster search
public double weight;
diff --git a/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/Makelangelo5.java b/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/Makelangelo5.java
index a7a2d381e..3e105ef7a 100644
--- a/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/Makelangelo5.java
+++ b/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/Makelangelo5.java
@@ -1,29 +1,31 @@
package com.marginallyclever.makelangelo.plotter.plotterrenderer;
import com.jogamp.opengl.GL2;
-import com.jogamp.opengl.util.texture.Texture;
import com.marginallyclever.convenience.Point2D;
+import com.marginallyclever.makelangelo.texture.TextureFactory;
+import com.marginallyclever.makelangelo.texture.TextureWithMetadata;
import com.marginallyclever.makelangelo.plotter.Plotter;
import com.marginallyclever.makelangelo.plotter.plottersettings.PlotterSettings;
-import static com.marginallyclever.convenience.helpers.DrawingHelper.*;
+import static com.marginallyclever.convenience.helpers.DrawingHelper.drawCircle;
+import static com.marginallyclever.convenience.helpers.DrawingHelper.paintTexture;
public class Makelangelo5 implements PlotterRenderer {
- private static Texture textureMainBody;
- private static Texture textureMotors;
- private static Texture textureLogo;
- private static Texture textureWeight;
- private static Texture textureGondola;
- private static Texture textureArm;
+ private static TextureWithMetadata textureMainBody;
+ private static TextureWithMetadata textureMotors;
+ private static TextureWithMetadata textureLogo;
+ private static TextureWithMetadata textureWeight;
+ private static TextureWithMetadata textureGondola;
+ private static TextureWithMetadata textureArm;
@Override
public void render(GL2 gl2, Plotter robot) {
- if (textureMainBody == null) textureMainBody = loadTexture("/textures/makelangelo5.png");
- if (textureMotors == null) textureMotors = loadTexture("/textures/makelangelo5-motors.png");
- if (textureLogo == null) textureLogo = loadTexture("/logo.png");
- if (textureWeight == null) textureWeight = loadTexture("/textures/weight.png");
- if (textureGondola == null) textureGondola = loadTexture("/textures/phBody.png");
- if (textureArm == null) textureArm = loadTexture("/textures/phArm2.png");
+ if (textureMainBody == null) textureMainBody = TextureFactory.loadTexture("/textures/makelangelo5.png");
+ if (textureMotors == null) textureMotors = TextureFactory.loadTexture("/textures/makelangelo5-motors.png");
+ if (textureLogo == null) textureLogo = TextureFactory.loadTexture("/logo.png");
+ if (textureWeight == null) textureWeight = TextureFactory.loadTexture("/textures/weight.png");
+ if (textureGondola == null) textureGondola = TextureFactory.loadTexture("/textures/phBody.png");
+ if (textureArm == null) textureArm = TextureFactory.loadTexture("/textures/phArm2.png");
if (textureMainBody == null) {
paintControlBoxPlain(gl2, robot);
@@ -161,7 +163,7 @@ private void paintCounterweight(GL2 gl2,double x,double y) {
paintTexture(gl2, textureWeight, x-20, y-74, 40,80);
}
- private void paintControlBoxFancy(GL2 gl2, Plotter robot,Texture texture) {
+ private void paintControlBoxFancy(GL2 gl2, Plotter robot,TextureWithMetadata texture) {
double left = robot.getSettings().getDouble(PlotterSettings.LIMIT_LEFT);
// double top = robot.getSettings().getDouble(PlotterSettings.LIMIT_TOP);
diff --git a/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/Makelangelo5Huge.java b/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/Makelangelo5Huge.java
index 9add28077..b608ca539 100644
--- a/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/Makelangelo5Huge.java
+++ b/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/Makelangelo5Huge.java
@@ -1,29 +1,31 @@
package com.marginallyclever.makelangelo.plotter.plotterrenderer;
import com.jogamp.opengl.GL2;
-import com.jogamp.opengl.util.texture.Texture;
import com.marginallyclever.convenience.Point2D;
+import com.marginallyclever.makelangelo.texture.TextureFactory;
+import com.marginallyclever.makelangelo.texture.TextureWithMetadata;
import com.marginallyclever.makelangelo.plotter.Plotter;
import com.marginallyclever.makelangelo.plotter.plottersettings.PlotterSettings;
-import static com.marginallyclever.convenience.helpers.DrawingHelper.*;
+import static com.marginallyclever.convenience.helpers.DrawingHelper.drawCircle;
+import static com.marginallyclever.convenience.helpers.DrawingHelper.paintTexture;
public class Makelangelo5Huge implements PlotterRenderer {
- private static Texture textureMainBody;
- private static Texture textureMotorMounts;
- private static Texture textureLogo;
- private static Texture textureWeight;
- private static Texture textureGondola;
- private static Texture textureArm;
+ private static TextureWithMetadata textureMainBody;
+ private static TextureWithMetadata textureMotorMounts;
+ private static TextureWithMetadata textureLogo;
+ private static TextureWithMetadata textureWeight;
+ private static TextureWithMetadata textureGondola;
+ private static TextureWithMetadata textureArm;
@Override
public void render(GL2 gl2, Plotter robot) {
- if (textureMainBody == null) textureMainBody = loadTexture("/textures/huge.png");
- if (textureMotorMounts == null) textureMotorMounts = loadTexture("/textures/huge-motors.png");
- if (textureLogo == null) textureLogo = loadTexture("/logo.png");
- if (textureWeight == null) textureWeight = loadTexture("/textures/weight.png");
- if (textureGondola == null) textureGondola = loadTexture("/textures/phBody.png");
- if (textureArm == null) textureArm = loadTexture("/textures/phArm2.png");
+ if (textureMainBody == null) textureMainBody = TextureFactory.loadTexture("/textures/huge.png");
+ if (textureMotorMounts == null) textureMotorMounts = TextureFactory.loadTexture("/textures/huge-motors.png");
+ if (textureLogo == null) textureLogo = TextureFactory.loadTexture("/logo.png");
+ if (textureWeight == null) textureWeight = TextureFactory.loadTexture("/textures/weight.png");
+ if (textureGondola == null) textureGondola = TextureFactory.loadTexture("/textures/phBody.png");
+ if (textureArm == null) textureArm = TextureFactory.loadTexture("/textures/phArm2.png");
if (textureMainBody == null) {
paintControlBoxPlain(gl2, robot);
@@ -49,7 +51,7 @@ public void render(GL2 gl2, Plotter robot) {
}
}
- private void paintControlBoxFancy(GL2 gl2, Plotter robot,Texture texture) {
+ private void paintControlBoxFancy(GL2 gl2, Plotter robot,TextureWithMetadata texture) {
double left = robot.getSettings().getDouble(PlotterSettings.LIMIT_LEFT);
final double scaleX = 1366 / 943.0; // machine is 1366 motor-to-motor. texture is 922. scaleX accordingly.
diff --git a/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/MakelangeloCustom.java b/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/MakelangeloCustom.java
index cbc972577..776bfc3f7 100644
--- a/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/MakelangeloCustom.java
+++ b/src/main/java/com/marginallyclever/makelangelo/plotter/plotterrenderer/MakelangeloCustom.java
@@ -1,8 +1,9 @@
package com.marginallyclever.makelangelo.plotter.plotterrenderer;
import com.jogamp.opengl.GL2;
-import com.jogamp.opengl.util.texture.Texture;
import com.marginallyclever.convenience.Point2D;
+import com.marginallyclever.makelangelo.texture.TextureFactory;
+import com.marginallyclever.makelangelo.texture.TextureWithMetadata;
import com.marginallyclever.makelangelo.plotter.Plotter;
import com.marginallyclever.makelangelo.plotter.plottersettings.PlotterSettings;
@@ -14,7 +15,7 @@ public class MakelangeloCustom implements PlotterRenderer {
public final static double COUNTERWEIGHT_H = 60;
public final static double PULLEY_RADIUS = 1.27;
public final static double MOTOR_WIDTH = 42;
- private static Texture controlBoard;
+ private static TextureWithMetadata controlBoard;
@Override
public void render(GL2 gl2,Plotter robot) {
@@ -58,7 +59,7 @@ private void paintControlBox(GL2 gl2, PlotterSettings settings) {
gl2.glEnd();
float shiftX = (float) right / 2;
- if (controlBoard == null) controlBoard = loadTexture("/textures/rampsv14.png");
+ if (controlBoard == null) controlBoard = TextureFactory.loadTexture("/textures/rampsv14.png");
if (controlBoard != null) {
final double scale = 0.1;
if (shiftX < 100) {
diff --git a/src/main/java/com/marginallyclever/makelangelo/plotter/plottersettings/PlotterSettingsPanel.java b/src/main/java/com/marginallyclever/makelangelo/plotter/plottersettings/PlotterSettingsPanel.java
index fef8ff5eb..e81fa483c 100644
--- a/src/main/java/com/marginallyclever/makelangelo/plotter/plottersettings/PlotterSettingsPanel.java
+++ b/src/main/java/com/marginallyclever/makelangelo/plotter/plottersettings/PlotterSettingsPanel.java
@@ -135,8 +135,8 @@ private SelectPanel rebuildTabPen() {
addToPanel(panel,penLowerRate = new SelectDouble("lowerSpeed", Translator.get("PlotterSettingsPanel.penToolLowerSpeed" ),settings.getDouble(PlotterSettings.PEN_ANGLE_DOWN_TIME)));
addToPanel(panel,penUpAngle = new SelectDouble("up", Translator.get("PlotterSettingsPanel.penToolUp" ),settings.getDouble(PlotterSettings.PEN_ANGLE_UP)));
addToPanel(panel,penDownAngle = new SelectDouble("down", Translator.get("PlotterSettingsPanel.penToolDown" ),settings.getDouble(PlotterSettings.PEN_ANGLE_DOWN)));
- addToPanel(panel,selectPenUpColor = new SelectColor("colorUp", Translator.get("PlotterSettingsPanel.pen up color" ),settings.getColor(PlotterSettings.PEN_UP_COLOR),this));
- addToPanel(panel,selectPenDownColor = new SelectColor("colorDown", Translator.get("PlotterSettingsPanel.pen down color" ),settings.getColor(PlotterSettings.PEN_DOWN_COLOR_DEFAULT),this));
+ addToPanel(panel,selectPenUpColor = new SelectColor("colorUp", Translator.get("PlotterSettingsPanel.penUpColor" ),settings.getColor(PlotterSettings.PEN_UP_COLOR),this));
+ addToPanel(panel,selectPenDownColor = new SelectColor("colorDown", Translator.get("PlotterSettingsPanel.penDownColor" ),settings.getColor(PlotterSettings.PEN_DOWN_COLOR_DEFAULT),this));
addToPanel(panel,zMotorType = new SelectOneOfMany("zMotorType",Translator.get("PlotterSettings.zMotorType"),new String[]{
Translator.get("PlotterSettings.zMotorType.servo"), // PlotterSettings.Z_MOTOR_TYPE_SERVO = 1
Translator.get("PlotterSettings.zMotorType.stepper"), // PlotterSettings.Z_MOTOR_TYPE_STEPPER = 2
diff --git a/src/main/java/com/marginallyclever/makelangelo/MakelangeloDropTarget.java b/src/main/java/com/marginallyclever/makelangelo/preview/PreviewDropTarget.java
similarity index 65%
rename from src/main/java/com/marginallyclever/makelangelo/MakelangeloDropTarget.java
rename to src/main/java/com/marginallyclever/makelangelo/preview/PreviewDropTarget.java
index 591b73604..25ab7ffba 100644
--- a/src/main/java/com/marginallyclever/makelangelo/MakelangeloDropTarget.java
+++ b/src/main/java/com/marginallyclever/makelangelo/preview/PreviewDropTarget.java
@@ -1,5 +1,6 @@
-package com.marginallyclever.makelangelo;
+package com.marginallyclever.makelangelo.preview;
+import com.marginallyclever.makelangelo.Makelangelo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -11,11 +12,14 @@
import java.io.File;
import java.util.List;
-public class MakelangeloDropTarget extends DropTargetAdapter {
- private static final Logger logger = LoggerFactory.getLogger(MakelangeloDropTarget.class);
+/**
+ * Allows the user to drag and drop a file onto the {@link PreviewPanel}.
+ */
+public class PreviewDropTarget extends DropTargetAdapter {
+ private static final Logger logger = LoggerFactory.getLogger(PreviewDropTarget.class);
private final Makelangelo app;
- public MakelangeloDropTarget(Makelangelo app) {
+ public PreviewDropTarget(Makelangelo app) {
super();
this.app = app;
}
@@ -30,15 +34,12 @@ public void drop(DropTargetDropEvent dtde) {
if (flavor.isFlavorJavaFileListType()) {
dtde.acceptDrop(DnDConstants.ACTION_COPY);
Object o = tr.getTransferData(flavor);
- if (o instanceof List>) {
- List> list = (List>) o;
- if (list.size() > 0) {
- o = list.get(0);
- if (o instanceof File) {
- app.openFile(((File) o).getAbsolutePath());
- dtde.dropComplete(true);
- return;
- }
+ if (o instanceof List> list && !list.isEmpty()) {
+ o = list.getFirst();
+ if (o instanceof File file) {
+ app.openFile(file.getAbsolutePath());
+ dtde.dropComplete(true);
+ return;
}
}
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/preview/PreviewPanel.java b/src/main/java/com/marginallyclever/makelangelo/preview/PreviewPanel.java
index 0a930840f..8970500a5 100644
--- a/src/main/java/com/marginallyclever/makelangelo/preview/PreviewPanel.java
+++ b/src/main/java/com/marginallyclever/makelangelo/preview/PreviewPanel.java
@@ -5,6 +5,7 @@
import com.jogamp.opengl.glu.GLU;
import com.jogamp.opengl.util.FPSAnimator;
import com.marginallyclever.convenience.Point2D;
+import com.marginallyclever.makelangelo.texture.TextureFactory;
import com.marginallyclever.util.PreferencesHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -12,9 +13,7 @@
import javax.swing.*;
import javax.vecmath.Vector2d;
import java.awt.*;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseWheelEvent;
+import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.Preferences;
@@ -24,12 +23,14 @@
* @author Dan Royer
*
*/
-public class PreviewPanel extends GLJPanel implements GLEventListener {
+public class PreviewPanel extends JPanel implements GLEventListener, MouseWheelListener, MouseListener, MouseMotionListener {
private static final Logger logger = LoggerFactory.getLogger(PreviewPanel.class);
// Use debug pipeline?
private static final boolean DEBUG_GL_ON = false;
private static final boolean TRACE_GL_ON = false;
+ private GLJPanel glCanvas;
+ private int canvasWidth,canvasHeight;
private final List previewListeners = new ArrayList<>();
@@ -58,104 +59,111 @@ public class PreviewPanel extends GLJPanel implements GLEventListener {
private FPSAnimator animator;
public PreviewPanel() {
- super();
+ super(new BorderLayout());
try {
- logger.debug(" get GL capabilities...");
- GLProfile glProfile = GLProfile.getDefault();
- GLCapabilities caps = new GLCapabilities(glProfile);
- // caps.setSampleBuffers(true);
- // caps.setHardwareAccelerated(true);
- // caps.setNumSamples(4);
- setRequestedGLCapabilities(caps);
+ logger.info("availability="+ GLProfile.glAvailabilityToString());
+ GLCapabilities capabilities = getCapabilities();
+ logger.info("create canvas");
+ glCanvas = new GLJPanel(capabilities);
} catch(GLException e) {
logger.error("I failed the very first call to OpenGL. Are your native libraries missing?", e);
System.exit(1);
}
- addGLEventListener(this);
-
- final JPanel me = this;
-
- // scroll the mouse wheel to zoom
- addMouseWheelListener(new MouseAdapter() {
- @Override
- public void mouseWheelMoved(MouseWheelEvent e) {
- int notches = e.getWheelRotation();
- if (notches == 0) return;
-
- Point2D p = new Point2D(e.getPoint().x,e.getPoint().y);
- Rectangle r = me.getBounds();
- p.x -= r.getCenterX();
- p.y -= r.getCenterY();
-
- if (notches < 0) {
- if (mouseLastZoomDirection == -1) camera.zoom(-1,p);
- mouseLastZoomDirection = -1;
- } else {
- if (mouseLastZoomDirection == 1) camera.zoom(1,p);
- mouseLastZoomDirection = 1;
- }
- repaint();
- }
- });
-
- // remember button states for when we need them.
- addMouseListener(new MouseAdapter() {
-
- @Override
- public void mousePressed(MouseEvent e) {
- buttonPressed = e.getButton();
- mouseOldX = e.getX();
- mouseOldY = e.getY();
- }
-
- @Override
- public void mouseReleased(MouseEvent e) {
- buttonPressed = MouseEvent.NOBUTTON;
- }
- });
-
-
- // left click + drag to move the camera around.
- addMouseMotionListener(new MouseAdapter() {
- @Override
- public void mouseDragged(MouseEvent e) {
- int x = e.getX();
- int y = e.getY();
- mouseX = x;
- mouseY = y;
- setTipXY();
-
- if (buttonPressed == MouseEvent.BUTTON1) {
- int dx = x - mouseOldX;
- int dy = y - mouseOldY;
- mouseOldX = x;
- mouseOldY = y;
- camera.moveRelative(-dx, -dy);
- repaint();
- }
- }
+ add(glCanvas, BorderLayout.CENTER);
- @Override
- public void mouseMoved(MouseEvent e) {
- int x = e.getX();
- int y = e.getY();
- mouseOldX = x;
- mouseOldY = y;
- mouseX = x;
- mouseY = y;
- setTipXY();
- }
- });
-
// start animation system
logger.debug(" starting animator...");
animator = new FPSAnimator(1);
- animator.add(this);
+ animator.add(glCanvas);
animator.start();
}
+ @Override
+ public void mousePressed(MouseEvent e) {
+ buttonPressed = e.getButton();
+ mouseOldX = e.getX();
+ mouseOldY = e.getY();
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ buttonPressed = MouseEvent.NOBUTTON;
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {}
+
+ @Override
+ public void mouseExited(MouseEvent e) {}
+
+ @Override
+ public void mouseClicked(MouseEvent e) {}
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ int x = e.getX();
+ int y = e.getY();
+ mouseX = x;
+ mouseY = y;
+ setTipXY();
+
+ if (buttonPressed == MouseEvent.BUTTON1) {
+ int dx = x - mouseOldX;
+ int dy = y - mouseOldY;
+ mouseOldX = x;
+ mouseOldY = y;
+ camera.moveRelative(-dx, -dy);
+ repaint();
+ }
+ }
+
+ @Override
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ int notches = e.getWheelRotation();
+ if (notches == 0) return;
+
+ Point2D p = new Point2D(e.getPoint().x,e.getPoint().y);
+ Rectangle r = this.getBounds();
+ p.x -= r.getCenterX();
+ p.y -= r.getCenterY();
+
+ if (notches < 0) {
+ if (mouseLastZoomDirection == -1) camera.zoom(-1,p);
+ mouseLastZoomDirection = -1;
+ } else {
+ if (mouseLastZoomDirection == 1) camera.zoom(1,p);
+ mouseLastZoomDirection = 1;
+ }
+ repaint();
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ int x = e.getX();
+ int y = e.getY();
+ mouseOldX = x;
+ mouseOldY = y;
+ mouseX = x;
+ mouseY = y;
+ setTipXY();
+ }
+
+ private GLCapabilities getCapabilities() {
+ GLProfile profile = GLProfile.getMaxProgrammable(true);
+ GLCapabilities capabilities = new GLCapabilities(profile);
+ capabilities.setHardwareAccelerated(true);
+ capabilities.setBackgroundOpaque(true);
+ capabilities.setDoubleBuffered(true);
+ capabilities.setStencilBits(8);
+ capabilities.setDepthBits(32); // 32 bit depth buffer is floating point
+ StringBuilder sb = new StringBuilder();
+ capabilities.toString(sb);
+ logger.info("capabilities="+sb);
+ return capabilities;
+ }
+
public void addListener(PreviewListener arg0) {
previewListeners.add(arg0);
}
@@ -169,17 +177,9 @@ public void removeListener(PreviewListener arg0) {
*/
@Override
public void reshape(GLAutoDrawable glautodrawable, int x, int y, int width, int height) {
- GL2 gl2 = glautodrawable.getGL().getGL2();
-
- camera.setWidth(width);
- camera.setHeight(height);
-
- gl2.glMatrixMode(GL2.GL_PROJECTION);
- gl2.glLoadIdentity();
- // orthographic projection
- float w2 = width/2.0f;
- float h2 = height/2.0f;
- glu.gluOrtho2D(-w2, w2, -h2, h2);
+ System.out.println("reshape "+width+"x"+height);
+ canvasWidth = width;
+ canvasHeight = height;
}
public Vector2d getMousePositionInWorld() {
@@ -202,6 +202,7 @@ private void setTipXY() {
*/
@Override
public void init(GLAutoDrawable glautodrawable) {
+ logger.debug("init");
GL gl = glautodrawable.getGL();
if (DEBUG_GL_ON) {
@@ -224,10 +225,22 @@ public void init(GLAutoDrawable glautodrawable) {
}
glu = GLU.createGLU(gl);
+
+ // turn on vsync
+ gl.setSwapInterval(1);
+
+ // make things pretty
+ gl.glEnable(GL3.GL_LINE_SMOOTH);
+ gl.glEnable(GL3.GL_POLYGON_SMOOTH);
+ gl.glHint(GL3.GL_POLYGON_SMOOTH_HINT, GL3.GL_NICEST);
+ gl.glEnable(GL3.GL_MULTISAMPLE);
}
@Override
- public void dispose(GLAutoDrawable glautodrawable) {}
+ public void dispose(GLAutoDrawable glautodrawable) {
+ logger.info("dispose");
+ TextureFactory.dispose(glautodrawable.getGL());
+ }
/**
* Refresh the image in the view. This is where drawing begins.
@@ -237,6 +250,9 @@ public void display(GLAutoDrawable glautodrawable) {
// draw the world
GL2 gl2 = glautodrawable.getGL().getGL2();
+ camera.setWidth(canvasWidth);
+ camera.setHeight(canvasHeight);
+
// set some render quality options
Preferences prefs = PreferencesHelper.getPreferenceNode(PreferencesHelper.MakelangeloPreferenceKey.GRAPHICS);
if(prefs != null && prefs.getBoolean("antialias", true)) {
@@ -263,12 +279,20 @@ public void display(GLAutoDrawable glautodrawable) {
}
}
+
/**
* Set up the correct modelview so the robot appears where it should.
*
* @param gl2 OpenGL context
*/
private void paintCamera(GL2 gl2) {
+ gl2.glMatrixMode(GL2.GL_PROJECTION);
+ gl2.glLoadIdentity();
+ // orthographic projection
+ float w2 = canvasWidth/2.0f;
+ float h2 = canvasHeight/2.0f;
+ glu.gluOrtho2D(-w2, w2, -h2, h2);
+
gl2.glMatrixMode(GL2.GL_MODELVIEW);
gl2.glLoadIdentity();
gl2.glScaled(camera.getZoom(),camera.getZoom(),1);
@@ -288,15 +312,7 @@ private void paintBackground(GL2 gl2) {
(float)backgroundColor.getBlue()/255.0f,
0.0f);
- // Special handling for the case where the GLJPanel is translucent
- // and wants to be composited with other Java 2D content
- if (GLProfile.isAWTAvailable()
- && !isOpaque()
- && shouldPreserveColorBufferIfTranslucent()) {
- gl2.glClear(GL2.GL_DEPTH_BUFFER_BIT);
- } else {
- gl2.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
- }
+ gl2.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT);
}
public void stop() {
@@ -306,4 +322,22 @@ public void stop() {
public void setCamera(Camera camera) {
this.camera = camera;
}
+
+ @Override
+ public void addNotify() {
+ super.addNotify();
+ glCanvas.addGLEventListener(this);
+ glCanvas.addMouseListener(this);
+ glCanvas.addMouseMotionListener(this);
+ glCanvas.addMouseWheelListener(this);
+ }
+
+ @Override
+ public void removeNotify() {
+ super.removeNotify();
+ glCanvas.removeGLEventListener(this);
+ glCanvas.removeMouseListener(this);
+ glCanvas.removeMouseMotionListener(this);
+ glCanvas.removeMouseWheelListener(this);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/marginallyclever/makelangelo/texture/TextureFactory.java b/src/main/java/com/marginallyclever/makelangelo/texture/TextureFactory.java
new file mode 100644
index 000000000..797e24e45
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/texture/TextureFactory.java
@@ -0,0 +1,38 @@
+package com.marginallyclever.makelangelo.texture;
+
+import com.jogamp.opengl.GL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * By default OpenGL uses a Texture class. this class can become invalidated if the OpenGL context is lost.
+ * This class is a wrapper around Texture that can be reloaded if the context is lost.
+ */
+public class TextureFactory {
+ private static final Logger logger = LoggerFactory.getLogger(TextureFactory.class);
+
+ private static final List textures = new ArrayList<>();
+
+ /**
+ * The OpenGL context has just died. release all Textures.
+ */
+ public static void dispose(GL gl) {
+ for (TextureWithMetadata tex : textures) {
+ tex.dispose(gl);
+ }
+ }
+
+ /**
+ * Load the given file from the classpath. Make sure the size of the picture is a power of 2
+ * @param name filename
+ * @return a texture
+ */
+ public static TextureWithMetadata loadTexture(String name) {
+ TextureWithMetadata tex = new TextureWithMetadata(name);
+ textures.add(tex);
+ return tex;
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/texture/TextureWithMetadata.java b/src/main/java/com/marginallyclever/makelangelo/texture/TextureWithMetadata.java
new file mode 100644
index 000000000..0bf2f5382
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/texture/TextureWithMetadata.java
@@ -0,0 +1,50 @@
+package com.marginallyclever.makelangelo.texture;
+
+import com.jogamp.opengl.GL;
+import com.jogamp.opengl.util.texture.Texture;
+import com.jogamp.opengl.util.texture.TextureIO;
+import com.marginallyclever.convenience.FileAccess;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+
+/**
+ * Contains the raw OpenGL texture and the source filename.
+ */
+public class TextureWithMetadata {
+ private static final Logger logger = LoggerFactory.getLogger(TextureWithMetadata.class);
+
+ private final String source;
+ private Texture texture;
+
+ public TextureWithMetadata(String source) {
+ this.source = source;
+ }
+
+ public Texture getTexture() {
+ try (BufferedInputStream bis = FileAccess.open(source)) {
+ return TextureIO.newTexture(bis, false, source.substring(source.lastIndexOf('.') + 1));
+ } catch (IOException e) {
+ logger.warn("Can't load {}", source, e);
+ }
+ return null;
+ }
+
+ public void bind(GL gl) {
+ if(texture==null) {
+ texture = getTexture();
+ }
+ if(texture==null) return;
+
+ texture.bind(gl);
+ }
+
+ public void dispose(GL gl) {
+ if(texture!=null) {
+ texture.destroy(gl);
+ texture=null;
+ }
+ }
+}
diff --git a/src/main/java/com/marginallyclever/makelangelo/turtle/Turtle.java b/src/main/java/com/marginallyclever/makelangelo/turtle/Turtle.java
index 927e9df20..0b3a770b8 100644
--- a/src/main/java/com/marginallyclever/makelangelo/turtle/Turtle.java
+++ b/src/main/java/com/marginallyclever/makelangelo/turtle/Turtle.java
@@ -548,27 +548,28 @@ public double getDrawDistance() {
* Returns a point along the drawn lines of this {@link Turtle}
* @param t a value from 0...{@link Turtle#getDrawDistance()}, inclusive.
* @return a point along the drawn lines of this {@link Turtle}
+ * @deprecated since 7.63.0 use {@link TurtlePathWalker} instead.
*/
+ @Deprecated(since="7.63.0")
public Point2D interpolate(double t) {
- double d=0;
+ double segmentDistanceSum=0;
TurtleMove prev = new TurtleMove(0,0,MovementType.TRAVEL);
for( TurtleMove m : history) {
if(m.type == MovementType.DRAW_LINE) {
double dx = m.x-prev.x;
double dy = m.y-prev.y;
- double change = Math.sqrt(dx*dx+dy*dy);
- if(d+change>=t) { // d < t < d+change
- double v = (t-d==0)? 0 : (t-d) / change;
- v = Math.max(Math.min(v,1),0);
+ double segmentDistance = Math.sqrt(dx*dx+dy*dy);
+ if(segmentDistanceSum+segmentDistance>=t) { // currentDistance < t < currentDistance+segmentDistance
+ double ratio = Math.max(Math.min((t-segmentDistanceSum) / segmentDistance,1),0);
return new Point2D(
- prev.x + dx * v,
- prev.y + dy * v);
+ prev.x + dx * ratio,
+ prev.y + dy * ratio);
}
- d += change;
+ segmentDistanceSum += segmentDistance;
prev = m;
} else if(m.type == MovementType.TRAVEL) {
prev = m;
- }
+ } // else tool change, ignore.
}
return new Point2D(prev.x,prev.y);
}
diff --git a/src/main/java/com/marginallyclever/makelangelo/turtle/TurtlePathWalker.java b/src/main/java/com/marginallyclever/makelangelo/turtle/TurtlePathWalker.java
new file mode 100644
index 000000000..7cfb93389
--- /dev/null
+++ b/src/main/java/com/marginallyclever/makelangelo/turtle/TurtlePathWalker.java
@@ -0,0 +1,93 @@
+package com.marginallyclever.makelangelo.turtle;
+
+import com.marginallyclever.convenience.Point2D;
+
+import java.util.Iterator;
+
+/**
+ * Walks a turtle along a path more efficiently than using {@link Turtle#interpolate(double)}.
+ */
+public class TurtlePathWalker {
+ private final Iterator iterator;
+ private TurtleMove prev;
+ private TurtleMove m;
+ private final double drawDistance;
+ private double tSum;
+ private double segmentDistance;
+ private double segmentDistanceSum;
+
+ public TurtlePathWalker(Turtle turtle) {
+ iterator = turtle.history.iterator();
+ prev = new TurtleMove(0, 0, MovementType.TRAVEL);
+ drawDistance = turtle.getDrawDistance();
+ tSum = 0;
+ segmentDistanceSum = 0;
+ advance();
+ }
+
+ /**
+ * Advance to the next movement that is a draw command.
+ */
+ private void advance() {
+ while (iterator.hasNext()) {
+ m = iterator.next();
+ if (m.type == MovementType.DRAW_LINE) {
+ double dx = m.x - prev.x;
+ double dy = m.y - prev.y;
+ segmentDistance = Math.sqrt(dx*dx+dy*dy);
+ return;
+ } else if(m.type == MovementType.TRAVEL) {
+ prev = m;
+ }
+ }
+ // in case we run out of moves
+ m = null;
+ segmentDistance = 0;
+ }
+
+ /**
+ * Advance along the drawn portion of the {@link Turtle} path by the given relative distance.
+ * @param distance the relative distance to move
+ * @return the new position of the turtle
+ * @throws IllegalArgumentException if distance is negative.
+ */
+ public Point2D walk(double distance) {
+ if(distance<0) throw new IllegalArgumentException("distance must be positive");
+
+ tSum+=distance;
+ while (segmentDistanceSum <= tSum ) {
+ if (segmentDistanceSum+segmentDistance>=tSum) {
+ double dx = m.x - prev.x;
+ double dy = m.y - prev.y;
+ double ratio = Math.max(Math.min((tSum - segmentDistanceSum) / segmentDistance,1),0);
+ double newX = prev.x + ratio * dx;
+ double newY = prev.y + ratio * dy;
+ return new Point2D(newX, newY);
+ } else {
+ segmentDistanceSum += segmentDistance;
+ prev = m;
+ advance();
+ if(m==null) break;
+ }
+ }
+ return new Point2D(prev.x, prev.y);
+ }
+
+ public boolean isDone() {
+ return drawDistance <= tSum;
+ }
+
+ /**
+ * @return the distanced travelled so far.
+ */
+ public double getTSum() {
+ return tSum;
+ }
+
+ /**
+ * @return the total distance of the path.
+ */
+ public double getDrawDistance() {
+ return drawDistance;
+ }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 4d6993884..106bd7304 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -16,18 +16,48 @@
requires org.jetbrains.annotations;
requires com.formdev.flatlaf;
requires org.reflections;
-
requires org.locationtech.jts;
requires org.apache.httpcomponents.httpclient;
requires org.apache.httpcomponents.httpcore;
+ requires modern_docking.api;
+ requires modern_docking.single_app;
+ requires modern_docking.ui_ext;
+ requires com.github.weisj.jsvg;
+ requires com.marginallyclever.donatello;
+ requires com.marginallyclever.nodegraphcore;
+
+ exports com.marginallyclever.communications;
+ exports com.marginallyclever.makelangelo.select;
+ exports com.marginallyclever.makelangelo.makeart;
+ exports com.marginallyclever.makelangelo.makeart.imagefilter;
+ exports com.marginallyclever.makelangelo.makeart.turtletool;
+ exports com.marginallyclever.makelangelo.paper;
+ exports com.marginallyclever.makelangelo.donatelloimpl to com.marginallyclever.nodegraphcore;
+ exports com.marginallyclever.makelangelo.donatelloimpl.nodes to com.marginallyclever.nodegraphcore;
+ exports com.marginallyclever.makelangelo.donatelloimpl.nodes.shapes to com.marginallyclever.nodegraphcore;
+ exports com.marginallyclever.convenience.log to ch.qos.logback.core;
opens com.marginallyclever.convenience;
- opens com.marginallyclever.convenience.voronoi;
- opens com.marginallyclever.makelangelo.makeart.io;
- opens com.marginallyclever.makelangelo.plotter.plottercontrols;
opens com.marginallyclever.makelangelo.turtle;
+ opens com.marginallyclever.makelangelo;
+ opens com.marginallyclever.makelangelo.makeart.io;
+ opens com.marginallyclever.makelangelo.texture;
opens com.marginallyclever.convenience.noise;
opens com.marginallyclever.convenience.helpers;
-
opens com.marginallyclever.convenience.log to ch.qos.logback.core;
+ opens com.marginallyclever.makelangelo.preview;
+
+ // A Java module that wants to implement a service interface from a service interface module must:
+ // - Require the service interface module in its own module descriptor.
+ // - Implement the service interface with a Java class.
+ // - Declare the service interface implementation in its module descriptor.
+ // In order to use the service, the client module must declare in its module descriptor that it uses the service.
+ // http://tutorials.jenkov.com/java/modules.html
+ uses com.marginallyclever.nodegraphcore.NodeRegistry;
+ provides com.marginallyclever.nodegraphcore.NodeRegistry with
+ com.marginallyclever.makelangelo.donatelloimpl.DonatelloRegistry;
+
+ uses com.marginallyclever.nodegraphcore.DAORegistry;
+ provides com.marginallyclever.nodegraphcore.DAORegistry with
+ com.marginallyclever.makelangelo.donatelloimpl.DonatelloRegistry;
}
\ No newline at end of file
diff --git a/src/main/resources/META-INF/services/com.marginallyclever.nodegraphcore.DAORegistry b/src/main/resources/META-INF/services/com.marginallyclever.nodegraphcore.DAORegistry
index da0cf2df4..c5a03b0be 100644
--- a/src/main/resources/META-INF/services/com.marginallyclever.nodegraphcore.DAORegistry
+++ b/src/main/resources/META-INF/services/com.marginallyclever.nodegraphcore.DAORegistry
@@ -1 +1 @@
-com.marginallyclever.makelangelo.donatelloimpl.DonatelloRegistry
+com.marginallyclever.makelangelo.donatelloimpl.DonatelloRegistry
\ No newline at end of file
diff --git a/src/main/resources/META-INF/services/com.marginallyclever.nodegraphcore.NodeRegistry b/src/main/resources/META-INF/services/com.marginallyclever.nodegraphcore.NodeRegistry
index da0cf2df4..c5a03b0be 100644
--- a/src/main/resources/META-INF/services/com.marginallyclever.nodegraphcore.NodeRegistry
+++ b/src/main/resources/META-INF/services/com.marginallyclever.nodegraphcore.NodeRegistry
@@ -1 +1 @@
-com.marginallyclever.makelangelo.donatelloimpl.DonatelloRegistry
+com.marginallyclever.makelangelo.donatelloimpl.DonatelloRegistry
\ No newline at end of file
diff --git a/src/main/resources/com/marginallyclever/makelangelo/icons8-reset-16.png b/src/main/resources/com/marginallyclever/makelangelo/icons8-reset-16.png
new file mode 100644
index 000000000..21a688a73
Binary files /dev/null and b/src/main/resources/com/marginallyclever/makelangelo/icons8-reset-16.png differ
diff --git a/src/main/resources/com/marginallyclever/makelangelo/icons8-zoom-to-fit-16.png b/src/main/resources/com/marginallyclever/makelangelo/icons8-zoom-to-fit-16.png
new file mode 100644
index 000000000..0058c4430
Binary files /dev/null and b/src/main/resources/com/marginallyclever/makelangelo/icons8-zoom-to-fit-16.png differ
diff --git a/src/main/resources/languages/arabic.xml b/src/main/resources/languages/arabic.xml
deleted file mode 100644
index 6dd2ac29c..000000000
--- a/src/main/resources/languages/arabic.xml
+++ /dev/null
@@ -1,389 +0,0 @@
-
-
-
-
-
- Arabic
-
- Zainab Abdul Kareem Dinar - github.com/ZainabAKD
-
-
- BorderNameورقة التخطيط
- Generator_TruchetTiles.Nameمربعات الألواØ
- Generator_TruchetTiles.LineSpacingالمساÙØ© بين الخطوط (ملم)
- Generator_TruchetTiles.LinesPerTileعدد الخطوط ÙÙŠ المربع
- LissajousNameالأشكال
- LissajousA منØني Ø£ أكبر من صÙر
- LissajousBمنØني ب أكبر من صÙر
- LissajousDeltaمثلث
- SpirographNameرسم هندسي
- SpirographMajorRadiusالقطر الرئيسي (R)
- SpirographMinorRadiusالقطر الثانوي (r)
- SpirographPScaleالØجم (p)
- SpirographNumSamplesنماذج (الجودة)
- SpirographEpitrochoid الدØروج
- ConverterRandomLinesNameخطوط عشوائية
- ConverterRandomLinesCountعدد الخطوط
- StartAtLastPenDownالرجوع لأخر نقطة رسم
- StartAtAddPenDownأضاÙØ© نقطة رسم
- StartAtExactlyلا تÙعل شيئاً مميزاً
- ConfirmQuitQuestionهل أنت متأكد أنك تريد الخروج؟
- ConfirmQuitTitleخروج
- ConvertImagePaperFillملئ الورقة
- ConvertImagePaperFitملائمة الصورة على الورقة
- Converter_CMYK_Crosshatch.NameCMYK
- Converter_CMYK_Crosshatch.Passesتخطي
- Converter_CMYK_Crosshatch.Note
- Converter_CMYK_Spiral.NameCMYK Øلزوني
- LoadFilePanel.titleمعاينة الملÙ
- OKمواÙÙ‚
- Cancelالغاء
- SaveØÙظ
- Translateنقل
- ScaleالØجم
- Rotateتدوير
- Widthالعرض
- HeightالأرتÙاع
- PaperSettings.Titleاعدادات الورقة
- PaperSettings.PaperSizeØجم الورقة
- PaperSettings.PaperWidthعرض الورقة (ملم)
- PaperSettings.PaperHeightأرتÙاع الورقة (ملم)
- PaperSettings.ShiftXأنتقال الى اليسار
- PaperSettings.ShiftYانتقال الى اعلى
- PaperSettings.Rotationتدوير
- PaperSettings.Landscapeمشهد
- PaperSettings.PaperMarginالØوا٠(%)
- PaperSettings.PaperColorلون الورقة
- TitlePrefixمايكل انجلو
- LoadErrorتعذر تØميل الملÙ
- SaveErrorتعذر ØÙظ الملÙ
- ConversionStyleنمط
- ConversionFillملئ
- OpenFileChooser.FileTypeImage(%1) نوع الصورة
- OpenFileChooser.AllSupportedFilesالملÙات المدعومة
- MetricsPreferences.Titleقياس
- MetricsPreferences.collectAnonymousMetrics
- أتعهد بمشاركة بيانات الأستخدام المجهول مع شركة
Maginally Clever Robots ltd