/**
Polargraph controller
Copyright Sandy Noble 2012.
This file is part of Polargraph Controller.
Polargraph Controller is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Polargraph Controller is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Polargraph Controller. If not, see .
Requires the excellent ControlP5 GUI library available from http://www.sojamo.de/libraries/controlP5/.
Requires the excellent Geomerative library available from http://www.ricardmarxer.com/geomerative/.
This is an application for controlling a polargraph machine, communicating using ASCII command language over a serial link.
sandy.noble@gmail.com
http://www.polargraph.co.uk/
http://code.google.com/p/polargraph/
*/
class DisplayMachine extends Machine
{
private Rectangle outline = null;
private float scaling = 1.0;
private Scaler scaler = null;
private PVector offset = null;
private float imageTransparency = 1.0;
private Set extractedPixels = new HashSet(0);
PImage scaledImage = null;
private PVector currentPixel = null;
public DisplayMachine(Machine m, PVector offset, float scaling)
{
// construct
super(m.getWidth(), m.getHeight(), m.getMMPerRev(), m.getStepsPerRev());
super.machineSize = m.machineSize;
super.page = m.page;
super.imageFrame = m.imageFrame;
super.pictureFrame = m.pictureFrame;
super.imageBitmap = m.imageBitmap;
super.imageFilename = m.imageFilename;
super.stepsPerRev = m.stepsPerRev;
super.mmPerRev = m.mmPerRev;
super.mmPerStep = m.mmPerStep;
super.stepsPerMM = m.stepsPerMM;
super.maxLength = m.maxLength;
super.gridSize = m.gridSize;
this.offset = offset;
this.scaling = scaling;
this.scaler = new Scaler(scaling, 100.0);
this.outline = null;
}
public Rectangle getOutline()
{
outline = new Rectangle(offset, new PVector(sc(super.getWidth()), sc(super.getHeight())));
return this.outline;
}
private Scaler getScaler()
{
if (scaler == null)
this.scaler = new Scaler(getScaling(), getMMPerStep());
return scaler;
}
public void setScale(float scale)
{
this.scaling = scale;
this.scaler = new Scaler(scale, getMMPerStep());
}
public float getScaling()
{
return this.scaling;
}
public float sc(float val)
{
return getScaler().scale(val);
}
public void setOffset(PVector offset)
{
this.offset = offset;
}
public PVector getOffset()
{
return this.offset;
}
public void setImageTransparency(float trans)
{
this.imageTransparency = trans;
}
public int getImageTransparency()
{
float f = 255.0 * this.imageTransparency;
f += 0.5;
int result = (int) f;
return result;
}
public PVector getCurrentPixel()
{
return this.currentPixel;
}
public void setCurrentPixel(PVector p)
{
this.currentPixel = p;
}
public void loadNewImageFromFilename(String filename)
{
super.loadImageFromFilename(filename);
super.sizeImageFrameToImageAspectRatio();
this.setExtractedPixels(new HashSet(0));
}
public final int DROP_SHADOW_DISTANCE = 4;
public String getZoomText()
{
NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK);
DecimalFormat df = (DecimalFormat)nf;
df.applyPattern("###");
String zoom = df.format(scaling * 100) + "% zoom";
return zoom;
}
public String getDimensionsAsText(Rectangle r)
{
return getDimensionsAsText(r.getSize());
}
public String getDimensionsAsText(PVector p)
{
String dim = inMM(p.x) + " x " + inMM(p.y) + "mm";
return dim;
}
public void drawForSetup()
{
// work out the scaling factor.
noStroke();
// draw machine outline
// drop shadow
fill(80);
rect(getOutline().getLeft()+DROP_SHADOW_DISTANCE, getOutline().getTop()+DROP_SHADOW_DISTANCE, getOutline().getWidth(), getOutline().getHeight());
fill(getMachineColour());
rect(getOutline().getLeft(), getOutline().getTop(), getOutline().getWidth(), getOutline().getHeight());
text("machine " + getDimensionsAsText(getSize()) + " " + getZoomText(), getOutline().getLeft(), getOutline().getTop());
if (displayingGuides)
{
// draw some guides
stroke(getGuideColour());
strokeWeight(1);
// centre line
line(getOutline().getLeft()+(getOutline().getWidth()/2), getOutline().getTop(),
getOutline().getLeft()+(getOutline().getWidth()/2), getOutline().getBottom());
// page top line
line(getOutline().getLeft(), getOutline().getTop()+sc(getHomePoint().y),
getOutline().getRight(), getOutline().getTop()+sc(getHomePoint().y));
}
// draw page
fill(getPageColour());
rect(getOutline().getLeft()+sc(getPage().getLeft()),
getOutline().getTop()+sc(getPage().getTop()),
sc(getPage().getWidth()),
sc(getPage().getHeight()));
text("page " + getDimensionsAsText(getPage()), getOutline().getLeft()+sc(getPage().getLeft()),
getOutline().getTop()+sc(getPage().getTop()));
fill(0);
text("offset " + getDimensionsAsText(getPage().getPosition()),
getOutline().getLeft()+sc(getPage().getLeft()),
getOutline().getTop()+sc(getPage().getTop())+10);
noFill();
// draw home point
noFill();
strokeWeight(5);
stroke(0, 128);
PVector onScreen = scaleToScreen(inMM(getHomePoint()));
ellipse(onScreen.x, onScreen.y, 15, 15);
strokeWeight(2);
stroke(255);
ellipse(onScreen.x, onScreen.y, 15, 15);
text("Home point", onScreen.x+ 15, onScreen.y-5);
text(int(inMM(getHomePoint().x)+0.5) + ", " + int(inMM(getHomePoint().y)+0.5), onScreen.x+ 15, onScreen.y+15);
if (displayingGuides
&& getOutline().surrounds(getMouseVector())
&& currentMode != MODE_MOVE_IMAGE
&& mouseOverControls().isEmpty()
)
{
drawHangingStrings();
drawLineLengthTexts();
cursor(CROSS);
}
else
{
cursor(ARROW);
}
}
public void drawLineLengthTexts()
{
PVector actual = inMM(asNativeCoords(inSteps(scaleToDisplayMachine(getMouseVector()))));
PVector cart = scaleToDisplayMachine(getMouseVector());
NumberFormat nf = NumberFormat.getNumberInstance(Locale.UK);
DecimalFormat df = (DecimalFormat)nf;
df.applyPattern("###.#");
text("Line 1: " + df.format(actual.x) + "mm", getDisplayMachine().getOutline().getLeft()+10, getDisplayMachine().getOutline().getTop()+18);
text("Line 2: " + df.format(actual.y) + "mm", getDisplayMachine().getOutline().getLeft()+10, getDisplayMachine().getOutline().getTop()+28);
text("X Position: " + df.format(cart.x) + "mm", getDisplayMachine().getOutline().getLeft()+10, getDisplayMachine().getOutline().getTop()+42);
text("Y Position: " + df.format(cart.y) + "mm", getDisplayMachine().getOutline().getLeft()+10, getDisplayMachine().getOutline().getTop()+52);
}
public void draw()
{
// work out the scaling factor.
noStroke();
// draw machine outline
// fill(80);
// rect(getOutline().getLeft()+DROP_SHADOW_DISTANCE, getOutline().getTop()+DROP_SHADOW_DISTANCE, getOutline().getWidth(), getOutline().getHeight());
fill(getMachineColour());
rect(getOutline().getLeft(), getOutline().getTop(), getOutline().getWidth(), getOutline().getHeight());
if (displayingGuides)
{
// draw some guides
stroke(getGuideColour());
strokeWeight(1);
// centre line
line(getOutline().getLeft()+(getOutline().getWidth()/2), getOutline().getTop(),
getOutline().getLeft()+(getOutline().getWidth()/2), getOutline().getBottom());
// page top line
line(getOutline().getLeft(), getOutline().getTop()+sc(getHomePoint().y),
getOutline().getRight(), getOutline().getTop()+sc(getHomePoint().y));
}
// draw page
fill(getPageColour());
rect(getOutline().getLeft()+sc(getPage().getLeft()),
getOutline().getTop()+sc(getPage().getTop()),
sc(getPage().getWidth()),
sc(getPage().getHeight()));
text("page " + getDimensionsAsText(getPage()), getOutline().getLeft()+sc(getPage().getLeft()),
getOutline().getTop()+sc(getPage().getTop())-3);
noFill();
// draw actual image
if (displayingImage && imageIsReady())
{
float ox = getOutline().getLeft()+sc(getImageFrame().getLeft());
float oy = getOutline().getTop()+sc(getImageFrame().getTop());
float w = sc(getImageFrame().getWidth());
float h = sc(getImageFrame().getHeight());
tint(255, getImageTransparency());
image(getImage(), ox, oy, w, h);
noTint();
strokeWeight(1);
stroke(150, 150, 150, 40);
rect(ox, oy, w-1, h-1);
fill(150, 150, 150, 40);
text("image", ox, oy-3);
noFill();
}
stroke(getBackgroundColour(),150);
strokeWeight(3);
noFill();
rect(getOutline().getLeft()-2, getOutline().getTop()-2, getOutline().getWidth()+3, getOutline().getHeight()+3);
stroke(getMachineColour(),150);
strokeWeight(3);
noFill();
rect(getOutline().getLeft()+sc(getPage().getLeft())-2,
getOutline().getTop()+sc(getPage().getTop())-2,
sc(getPage().getWidth())+4,
sc(getPage().getHeight())+4);
if (displayingSelectedCentres)
{
drawExtractedPixelCentres();
}
if (displayingDensityPreview)
{
drawExtractedPixelDensities();
}
if (displayingGuides)
{
drawPictureFrame();
}
if (displayingVector && getVectorShape() != null)
{
displayVectorImage();
}
if (displayingGuides
&& getOutline().surrounds(getMouseVector())
&& currentMode != MODE_MOVE_IMAGE
&& mouseOverControls().isEmpty()
)
{
drawHangingStrings();
drawRows();
cursor(CROSS);
}
else
{
cursor(ARROW);
}
}
public void drawForTrace()
{
// work out the scaling factor.
noStroke();
// draw machine outline
// liveImage = trace_buildLiveImage();
// draw actual image
// if (drawingLiveVideo)
// {
// displayLiveVideo();
// }
if (drawingTraceShape && traceShape != null)
{
displaytraceShape();
}
else
{
}
}
// public void displayLiveVideo()
// {
// // draw actual image, maximum size
// if (processedLiveImage != null)
// {
// // origin - top left of the corner
// float ox = getPanel(PANEL_NAME_WEBCAM).getOutline().getRight()+7;
// float oy = getPanel(PANEL_NAME_GENERAL).getOutline().getTop();
//
// // calculate size to display at.
// float aspectRatio = (rotateWebcamImage) ? 480.0/640.0 : 640.0/480.0; // rotated, remember
// float h = height - getPanel(PANEL_NAME_GENERAL).getOutline().getTop() -10;
// float w = h * (480.0/640.0);
//// println("height: " + h + ", width: " + w);
//// println("origin x: " + ox + ", y: " + oy);
//
// if (rotateWebcamImage)
// {
// float t = h;
// h = w;
// w = t;
// }
//
// //stroke(255);
// rect(ox,oy,w,h);
//
// tint(255, getImageTransparency());
// if (rotateWebcamImage)
// {
// translate(ox, oy);
// rotate(radians(270));
// image(processedLiveImage, -w, 0, w, h);
// image(liveImage, -w, (w-(w/4))+10, w/4, h/4);
//// stroke(0,255,0);
//// ellipse(0,0,80,40);
//// stroke(0,0,255);
//// ellipse(-w,0,80,40);
// rotate(radians(-270));
// translate(-ox, -oy);
// }
// else
// {
// translate(ox, oy);
// image(processedLiveImage, 0, 0, h, w);
// image(liveImage, h-(h/4), w+10, h/4, w/4);
// translate(-ox, -oy);
// }
// noTint();
// noFill();
// }
// }
public void displaytraceShape()
{
strokeWeight(1);
if (captureShape != null)
{
//displaytraceShapeAtFullSize(traceShape, false, color(150,150,150));
displaytraceShapeAtFullSize(captureShape, true, color(0,0,0));
}
else
{
displaytraceShapeAtFullSize(traceShape, false, color(255,255,255));
}
}
public void displaytraceShapeAtFullSize(RShape vec, boolean illustrateSequence, Integer colour)
{
RG.ignoreStyles();
// work out scaling to make it full size on the screen
float aspectRatio = vec.getWidth()/vec.getHeight(); // rotated, remember
float h = height - getPanel(PANEL_NAME_GENERAL).getOutline().getTop() -10;
float w = h * aspectRatio;
float scaler = h / vec.getWidth();
if (rotateWebcamImage)
scaler = h / vec.getHeight();
PVector position = new PVector(getPanel(PANEL_NAME_TRACE).getOutline().getRight()+7, getPanel(PANEL_NAME_GENERAL).getOutline().getTop());
noFill();
RPoint[][] pointPaths = vec.getPointsInPaths();
if (illustrateSequence)
pointPaths = sortPathsCentreFirst(vec, pathLengthHighPassCutoff);
if (pointPaths != null)
{
float incPerPath = 0.0;
if (illustrateSequence)
incPerPath = 255.0 / (float) pointPaths.length;
for(int i = 0; i= pathLengthHighPassCutoff)
// {
if (pointPaths[i] != null)
{
if (illustrateSequence)
stroke((int)col, (int)col, (int)col, 128);
else
stroke(colour);
beginShape();
for (int j = 0; j= pixelExtractDarkThreshold))
{
// scale em, danno.
PVector scaledPos = scaleToScreen(cartesianPos);
noStroke();
fill(cartesianPos.z);
switch (getDensityPreviewStyle())
{
case DENSITY_PREVIEW_ROUND:
previewRoundPixel(scaledPos, pixelSize);
break;
case DENSITY_PREVIEW_ROUND_SIZE:
fill(0);
previewRoundPixel(scaledPos, map(cartesianPos.z, 1, 255, pixelSize, 1));
break;
case DENSITY_PREVIEW_DIAMOND:
previewDiamondPixel(scaledPos, pixelSize, pixelSize, cartesianPos.z);
break;
case DENSITY_PREVIEW_NATIVE:
previewNativePixel(scaledPos, pixelSize, cartesianPos.z);
break;
case DENSITY_PREVIEW_NATIVE_SIZE:
previewNativePixel(scaledPos, map(cartesianPos.z, 1, 255, pixelSize, 1), 0);
break;
case DENSITY_PREVIEW_NATIVE_ARC:
previewRoundPixel(scaledPos, pixelSize*0.8);
previewNativeArcPixel(scaledPos, pixelSize, cartesianPos.z);
break;
default:
previewRoundPixel(scaledPos, pixelSize);
break;
}
}
}
}
noFill();
}
void previewDiamondPixel(PVector pos, float wide, float high, float brightness)
{
wide*=1.4;
high*=1.4;
// shall I try and draw a diamond here instead? OK! I'll do it! Ha!
float halfWidth = wide / 2.0;
float halfHeight = high / 2.0;
fill(0,0,0, 255-brightness);
quad(pos.x, pos.y-halfHeight, pos.x+halfWidth, pos.y, pos.x, pos.y+halfHeight, pos.x-halfWidth, pos.y);
}
void previewNativePixel(PVector pos, float size, float brightness)
{
float half = size / 2.0;
fill(0,0,0, 255-brightness);
beginShape();
// arcs from the left-hand corner
float distFromPointA = getOutline().getTopLeft().dist(pos);
float distFromPointB = getOutline().getTopRight().dist(pos);
List int1 = findIntersections(getOutline().getLeft(), distFromPointA-half, getOutline().getRight(), distFromPointB-half, size);
List int2 = findIntersections(getOutline().getLeft(), distFromPointA+half, getOutline().getRight(), distFromPointB-half, size);
// plot out the vertexes
vertex(int1.get(0).x, int1.get(0).y);
vertex(int2.get(0).x, int2.get(0).y);
vertex(int2.get(1).x, int2.get(1).y);
vertex(int1.get(1).x, int1.get(1).y);
vertex(int1.get(0).x, int1.get(0).y);
endShape();
}
void previewNativeArcPixel(PVector pos, float size, float brightness)
{
float half = size / 2.0;
// fill(0,0,0, 255-brightness);
beginShape();
// arcs from the left-hand corner
float distFromPointA = getOutline().getTopLeft().dist(pos);
float distFromPointB = getOutline().getTopRight().dist(pos);
List int1 = findIntersections(getOutline().getLeft(), distFromPointA-half, getOutline().getRight(), distFromPointB-half, size);
List int2 = findIntersections(getOutline().getLeft(), distFromPointA+half, getOutline().getRight(), distFromPointB-half, size);
// plot out the vertexes
noFill();
stroke(0,0,0, 255-brightness);
float i1Angle1 = atan2(int1.get(0).y-getOutline().getTop(), int1.get(0).x-getOutline().getLeft());
float i1Angle2 = atan2(int1.get(1).y-getOutline().getTop(), int1.get(1).x-getOutline().getLeft());
arc(getOutline().getLeft(), getOutline().getTop(), (distFromPointA-half)*2, (distFromPointA-half)*2, i1Angle1, i1Angle2);
i1Angle1 = atan2(int2.get(0).y-getOutline().getTop(), int2.get(0).x-getOutline().getLeft());
i1Angle2 = atan2(int2.get(1).y-getOutline().getTop(), int2.get(1).x-getOutline().getLeft());
arc(getOutline().getLeft(), getOutline().getTop(), (distFromPointA+half)*2, (distFromPointA+half)*2, i1Angle1, i1Angle2);
i1Angle1 = atan2( int1.get(0).y-getOutline().getTop(), int1.get(0).x-getOutline().getRight());
i1Angle2 = atan2( int2.get(0).y-getOutline().getTop(), int2.get(0).x-getOutline().getRight());
arc(getOutline().getRight(), getOutline().getTop(), (distFromPointB-half)*2, (distFromPointB-half)*2, i1Angle2, i1Angle1);
i1Angle1 = atan2( int1.get(1).y-getOutline().getTop(), int1.get(1).x-getOutline().getRight());
i1Angle2 = atan2( int2.get(1).y-getOutline().getTop(), int2.get(1).x-getOutline().getRight());
arc(getOutline().getRight(), getOutline().getTop(), (distFromPointB+half)*2, (distFromPointB+half)*2, i1Angle2, i1Angle1);
endShape();
}
void previewRoundPixel(PVector pos, float dia)
{
ellipse(pos.x, pos.y, dia*1.1, dia*1.1);
}
// compute and draw intersections
/**
circle1 = c1x is the centre, and r1 is the radius of the arc to be drawn.
circle2 = c2x, r2 describe the arc that is used to calculate the start and
end point of the drawn arc.
circle3 = c2x, r3 is calculated by adding size to r2.
The drawn arc should start at the intersection with the circle1,
and end at the intersection with circle3.
The clever bits of this are nicked off http://processing.org/discourse/beta/num_1223494826.html
*/
List findIntersections(float c1x, float r1, float c2x, float r2, float pixelSize)
{
float c1y = getOutline().getTop();
float c2y = getOutline().getTop();
float d=getOutline().getWidth(); // distance between centers
float base1, h1, base2, h2; // auxiliary distances
// p, middle point between q1 and q2
// q1 dn q2 intersection points
float p1x,p1y,p2x,p2y, q1x,q1y,q2x,q2y;
if(dr1+r2)
{
println("C1 and C2 do not intersect");
return new ArrayList();
}
else if(d==r1+r2)
{ // outside each other, intersect in one point
return new ArrayList();
}
else
{
// intersect in two points
base1 = (r1*r1-r2*r2+d*d) / (2*d);
h1 = sqrt(r1*r1-base1*base1);
p1x = c1x+base1*(c2x-c1x)/d;
p1y = c1y+base1*(c2y-c1y)/d;
q1x=abs(p1x-h1*(c2y-c1y)/d);
q1y=abs(p1y+h1*(c2x-c1x)/d);
float r3 = r2+pixelSize;
base2 = (r1*r1-r3*r3+d*d) / (2*d);
h2 = sqrt(r1*r1-base2*base2);
p2x = c1x+base2*(c2x-c1x)/d;
p2y = c1y+base2*(c2y-c1y)/d;
q2x=abs(p2x-h2*(c2y-c1y)/d);
q2y=abs(p2y+h2*(c2x-c1x)/d);
List l = new ArrayList(2);
l.add(new PVector(q1x, q1y));
l.add(new PVector(q2x, q2y));
return l;
}
}
color getPixelAtScreenCoords(PVector pos)
{
pos = scaleToDisplayMachine(pos);
pos = inSteps(pos);
float scalingFactor = getImage().width / getImageFrame().getWidth();
color col = super.getPixelAtMachineCoords(pos, scalingFactor);
return col;
}
Set getExtractedPixels()
{
return this.extractedPixels;
}
void setExtractedPixels(Set p)
{
this.extractedPixels = p;
}
/* This will return a list of pixels that are included in the area in the
parameter. All coordinates are for the screen.
*/
Set getPixelsPositionsFromArea(PVector p, PVector s, float rowSize)
{
extractPixelsFromArea(p, s, rowSize, 0.0);
return getExtractedPixels();
}
public void extractPixelsFromArea(PVector p, PVector s, float rowSize, float sampleSize)
{
// get the native positions from the superclass
Set nativePositions = super.getPixelsPositionsFromArea(inSteps(p), inSteps(s), rowSize, sampleSize);
// work out the cartesian positions
Set cartesianPositions = new HashSet(nativePositions.size());
for (PVector nativePos : nativePositions)
{
// convert to cartesian
PVector displayPos = super.asCartesianCoords(nativePos);
displayPos = inMM(displayPos);
displayPos.z = nativePos.z;
cartesianPositions.add(displayPos);
}
setExtractedPixels(cartesianPositions);
}
public Set extractNativePixelsFromArea(PVector p, PVector s, float rowSize, float sampleSize)
{
// get the native positions from the superclass
Set nativePositions = super.getPixelsPositionsFromArea(inSteps(p), inSteps(s), rowSize, sampleSize);
return nativePositions;
}
protected PVector snapToGrid(PVector loose, float rowSize)
{
PVector snapped = inSteps(loose);
snapped = super.snapToGrid(snapped, rowSize);
snapped = inMM(snapped);
return snapped;
}
public boolean pixelsCanBeExtracted()
{
if (super.getImage() == null)
return false;
else
return true;
}
}