Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
package com.marginallyclever.makelangelo.makeart.imageconverter;
import com.marginallyclever.convenience.Clipper2D;
import com.marginallyclever.convenience.helpers.MathHelper;
import com.marginallyclever.donatello.select.Select;
import com.marginallyclever.makelangelo.makeart.TransformedImage;
import com.marginallyclever.makelangelo.paper.Paper;
import com.marginallyclever.makelangelo.plotter.plottersettings.PlotterSettings;
import com.marginallyclever.makelangelo.turtle.Turtle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.vecmath.Point2d;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
/**
* Converts a {@link TransformedImage} to {@link Turtle}
* @author Dan Royer
*/
public abstract class ImageConverter {
private static final Logger logger = LoggerFactory.getLogger(ImageConverter.class);
protected TransformedImage myImage;
protected Paper myPaper;
protected PlotterSettings settings;
protected Turtle turtle = new Turtle();
// for previewing the image
private BufferedImage texture = null;
private final List<Select> panelElements = new ArrayList<>();
public void setPlotterSettings(PlotterSettings arg0) {
settings = arg0;
}
/**
* @return the translated name.
*/
abstract public String getName();
/**
* Start the conversion process.
* @param paper the bounds of the final output.
* @param img the {@code TransformedImage} this filter is using as source material.
*/
public void start(Paper paper,TransformedImage img) {
myPaper = paper;
myImage = img;
texture = null;
}
/**
* Stop the conversion process. Called by the GUI when the user cancels the conversion, either by changing the
* style or halting altogether. In both cases the conversion should be aborted.
*/
public void stop() {}
/**
* Live preview as the system is converting pictures.
* draw the results as the calculation is being performed.
*/
public void render(Graphics graphics) {
if( texture==null && myImage!=null) {
texture = myImage.getSourceImage();
}
if(texture!=null) {
double w = myImage.getSourceImage().getWidth() * myImage.getScaleX();
double h = myImage.getSourceImage().getHeight() * myImage.getScaleY();
Graphics2D g2d = (Graphics2D) graphics;
g2d.drawImage(texture,
(int)(-w/2),
(int)(-h/2),
(int)(w),
(int)(h),
null);
}
}
/**
* Drag the pen across the paper from p0 to p1, sampling (p1-p0)/stepSize times. If the intensity of img
* at a sample location is greater than the channelCutff, raise the pen. Print the gcode results to out.
* This method is used by several converters.
*
* @param x0 starting position on the paper.
* @param y0 starting position on the paper.
* @param x1 ending position on the paper.
* @param y1 ending position on the paper.
* @param stepSize mm level of detail for this line.
* @param channelCutoff only put pen down when color below this amount.
* @param img the image to sample while converting along the line.
*/
protected void convertAlongLine(double x0,double y0,double x1,double y1,double stepSize,double channelCutoff,TransformedImage img) {
Point2d P0 = new Point2d(x0,y0);
Point2d P1 = new Point2d(x1,y1);
Rectangle2D.Double rect = myPaper.getMarginRectangle();
Point2d rMax = new Point2d(rect.getMaxX(),rect.getMaxY());
Point2d rMin = new Point2d(rect.getMinX(),rect.getMinY());
if(!Clipper2D.clipLineToRectangle(P0, P1, rMax, rMin)) {
// entire line clipped
return;
}
double cx = myPaper.getCenterX();
double cy = myPaper.getCenterY();
var o = turtle.getPosition();
o.sub(P0);
boolean firstJump = MathHelper.lengthSquared(o.x, o.y)>2;
if(firstJump) turtle.jumpTo(cx+P0.x,cy+P0.y);
double b;
double dx=P1.x-P0.x;
double dy=P1.y-P0.y;
double halfStep = stepSize/2.0;
double distance = Math.sqrt(dx*dx+dy*dy);
double n,x,y,v;
for( b = 0; b <= distance; b+=stepSize ) {
n = b / distance;
x = dx * n + P0.x;
y = dy * n + P0.y;
v = img.sample( x, y , halfStep);
x += cx;
y += cy;
if(v<channelCutoff) turtle.moveTo(x,y);
else turtle.jumpTo(x,y);
}
}
/**
* Drag the pen across the paper from p0 to p1, sampling (p1-p0)/stepSize times. If the intensity of img
* at a sample location is greater than the channelCutff, raise the pen. Print the gcode results to out.
* This method is used by several converters.
*
* @param x0 starting position on the paper.
* @param y0 starting position on the paper.
* @param x1 ending position on the paper.
* @param y1 ending position on the paper.
* @param stepSize mm level of detail for this line.
* @param channelCutoff only put pen down when color below this amount.
* @param img the image to sample while converting along the line.
*/
protected void convertAlongLineErrorTerms(double x0,double y0,double x1,double y1,double stepSize,double channelCutoff,double [] error0,double [] error1,TransformedImage img) {
double b;
double dx=x1-x0;
double dy=y1-y0;
double halfStep = stepSize/2.0;
double distance = Math.sqrt(dx*dx+dy*dy);
double n,x,y,oldPixel,newPixel;
boolean wasInside = false;
boolean isInside;
boolean penUp;
int steps=0;
Rectangle2D.Double rect = myPaper.getMarginRectangle();
for (b = 0; b <= distance; b+=stepSize) {
n = b / distance;
x = dx * n + x0;
y = dy * n + y0;
isInside = rect.contains(x,y);
if(isInside) {
oldPixel = img.sample( x, y, halfStep);
int b2 = (int)Math.min(b, error0.length-2);
oldPixel += error0[b2];
newPixel = oldPixel>=channelCutoff? 255:0;
double quantError = oldPixel - newPixel;
if(b2+1< steps) error0[b2+1] += quantError * 7.0/16.0;
if(b2-1>=0 ) error1[b2-1] += quantError * 3.0/16.0;
error1[b2 ] += quantError * 5.0/16.0;
if(b2+1< steps) error1[b2+1] += quantError * 1.0/16.0;
penUp = (newPixel==255);
} else {
penUp=true;
}
if(penUp) turtle.penUp();
else turtle.penDown();
turtle.moveTo(x,y);
steps++;
if( wasInside && !isInside ) break; // done
wasInside=isInside;
}
turtle.penUp();
}
// Observer pattern notified when a converter has finished a job.
private final List<ImageConverterListener> listeners = new ArrayList<>();
public void addImageConverterListener(ImageConverterListener listener) {
listeners.add(listener);
}
public void removeImageConverterListener(ImageConverterListener listener) {
listeners.remove(listener);
}
protected void fireRestart() {
for(ImageConverterListener listener:listeners) listener.onRestart(this);
}
/**
* Called when the converter has successfully finished a job.
*/
protected void fireConversionFinished() {
logger.debug("fire conversion finished");
for(ImageConverterListener listener : listeners) {
listener.onConvertFinished(turtle);
}
}
public void add(Select element) {
panelElements.add(element);
}
public List<Select> getPanelElements() {
return panelElements;
}
/**
* @return the plotter settings for this converter.
*/
public PlotterSettings getPlotterSettings() {
if(settings == null) {
throw new RuntimeException("PlotterSettings is null. Did you forget to call setPlotterSettings()?");
}
return settings;
}
}
f99985dc8This file is pinned to the selected release. Download the original to open it in your design tool.