#!/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");
}