Hi every one,
first sorry for my english. Those last days I wrote a code for export a usable Gcode file for pen plotter or laser, from a bitmap black and white image. The point is to find path along black pixel and don’t make a line by line drawing who can’t be use by a pen plotter. It work, but I think it could be more efficient : do you have any idea for making a " nearest neighbor " function?
Before sharing the code, I want to precise that i’m not a develloper, I’m just an artist who like to use machine for doing the job… So my code is probably messy ( like my english ) and maybe my comment are not so well… So here is the main code :
PImage img;
String tool ="M4 S"; // For a laser tool use the M4 command, M3 for a pen Plotter
int powerOn = 255; // For me only : For a laser it's 255 max, for my cartesian pen plotter it's 50, 10 for the coreXY
int powerOff = 0; // For me only : For a laser it's 0, for my cartesian pen plotter it's 150, 30 for the coreXY
int workSpeed = 3000;
int travelSpeed = 3000;
int seuil = 1;
///////////////////////////// Here there are variable that you have to adapt for your own plotter. Here it's for a laser, it can easily be adapt for a pen plotter
String text[];
String list[];
String tempX;
String tempY;
float echelle;
float widthOrg;
String oldDimX;
String oldDimY;
float X;
float Y;
float newWidth = 100; // you can modify this variable, it give you the width in millimeter off your final NC code if you use the reSize() function
///////////////////////////// Here there are the variable use by the reSize() function.
boolean display = false; // You can display or not with point() the path find by the code. Display it increase the time of execution : 9 sec at display = false / 29 sec at display = true for my computer
boolean continu;
boolean black = false;
color w = color(255,255,255);
color g = color(0,255,0); // It's the color of the display point if you use display = true
ArrayList<PVector> V = new ArrayList<PVector>();
int t;
PVector v;
String lastLine="";
float diff;
float oldEcart;
Data gCodeFile;
PrintWriter newSize;
///////////////////////////// Here there are the variable use by the lookAround() and draw() function.
void setup( ) {
gCodeFile = new Data();
img = loadImage(dataPath( "image4.png" ));
size(100, 100);
surface.setResizable(true);
surface.setSize(img.width, img.height);
widthOrg = img.width;
echelle = widthOrg/newWidth;
background(255);
img.loadPixels();
frameRate(1000000);
for (int y = 0; y < img.height; y++) {
for (int x = 0; x < img.width; x++) {
if(brightness(img.get(x,y))<seuil){ // Here I begin by filling the V array with black pixel
V.add(new PVector(x, y));
}
}
}
println("Pixels noirs = "+V.size()+" sur = "+img.pixels.length+" pixels"); // I just check the number of black pixel.
gCodeFile.beginSave();
println(img.width+" "+img.height);
gCodeFile.add( tool+powerOff );
gCodeFile.add( "G0 X0 Y0 F"+travelSpeed );
}
void lookAround(float x, float y){ // Ok, this is the most important function ; it will find the path of black pixels
black = false;
img.loadPixels();
//////////////////////////////////////////////////////////////////////////////// I have 9 case to check, this one is the more common : the black pixel is not on a edge of the picture, so I check the height pixel around it.
if((y>0)&&(x>0)&&(y<img.height-1)&&(x<img.width-1)){
if(brightness( img.pixels[int(y+1) * img.width + int(x+1) ])<seuil){
black = true;
goFirstPix(x,y);
x = x+1;
y = y+1;
//println("black!1.1");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y+1;
//println("black!1.2");
}else if(brightness( img.pixels[int(y) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
//println("black!1.3");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
y = y+1;
//println("black!1.4");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
y = y-1;
//println("black!1.5");
}else if(brightness( img.pixels[int(y) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
//println("black!1.6");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y-1;
//println("black!1.7");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
y = y-1;
//println("black!1.8");
}
//////////////////////////////////////////////////////////////////////////////// second case, the black pixel is in the top left corner (x0, y0)
}else if((y==0)&&(x==0)&&(y<img.height-1)&&(x<img.width-1)){
if(brightness( img.pixels[int(y+1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y+1;
//println("black!2.1");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
y = y+1;
//println("black!2.2");
}else if(brightness( img.pixels[int(y) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
//println("black!2.3");
}
//////////////////////////////////////////////////////////////////////////////// third case, the black pixel is on the top edge (y0)
}else if((y==0)&&(x>0)&&(y<img.height-1)&&(x<img.width-1)){
if(brightness( img.pixels[int(y+1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y+1;
//println("black!3.1");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
y = y+1;
//println("black!3.2");
}else if(brightness( img.pixels[int(y) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
//println("black!3.3");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
y = y+1;
//println("black!3.4");
}else if(brightness( img.pixels[int(y) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
//println("black!3.5");
}
//////////////////////////////////////////////////////////////////////////////// fourth case, the black pixel is on the top right corner (y0 xMax)
}else if((y==0)&&(x==img.width-1)){
if(brightness( img.pixels[int(y+1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y+1;
//println("black!4.1");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y+1;
//println("black!4.2");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
y = y+1;
//println("black!4.3");
}else if(brightness( img.pixels[int(y) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
//println("black!4.4");
}
//////////////////////////////////////////////////////////////////////////////// fifth case, the black pixel is on the left edge (x0)
}else if((y>0)&&(x==0)&&(y<img.height-1)&&(x<img.width-1)){
if(brightness( img.pixels[int(y+1) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
y = y+1;
//println("black!5.1");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y+1;
//println("black!5.2");
}else if(brightness( img.pixels[int(y) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
//println("black!5.3");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
y = y-1;
//println("black!5.4");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y-1;
//println("black!5.5");
}
//////////////////////////////////////////////////////////////////////////////// sixth case, the black pixel is on the right edge (xMax)
}else if((y>0)&&(y<img.height-1)&&(x==img.width-1)){
if(brightness( img.pixels[int(y+1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y+1;
//println("black!6.1");
}else if(brightness( img.pixels[int(y+1) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
y = y+1;
//println("black!6.2");
}else if(brightness( img.pixels[int(y) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
//println("black!6.3");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y-1;
//println("black!6.4");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
y = y-1;
//println("black!6.5");
}
//////////////////////////////////////////////////////////////////////////////// seventh case, the black pixel is on the bottom left corner (x0 yMax)
}else if((y==img.height-1)&&(x==0)){
if(brightness( img.pixels[int(y) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
//println("black!7.1");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
y = y-1;
//println("black!7.3");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y-1;
//println("black!7.2");
}
//////////////////////////////////////////////////////////////////////////////// eighth case, the black pixel is on the bottom edge (yMax)
if(brightness( img.pixels[int(y) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
//println("black!8.1");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x+1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x+1;
y = y-1;
//println("black!8.2");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y-1;
//println("black!8.3");
}else if(brightness( img.pixels[int(y) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
//println("black!8.4");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
y = y-1;
//println("black!8.5");
}
//////////////////////////////////////////////////////////////////////////////// last case, the black pixel is on the right bottom corner (xMax yMax)
}else if((y==img.height-1)&&(x==img.width-1)){
if(brightness( img.pixels[int(y) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
//println("black!9.1");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x) ])<seuil){
goFirstPix(x,y);
black = true;
y = y-1;
//println("black!9.2");
}else if(brightness( img.pixels[int(y-1) * img.width + int(x-1) ])<seuil){
goFirstPix(x,y);
black = true;
x = x-1;
y = y-1;
//println("black!9.3");
}
}
if(black == true){ // If a black pixel was found, I continue
img.pixels[int(y) * img.width + int(x) ]=w; // I turn this pixel in white, it will not be explore a second time
img.updatePixels();
if(display == true){ // I draw it on screen
stroke(g);
point(x,y);
}
continu = true; // I set continu at true
gCodeFile.add( "G1 X"+x+" Y"+y ); // I write the coordinate of the pixel in my Gcode
lastLine=("G1 X"+x+" Y"+y); // I keep a trace of the last wrote line
v.x = x; // I dont know why but it don't work if I don't do that
v.y = y;
for(int i = 0 ; i<V.size() ; i++){ // Now I remove the draw pixel fro my array
PVector c = V.get(i);
diff = ((v.x)+(v.y))-((c.x)+(c.y));
if((c.x == v.x)&&( c.y== v.y)){
V.remove(i);
}
}
}else{ // If no black pixel was found around the first one I put the tool off ONE TIME
if(lastLine.equals(tool+powerOff)==false){
gCodeFile.add( tool+powerOff );
lastLine=(tool+powerOff);
}
continu = false; // Continu is false so let's see the next pixel store in the array
}
}
void goFirstPix(float x, float y){ // I take a pixel in my array, if it is alone, I don't draw it, else, I go to it with tool off and then I put tool on
if(continu == false){
gCodeFile.add("G0 X"+x+" Y"+y );
gCodeFile.add( tool+powerOn );
gCodeFile.add("G1 X"+x+" Y"+y );
lastLine=("G1 X"+x+" Y"+y);
img.pixels[int(y) * img.width + int(x) ]=w;
img.updatePixels();
V.remove(t); // In both case, I remove it from my array
}
}
void draw() { // And here we are, I think no more comment are needed... Sorry for my english, sorry if I was'nt clear
if(t<V.size()){
if(continu == false){
v = V.get(t);
lookAround(v.x,v.y);
if(continu == false){
t=t+1;
}
}else{
if(continu == true){
lookAround(v.x,v.y);
}
}
}else{
gCodeFile.add("M5");
gCodeFile.endSave( dataPath( "gCode-.nc" ));
newSize = createWriter(dataPath("GcodeReSized.nc"));
reSize("gCode-.nc");
println("done in "+millis()/1000+" sec");
exit();
}
}
void reSize(String T) {
ArrayList<String> valeurs = new ArrayList<String>();
text = loadStrings(T);
for(int i = 0; i<text.length; i++){
list = split(text[i], ' ');
for(int y=0; y<list.length;y++){
valeurs.add(list[y]);
}
}
for(int x=0; x<valeurs.size();x++){
if(x<valeurs.size()-1){
if(valeurs.get(x).charAt(0) == 'X'){
oldDimX = valeurs.get(x);
tempX = oldDimX.substring(1, oldDimX.length());
X = float(tempX);
newSize.print("X"+X/echelle+" ");
}else if(valeurs.get(x).charAt(0) == 'Y'){
oldDimY = valeurs.get(x);
tempY = oldDimY.substring(1, oldDimY.length());
Y = float(tempY);
newSize.print("Y"+Y/echelle+' ');
}else if(valeurs.get(x).charAt(0) == 'G'){
newSize.print('\n'+valeurs.get(x)+' ');
}else if(valeurs.get(x).charAt(0) == 'F'){
newSize.print(valeurs.get(x));
}else if(valeurs.get(x).charAt(0) == 'M'){
newSize.print('\n'+valeurs.get(x)+' ');
}else if(valeurs.get(x).charAt(0) == 'S'){
newSize.print(valeurs.get(x)+' ');
}
}else if(x == valeurs.size()-1){
newSize.print('\n'+valeurs.get(x));
}
newSize.flush();
}
println(valeurs.get(valeurs.size()-1));
}
In the sketch path, you have to copy the following code as a .pde file and import it as a new tab in your code. You also will have to create a folder name data in your sketch path and put your test image into it.
class Data {
ArrayList datalist;
String filename, data[];
int datalineId;
// begin data saving
void beginSave() {
datalist=new ArrayList();
}
void add(String s) {
datalist.add(s);
}
void add(float val) {
datalist.add(""+val);
}
void add(int val) {
datalist.add(""+val);
}
void add(boolean val) {
datalist.add(""+val);
}
void endSave(String _filename) {
filename=_filename;
data=new String[datalist.size()];
data=(String [])datalist.toArray(data);
saveStrings(filename, data);
println("Saved data to '"+filename+
"', "+data.length+" lines.");
}
void load(String _filename) {
filename=_filename;
datalineId=0;
data=loadStrings(filename);
println("Loaded data from '"+filename+
"', "+data.length+" lines.");
}
float readFloat() {
return float(data[datalineId++]);
}
int readInt() {
return int(data[datalineId++]);
}
boolean readBoolean() {
return boolean(data[datalineId++]);
}
String readString() {
return data[datalineId++];
}
}
I recommand to use a simple black and white image to test the code. I join my test image, generate from a picture by another of my code… If someone have any idea for optimize that, I take it!
Thanks for reading.