Example #1
0
// 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;
};
Example #2
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;
};
Example #3
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++;
};
Example #4
0
// 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++;
};
Example #5
0
// 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++;
};
Example #6
0
// 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++;
};
Example #7
0
// 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++;
};
Example #8
0
// 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;
};
Example #9
0
// 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;
};
Example #10
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;
};