#!/usr/bin/pike /* * cropzoomer.pike * * Utility to zoom a little on scanned pages (req. RGB page) * * Author: Dario Rodriguez dario@softhome.net * The program is licensed under the terms of the MIT/X license. */ int bgthreshold=64; // from 0 to 255 int amplitude=15; // from 1 to 255 int darkenfactor=2; // from 1 to 255 int main(int argc, array(string) argv) { Image.Image in,out,inhsv,indistance; string infile,outfile; if(argc!=3 || argv[argc-1]=="--help") { write("Syntax: "+argv[0]+" infile.jpg outfile.png\n"); return(1); } write(argv[1]+" -> "+argv[2]+"\n"); write("* Generating intermediate representations..."); infile=argv[1]; outfile=argv[2]; in=Image.load(infile); inhsv=in->copy()->rgb_to_hsv(); indistance=in->copy()->distancesq(255,255,255); out=Image.Image(in.xsize(),in.ysize()); // Guess the most used bg color write("done.\n* Guessing most used bg hue..."); array(int) hues=allocate(256); int x,y; int h,n,selh,seln,j; int c; for(y=0;y<in.ysize();y++) { for(x=0;x<in.xsize();x++) { c=indistance.getpixel(x,y)[0]; if(c<bgthreshold) { // possible bg c=inhsv.getpixel(x,y)[0]; hues[c]++; } } if(!(y%1000)) write("."); } for(selh=0,seln=0,h=0;h<256;h++) { for(n=0,j=-amplitude;j<amplitude;j++) { n+=hues[(h+256+j)%256]; } if(n>seln) { seln=n; selh=h; } } write(""+selh); // Getting image statistics write(".\n* Getting image statistics..."); int d; array(int) ycoverage=allocate(in.ysize()); for(y=0;y<in.ysize();y++) ycoverage[y]=0; array(int) xcoverage=allocate(in.xsize()); for(x=0;x<in.xsize();x++) xcoverage[x]=0; for(y=0;y<in.ysize();y++) { for(x=0;x<in.xsize();x++) { c=indistance.getpixel(x,y)[0]; h=inhsv.getpixel(x,y)[0]; // calc. hue distance to bg hue wrapping on the color wheel if(h<selh) { if((selh-h)<(h+256-selh)) d=selh-h; else d=h+256-selh; } else { if((h-selh)<(selh+256-h)) d=h-selh; else d=selh+256-h; } // heuristics if(!(c<bgthreshold && d<bgthreshold)) { ycoverage[y]++; xcoverage[x]++; } } if(!(y%1000)) write("."); } // Looking for largest white zone write(".\n* Looking for white zone from the center..."); // average numbers to a 40th of the length int pos; int i; int yblock=(int) in.ysize()/40; int xblock=(int) in.xsize()/40; if(yblock>=2) { array(int) last=allocate(yblock); for(pos=0;pos<yblock;pos++) last[pos]=ycoverage[pos]; for(y=0,pos=0;y<in.ysize();y++,pos++,pos%=yblock) { last[pos]=ycoverage[y]; for(c=0,i=0;i<yblock;i++) c+=last[i]; ycoverage[y]=(int) (c/yblock); } } if(xblock>=2) { array(int) last=allocate(xblock); for(pos=0;pos<xblock;pos++) last[pos]=xcoverage[pos]; for(x=0,pos=0;x<in.xsize();x++,pos++,pos%=xblock) { last[pos]=xcoverage[x]; for(c=0,i=0;i<xblock;i++) c+=last[i]; xcoverage[x]=(int) (c/xblock); } } // get ymin/ymax and xmin/xmax int ymax=0,ymin=in.xsize(); for(y=0;y<sizeof(ycoverage);y++) { if(ycoverage[y]<ymin) ymin=ycoverage[y]; if(ycoverage[y]>ymax) ymax=ycoverage[y]; } int xmax=0,xmin=in.xsize(); for(x=0;x<sizeof(xcoverage);x++) { if(xcoverage[x]<xmin) xmin=xcoverage[x]; if(xcoverage[x]>xmax) xmax=xcoverage[x]; } int xdiff=(xmax-xmin); int ydiff=(ymax-ymin); int xminthreshold=xmin+xdiff/50; int yminthreshold=ymin+ydiff/50; // look for first dip under yminthreshold int y0,y1,x0,x1; for(y=in.ysize()/4;y>0;y--) { if(ycoverage[y]<yminthreshold) break; } y0=y-2*yblock; y0=(y0<0)?0:y0; for(y=in.ysize()*3/4;y<(in.ysize()-1);y++) { if(ycoverage[y]<yminthreshold) break; } y1=y+2*yblock; y1=(y1>=in.ysize())?(in.ysize()-1):y1; // look for first dip under xminthreshold for(x=in.xsize()/4;x>0;x--) { if(xcoverage[x]<xminthreshold) break; } x0=x-2*xblock; x0=(x0<0)?0:x0; for(x=in.xsize()*3/4;x<(in.xsize()-1);x++) { if(xcoverage[x]<xminthreshold) break; } x1=x+2*xblock; x1=(x1>=in.xsize())?(in.xsize()-1):x1; // Crop write(".\n* Cropping from "+out.xsize()+"x"+out.ysize()+" rectangle ("+x0+","+y0+")-("+x1+","+y1+")..."); out=in->copy(x0,y0,x1,y1,255,255,255); // Write result write("done.\n* Writing result to disk..."); Stdio.write_file(outfile,Image.PNG.encode(out)); write("done.\n* Process finished.\n"); }