// compile as: // cc -std=c99 -o qr2laser `pkg-config --cflags --libs MagickWand` qr2laser.c // on less-ancient machine, install dependency // apt install libmagickcore-6.q16hdri-dev // then compile: // cc -std=c99 -o qr2laser qr2laser.c `pkg-config --cflags --libs MagickWand-6.Q16HDRI` -lm // for g-code view, use e.g. http://jherrm.com/gcode-viewer/ #include #include #include #include #include "Base64.h" #include "Base64.cpp" // default spacing, width of the space #define DEF_SPACE 0.8 // minimum resolvable pixel size #define MIN_XSIZE 2.0 // minimum resolvable spacing #define MIN_XSPACE 0.6 // maximum platform size // TODO: x,y differences, definable as parameter #define MAX_TOTSIZE 200.0 // system limits #define MAXWIDTH 20000 #define MAXSIZE 10000*2000 unsigned char*imgarrI; unsigned char*lineconv; unsigned char* edges_x; unsigned char* edges_y; int img_h,img_w,img_wb,img_wmax,img_wbmax; int stripborder=1; int num_decimals=100; int tmp_x,tmp_y; typedef enum { MODE_ASCII, MODE_SCAD, MODE_DXF, MODE_GCODE } enum_mode; // TODO: add MODE_SVG typedef enum { INPUT_BITMAP, INPUT_BITMAP_RGB, INPUT_ASCII } enum_input; typedef enum { MODE_ASCII_NARROW, MODE_ASCII_WIDE } enum_mode_ascii; typedef enum { MODE_G_PECK, MODE_G_LINE, MODE_G_NEARESTPECK, MODE_G_G7 } enum_gmode; typedef enum{ PIX_BW, PIX_G8 } enum_pixtype; typedef enum{ PIX_PECK, PIX_G7P } enum_gcodepix; enum_mode_ascii mode_ascii=MODE_ASCII_WIDE; // pixel size for conversion to monochrome - 4 because of RGB+alpha #define PIXSIZE 4 // threshold for transparency below which the pixel is considered empty #define ALPHATHRESHOLD 0x40 int alphathreshold=ALPHATHRESHOLD; // tolerance for RGB values when set not as a range #define RGB_TOLERANCE_DEF 5 enum_input inputtype=INPUT_BITMAP_RGB; // what sort of output enum_mode pixelmode=MODE_ASCII; enum_gmode gcodemode=MODE_G_PECK; enum_pixtype pixtype=PIX_BW; enum_gcodepix gcodepix=PIX_PECK; enum_pixtype outpixtype=PIX_BW; int gcodezigzag=0; int gcodequicknext=1; // invert mask, hole for black vs hole for white int pixelinvert=0; // flip vertical for DXF output //int dxf_inverty=0; // width of added border int edgepixx=0; int edgepixy=0; // extra progress messages int debug=0; double laserjmult=1; // dimensions - pixel size, pixel spacing, whole area size; xsize[mm],totalx[mm],dpi[1] double dim_xsize=0.0,dim_ysize=0.0,dim_xspace=DEF_SPACE,dim_yspace=0.0,dim_totalx=0.0,dim_totaly=0.0,dim_dpi=0; // pixel count in QR int xcount=0,ycount=0; // pixel count without border int xcount_real,ycount_real; // pixel count with added border int xcount_real_withedge,ycount_real_withedge; // presence of frame on the QR code int isframe_left=1,isframe_top=1,isframe_right=1,isframe_bottom=1; // offset, to sample pixels from their approximate middle int pixeloffs_x=-1,pixeloffs_y=-1; double mv_offsx=0,mv_offsy=0; int mv_flipv=0,mv_fliph=0; int g7_linesperline=1; int g7_pulsewait=0; int gnozero=0; /* ===================================================================== */ double roundzero(double x){ if(x<0.000001)if(x>-0.000001)return 0.0; return x; } double xround(double x) { x=roundzero(x); return (round(x*num_decimals)/num_decimals); } void setimgwb() { if(pixtype==PIX_BW){ img_wb=(img_w+7)/8; img_wbmax=(img_wmax+7)/8; }else if(pixtype==PIX_G8){ img_wb=img_w; img_wbmax=img_wmax; } } size_t getimgsize() //{ return img_wb*img_h;} { return img_wbmax*img_h;} unsigned char getbit(int n) { return 1<>3); if(p)imgarrI[i]|=getbit (x&0x07); else imgarrI[i]&=getibit(x&0x07); }else if(pixtype==PIX_G8){ i=img_wbmax*y+x; imgarrI[i]=p; } } int getpixel_def=0; int getpixel(int x,int y) { size_t i,i1; //printf("<%i:%i>\n",x,y); if(x<0)return getpixel_def; // if(x>img_wbmax-1)return getpixel_def; if(y<0)return getpixel_def; // if(y>img_h)return getpixel_def; if(pixtype==PIX_BW){ i=img_wbmax*y+(x>>3); if(imgarrI[i]&getbit(x&0x07))return 1;else return 0; }else if(pixtype==PIX_G8){ i=img_wbmax*y+x; return imgarrI[i]; } } /* ===================================================================== */ int ishexdigit(char c) { if(c>='0')if(c<='9')return 1; if(c>='A')if(c<='F')return 1; if(c>='a')if(c<='f')return 1; return 0; } int gethexdigit(char c) { if(c>='0')if(c<='9')return c-'0'; if(c>='A')if(c<='F')return c-'A'+0x0a; if(c>='a')if(c<='f')return c-'a'+0x0a; return 0; } int gethex(char*sp) { //int c=0; return (gethexdigit(sp[0])<<4) + gethexdigit(sp[1]); } /* ===================================================================== */ void setqrpixeladdr(int x,int y) { int pos_x=0,pos_y=0; int tx,ty; if(inputtype==INPUT_ASCII){ tmp_x=x,tmp_y=y; } for(tx=0;txedgepixx+xcount_real+isframe_left)return 0; if(yedgepixy+ycount_real+isframe_top)return 0; return getqrpixel(x-edgepixx,y-edgepixy); } void setqrpixel_edge(int x,int y,int p) { if(xedgepixx+xcount_real+isframe_left)return; if(yedgepixy+ycount_real+isframe_top)return; setqrpixel(x-edgepixx,y-edgepixy,p); } // xsize, ysize - dimensions of pixel square in mm // xspace, yspace - spacing between pixels (for qr stencil) // totalx, totaly - dimensions of image in mm // TODO: add to options parser for "in-house" scaling for DPI, dots per x, lines/dots per y int img_scaletow=0; int img_scaletoh=0; //int img_scaletow=120; //int img_scaletoh=160; void calculatedims() { //fprintf(stderr,"totx:%f,tot2y:%f\n",dim_totalx,dim_totaly); if(!dim_ysize)dim_ysize=dim_xsize; if(!dim_yspace)dim_yspace=dim_xspace; if(!dim_totaly)if(dim_totalx)dim_totaly=dim_totalx*ycount_real_withedge/xcount_real_withedge; //fprintf(stderr,"totx:%f,toty:%f\n",dim_totalx,dim_totaly); // got size and pixel size - scaleto new // if(dim_xsize)if(dim_totalx){ // img_scaletow=(dim_totalx/dim_xsize); // if(dim_ysize)img_scaletoh=(dim_totaly/dim_ysize); // } if(!dim_xsize){ dim_xsize=(dim_totalx-(xcount_real_withedge-1)*dim_xspace)/xcount_real_withedge; dim_ysize=(dim_totaly-(ycount_real_withedge-1)*dim_yspace)/ycount_real_withedge; } else{ dim_totalx=xcount_real_withedge*(dim_xsize+dim_xspace)-dim_xspace; dim_totaly=ycount_real_withedge*(dim_ysize+dim_yspace)-dim_yspace; } //fprintf(stderr,"totx:%f,toty:%f\n",dim_totalx,dim_totaly); } /* ===================================================================== */ // DXF string value void dxf_tuples(int i,char*s) { printf("%3i\n",i); printf("%s\n",s); } // DXF integer value void dxf_tuplei(int i,int i2) { printf("%3i\n",i); printf("%i\n",i2); } // DXF integer float void dxf_tupled(int i,double d) { printf("%3i\n",i); if(d<0.000001)if(d>-0.000001)d=0.0;//get rid of the fucking negative zeroes! printf("%f\n",d); } // DXF draw square void dxf_square(double x,double y,double xw,double yw) { dxf_tuples(0,"LWPOLYLINE"); dxf_tuplei(90,5); dxf_tuplei(70,0); dxf_tupled(10,x); dxf_tupled(20,y); dxf_tupled(10,x+xw);dxf_tupled(20,y); dxf_tupled(10,x+xw);dxf_tupled(20,y+yw); dxf_tupled(10,x); dxf_tupled(20,y+yw); dxf_tupled(10,x); dxf_tupled(20,y); } double g_zupfar=3; double g_zupnear=1; double g_zdown=0; int g_fup=6000; int g_fdown=2000; int g_fmove=6000; int g_fdraw=300; double g_zpark=150; #define GDOWNCODEMAX 255 char g_downcode[GDOWNCODEMAX]=""; double gpos_x=-999999,gpos_y=-999999,gpos_z=-999999,gpos_f=-999999; long pecknum=0; // Gcode peck retract void gcode_up() { double newz; if(gcodepix==PIX_G7P)return; if(gcodemode==MODE_G_G7)return; if(gcodequicknext)newz=g_zupnear;else newz=g_zupfar; if(gpos_z==newz)return; printf("G0 Z%g",newz); if(gpos_f!=g_fup)printf(" F%i",g_fup); printf("\n"); gpos_z=newz; gpos_f=g_fup; // {printf("G0 Z%g F%i\n",g_zupnear,g_fup);gpos_z=g_zupnear;gpos_f=g_fup;} // else {printf("G0 Z%g F%i\n",g_zupfar,g_fup);gpos_z=g_zupfar;} } // Gcode peck retract for travel void gcode_upper() { if(gcodepix==PIX_G7P)return; if(gcodemode==MODE_G_G7)return; if(gcodequicknext)printf("G0 Z%g F%i\n",g_zupfar,g_fup); gpos_z=g_zupfar; gpos_f=g_fup; } // Gcode peck down void gcode_down() { if(gcodepix==PIX_G7P)return; if(gcodemode==MODE_G_G7)return; // printf("G0 Z%f F%i\n",g_zdown,g_fdown); printf("%s\n",g_downcode); gpos_z=-999999; // FIXME } // TODO: long or short up/down depending if near or far // Gcode peck action void gcode_peck() { gcode_down(); gcode_up(); } void gcode_g0g1(int g,double x,double y,int f){ x=xround(x);y=xround(y); if(x==gpos_x)if(y==gpos_y)if(gpos_f==g_fmove)return; printf("G%i",g); if(x!=gpos_x)printf(" X%g",x); if(y!=gpos_y)printf(" Y%g",y); if(f!=gpos_f)printf(" F%i",f); printf("\n"); gpos_x=x;gpos_y=y;gpos_f=f; } void gcode_gotoxy(double x,double y) { // printf("G0 X%g Y%g F%i\n",x,y,g_fmove); gcode_g0g1(0,x,y,g_fmove); } void gcode_drawtoxy(double x,double y) { //x=xround(x);y=xround(y); //printf("G1 X%g Y%g F%i\n",xround(x),xround(y),g_fdraw); gcode_g0g1(1,x,y,g_fdraw); } void gcode_peckpixel(double x,double y) { //printf("; %g %g\n",x,y); gcode_gotoxy(x,y); gcode_peck(); pecknum++; } void gcode_burn(int energy){ if(g7_pulsewait)printf("G4 P100\n"); printf("G7 P0 J%.2f",energy*laserjmult); if(g7_pulsewait)printf(" W"); printf("\n"); } // material-dependent gamma curves here int gcode_dark2energy(int darkness){ return darkness; // dummy function for now } void gcode_burnpixel(double x,double y, int value) { //printf("; %g %g\n",x,y); gcode_gotoxy(x,y); gcode_burn(gcode_dark2energy(value)); // 0=no energy, 255=max burn pecknum++; } /* ===================================================================== */ void printheader() { if(pixelmode==MODE_ASCII)return; if(pixelmode==MODE_SCAD){ printf("// OpenSCAD source code for a QR-code matrix\n"); printf("// generated by Shad's qr2dxf\n"); printf("\n"); printf("$xcount=%i;\n",xcount_real_withedge); printf("$ycount=%i;\n",ycount_real_withedge); printf("\n"); printf("$totalxsize=%g;\n",dim_totalx); printf("$xspace=%g;\n",dim_xspace); printf("\n"); if(dim_totalx==dim_totaly)printf("$totalysize=$totalxsize;\n"); else printf("$totalysize=%g;\n",dim_totaly); if(dim_xspace==dim_yspace)printf("$yspace=$xspace;\n"); else printf("$yspace=%g;\n",dim_xspace); printf("\n"); printf("$xsize=($totalxsize-($xcount-1)*$xspace)/$xcount;\n"); printf("$ysize=($totalysize-($ycount-1)*$yspace)/$ycount;\n"); printf("$maxysize=$totalysize-$ysize-$yspace;\n"); printf("\n"); printf("module qrpixel($x,$y)\n"); printf("{\n"); printf(" translate([$x*($xsize+$xspace),$maxysize - $y*($ysize+$yspace)]) square([$xsize,$ysize]);\n"); printf("}\n"); printf("module qrcode()\n"); printf("{\n"); return; } if(pixelmode==MODE_DXF){ dxf_tuples(999,"-- created by Shad's qr2dxf utility"); dxf_tuples(0,"SECTION"); dxf_tuples(2,"HEADER"); dxf_tuples(9,"$ACADVER"); dxf_tuples(1,"AC1015"); dxf_tuples(9,"$INSUNITS"); dxf_tuplei(70,4); dxf_tuples(0,"ENDSEC"); dxf_tuples(0,"SECTION"); return; } if(pixelmode==MODE_GCODE){ //gcode_up(); if(!gnozero)printf("G92 X0 Y0\n"); if(gcodemode==MODE_G_G7)printf("M649 R%.4f B2\n",dim_xsize); else gcode_upper(); // printf("G0 Z%g F%i\n",g_zupfar,g_fmove); return; } } void printfooter() { if(pixelmode==MODE_ASCII)return; if(pixelmode==MODE_SCAD){ printf("}\n"); printf("\n"); printf("qrcode();\n"); printf("\n"); return; } if(pixelmode==MODE_DXF){ dxf_tuples(0,"ENDSEC"); dxf_tuples(0,"SECTION"); dxf_tuples(2,"OBJECTS"); dxf_tuples(0,"DICTIONARY"); dxf_tuples(0,"ENDSEC"); dxf_tuples(0,"EOF"); return; } if(pixelmode==MODE_GCODE){ printf("G0 Z20 F%i\n",g_fmove); printf("G0 X0 Y0\n"); printf("G0 Z%g\n",g_zpark); return; } } // dim_?size pixel size in mm // dim_?space pixel spacing in mm, adds to ?size // dim_total? total length in mm // mv_offs? offset from the edge double xpix2mm(int x) { double dx; dx=x*(dim_xsize+dim_xspace); if(mv_fliph)dx=dim_totalx-dx-dim_xsize; dx+=mv_offsx; return dx; } double ypix2mm(int y) { double dy; dy=y*(dim_ysize+dim_yspace); if(mv_flipv)dy=dim_totaly-dy-dim_ysize; dy+=mv_offsy; return dy; } char*byte2ascii(int ispix) { // static char s[256];if(!ispix)return ".. ";sprintf(s,"%02x ",ispix);return s; int c; if(pixtype==PIX_BW){ if(mode_ascii==MODE_ASCII_WIDE){if(ispix)return("[]");else return(" ");} else {if(ispix)return("X" );else return(" " );} } if(pixtype==PIX_G8){ static char s[4]=" "; if(ispix==0)c=' ';else if(ispix<0x10)c='.';else if(ispix<0x30)c=',';else if(ispix<0x60)c=':';else if(ispix<0x80)c='+';else if(ispix<0xd0)c='o';else if(ispix<0xFF)c='x';else c='X'; if(mode_ascii==MODE_ASCII_WIDE){s[0]=c;s[1]=c;s[2]=0;} else{s[0]=c;s[1]=0;} return s; } return("E!"); } void printpixelxy(int ispix,int x, int y) { if(ispix>255)ispix=255;else if(ispix<0)ispix=0; // clamp // disable - we're inverting at read //if(pixelinvert){ // if(pixtype==PIX_G8)ispix=255-ispix; // else ispix=!ispix; // bool // } //printf("[%i]",ispix); // if(pixelmode==MODE_ASCII){if(mode_ascii==MODE_ASCII_WIDE){if(ispix)printf("[]");else printf(" ");return;} // else {if(ispix)printf("X" );else printf(" " );return;}} if(pixelmode==MODE_ASCII){printf(byte2ascii(ispix));return;} // if(pixelmode==MODE_ASCII){if(ispix)printf("X");else printf(" ");return;} if(!ispix)return; if(pixelmode==MODE_SCAD){ printf(" qrpixel(%3i,%3i);\n",x,y); return; } if(pixelmode==MODE_DXF){ dxf_square(xpix2mm(x),ypix2mm(y),dim_xsize,dim_ysize); return; } if(pixelmode==MODE_GCODE){ //printf("DEBUG:"); // remove if(gcodepix==PIX_PECK)gcode_peckpixel(xpix2mm(x),ypix2mm(y));else // if(gcodepix==PIX_PECK)gcode_peckpixel((double)x ,(double)y );else if(gcodepix==PIX_G7P)gcode_burnpixel(xpix2mm(x),ypix2mm(y),ispix);else fprintf(stderr,"ERROR: gcodepix not specified!\n"); return; } } int sub_xstart=0,sub_ystart=0; int sub_xcount=-1,sub_ycount=-1; int isleftrightedgepix(int x,int dx, int totx) { if(dx>0)if(x>=(totx-1))return 1; if(dx<0)if(x<=0)return 1; return 0; } //Program received signal SIGSEGV, Segmentation fault. //0x080493d6 in getpixel (x=556, y=354) at qr2laser.c:186 int xpixelinvert(int ispix){ if(ispix>255)ispix=255;else if(ispix<0)ispix=0; // clamp if(pixelinvert){ if(pixtype==PIX_G8)ispix=255-ispix; else {if(ispix)ispix=0;else ispix=1;} // bool } return ispix; } int tmp_pix; int xgetqrpixel(int x,int y){ // return getpixel(x,y); // return xpixelinvert(getpixel(x,y)); // printf("[%i:%i=%02x]",x,y,i); // printf("[%02x]",i); int i=xpixelinvert(getpixel(x,y)); tmp_pix=i; return i; } void xzeroqrpixel(int x,int y){ if(pixelinvert)setpixel(x,y,255);else setpixel(x,y,0); } void handlenearestpx(int x,int y) { //setqrpixel_edge(x,y,0); xzeroqrpixel(x,y); tmp_x=x;tmp_y=y;// MUST BE HERE - setqrpixel will screw the variables //fprintf(stderr,"X"); } int getnearestpixel(int x,int y,int totx,int toty) { int tx,ty,t,p,lastx,lasty; int dxp,dxn,dyp,dyn,dmax,d; //printf("[[%i:%i,%i]]\n",x,y,dmax); if(x>totx)return -1;if(y>toty)return -1; //if(xgetqrpixel(x,y)==1){handlenearestpx(x,y);tmp_x=x;tmp_y=y;return 0;} if(xgetqrpixel(x,y)){handlenearestpx(x,y);tmp_x=x;tmp_y=y;return 0;} dxp=totx-x-1;dxn=x-1; dyp=toty-y-1;dyn=y-1; dmax=dxp;if(dyp>dmax)dmax=dyp;if(dyp>dmax)dmax=dyp;if(dyn>dmax)dmax=dyn;//max dimension we can go for(d=1;d<=dmax;d++){ //printf("[[[%i]]]\n",d); if(d<=dyn){ for(tx=x;tx<=x+d;tx++) {if(tx>=totx)break;if(xgetqrpixel(tx,y-d)){handlenearestpx(tx,y-d);return d;}} //look top-right for(tx=x-1;tx>=x-d;tx--){if(tx<0 )break;if(xgetqrpixel(tx,y-d)){handlenearestpx(tx,y-d);return d;}} //look top-left } if(d<=dxp){ for(ty=y-1;ty>=y-d;ty--){if(ty<0 )break;if(xgetqrpixel(x+d,ty)){handlenearestpx(x+d,ty);return d;}} // look right-top for(ty=y;ty<=y+d;ty++) {if(ty>=toty)break;if(xgetqrpixel(x+d,ty)){handlenearestpx(x+d,ty);return d;}} // look right-bottom } if(d<=dyp){ for(tx=x;tx<=x+d;tx++) {if(tx>=totx)break;if(xgetqrpixel(tx,y+d)){handlenearestpx(tx,y+d);return d;}} //look bottom-right for(tx=x-1;tx>=x-d;tx--){if(tx<0 )break;if(xgetqrpixel(tx,y+d)){handlenearestpx(tx,y+d);return d;}} //look bottom-left } if(d<=dxn){ for(ty=y-1;ty>=y-d;ty--){if(ty<0 )break;if(xgetqrpixel(x-d,ty)){handlenearestpx(x-d,ty);return d;}} // look left-top for(ty=y;ty<=y+d;ty++) {if(ty>=toty)break;if(xgetqrpixel(x-d,ty)){handlenearestpx(x-d,ty);return d;}} // look left-bottom } } return -1; } void gcode_print_xy_nearest() { int t,x,y; int totx=xcount_real+2*edgepixx; int toty=ycount_real+2*edgepixy; int areas=0; int pixels=0; x=0;y=0; fprintf(stderr,"totx:toty:%i:%i\n",totx,toty); while(1){ t=getnearestpixel(x,y,totx,toty); //fprintf(stderr,"[t:%i]=>%i:%i\n",t,tmp_x,tmp_y); if(t<0)return; x=tmp_x;y=tmp_y; if((t==0)||(t>1))areas++; //fprintf(stderr,"[%i:%i]",x,y); printpixelxy(tmp_pix,x,y);if(t>1)gcode_upper(); pixels++; } // fprintf(stderr,"Areas: %i\n",areas);// inaccurate, just for check fprintf(stderr,"Pixels: %i\n",pixels); } typedef enum{ G7_RTL=0, G7_LTR=1 } enum_g7dir; // LASER_MAX_RASTER_LINE in Marlin = 68! #define G7_BUFLEN 24 unsigned char g7buf[G7_BUFLEN+1]; int g7bufcnt=0; int g7segnum=0; enum_g7dir g7dir=G7_LTR; // G7 laser raster // L (raw length of BASE64 encoded data string) // D (BASE64 encoded pixel data) // $<0|1> (autoshift Y, set line direction, 0=RTL,1=LTR) //int g7_start_dir_show=0; int g7_start_dir=0; int g7_isfirst=1; void g7_flush(int dir){ int blen; char bstr[G7_BUFLEN*4/3+5]; // ad-hoc padded just to be sure // if(g7bufcnt==0)return; // no action if no data // blen=base64_encode(bstr,g7buf,g7bufcnt); g7segnum++; fprintf(stderr,"data: ");for(int t=0;t255)pix=255; //if(pixelinvert)pix=255-pix; // inverting in getpixel already // laser gamma curve optionally here, if not in firmware g7buf[g7bufcnt]=(unsigned char)pix; g7bufcnt++; } #define G7PADDING (12*2) void g7_endline(int g7dir){ // pad with zeroes for deceleration for(int t=0;t=totx)continue; // empty line // } for(int t=0;t1)gcode_drawtoxy(xpix2mm(xpos),ypix2mm(ty));gcode_up();gcode_upper();}} } else if(gcodemode==MODE_G_PECK){ pixels++;printpixelxy(p,xpos,ty);if(!p)if(lastp)gcode_upper(); } } xpos+=dx; } // gayscale pixels - laserburn line or pixel else if(pixtype==PIX_G8){ //p=xgetqrpixel(xpos+isframe_left+sub_xstart,ty+isframe_top+sub_ystart); p=xgetqrpixel(xpos,ty); if(p)pcnt++;// for line mode if(gcodemode==MODE_G_PECK){ pixels++;printpixelxy(p,xpos,ty);if(!p)if(lastp)gcode_upper(); } else if(gcodemode==MODE_G_G7){ g7_addpixel(xgetqrpixel(xpos,ty),g7dir); } lastp=p;if(!p)pcnt=0; xpos+=dx; } } // end-of-line code here if(gcodemode==MODE_G_G7){ g7_endline(g7dir); //printf(""); printf("\n"); } //gcode_upper(); } } } // gcode_up();gcode_upper(); //// fprintf(stderr,"Areas: %i\n",areas);// inaccurate, just for check fprintf(stderr,"Pixels: %i\n",pixels); if(gcodemode==MODE_G_LINE)fprintf(stderr,"Lines: %i\n",lines); // dump_mem_bitmap();return; } void print_qr_xy() { int tx,ty,t,p; int realx=0,realy=0; char*eol; int totx=xcount_real+2*edgepixx; int toty=ycount_real+2*edgepixy; printheader(); if(pixelmode==MODE_ASCII)eol="\n";else eol=""; totx-=sub_xstart;if(totx<=0)return; toty-=sub_ystart;if(toty<=0)return; if(sub_xcount>=0)if(totx>sub_xcount)totx=sub_xcount; if(sub_ycount>=0)if(toty>sub_ycount)toty=sub_ycount; //printf("ifr:%i,ift=%i,sxst=%i\n",isframe_left,isframe_top,sub_xstart); // KLUDGE: bug in variable values calculation!!! if(isframe_left==1)isframe_left=0; if(isframe_top==1)isframe_top=0; if(pixelmode==MODE_GCODE)gcode_print_xy(); else { for(ty=0;ty255)r=255;if(g>255)g=255;if(b>255)b=255; //printf("[%02x%02x%02x]",r,g,b); //return 1; //printf("%02x%02x%02x:%02x%02x%02x:%02x%02x%02x=", //r,g,b,rgblimit[0][0],rgblimit[0][1],rgblimit[0][2],rgblimit[1][0],rgblimit[1][1],rgblimit[1][2]); if(r>=rgblimit[0][0])if(r<=rgblimit[1][0]) if(g>=rgblimit[0][1])if(g<=rgblimit[1][1]) if(b>=rgblimit[0][2])if(b<=rgblimit[1][2]) { //printf("X\n"); return 0;} //printf("0\n"); return 1; } #define QUANTCOLORS 256 int qcolors[QUANTCOLORS][5]; // r,g,b,pixels,matches int qcolornum=0; int quantizeto=QUANTCOLORS; void checkqcolor(int r,int g,int b,int match) { int t; //printf("XXX: %02x%02x%02x\n",r,g,b); if(r>255)r=255;if(g>255)g=255;if(b>255)b=255; if(match)match=0;else match=1; for(t=0;t=QUANTCOLORS-1)return; qcolors[qcolornum][0]=r; qcolors[qcolornum][1]=g; qcolors[qcolornum][2]=b; qcolors[qcolornum][3]=1; qcolors[qcolornum][4]=match; qcolornum++; } void swapcolors(int a,int b){ int i,t; for(t=0;t<5;t++){i=qcolors[a][t];qcolors[a][t]=qcolors[b][t];qcolors[b][t]=i;} } void sortcolor(int pri,int sec){ int swapped=1; while(swapped){ swapped=0; for(int t=0;tqcolors[t+1][pri]){swapcolors(t,t+1);swapped++;continue;} if(qcolors[t][pri]qcolors[t+1][sec]){swapcolors(t,t+1);swapped++;continue;} } } } void dumpqcolor(FILE*f) { int t; fprintf(f,"Colors: %i (quant.%i)\n",qcolornum,quantizeto); sortcolor(0,3); for(t=0;t scaletow=w*scaletoh/h if(!img_scaletow){ long l; l=img_w;l*=img_scaletoh;l/=img_h; img_scaletow=l; } if(!img_scaletoh){ long l; l=img_h;l*=img_scaletow;l/=img_w; img_scaletoh=l; } //printf("%i:%i:xxx\n",img_scaletow,img_scaletoh); if((MagickGetImageWidth(mw)!=img_scaletow)||(MagickGetImageHeight(mw)!=img_scaletoh)){ fprintf(stderr,"resizing image from %ix%i to %ix%i\n",img_w,img_h,img_scaletow,img_scaletoh); MagickResizeImage(mw,img_scaletow,img_scaletoh,BoxFilter,1); // MagickResizeImage(mw,img_scaletox,img_scaletoy,LanczosFilter,1); }} if(img_gamma!=1.0){ MagickGammaImage(mw,img_gamma); } // Smooths the contours of the current active image while still preserving edge information. // The algorithm works by replacing each pixel with its neighbor closest in value. // A neighbor is defined by radius. Use a radius of 0 and ReduceNoise() selects a suitable radius for you. //if(img_rednoise)MagickReduceNoiseImage(mw,0); if(1){ double min,max; MagickGetImageChannelRange(mw, GrayChannel,&min, &max); fprintf(stderr,"info: GrayChannel range: %f .. %f\n", min,max); } img_w=MagickGetImageWidth(mw); img_h=MagickGetImageHeight(mw); img_wmax=img_w; setimgwb(); if(debug){fprintf(stderr,"width: %i\n",img_w); fprintf(stderr,"height: %i\n",img_h);} imgarrI=(unsigned char*)malloc(img_wb*img_h*sizeof(char)+2); edges_x=(unsigned char*)malloc(img_w*sizeof(char)+2); edges_y=(unsigned char*)malloc(img_h*sizeof(char)+2); if(debug)fprintf(stderr,"Allocated %li+%li+%li+%li bytes \n",img_wb*img_h*sizeof(char)+2,img_w*sizeof(char)+2,img_w*sizeof(char)+2,img_h*sizeof(char)+2); if(img_w>=MAXWIDTH){fprintf(stderr,"ERROR: image wider than %i pixels, aborting.\n",MAXWIDTH);return 1;} if(img_wb*img_h/8>=MAXSIZE){fprintf(stderr,"ERROR: image data bigger than %i bytes, aborting.\n",MAXSIZE);return 1;} if(pixtype==PIX_G8){ // if(debug)fprintf(stderr,"Quantizing to 256 grays...\n"); fprintf(stderr,"Quantizing to 256 grays... (PIX_G8)\n"); MagickQuantizeImage(mw,quantizeto,GRAYColorspace,0,MagickFalse,MagickFalse); // quant to 256 grays }else if(inputtype==INPUT_BITMAP_RGB){ // if(debug)fprintf(stderr,"Quantizing to %i colors...\n",quantizeto); fprintf(stderr,"Quantizing to %i colors... (INPUT_BITMAP_RGB)\n",quantizeto); MagickQuantizeImage(mw,quantizeto,RGBColorspace,0,MagickFalse,MagickFalse); // quant to 256 colors }else{ // if(debug)fprintf(stderr,"Quantizing to two-level...\n"); fprintf(stderr,"Quantizing to two-level...\n"); MagickQuantizeImage(mw,2,GRAYColorspace,0,MagickFalse,MagickFalse); // quant to 2 colors } if((img_w<1)||(img_h<1)){fprintf(stderr,"Image size too small (%ix%i), aborting.\n",img_w,img_h);return 1;} if(debug)fprintf(stderr,"Reading...\n"); // convert data to monochrome is-or-is-not pixel array int totalpx=0,totalpxtot=0; // copy gray if(pixtype==PIX_G8){ lineconv=(unsigned char*)malloc(img_w*sizeof(char)*PIXSIZE+2*PIXSIZE); for(ty=0;ty=0;t--){ for(t1=0;t1=0;t--){ for(t1=0;t1t-n1)minly=t-n1; n1=t; }} fprintf(stderr,"\n"); fprintf(stderr,"min y segment=%i\n",minly); n1=0; for(t=0;t(minly+minly/2+minly/4)){ if((t-n1)>(minly+minly/4)){ //fprintf(stderr,"XX\n"); for(t1=n1+minly;t1xsub_xcount)totx=xsub_xcount; if(toty>xsub_ycount)toty=xsub_ycount; fprintf(f,"Input bitmap dimensions: %ix%i\n",img_w,img_h); fprintf(f,"Pixel array dimensions: %ix%i\n",xcount_real,ycount_real); if(edgepixx||edgepixy)fprintf(f,"Pixel array w/ edge: %ix%i\n",xcount_real_withedge,ycount_real_withedge); //printf("Physical size: %.2g",dim_totalx);if(dim_totalx!=dim_totaly)printf("x%.2g",dim_totaly);printf("\n"); fprintf(f,"Pixel size: ");printsize_d(f,dim_xsize,dim_ysize); fprintf(f,"Pixel spacing: ");printsize_d(f,dim_xspace,dim_yspace); fprintf(f,"DPI: %g\n",dim_dpi); if(sub_xstart||sub_ystart||(sub_xcount>=0)||(sub_ycount>=0)){ fprintf(f,"Sub-area start: %i,%i\n",sub_xstart,sub_ystart); fprintf(f,"Sub-area size: %ix%i\n",totx,toty); fprintf(f,"Sub-area dimensions: ");printsize_d(f,(dim_xsize+dim_xspace)*totx-dim_xspace,(dim_ysize+dim_yspace)*toty-dim_yspace); } fprintf(f,"Total area size: ");printsize_d(f,dim_totalx,dim_totaly); //fprintf(f,"Offsets: x=%.2f,y=%.2f\n",mv_offsx,mv_offsy); fprintf(f,"x range: %.2f .. %.2f\n",mv_offsx,dim_totalx+mv_offsx); fprintf(f,"y range: %.2f .. %.2f\n",mv_offsy,dim_totaly+mv_offsy); if(gcodemode==MODE_G_G7){ fprintf(f,"G7 lines per line: %i\n",g7_linesperline); fprintf(f,"G7 line spacing: %.3f\n",dim_totaly/(ycount_real*g7_linesperline)); } fprintf(f,"\nG-code for Marlin ABL_GRID: G29 V3 B%g F%g L%g R%g\n",xround(dim_totalx+mv_offsx),xround(mv_offsx),xround(mv_offsy),xround(dim_totaly+mv_offsy)); } void help() { int n; printf("qr2laser - analyzes bitmap images of QR codes and pixel art, converts to ASCII, SCAD or DXF.\n"); printf(" Represents the pixels as separate squares, for laser cutting of stencils.\n"); printf(" Also can output G-code for peck-printing or laser engraving.\n"); printf("Usage: qr2laser [options] \n"); //printf("Defaults: -i%i -b%i -w%i\n",DEF_ITERATIONS,MINBLACK,MINWHITE); printf("Options:\n"); printf("Output formats:\n"); printf(" -a output as ASCII, two characters per pixel (default)\n"); printf(" -an output as ASCII, narrow (one character per pixel)\n"); printf(" -s output as SCAD for OpenSCAD\n"); printf(" -d output as DXF\n"); printf(" -gl output as G-code, lines\n"); printf(" -gp output as G-code, pecking\n"); printf(" -gn output as G-code, pecking-nearest\n"); printf(" -g7pp output as G-code, G7P laser pulse pecking\n"); printf(" -g7pn output as G-code, G7P laser pulse pecking-nearest\n"); printf(" -g7 output as G-code, G7 engraving\n"); printf(" -n no output, debug and size messages only\n"); printf(" -q suppress size messages\n"); printf(" -D debug messages\n"); printf("G-code options:\n"); printf(" -gnozero do not set current position as zero coords (G92 X0 Y0)\n"); printf(" -gzz move zigzag instead of left-to-right (can make wiggly verticals)\n"); printf(" -gnqn no quicker step (lower retract) to neighbour dot\n"); printf(" -gzupfar z coordinate for long moves, default %g\n",g_zupfar); printf(" -gzupnear z coordinate for short interpixel moves, default %g\n",g_zupnear); printf(" -gzdown z coordinate for tool-down position, default %g\n",g_zupfar); printf(" -gzpark z coordinate for end of operation, default %g\n",g_zpark); printf(" -gfup speed for going up, default %i\n",g_fup); printf(" -gfdown speed for going down, default %i\n",g_fdown); printf(" -gfmove speed for lateral moves, default %i\n",g_fmove); printf(" -gfdraw speed for drawing lines, for -gl, default %i\n",g_fdraw); printf(" -gcode 'string' G-code for the tool down operation, default '%s'\n",g_downcode); printf(" (use '|' for multiline separator)\n"); printf(" -g7lpl laser scan lines per line of pixels\n"); printf(" -gpw stop head for each pixel pulse\n"); printf(" -jmult laser G7 J-multiplier\n"); n=0;while(num_decimals>1){n++;num_decimals/=10;} printf(" -prec decimal points of xy coordinates, default %i\n",n); printf("Output dimensions:\n"); printf(" -size total horizontal size (vertical scales)\n"); printf(" -ysize total vertical size (to override scaled)\n"); printf(" -sp spacing, default %g (for QR)\n",dim_xspace); printf(" -sw pixel width\n",dim_xsize); printf(" -sh pixel height\n",dim_ysize); printf(" -dpi pixel size, in dots per inch\n",dim_dpi); printf(" -offsx horizontal offset\n"); printf(" -offsy vertical offset\n"); printf(" -fv flip vertically\n"); printf(" -fh flip horizontally\n"); printf(" -center move to center\n"); printf(" -scaleto scale bitmap to pixels (0 is calculation placeholder)\n"); printf("Output framing:\n"); printf(" -i invert pixels\n"); printf(" -e edge frame, especially for inverted pixels; can be negative\n"); printf("Output crop:\n"); printf(" -startx skip px pixels from left\n"); printf(" -starty skip px pixels from top\n"); printf(" -countx output only px pixels horizontally\n"); printf(" -county output only px pixels vertically\n"); printf("Generic bitmap and other functions:\n"); printf(" -ye edge frame on top and bottom, especially for inverted pixels\n"); printf(" -ysw pixel height\n"); printf(" -nsb do not strip borders of identical pixels\n"); printf(" -(x|y|xy)pix force resulting pixel count on the source image\n"); printf(" -(x|y|xy)pixsize force source pixels per output pixel, can be noninteger\n"); printf(" -(x|y|xy)po pixel sample offset (default: pixelsize/2)\n"); printf(" -quantize quantize colors to n levels, default %i\n",QUANTCOLORS); printf(" -alpha transparency threshold to not ignore pixels, default %i\n",ALPHATHRESHOLD); printf(" -rgb detect color for pixel, in range\n"); printf(" -rgb detect color for pixel, plusminus %i\n",RGB_TOLERANCE_DEF); printf(" -rgb detect color for pixel, plusminus 0xtt\n"); printf(" -listcolors list colors in the image, post-quantizing\n"); printf(" -gamma gamma adjustment, applied after reading-scaling\n"); exit(0); } void setrgblimits(char*ss) { int t; int rgbtol=RGB_TOLERANCE_DEF; char s[16]; for(t=0;t<15;t++)s[t]=0; strncpy(s,ss,15); if(strlen(s)>10){ rgblimit[0][0]=gethex(s+0); rgblimit[0][1]=gethex(s+2); rgblimit[0][2]=gethex(s+4); rgblimit[1][0]=gethex(s+7); rgblimit[1][1]=gethex(s+9); rgblimit[1][2]=gethex(s+11); } else{ if(s[6]=='+')rgbtol=gethex(s+7); rgblimit[0][0]=gethex(s+0)-rgbtol; rgblimit[0][1]=gethex(s+2)-rgbtol; rgblimit[0][2]=gethex(s+4)-rgbtol; rgblimit[1][0]=gethex(s+0)+rgbtol; rgblimit[1][1]=gethex(s+2)+rgbtol; rgblimit[1][2]=gethex(s+4)+rgbtol; } if(debug)fprintf(stderr,"RGB range: %02X%02X%02X-%02X%02X%02X\n",rgblimit[0][0],rgblimit[0][1],rgblimit[0][2],rgblimit[1][0],rgblimit[1][1],rgblimit[1][2]); } int atoix(char*s) { if(s[0]=='0')s++; if(s[0]=='x'){ int hex=0; while(ishexdigit(s[0])){ hex<<4;hex+=gethexdigit(s[0]);s++; if(s[0]==0)break; } } return atoi(s); } int pixelmode_isstencil() { if(pixelmode==MODE_DXF)return 1; if(pixelmode==MODE_SCAD)return 1; return 0; } int main(int argc,char*argv[]) { int n,t; if(argc<1){help();return 0;} char*fn="",*sp; int nooutimage=0; int quiet=0; int listcolors=0; int showdimensions=1; int forcex=0,forcey=0; int flipv=0; int center=0; int parseqr=0; double xspace=0; double forcexsize=0.0,forceysize=0.0; inputtype=INPUT_BITMAP_RGB; for(t=1;t=0)if(n<=5){num_decimals=1;while(n>0){num_decimals*=10;n--;}};continue;} // if(!strcmp(argv[t],"-forcegray")){pixtype=PIX_G8;continue;} // if(!strcmp(argv[t],"-gzupfar")) {t++;g_zupfar=atof(argv[t]);continue;} if(!strcmp(argv[t],"-gzupnear")){t++;g_zupnear=atof(argv[t]);continue;} if(!strcmp(argv[t],"-gzpark")) {t++;g_zpark=atof(argv[t]);continue;} if(!strcmp(argv[t],"-gzdown")) {t++;g_zdown=atof(argv[t]);continue;} // if(!strcmp(argv[t],"-gfup")) {t++;g_fup=atoi(argv[t]);continue;} if(!strcmp(argv[t],"-gfdown")) {t++;g_fdown=atoi(argv[t]);continue;} if(!strcmp(argv[t],"-gfmove")) {t++;g_fmove=atoi(argv[t]);continue;} if(!strcmp(argv[t],"-gfdraw")) {t++;g_fdraw=atoi(argv[t]);continue;} if(!strcmp(argv[t],"-gcode")) {t++;n=strlen(argv[t]);strncpy(g_downcode,argv[t],GDOWNCODEMAX-1); g_downcode[GDOWNCODEMAX-1]=0; sp=g_downcode;while(sp[0]){sp[0]=toupper(sp[0]);if(sp[0]=='|')sp[0]='\n';sp++;} continue;} if(!strcmp(argv[t],"-g7lpl")) {t++;g7_linesperline=atoi(argv[t]);if(g7_linesperline<1)g7_linesperline=1;continue;} // if(!strcmp(argv[t],"-qr")){parseqr=1;continue;} if(!strcmp(argv[t],"-sp")){t++;xspace=atof(argv[t]);continue;} if(!strcmp(argv[t],"-sw")){t++;dim_xsize=atof(argv[t]);continue;} if(!strcmp(argv[t],"-sh")){t++;dim_ysize=atof(argv[t]);continue;} if(!strcmp(argv[t],"-e")){t++;edgepixx=atoix(argv[t]);edgepixy=atoi(argv[t]);continue;} if(!strcmp(argv[t],"-ye")){t++;edgepixy=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-n")){nooutimage=1;continue;} if(!strcmp(argv[t],"-q")){showdimensions=0;continue;} if(!strcmp(argv[t],"-startx")){t++;sub_xstart=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-starty")){t++;sub_ystart=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-countx")){t++;sub_xcount=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-county")){t++;sub_ycount=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-xpix")){t++;forcex=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-ypix")){t++;forcey=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-xypix")){t++;forcex=atoix(argv[t]);forcey=forcex;continue;} if(!strcmp(argv[t],"-xpixsize")){t++;forcexsize=atof(argv[t]);continue;} if(!strcmp(argv[t],"-ypixsize")){t++;forceysize=atof(argv[t]);continue;} if(!strcmp(argv[t],"-xypixsize")){t++;forcexsize=atof(argv[t]);forceysize=forcexsize;continue;} if(!strcmp(argv[t],"-xpo")){t++;pixeloffs_x=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-ypo")){t++;pixeloffs_y=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-xypo")){t++;pixeloffs_x=atoix(argv[t]);pixeloffs_y=pixeloffs_x;continue;} if(!strcmp(argv[t],"-rgb")){t++;setrgblimits(argv[t]);continue;} if(!strcmp(argv[t],"-quantize")){t++;quantizeto=atoix(argv[t]);if(quantizeto<2)quantizeto=2;if(quantizeto>QUANTCOLORS)quantizeto=QUANTCOLORS;continue;} if(!strcmp(argv[t],"-alpha")){t++;alphathreshold=atoix(argv[t]);continue;} if(!strcmp(argv[t],"-listcolors")){listcolors=1;continue;} if(!strcmp(argv[t],"-scaleto")){char*sp;t++; sp=strchr(argv[t],',');if(!sp)sp=strchr(argv[t],'x');if(sp)sp++; img_scaletow=atoi(argv[t]);if(sp)img_scaletoh=atoi(sp);continue;} if(!strcmp(argv[t],"-jmult")){t++;laserjmult=atof(argv[t]);continue;} if(!strcmp(argv[t],"-gamma")) {t++;img_gamma=atof(argv[t]);continue;} if(!strncmp(argv[t],"-",1))if(strcmp(argv[t],"-")){fprintf(stderr,"Error: unknown option '%s'. Aborting.\n",argv[t]);return(1);} fn=argv[t]; } if(!fn)help(); if(!fn[0])help(); if(pixelmode==MODE_GCODE)if(gcodemode!=MODE_G_G7)snprintf(g_downcode,GDOWNCODEMAX-1,"G0 Z%g F%i",g_zdown,g_fdown); // prepare resize if(dim_xsize)if(dim_totalx)img_scaletow=dim_totalx/dim_xsize; if(dim_ysize)if(dim_totaly)img_scaletoh=dim_totaly/dim_ysize; // read image from input if(inputtype==INPUT_ASCII){if(readasciifile(fn,0))return 1;} else{if(readbmpfile(fn))return 1;} // flip vertical axis for output formats where needed if((pixelmode==MODE_GCODE)||(pixelmode==MODE_SCAD)||(pixelmode==MODE_DXF)){if(mv_flipv)mv_flipv=0;else mv_flipv=1;} if(pixelmode_isstencil())if(xspace==0)xspace=dim_xspace; dim_xspace=xspace; if(!dim_xsize)if(!dim_totalx){ //fprintf(stderr,"calculating dpi/xsize\n"); if(dim_dpi){dim_xsize=25.4/dim_dpi;dim_xspace=0;dim_ysize=dim_xsize;dim_yspace=0;} else dim_dpi=(dim_xsize+dim_xspace)*25.4;} if(forcex)if(!forcey)forcey=forcex; memset(edges_x,0,img_w); memset(edges_y,0,img_h); // if(showdimensions)printsize(stderr); if(inputtype!=INPUT_ASCII){ if(forcex)populateedgearray_pixelcount(forcex,forcey); else if(forcexsize!=0.0) populateedgearray_pixelsize(forcexsize,forceysize); else if(parseqr)parseqrbmp();else noparseqrbmp(); } if(pixelmode!=MODE_ASCII)if((dim_xsize<=0)&&(dim_totalx<=0)){fprintf(stderr,"Need total (-size) or pixel width (-sw) or density (-dpi). Aborting.\n");return 1;} // if(showdimensions)printsize(stderr); calculatedims(); if(center){mv_offsx-=dim_totalx/2;mv_offsy-=dim_totaly/2;} if(pixelmode!=MODE_ASCII){ if((dim_xsize<0)||(dim_ysize<0)){fprintf(stderr,"Pixel size less than zero (%gx%g mm), aborting.\n",dim_xsize,dim_ysize);return 1;} if((dim_xspace<0)||(dim_yspace<0)){fprintf(stderr,"Pixel spacing less than zero (%gx%g mm), aborting.\n",dim_xspace,dim_yspace);return 1;} } if(!nooutimage){ if(listcolors)dumpqcolor(stderr); print_qr_xy(); if(showdimensions)printsize(stderr); }else{ if(listcolors)dumpqcolor(stderr); if(showdimensions)printsize(stdout); } if(showdimensions)if(pixelmode_isstencil()){ if((dim_xsizeMAX_TOTSIZE)||(dim_totaly>MAX_TOTSIZE)){fprintf(stderr,"Warning: Total size too large (%gx%g mm), limit %g.\n",dim_totalx,dim_totaly,MAX_TOTSIZE);} } if(pecknum)fprintf(stderr,"Peck count: %li\n",pecknum); //printf("mode:%i\n",pixelmode); return 0; }