BMP image to Gcode for plotter : sharing and asking for idea

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.

1 Like

Very nice work and creative

1 Like

Look at this link

Hi thanks for your answer, I read the link, it’s interesting. Now I’m looking for a good way to find the nearest neiboor of the last pixel of a path… I edit my code in the post : I made a mistake in the reSize() function…