(C) Relatively high density file backups on paper. Cross-platform CLI port of Ollydbg's Paperback from Windows and Borland C.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1220 lines
43 KiB

////////////////////////////////////////////////////////////////////////////////
// //
// PaperBack -- high density backups on the plain paper //
// //
// Copyright (c) 2007 Oleh Yuschuk //
// ollydbg at t-online de (set Subject to 'paperback' or be filtered out!) //
// //
// //
// This file is part of PaperBack. //
// //
// Paperback is free software; you can redistribute it and/or modify it under //
// the terms of the GNU General Public License as published by the Free //
// Software Foundation; either version 3 of the License, or (at your option) //
// any later version. //
// //
// PaperBack is distributed in the hope that it will be useful, but WITHOUT //
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or //
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for //
// more details. //
// //
// You should have received a copy of the GNU General Public License along //
// with this program. If not, see <http://www.gnu.org/licenses/>. //
// //
// //
// Note that bzip2 compression/decompression library, which is the part of //
// this project, is covered by different license, which, in my opinion, is //
// compatible with GPL. //
// //
////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <cstring>
#include <math.h>
#include <stdio.h>
#include <sstream>
#include "Crc16.h"
#include "Bitmap.h"
#include "Decoder.h"
#include "Ecc.h"
#include "Fileproc.h"
#define NHYST 1024 // Number of points in histogramm
#define NPEAK 32 // Maximal number of peaks
#define SUBDX 8 // X size of subblock, pixels
#define SUBDY 8 // Y size of subblock, pixels
char inbmp[MAXPATH]; // extern
t_procdata procdata; // extern
// Given hystogramm h of length n points, locates black peaks and determines
// phase and step of the grid.
static float Findpeaks(int *h,int n,float *bestpeak,float *beststep) {
int i,j,k,ampl,amin,amax,d,l[NHYST],limit,sum;
int npeak,dist,bestdist,bestcount,height[NPEAK];
float area,moment,peak[NPEAK],weight[NPEAK];
float x0,step,sn,sx,sy,sxx,syy,sxy;
// I expect at least 16 and at most NHYST points in the histogramm.
if (n<16) return 0.0;
if (n>NHYST) n=NHYST;
// Get absolute minimum and maximum.
amin=amax=h[0];
for (i=1; i<n; i++) {
if (h[i]<amin) amin=h[i];
if (h[i]>amax) amax=h[i]; };
// Remove gradients by shadowing over 32 pixels. May create small artefacts
// in the vicinity of the main peak.
d=(amax-amin+16)/32;
ampl=h[0];
for (i=0; i<n; i++) {
l[i]=ampl=std::max(ampl-d,h[i]); };
amax=0;
for (i=n-1; i>=0; i--) {
ampl=std::max(ampl-d,l[i]);
l[i]=ampl-h[i];
amax=std::max(amax,l[i]); };
// TRY TO COMPARE WITH SECOND LARGE PEAK?
// I set peak limit to 3/4 of the amplitude of the highest peak. This
// solution at least works in 90% of all cases.
limit=amax*3/4;
if (limit==0) limit=1;
// Start search and skip incomplete first peak.
i=0; npeak=0;
while (i<n && l[i]>limit) i++;
// Find peaks.
while (i<n && npeak<NPEAK) {
// Find next peak.
while (i<n && l[i]<=limit) i++;
// Calculate peak parameters.
area=0.0; moment=0.0; amax=0;
while (i<n && l[i]>limit) {
ampl=l[i]-limit;
area+=ampl;
moment+=ampl*i;
amax=std::max(amax,l[i]);
i++; };
// Don't process incomplete peaks.
if (i>=n) break;
// Add peak to the list, removing weak artefacts.
if (npeak>0) {
if (amax*8<height[npeak-1]) continue;
if (amax>height[npeak-1]*8) npeak--; };
peak[npeak]=moment/area;
weight[npeak]=area;
height[npeak]=amax;
npeak++;
};
// At least two peaks are necessary to detect the step.
if (npeak<2) return 0.0;
// Calculate all possible distances between the found peaks.
for (i=0; i<n; i++) l[i]=0;
for (i=0; i<npeak-1; i++) {
for (j=i+1; j<npeak; j++) {
l[(int)(peak[j]-peak[i])]++;
};
};
// Find group with the maximal number of peaks. I allow for approximately 3%
// dispersion. Distances under 16 pixels are too short to be real. Caveat:
// this method can't distinguish direct sequence from interleaved.
bestdist=0; bestcount=0;
for (i=16; i<n; i++) {
if (l[i]==0) continue;
sum=0;
for (j=i; j<=i+i/33+1 && j<n; j++) sum+=l[j];
if (sum>bestcount) { // Shorter is better
bestdist=i;
bestcount=sum;
};
};
if (bestdist==0) return 0.0;
// Now determine the parameters of the sequence. The method I use is not very
// good but usually sufficient.
sn=sx=sy=sxx=syy=sxy=0.0;
moment=0.0;
for (i=0; i<npeak-1; i++) {
for (j=i+1; j<npeak; j++) {
dist=peak[j]-peak[i];
if (dist<bestdist || dist>=bestdist+bestdist/33+1) continue;
if (sn==0.0) // First link
k=0;
else {
x0=(sx*sxy-sxx*sy)/(sx*sx-sn*sxx);
step=(sx*sy-sn*sxy)/(sx*sx-sn*sxx);
k=(peak[i]-x0+step/2.0)/step; };
sn+=2.0;
sx+=k*2+1;
sy+=peak[i]+peak[j];
sxx+=k*k+(k+1)*(k+1);
syy+=peak[i]*peak[i]+peak[j]*peak[j];
sxy+=peak[i]*k+peak[j]*(k+1);
moment+=height[i]+height[j];
};
};
*bestpeak=(sx*sxy-sxx*sy)/(sx*sx-sn*sxx);
*beststep=(sx*sy-sn*sxy)/(sx*sx-sn*sxx);
return moment/sn;
};
// Given grid of recognized dots, extracts saved information. Returns number of
// corrected erorrs (0..16) on success and 17 if information is not readable.
static int Recognizebits(t_data *result,uchar grid[NDOT][NDOT],
t_procdata *pdata) {
int i,j,k,q,r,factor,lcorr,c,cmin,cmax,limit;
int grid1[NDOT][NDOT],answer,bestanswer;
static int lastgood;
ushort crc;
t_data uncorrected,bestresult;
cmin=pdata->cmin;
cmax=pdata->cmax;
bestanswer=17;
// If orientation is not yet known, try all possible orientations + mirroring.
for (r=0; r<8; r++) {
if (pdata->orientation>=0 && r!=pdata->orientation) continue;
// Try 3 different point overlapping factors, combined with 3 different
// thresholds. Usually all cells are alike, so I remember the last known
// good combination and start with it.
for (k=0; k<9; k++) {
q=(k+lastgood)%9;
switch (q) {
case 0: factor=1000; lcorr=0; break;
case 1: factor=32; lcorr=0; break;
case 2: factor=16; lcorr=0; break;
case 3: factor=1000; lcorr=(cmin-cmax)/16; break;
case 4: factor=32; lcorr=(cmin-cmax)/16; break;
case 5: factor=16; lcorr=(cmin-cmax)/16; break;
case 6: factor=1000; lcorr=(cmax-cmin)/16; break;
case 7: factor=32; lcorr=(cmax-cmin)/16; break;
case 8: factor=16; lcorr=(cmax-cmin)/16; break;
default: factor=1000; lcorr=0; lastgood=0; break; };
// Correct grid for overlapping dots and calculate limit between black
// and white. I take into account only adjacent dots; the influence of
// diagonals is significantly lower.
limit=0;
for (j=0; j<NDOT; j++) {
for (i=0; i<NDOT; i++) {
c=grid[i][j]*factor;
if (i>0) c-=grid[j][i-1]; else c-=cmax;
if (i<31) c-=grid[j][i+1]; else c-=cmax;
if (j>0) c-=grid[j-1][i]; else c-=cmax;
if (j<31) c-=grid[j+1][i]; else c-=cmax;
grid1[j][i]=c;
limit+=c;
};
};
limit=limit/1024+lcorr*factor;
// Extract data according to the selected orientation.
memset(result,0,sizeof(t_data));
for (j=0; j<NDOT; j++) {
for (i=0; i<NDOT; i++) {
switch (r) {
case 0: c=grid1[j][i]; break;
case 1: c=grid1[i][NDOT-1-j]; break;
case 2: c=grid1[NDOT-1-j][NDOT-1-i]; break;
case 3: c=grid1[NDOT-1-i][j]; break;
case 4: c=grid1[i][j]; break;
case 5: c=grid1[j][NDOT-1-i]; break;
case 6: c=grid1[NDOT-1-i][NDOT-1-j]; break;
case 7: c=grid1[NDOT-1-j][i]; break;
};
if (c<limit) {
((ulong *)result)[j]|=1<<i;
};
};
};
// XOR with grid that corrects mean brightness.
for (j=0; j<NDOT; j++) {
((ulong *)result)[j]^=(j & 1?0xAAAAAAAA:0x55555555); };
// Apply ECC to restore invalid data.
if (pdata->mode & M_BEST)
memcpy(&uncorrected,result,sizeof(t_data));
else
memcpy(&pdata->uncorrected,result,sizeof(t_data));
answer=Decode8((uchar *)result,NULL,0,127);
if (answer<0) answer=17;
// Verify data for correctness by calculating CRC.
if (answer<=16) {
crc=(ushort)(Crc16((uchar *)result,NDATA+4)^0x55AA);
if (crc==result->crc) {
// Data recognized correctly, save orientation of actually processed
// page and factoring.
pdata->orientation=r;
// Report success.
if ((pdata->mode & M_BEST)==0) {
lastgood=q;
return answer; }
else if (answer<bestanswer) {
bestanswer=answer;
bestresult=*result;
memcpy(&pdata->uncorrected,&uncorrected,sizeof(t_data));
};
};
};
};
};
if (pdata->mode & M_BEST)
*result=bestresult;
return bestanswer;
};
// Determines rough grid position.
void Getgridposition(t_procdata *pdata) {
int i,j,nx,ny,stepx,stepy,sizex,sizey;
int c,cmin,cmax,distrx[256],distry[256],limit;
uchar *data,*pd;
// Get frequently used variables.
sizex=pdata->sizex;
sizey=pdata->sizey;
data=pdata->data;
// Check overall bitmap size.
if (sizex<=3*NDOT || sizey<=3*NDOT) {
Reporterror("Bitmap is too small to process");
pdata->step=0; return; };
// Select horizontal and vertical lines (at most 256 in each direction) to
// check for grid location.
stepx=sizex/256+1; nx=(sizex-2)/stepx; if (nx>256) nx=256;
stepy=sizey/256+1; ny=(sizey-2)/stepy; if (ny>256) ny=256;
// The main problem in determining the grid location are the black and/or
// white borders around the grid. To distinguish between borders with more or
// less constant intensity and quickly changing raster, I take into account
// only the fast intensity changes over the short distance (2 pixels).
// Caveat: this approach may fail for artificially created bitmaps.
memset(distrx,0,nx*sizeof(int));
memset(distry,0,ny*sizeof(int));
for (j=0; j<ny; j++) {
pd=data+j*stepy*sizex;
for (i=0; i<nx; i++,pd+=stepx) {
c=pd[0]; cmin=c; cmax=c;
c=pd[2]; cmin=std::min(cmin,c); cmax=std::max(cmax,c);
c=pd[sizex+1]; cmin=std::min(cmin,c); cmax=std::max(cmax,c);
c=pd[2*sizex]; cmin=std::min(cmin,c); cmax=std::max(cmax,c);
c=pd[2*sizex+2]; cmin=std::min(cmin,c); cmax=std::max(cmax,c);
distrx[i]+=cmax-cmin;
distry[j]+=cmax-cmin;
};
};
// Get rough bitmap limits in horizontal direction (at the level 50% of
// maximum).
limit=0;
for (i=0; i<nx; i++) {
if (distrx[i]>limit) limit=distrx[i]; };
limit/=2;
for (i=0; i<nx-1; i++) {
if (distrx[i]>=limit) break; };
pdata->gridxmin=i*stepx;
for (i=nx-1; i>0; i--) {
if (distrx[i]>=limit) break; };
pdata->gridxmax=i*stepx;
// Get rough bitmap limits in vertical direction.
limit=0;
for (j=0; j<ny; j++) {
if (distry[j]>limit) limit=distry[j]; };
limit/=2;
for (j=0; j<ny-1; j++) {
if (distry[j]>=limit) break; };
pdata->gridymin=j*stepy;
for (j=ny-1; j>0; j--) {
if (distry[j]>=limit) break; };
pdata->gridymax=j*stepy;
// Step finished.
pdata->step++;
};
// Selects search range, determines grid intensity and estimates sharpness.
void Getgridintensity(t_procdata *pdata) {
int i,j,sizex,sizey,centerx,centery,dx,dy,n;
int searchx0,searchy0,searchx1,searchy1;
int distrc[256],distrd[256],cmean,cmin,cmax,limit,sum,contrast;
uchar *data,*pd;
// Get frequently used variables.
sizex=pdata->sizex;
sizey=pdata->sizey;
data=pdata->data;
// Select X and Y ranges to search for the grid. As I use affine transforms
// instead of more CPU-intensive rotations, these ranges are determined for
// Y=0 (searchx0,searchx1) and for X=0 (searchy0,searchy1).
centerx=(pdata->gridxmin+pdata->gridxmax)/2;
centery=(pdata->gridymin+pdata->gridymax)/2;
searchx0=centerx-NHYST/2; if (searchx0<0) searchx0=0;
searchx1=searchx0+NHYST; if (searchx1>sizex) searchx1=sizex;
searchy0=centery-NHYST/2; if (searchy0<0) searchy0=0;
searchy1=searchy0+NHYST; if (searchy1>sizey) searchy1=sizey;
dx=searchx1-searchx0;
dy=searchy1-searchy0;
// Determine mean, minimal and maximal intensity of the central area, and
// sharpness of the image. As a minimum I take the level not reached by 3%
// of all pixels, as a maximum - level exceeded by 3% of pixels.
memset(distrc,0,sizeof(distrc));
memset(distrd,0,sizeof(distrd));
cmean=0; n=0;
std::cout << "dy: " << dy << std::endl;
for (j=0; j<dy-1; j++) {
pd=data+(searchy0+j)*sizex+searchx0;
for (i=0; i<dx-1; i++,pd++) {
distrc[*pd]++; cmean+=*pd; n++;
distrd[abs(pd[1]-pd[0])]++;
distrd[abs(pd[sizex]-pd[0])]++;
};
};
// Calculate mean, minimal and maximal image intensity.
std::cout << cmean << " / " << n << " causing sigfpe" << std::endl;
cmean/=n;
limit=n/33; // 3% of the total number of pixels
for (cmin=0,sum=0; cmin<255; cmin++) {
sum+=distrc[cmin];
if (sum>=limit) break; };
for (cmax=255,sum=0; cmax>0; cmax--) {
sum+=distrc[cmax];
if (sum>=limit) break; };
if (cmax-cmin<1) {
Reporterror("No Image");
pdata->step=0;
return; };
// Estimate image sharpness. The factor is rather empirical. Later, when
// dot size is known, this value will be corrected.
limit=n/10; // 5% (each point is counted twice)
for (contrast=255,sum=0; contrast>1; contrast--) {
sum+=distrd[contrast];
if (sum>=limit) break; };
pdata->sharpfactor=(cmax-cmin)/(2.0*contrast)-1.0;
// Save results.
pdata->searchx0=searchx0;
pdata->searchx1=searchx1;
pdata->searchy0=searchy0;
pdata->searchy1=searchy1;
pdata->cmean=cmean;
pdata->cmin=cmin;
pdata->cmax=cmax;
// Step finished.
pdata->step++;
};
// Find angle and step of vertical grid lines.
void Getxangle(t_procdata *pdata) {
int i,j,a,x,y,x0,y0,dx,dy,sizex;
int h[NHYST],nh[NHYST],ystep;
uchar *data,*pd;
float weight,xpeak,xstep;
float maxweight,bestxpeak,bestxangle,bestxstep;
// Get frequently used variables.
sizex=pdata->sizex;
data=pdata->data;
x0=pdata->searchx0;
y0=pdata->searchy0;
dx=pdata->searchx1-x0;
dy=pdata->searchy1-y0;
// Calculate vertical step. 256 lines are sufficient. Warning: danger of
// moire, especially on synthetic bitmaps!
ystep=dy/256; if (ystep<1) ystep=1;
maxweight=0.0;
xstep=bestxstep=0.0;
// Determine rough angle, step and base for the vertical grid lines. Due to
// the oversimplified conversion, cases a=+-1 are almost identical to a=0.
// Maximal allowed angle is approx. +/-5 degrees (1/10 radian).
for (a=-(NHYST/20)*2; a<=(NHYST/20)*2; a+=2) {
// Clear histogramm.
memset(h,0,dx*sizeof(int));
memset(nh,0,dx*sizeof(int));
// Gather histogramm.
for (j=0; j<dy; j+=ystep) {
y=y0+j;
x=x0+(y0+j)*a/NHYST; // Affine transformation
pd=data+y*sizex+x;
for (i=0; i<dx; i++,x++,pd++) {
if (x<0) continue;
if (x>=sizex) break;
h[i]+=*pd; nh[i]++;
};
};
// Normalize histogramm.
for (i=0; i<dx; i++) {
if (nh[i]>0) h[i]/=nh[i]; };
// Find peaks. On small synthetic bitmaps (height less than NHYST/2
// pixels) weights for a=0 and +/-2 are the same and routine would select
// -2 as a best angle. To solve this problem, I add small correction that
// preferes zero angle.
weight=Findpeaks(h,dx,&xpeak,&xstep)+1.0/(abs(a)+10.0);
if (weight>maxweight) {
bestxpeak=xpeak+x0;
bestxangle=(float)a/NHYST;
bestxstep=xstep;
maxweight=weight;
};
};
// Analyse and save results.
if (maxweight==0.0 || bestxstep<NDOT) {
Reporterror("No grid");
pdata->step=0;
return; };
pdata->xpeak=bestxpeak;
pdata->xstep=bestxstep;
pdata->xangle=bestxangle;
// Step finished.
pdata->step++;
};
// Find angle and step of horizontal grid lines. Very similar to Getxangle().
void Getyangle(t_procdata *pdata) {
int i,j,a,x,y,x0,y0,dx,dy,sizex,sizey;
int h[NHYST],nh[NHYST],xstep;
uchar *data,*pd;
float weight,ypeak,ystep;
float maxweight,bestypeak,bestyangle,bestystep;
// Get frequently used variables.
sizex=pdata->sizex;
sizey=pdata->sizey;
data=pdata->data;
x0=pdata->searchx0;
y0=pdata->searchy0;
dx=pdata->searchx1-x0;
dy=pdata->searchy1-y0;
// Calculate vertical step. 256 lines are sufficient. Warning: danger of
// moire, especially on synthetic bitmaps!
xstep=dx/256; if (xstep<1) xstep=1;
maxweight=0.0;
ystep=bestystep=0.0;
// Determine rough angle, step and base for the vertical grid lines. I do not
// take into account the changes of angle caused by the X transformation.
for (a=-(NHYST/20)*2; a<=(NHYST/20)*2; a+=2) {
// Clear histogramm.
memset(h,0,dy*sizeof(int));
memset(nh,0,dy*sizeof(int));
for (i=0; i<dx; i+=xstep) {
x=x0+i;
y=y0+(x0+i)*a/NHYST; // Affine transformation
pd=data+y*sizex+x;
for (j=0; j<dy; j++,y++,pd+=sizex) {
if (y<0) continue;
if (y>=sizey) break;
h[j]+=*pd; nh[j]++;
};
};
// Normalize histogramm.
for (j=0; j<dy; j++) {
if (nh[j]>0) h[j]/=nh[j]; };
// Find peaks.
weight=Findpeaks(h,dy,&ypeak,&ystep)+1.0/(abs(a)+10.0);
if (weight>maxweight) {
bestypeak=ypeak+y0;
bestyangle=(float)a/NHYST;
bestystep=ystep;
maxweight=weight;
};
};
// Analyse and save results.
if (maxweight==0.0 || bestystep<NDOT ||
bestystep<pdata->xstep*0.40 ||
bestystep>pdata->xstep*2.50
) {
Reporterror("No grid");
pdata->step=0;
return; };
pdata->ypeak=bestypeak;
pdata->ystep=bestystep;
pdata->yangle=bestyangle;
// Step finished.
pdata->step++;
};
// Prepare data and allocate memory for data decoding.
void Preparefordecoding(t_procdata *pdata) {
int sizex,sizey,dx,dy;
float xstep,ystep,border,sharpfactor,shift,maxxshift,maxyshift,dotsize;
// Get frequently used variables.
sizex=pdata->sizex;
sizey=pdata->sizey;
xstep=pdata->xstep;
ystep=pdata->ystep;
border=pdata->blockborder;
sharpfactor=pdata->sharpfactor;
// Empirical formula: the larger the angle, the more imprecise is the
// expected position of the block.
if (border<=0.0) {
border=std::max(fabs(pdata->xangle),fabs(pdata->yangle))*5.0+0.4;
pdata->blockborder=border; };
// Correct sharpness for known dot size. This correction is empirical.
dotsize=std::max(xstep,ystep)/(NDOT+3.0);
sharpfactor+=1.3/dotsize-0.1;
if (sharpfactor<0.0) sharpfactor=0.0;
else if (sharpfactor>2.0) sharpfactor=2.0;
pdata->sharpfactor=sharpfactor;
// Calculate start coordinates and number of block that fit onto the page
// in X direction.
maxxshift=fabs(pdata->xangle*sizey);
if (pdata->xangle<0.0)
shift=0.0;
else
shift=maxxshift;
while (pdata->xpeak-xstep>-shift-xstep*border)
pdata->xpeak-=xstep;
pdata->nposx=(int)((sizex+maxxshift)/xstep);
// The same in Y direction.
maxyshift=fabs(pdata->yangle*sizex);
if (pdata->yangle<0.0)
shift=0.0;
else
shift=maxyshift;
while (pdata->ypeak-ystep>-shift-ystep*border)
pdata->ypeak-=ystep;
pdata->nposy=(int)((sizey+maxyshift)/ystep);
// Start new quality map. Note that this call doesn't force map to be
// displayed.
//Initqualitymap(pdata->nposx,pdata->nposy);
//!!! While a display is not planned, making the quality data
//!!! available is desirable
// Allocate block buffers.
dx=xstep*(2.0*border+1.0)+1.0;
dy=ystep*(2.0*border+1.0)+1.0;
pdata->buf1=(uchar *)malloc(dx*dy); //GMEM_FIXED
pdata->buf2=(uchar *)malloc(dx*dy); //GMEM_FIXED
pdata->bufx=(int *)malloc(dx*sizeof(int)); //GMEM_FIXED
pdata->bufy=(int *)malloc(dy*sizeof(int)); //GMEM_FIXED
pdata->blocklist=(t_block *)
malloc(pdata->nposx*pdata->nposy*sizeof(t_block)); //GMEM_FIXED
// Check that we have enough memory.
if (pdata->buf1==NULL || pdata->buf2==NULL ||
pdata->bufx==NULL || pdata->bufy==NULL || pdata->blocklist==NULL
) {
if (pdata->buf1!=NULL) free(pdata->buf1);
if (pdata->buf2!=NULL) free(pdata->buf2);
if (pdata->bufx!=NULL) free(pdata->bufx);
if (pdata->bufy!=NULL) free(pdata->bufy);
if (pdata->blocklist!=NULL) free(pdata->blocklist);
Reporterror("Low memory");
pdata->step=0;
return; };
// Determine maximal size of the dot on the bitmap.
if (xstep<2*(NDOT+3) || ystep<2*(NDOT+3))
pdata->maxdotsize=1;
else if (xstep<3*(NDOT+3) || ystep<3*(NDOT+3))
pdata->maxdotsize=2;
else if (xstep<4*(NDOT+3) || ystep<4*(NDOT+3))
pdata->maxdotsize=3;
else
pdata->maxdotsize=4;
// Prepare superblock.
memset(&pdata->superblock,0,sizeof(t_superblock));
// Initialize remaining items.
pdata->bufdx=dx;
pdata->bufdy=dy;
pdata->orientation=-1; // As yet, unknown page orientation
pdata->ngood=0;
pdata->nbad=0;
pdata->nsuper=0;
pdata->nrestored=0;
pdata->posx=pdata->posy=0; // First block to scan
// Step finished.
pdata->step++;
};
// The most important routine, converts scanned blocks into data. Used both by
// data decoder and by block display. Returns -1 if block cannot be located,
// 0 to 16 if block is correctly decoded and 17 if block is unrecoverable.
int Decodeblock(t_procdata *pdata,int posx,int posy,t_data *result) {
int i,j,x,y,x0,y0,dx,dy,sizex,sizey,*bufx,*bufy;
int c,cmin,cmax,dotsize,shift,shiftmax,sum,answer,bestanswer;
float xangle,yangle,xbmp,ybmp,xres,yres,sharpfactor;
float xpeak,xstep,ypeak,ystep,halfdot;
float sy,syy,disp,dispmin,dispmax;
uchar *psrc,*pdest,*data,g[9][NDOT][NDOT],grid[NDOT][NDOT];
t_data uncorrected,bestresult;
// Get frequently used variables.
sizex=pdata->sizex;
sizey=pdata->sizey;
xangle=pdata->xangle;
yangle=pdata->yangle;
data=pdata->data;
cmin=pdata->cmin;
cmax=pdata->cmax;
sharpfactor=pdata->sharpfactor;
bufx=pdata->bufx;
bufy=pdata->bufy;
// Get block coordinates in the bitmap. Note that bitmap in memory is placed
// upside down.
x0=pdata->xpeak+pdata->xstep*(posx-pdata->blockborder);
y0=pdata->ypeak+pdata->ystep*(pdata->nposy-posy-1-pdata->blockborder);
dx=pdata->bufdx;
dy=pdata->bufdy;
// Rotate selected block to 'unsharp' buffer using bilinear interpolation.
// Fast discrete shifts are also thinkable but deliver significantly higher
// error rate.
if (sharpfactor>0.0)
pdest=pdata->buf2; // Sharping necessary
else
pdest=pdata->buf1;
pdata->unsharp=pdest;
for (j=0; j<dy; j++) {
xbmp=x0+(y0+j)*xangle;
if (xbmp>=0.0) x=xbmp; // Integer and fractional parts
else x=xbmp-1.0;
xres=xbmp-x;
for (i=0; i<dx; i++,pdest++,x++) {
ybmp=y0+j+(x0+i)*yangle;
if (ybmp>0.0) y=ybmp;
else y=ybmp-1.0;
yres=ybmp-y;
if (x<0 || x>=sizex-1 || y<0 || y>=sizey-1)
*pdest=(uchar)cmax; // Fill areas outside the page white
else {
psrc=data+y*sizex+x;
*pdest=(psrc[0]+(psrc[1]-psrc[0])*xres)*(1.0-yres)+
(psrc[sizex]+(psrc[sizex+1]-psrc[sizex])*xres)*yres;
};
};
};
// Sharpen rotated block, if necessary.
if (sharpfactor>0.0) {
psrc=pdata->buf2;
pdest=pdata->buf1;
for (j=0; j<dy; j++) {
for (i=0; i<dx; i++,psrc++,pdest++) {
if (i==0 || i==dx-1 || j==0 || j==dy-1)
*pdest=*psrc;
else {
*pdest=(uchar)std::max(cmin,std::min((int)(psrc[0]*(1.0+4.0*sharpfactor)-
(psrc[-dx]+psrc[-1]+psrc[1]+psrc[dx])*sharpfactor),cmax));
};
};
};
};
pdata->sharp=pdata->buf1;
// Find grid lines for the whole block. This works perfectly for laser
// printers. For bidirectional jet printers, splitting left and right
// borders into several pieces may give better results.
memset(bufx,0,dx*sizeof(int));
memset(bufy,0,dy*sizeof(int));
psrc=pdata->buf1;
for (j=0; j<dy; j++) {
for (i=0; i<dx; i++,psrc++) {
bufx[i]+=*psrc;
bufy[j]+=*psrc;
};
};
if (Findpeaks(bufx,dx,&xpeak,&xstep)<=0.0)
return -1; // No X grid
if (fabs(xstep-pdata->xstep)>pdata->xstep/16.0)
return -1; // Invalid grid step
if (Findpeaks(bufy,dy,&ypeak,&ystep)<=0.0)
return -1; // No Y grid
if (fabs(ystep-pdata->ystep)>pdata->ystep/16.0)
return -1; // Invalid grid step
// Save block position for displaying purposes.
pdata->blockxpeak=xpeak;
pdata->blockxstep=xstep;
pdata->blockypeak=ypeak;
pdata->blockystep=ystep;
// Calculate dot step and correct peaks so that they point to first dot.
xstep=xstep/(NDOT+3.0);
xpeak+=2.0*xstep;
ystep=ystep/(NDOT+3.0);
ypeak+=2.0*ystep;
// In search-for-the-best-quality mode, I look for the best possible
// decoding. Helps to estimate the overall quality of the picture.
bestanswer=17;
// Try different dot sizes, starting from 1x1 pixel. If scanner resolution
// is sufficient, 2x2 dot usually gives best results.
for (dotsize=1; dotsize<=pdata->maxdotsize; dotsize++) {
halfdot=dotsize/2.0-1.0;
for (j=0; j<NDOT; j++) {
y=ypeak+ystep*j-halfdot;
for (i=0; i<NDOT; i++) {
x=xpeak+xstep*i-halfdot;
// For each dot size I try +/- 1 pixel shifts in all possible
// directions.
for (shift=0; shift<9; shift++) {
switch (shift) {
case 0: psrc=pdata->buf1+(y-1)*dx+(x-1); break;
case 1: psrc=pdata->buf1+(y-1)*dx+(x+0); break;
case 2: psrc=pdata->buf1+(y-1)*dx+(x+1); break;
case 3: psrc=pdata->buf1+(y+0)*dx+(x-1); break;
case 4: psrc=pdata->buf1+(y+0)*dx+(x+0); break;
case 5: psrc=pdata->buf1+(y+0)*dx+(x+1); break;
case 6: psrc=pdata->buf1+(y+1)*dx+(x-1); break;
case 7: psrc=pdata->buf1+(y+1)*dx+(x+0); break;
case 8: psrc=pdata->buf1+(y+1)*dx+(x+1); break; };
switch (dotsize) {
case 4: // Rounded 4x4 dot (rarely works)
sum=(psrc[1]+psrc[2]+psrc[dx]+psrc[dx+1]+psrc[dx+2]+psrc[dx+3]+
psrc[2*dx]+psrc[2*dx+1]+psrc[2*dx+2]+psrc[2*dx+3]+
psrc[3*dx+1]+psrc[3*dx+2])/12;
break;
case 3: // 3x3 pixel
sum=(psrc[0]+psrc[1]+psrc[2]+psrc[dx]+psrc[dx+1]+psrc[dx+2]+
psrc[2*dx]+psrc[2*dx+1]+psrc[2*dx+2])/9;
break;
case 2: // 2x2 pixel (usually the best)
sum=(psrc[0]+psrc[1]+psrc[dx]+psrc[dx+1])/4;
break;
default: // 1x1 pixel dot (or internal error)
sum=psrc[0];
break; };
g[shift][j][i]=(uchar)sum;
};
};
};
// We have gathered 9 grids with 1-pixel shifts. Non-shifted grid is the
// most probable good candidate, try it first.
answer=Recognizebits(result,g[4],pdata);
// Don't stop if in search-for-the-best-quality mode.
if ((pdata->mode & M_BEST)!=0 && answer<bestanswer) {
bestanswer=answer;
bestresult=*result;
uncorrected=pdata->uncorrected;
if (answer!=0) answer=17; };
// If data recognition fails, combine grid from subblocks SUBDX*SUBDY dots
// with maximal dispersion. This compensates for small distortions, even
// nonlinear, and partially for bidirectional print.
if (answer==17) {
for (j=0; j<NDOT; j+=SUBDY) {
for (i=0; i<NDOT; i+=SUBDX) {
dispmin=1.0e99; dispmax=-1.0e99;
for (shift=0; shift<9; shift++) {
sy=0.0; syy=0.0;
for (y=j; y<j+SUBDY; y++) {
for (x=i; x<i+SUBDX; x++) {
c=g[shift][y][x];
sy+=c; syy+=c*c;
};
};
// Dispersion in the mathematical sense is a bit different beast
// (includes Division, Square Roots and Other Incomprehensible
// Things), but we are interested only in the shift corresponding
// to the maximum.
disp=syy*SUBDX*SUBDY-sy*sy;
if (disp<dispmin) dispmin=disp;
if (disp>dispmax) {
dispmax=disp;
shiftmax=shift;
};
};
// If difference between minimal and maximal dispersion is low (the
// case of mostly black/mostly white dots), I set shift to zero. 20%
// for disp equals to roughly 10% in strict mathematical sense.
if (dispmax-dispmin<dispmax/5.0)
shiftmax=4;
// Copy subblock with maximal dispersion to main grid.
for (y=j; y<j+SUBDY; y++) {
for (x=i; x<i+SUBDX; x++) {
grid[y][x]=g[shiftmax][y][x];
};
};
};
};
// Try to recognize data in the combined grid.
answer=Recognizebits(result,grid,pdata);
// Again, don't stop if in search-for-the-best-quality mode.
if ((pdata->mode & M_BEST)!=0 && answer<bestanswer) {
bestanswer=answer;
bestresult=*result;
uncorrected=pdata->uncorrected;
if (answer!=0) answer=17;
};
};
// If data is restored, we don't need different dot size.
if (answer<17) break;
};
if (pdata->mode & M_BEST) {
answer=bestanswer;
*result=bestresult;
pdata->uncorrected=uncorrected; };
return answer;
};
void Decodenextblock(t_procdata *pdata) {
int answer,ngroup,percent;
char s[TEXTLEN];
t_data result;
// Display percent of executed data and, if known, data name in progress bar.
if (pdata->superblock.name[0]=='\0')
sprintf(s,"Processing image");
else
sprintf(s,"%.64s (page %i)",
pdata->superblock.name,pdata->superblock.page);
percent=(pdata->posy*pdata->nposx+pdata->posx)*100/
(pdata->nposx*pdata->nposy);
Message(s,percent);
// Decode block.
answer=Decodeblock(pdata,pdata->posx,pdata->posy,&result);
// If we are unable to locate block, probably we are outside the raster.
if (answer<0)
goto finish;
// If this is the very first block located on the page, show it in the block
// display window.
/*if (pdata->ngood==0 && pdata->nbad==0 && pdata->nsuper==0)
Displayblockimage(pdata,pdata->posx,pdata->posy,answer,&result);*///GUI
// Analyze answer.
if (answer>=17) {
// Error, block is unreadable.
pdata->nbad++; }
else if (result.addr==SUPERBLOCK) {
// Superblock.
pdata->superblock.addr=SUPERBLOCK;
pdata->superblock.datasize=((t_superdata *)&result)->datasize;
pdata->superblock.pagesize=((t_superdata *)&result)->pagesize;
pdata->superblock.origsize=((t_superdata *)&result)->origsize;
pdata->superblock.mode=((t_superdata *)&result)->mode;
pdata->superblock.page=((t_superdata *)&result)->page;
pdata->superblock.modified=((t_superdata *)&result)->modified;
pdata->superblock.attributes=((t_superdata *)&result)->attributes;
pdata->superblock.filecrc=((t_superdata *)&result)->filecrc;
memcpy(pdata->superblock.name,((t_superdata *)&result)->name,64);
pdata->nsuper++;
pdata->nrestored+=answer; }
else if (pdata->ngood<pdata->nposx*pdata->nposy) {
// Success, place data block into the intermediate buffer.
pdata->blocklist[pdata->ngood].addr=result.addr & 0x0FFFFFFF;
ngroup=(result.addr>>28) & 0x0000000F;
if (ngroup>0) { // Recovery block
pdata->blocklist[pdata->ngood].recsize=ngroup*NDATA;
pdata->superblock.ngroup=ngroup; }
else // Data block
pdata->blocklist[pdata->ngood].recsize=0;
memcpy(pdata->blocklist[pdata->ngood].data,result.data,NDATA);
pdata->ngood++;
// Number of bytes corrected by ECC may be misleading (block is so good
// it can be read with wrong settings), but I have no better indicator
// of quality.
pdata->nrestored+=answer; };
// Add block to quality map.
//!!! for quality map
//Addblocktomap(pdata->posx,pdata->posy,answer);
// Block processed, set new coordinates.
finish:
pdata->posx++;
if (pdata->posx>=pdata->nposx) {
pdata->posx=0;
pdata->posy++;
if (pdata->posy>=pdata->nposy) {
pdata->step++; // Page processed
};
};
};
// Passes gathered data to file processor and frees resources allocated by call
// to Preparefordecoding().
void Finishdecoding(t_procdata *pdata) {
int i,fileindex;
// Pass gathered data to file processor.
if (pdata->superblock.addr==0)
Reporterror("Page label is not readable");
else {
fileindex=Startnextpage(&pdata->superblock);
if (fileindex>=0) {
for (i=0; i<pdata->ngood; i++)
Addblock(pdata->blocklist+i);
Finishpage(pdata->ngood+pdata->nsuper,pdata->nbad,pdata->nrestored);
;
};
};
// Page processed.
pdata->step=0;
};
// Extracts data from the bitmap in small slices. To start decoding, pass
// bitmap to Startbitmapdecoding().
void Nextdataprocessingstep(t_procdata *pdata) {
if (pdata==NULL)
return; // Invalid data descriptor
switch (pdata->step) {
case 0: // Idle data
return;
case 1: // Remove previous images
/*SetWindowPos(hwmain,HWND_TOP,0,0,0,0,
SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);
Initqualitymap(0,0);
Displayblockimage(NULL,0,0,0,NULL);
*///GUI
pdata->step++;
break;
case 2: // Determine grid size
Message("Searching for raster...",0);
Getgridposition(pdata);
break;
case 3: // Determine min and max intensity
Getgridintensity(pdata);
break;
case 4: // Determine step and angle in X
Message("Searching for grid lines...",0);
Getxangle(pdata);
break;
case 5: // Determine step and angle in Y
Getyangle(pdata);
break;
case 6: // Prepare for data decoding
Preparefordecoding(pdata);
break;
case 7: // Decode next block of data
Decodenextblock(pdata);
break;
case 8: // Finish data decoding
Finishdecoding(pdata);
break;
default: break; // Internal error
};
//if (pdata->step==0) Updatebuttons(); // Right or wrong, decoding finished
};
// Frees resources allocated by pdata.
void Freeprocdata(t_procdata *pdata) {
// Free data.
if (pdata->data!=NULL) {
free(pdata->data);
pdata->data=NULL; };
// Free allocated buffers.
if (pdata->buf1!=NULL) {
free(pdata->buf1);
pdata->buf1=NULL; };
if (pdata->buf2!=NULL) {
free(pdata->buf2);
pdata->buf2=NULL; };
if (pdata->bufx!=NULL) {
free(pdata->bufx);
pdata->bufx=NULL; };
if (pdata->bufy!=NULL) {
free(pdata->bufy);
pdata->bufy=NULL; };
if (pdata->blocklist!=NULL) {
free(pdata->blocklist);
pdata->blocklist=NULL;
};
};
// Starts decoding of the new bitmap. If previous decoding is still running,
// it will be stopped and all intermediate results will be discarded.
void Startbitmapdecoding(t_procdata *pdata,uchar *data,int sizex,int sizey) {
// Free resources allocated for the previous bitmap. User may want to
// browse bitmap while and after it is processed.
//Freeprocdata(pdata);
memset(pdata,0,sizeof(t_procdata));
pdata->data=data;
pdata->sizex=sizex;
pdata->sizey=sizey;
pdata->blockborder=0.0; // Autoselect
pdata->step=1;
//if (bestquality)
// pdata->mode|=M_BEST;
//Updatebuttons(); //GUI
};
// Stops bitmap decoding. Data decoded so far is discarded, but resources
// (especially, bitmap) remain in memory.
void Stopbitmapdecoding(t_procdata *pdata) {
if (pdata->step!=0) {
pdata->step=0;
};
};
// Opens and decodes bitmap. Returns 0 on success and -1 on error.
int Decodebitmap(const std::string &fileName) {
int i,size;
uchar *data,buf[sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)];
FILE *f;
BITMAPFILEHEADER *pbfh;
BITMAPINFOHEADER *pbih;
//HCURSOR prevcursor; //GUI
// Ask for file name.
//!!! REQUIRE input bmp exists before this can be run
//if (path==NULL || path[0]=='\0') {
/*if (Selectinbmp()!=0) {
return -1;
}
else { */
//strncpy(inbmp,path,sizeof(inbmp));
//inbmp[sizeof(inbmp)-1]='\0';
//}
//fnsplit(inbmp,NULL,NULL,fil,ext);
//sprintf(s,"Reading %s%s...",fil,ext);
//Message(s,0);
//Updatebuttons(); //GUI
// Open file and verify that this is the valid bitmap of known type.
f=fopen(fileName.c_str(),"rb");
if (f==NULL) { // Unable to open file
std::ostringstream oss;
oss << "Unable to open " << fileName << std::endl;
Reporterror(oss.str());
return -1;
};
// Reading 100-MB bitmap may take many seconds. Let's inform user by changing
// mouse pointer.
//prevcursor=SetCursor(LoadCursor(NULL,IDC_WAIT));
i=fread(buf,1,sizeof(buf),f);
//SetCursor(prevcursor);
if (i!=sizeof(buf)) { // Unable to read file
std::ostringstream oss;
oss << "Unable to read " << fileName << std::endl;
Reporterror(oss.str());
fclose(f);
return -1;
};
pbfh=(BITMAPFILEHEADER *)buf;
pbih=(BITMAPINFOHEADER *)(buf+sizeof(BITMAPFILEHEADER));
std::cout << "Size of bmp file header: " << sizeof(*pbfh) << std::endl;
std::cout << "Size of bmp info header: " << sizeof(*pbih) << std::endl;
if ( pbfh->bfType!=19778 ) {//First two bytes must be 'BM' (19778)
std::ostringstream oss;
oss << "Input file is not a bitmap: " << fileName << std::endl;
Reporterror(oss.str());
return -1;
}
if ( pbih->biCompression!=BI_RGB ) {
std::ostringstream oss;
oss << "Unsupported Bitmap: Must be uncompressed (not JPEG, PNG, or RLE) and not BITFIELDS-based: " << fileName << std::endl;
Reporterror(oss.str());
return -1;
}
std::cout << "Color bits per pixel: " << pbih->biBitCount << std::endl;
if ( pbih->biBitCount!=8 && pbih->biBitCount!=24) {
std::ostringstream oss;
oss << "Unsupported Bitmap: Must be 8-bit or 24-bit color: " << fileName << std::endl;
Reporterror(oss.str());
return -1;
}
std::cout << "# of colors (if 24-bit): " << pbih->biClrUsed << std::endl;
if ( pbih->biBitCount==24 && pbih->biClrUsed!=0) {
std::ostringstream oss;
oss << "Unsupported Bitmap: 24-bit color bitmaps required to specify number of colors used: " << fileName << std::endl;
Reporterror(oss.str());
return -1;
}
if ( pbih->biWidth<128 || pbih->biWidth>32768 ||
pbih->biHeight<128 || pbih->biHeight>32768
) {
std::ostringstream oss;
oss << "Unsupported Bitmap: Height and width must be between 128 and 32768: " << fileName << std::endl;
Reporterror(oss.str());
return -1;
}
if ( pbih->biSize!=sizeof(BITMAPINFOHEADER) || pbih->biPlanes!=1 ) {
std::ostringstream oss;
oss << "Unsupported Bitmap: size mismatch or invalid planes value (internal error): " << fileName << std::endl;
Reporterror(oss.str());
fclose(f);
return -1;
};
// Allocate buffer and read file.
fseek(f,0,SEEK_END);
size=ftell(f)-sizeof(BITMAPFILEHEADER);
data=(uchar *)malloc(size);
if (data==NULL) { // Unable to allocate memory
Reporterror("Low memory");
fclose(f);
return -1;
};
fseek(f,sizeof(BITMAPFILEHEADER),SEEK_SET);
i=fread(data,1,size,f);
fclose(f);
if (i!=size) { // Unable to read bitmap
std::ostringstream oss;
oss << "Unable to read: " << fileName << std::endl;
Reporterror(oss.str());
free(data);
return -1;
};
// Process bitmap.
ProcessDIB(data,pbfh->bfOffBits-sizeof(BITMAPFILEHEADER));
free(data);
return 0;
};
// Processes data from the scanner.
int ProcessDIB(void *hdata,int offset) {
int i,j,sizex,sizey,ncolor;
uchar scale[256],*data,*pdata,*pbits;
BITMAPINFO *pdib;
//pdib=(BITMAPINFO *)GlobalLock(hdata);
pdib =(BITMAPINFO *)hdata;
if (pdib==NULL)
return -1; // Something is wrong with this DIB
// Check that bitmap is more or less valid.
if (pdib->bmiHeader.biSize!=sizeof(BITMAPINFOHEADER) ||
pdib->bmiHeader.biPlanes!=1 ||
(pdib->bmiHeader.biBitCount!=8 && pdib->bmiHeader.biBitCount!=24) ||
(pdib->bmiHeader.biBitCount==24 && pdib->bmiHeader.biClrUsed!=0) ||
pdib->bmiHeader.biCompression!=BI_RGB ||
pdib->bmiHeader.biWidth<128 || pdib->bmiHeader.biWidth>32768 ||
pdib->bmiHeader.biHeight<128 || pdib->bmiHeader.biHeight>32768
) {
//GlobalUnlock(hdata);
return -1; }; // Not a known bitmap!
sizex=pdib->bmiHeader.biWidth;
sizey=pdib->bmiHeader.biHeight;
ncolor=pdib->bmiHeader.biClrUsed;
// Convert bitmap to 8-bit grayscale. Note that scan lines are DWORD-aligned.
data=(uchar *)malloc(sizex*sizey);
if (data==NULL) {
//GlobalUnlock(hdata);
return -1; };
if (pdib->bmiHeader.biBitCount==8) {
// 8-bit bitmap with palette.
if (ncolor>0) {
for (i=0; i<ncolor; i++) {
scale[i]=(uchar)((pdib->bmiColors[i].rgbBlue+
pdib->bmiColors[i].rgbGreen+pdib->bmiColors[i].rgbRed)/3);
}; }
else {
for (i=0; i<256; i++) scale[i]=(uchar)i; };
if (offset==0)
offset=sizeof(BITMAPINFOHEADER)+ncolor*sizeof(RGBQUAD);
pdata=data;
for (j=0; j<sizey; j++) {
offset=(offset+3) & 0xFFFFFFFC;
pbits=((uchar *)(pdib))+offset;
for (i=0; i<sizex; i++) {
*pdata++=scale[*pbits++]; };
offset+=sizex;
}; }
else {
// 24-bit bitmap without palette.
if (offset==0)
offset=sizeof(BITMAPINFOHEADER)+ncolor*sizeof(RGBQUAD);
pdata=data;
for (j=0; j<sizey; j++) {
offset=(offset+3) & 0xFFFFFFFC;
pbits=((uchar *)(pdib))+offset;
for (i=0; i<sizex; i++) {
*pdata++=(uchar)((pbits[0]+pbits[1]+pbits[2])/3);
pbits+=3; };
offset+=sizex*3;
};
};
// Decode bitmap. This is what we are for here.
Startbitmapdecoding(&procdata,data,sizex,sizey);
// Free original bitmap and report success.
//GlobalUnlock(hdata);
return 0;
};