// Opens and enables TWAIN interface. Returns 0 on success and -1 on error. int OpenTWAINinterface(void) { TW_UINT16 result; TW_USERINTERFACE interf; if (twainstate<3) // Manager inactive, try to initialize OpenTWAINmanager(); if (twainstate!=3) return -1; // Not a good time for this operation result=dsmentry(&appid,NULL, DG_CONTROL,DAT_IDENTITY,MSG_OPENDS,(TW_MEMREF)&source); if (result!=TWRC_SUCCESS) { // Unable to open source. The message is usually, but not always, correct. Reporterror("There are no scanner devices on the system"); return -1; }; interf.ShowUI=1; interf.ModalUI=0; interf.hParent=(TW_HANDLE)hwmain; result=dsmentry(&appid,&source, DG_CONTROL,DAT_USERINTERFACE,MSG_ENABLEDS,&interf); if (result!=TWRC_SUCCESS) { dsmentry(&appid,NULL, // Unable to enable, go back to state 3 DG_CONTROL,DAT_IDENTITY,MSG_CLOSEDS,(TW_MEMREF)&source); Reporterror("Unable to open scanner"); return -1; }; twainstate=5; return 0; };
// Passes gathered data to file processor and frees resources allocated by call // to Preparefordecoding(). static 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,fileindex); Finishpage(fileindex, pdata->ngood+pdata->nsuper,pdata->nbad,pdata->nrestored); ; }; }; // Page processed. pdata->step=0; };
// Prepare data and allocate memory for data decoding. static 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=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=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); // Allocate block buffers. dx=xstep*(2.0*border+1.0)+1.0; dy=ystep*(2.0*border+1.0)+1.0; pdata->buf1=(uchar *)GlobalAlloc(GMEM_FIXED,dx*dy); pdata->buf2=(uchar *)GlobalAlloc(GMEM_FIXED,dx*dy); pdata->bufx=(int *)GlobalAlloc(GMEM_FIXED,dx*sizeof(int)); pdata->bufy=(int *)GlobalAlloc(GMEM_FIXED,dy*sizeof(int)); pdata->blocklist=(t_block *) GlobalAlloc(GMEM_FIXED,pdata->nposx*pdata->nposy*sizeof(t_block)); // 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) GlobalFree((HGLOBAL)pdata->buf1); if (pdata->buf2!=NULL) GlobalFree((HGLOBAL)pdata->buf2); if (pdata->bufx!=NULL) GlobalFree((HGLOBAL)pdata->bufx); if (pdata->bufy!=NULL) GlobalFree((HGLOBAL)pdata->bufy); if (pdata->blocklist!=NULL) GlobalFree((HGLOBAL)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++; };
// Find angle and step of horizontal grid lines. Very similar to Getxangle(). static 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++; };
// Find angle and step of vertical grid lines. static 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++; };
// Selects search range, determines grid intensity and estimates sharpness. static 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; 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. 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++; };
// Determines rough grid position. static 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=min(cmin,c); cmax=max(cmax,c); c=pd[sizex+1]; cmin=min(cmin,c); cmax=max(cmax,c); c=pd[2*sizex]; cmin=min(cmin,c); cmax=max(cmax,c); c=pd[2*sizex+2]; cmin=min(cmin,c); cmax=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++; };
// Starts new decoded page. Returns non-negative index to table of processed // files on success or -1 on error. int Startnextpage(t_superblock *superblock) { int i,slot,freeslot; t_fproc *pf; // Check whether file is already in the list of processed files. If not, // initialize new descriptor. freeslot=-1; for (slot=0,pf=fproc; slot<NFILE; slot++,pf++) { if (pf->busy==0) { // Empty descriptor if (freeslot<0) freeslot=slot; continue; }; if (strnicmp(pf->name,superblock->name,64)!=0) continue; // Different file name if (pf->mode!=superblock->mode) continue; // Different compression mode if (pf->modified.dwLowDateTime!=superblock->modified.dwLowDateTime || pf->modified.dwHighDateTime!=superblock->modified.dwHighDateTime) continue; // Different timestamp - wrong version? if (pf->datasize!=superblock->datasize) continue; // Different compressed size if (pf->origsize!=superblock->origsize) continue; // Different original size // File found. Check for the case of two backup copies printed with // different settings. if (pf->pagesize!=superblock->pagesize) pf->pagesize=0; break; }; if (slot>=NFILE) { // No matching descriptor, create new one. if (freeslot<0) { Reporterror("Maximal number of processed files exceeded"); return -1; }; slot=freeslot; pf=fproc+slot; memset(pf,0,sizeof(t_fproc)); // Allocate block and recovery tables. pf->nblock=(superblock->datasize+NDATA-1)/NDATA; pf->datavalid=(uchar *)GlobalAlloc(GPTR,pf->nblock); pf->data=(uchar *)GlobalAlloc(GPTR,pf->nblock*NDATA); if (pf->datavalid==NULL || pf->data==NULL) { if (pf->datavalid!=NULL) GlobalFree((HGLOBAL)pf->datavalid); if (pf->data!=NULL) GlobalFree((HGLOBAL)pf->data); Reporterror("Low memory"); return -1; }; // Initialize remaining fields. memcpy(pf->name,superblock->name,64); pf->modified=superblock->modified; pf->attributes=superblock->attributes; pf->filecrc=superblock->filecrc; pf->datasize=superblock->datasize; pf->pagesize=superblock->pagesize; pf->origsize=superblock->origsize; pf->mode=superblock->mode; if (pf->pagesize>0) pf->npages=(pf->datasize+pf->pagesize-1)/pf->pagesize; else pf->npages=0; pf->ndata=0; for (i=0; i<pf->npages && i<8; i++) pf->rempages[i]=i+1; // Initialize statistics and declare descriptor as busy. pf->goodblocks=0; pf->badblocks=0; pf->restoredbytes=0; pf->recoveredblocks=0; pf->busy=1; }; // Invalidate page limits and report success. pf=fproc+slot; pf->page=superblock->page; pf->ngroup=superblock->ngroup; pf->minpageaddr=0xFFFFFFFF; pf->maxpageaddr=0; Updatefileinfo(slot,pf); return slot; };
// Saves file with specified index and closes file descriptor (if force is 1, // attempts to save data even if file is not yet complete). Returns 0 on // success and -1 on error. int Saverestoredfile(int slot,int force) { int n,success; ushort filecrc; ulong l,length; uchar *bufout,*data,*tempdata; t_fproc *pf; aes_context ctx; HANDLE hfile; if (slot<0 || slot>=NFILE) return -1; // Invalid index of file descriptor pf=fproc+slot; if (pf->busy==0 || pf->nblock==0) return -1; // Index points to unused descriptor if (pf->ndata!=pf->nblock && force==0) return -1; // Still incomplete data Message("",0); // If data is encrypted, decrypt it to temporary buffer. Decryption in place // is possible, but the whole data would be lost if password is incorrect. if (pf->mode & PBM_ENCRYPTED) { if (pf->datasize & 0x0000000F) { Reporterror("Encrypted data is not aligned"); return -1; }; if (Getpassword()!=0) return -1; // User cancelled decryption tempdata=(uchar *)GlobalAlloc(GMEM_FIXED,pf->datasize); if (tempdata==NULL) { Reporterror("Low memory, can't decrypt data"); return -1; }; n=strlen(password); while (n<PASSLEN) password[n++]=0; memset(&ctx,0,sizeof(ctx)); aes_set_key(&ctx,(uchar *)password,256); for (l=0; l<pf->datasize; l+=16) aes_decrypt(&ctx,pf->data+l,tempdata+l); filecrc=Crc16(tempdata,pf->datasize); if (filecrc!=pf->filecrc) { Reporterror("Invalid password, please try again"); GlobalFree((HGLOBAL)tempdata); return -1; } else { GlobalFree((HGLOBAL)pf->data); pf->data=tempdata; pf->mode&=~PBM_ENCRYPTED; }; }; // If data is compressed, unpack it to temporary buffer. if ((pf->mode & PBM_COMPRESSED)==0) { // Data is not compressed. data=pf->data; length=pf->origsize; bufout=NULL; } else { // Data is compressed. Create temporary buffer. if (pf->origsize==0) pf->origsize=pf->datasize*4; // Weak attempt to recover bufout=(uchar *)GlobalAlloc(GMEM_FIXED,pf->origsize); if (bufout==NULL) { Reporterror("Low memory"); return -1; }; // Unpack data. length=pf->origsize; success=BZ2_bzBuffToBuffDecompress((char *)bufout,(uint *)&length, pf->data,pf->datasize,0,0); if (success!=BZ_OK) { GlobalFree((HGLOBAL)bufout); Reporterror("Unable to unpack data"); return -1; }; data=bufout; }; // Ask user for file name. if (Selectoutfile(pf->name)!=0) { // Cancelled by user if (bufout!=NULL) GlobalFree((HGLOBAL)bufout); return -1; }; // Open file and save data. hfile=CreateFile(outfile,GENERIC_WRITE,0,NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); if (hfile==INVALID_HANDLE_VALUE) { if (bufout!=NULL) GlobalFree((HGLOBAL)bufout); Reporterror("Unable to create file"); return -1; }; WriteFile(hfile,data,length,&l,NULL); // Restore old modification date and time. SetFileTime(hfile,&pf->modified,&pf->modified,&pf->modified); // Close file and restore old basic attributes. CloseHandle(hfile); SetFileAttributes(outfile,pf->attributes); if (bufout!=NULL) GlobalFree((HGLOBAL)bufout); if (l!=length) { Reporterror("I/O error"); return -1; }; // Close file descriptor and report success. Closefproc(slot); Message("File saved",0); return 0; };
// Opens and decodes bitmap. Returns 0 on success and -1 on error. int Decodebitmap(char *path) { int i,size; char s[TEXTLEN+MAXPATH],fil[MAXFILE],ext[MAXEXT]; uchar *data,buf[sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)]; FILE *f; BITMAPFILEHEADER *pbfh; BITMAPINFOHEADER *pbih; HCURSOR prevcursor; // Ask for file name. 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(); // Open file and verify that this is the valid bitmap of known type. f=fopen(inbmp,"rb"); if (f==NULL) { // Unable to open file sprintf(s,"Unable to open %s%s",fil,ext); Reporterror(s); 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 sprintf(s,"Unable to read %s%s",fil,ext); Reporterror(s); fclose(f); return -1; }; pbfh=(BITMAPFILEHEADER *)buf; pbih=(BITMAPINFOHEADER *)(buf+sizeof(BITMAPFILEHEADER)); if (pbfh->bfType!='BM' || pbih->biSize!=sizeof(BITMAPINFOHEADER) || pbih->biPlanes!=1 || (pbih->biBitCount!=8 && pbih->biBitCount!=24) || (pbih->biBitCount==24 && pbih->biClrUsed!=0) || pbih->biCompression!=BI_RGB || pbih->biWidth<128 || pbih->biWidth>32768 || pbih->biHeight<128 || pbih->biHeight>32768 ) { // Invalid bitmap type sprintf(s,"Unsupported bitmap type: %s%s",fil,ext); Reporterror(s); fclose(f); return -1; }; // Allocate buffer and read file. fseek(f,0,SEEK_END); size=ftell(f)-sizeof(BITMAPFILEHEADER); data=(uchar *)GlobalAlloc(GMEM_FIXED,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 sprintf(s,"Unable to read %s%s",fil,ext); Reporterror(s); GlobalFree((HGLOBAL)data); return -1; }; // Process bitmap. ProcessDIB((HGLOBAL)data,pbfh->bfOffBits-sizeof(BITMAPFILEHEADER)); GlobalFree((HGLOBAL)data); return 0; };