[sane-devel] New bug introduced for Canon LiDE 30

Till Kamppeter till.kamppeter@gmx.net
Thu, 11 Sep 2003 18:03:55 +0200


Thanks for the patch.

Gerhard, what do you recommand to fix SANE 1.0.12 in Mandrake 9.2 so 
that the Canon LiDE scanners work? This patch or one of your tarballs as

http://www.gjaeger.de/scanner/test/plustek-0.46-TEST1.tar.gz

Or can you give me a patch for 1.0.12 which contains all the latest fixes?

    Till


Monty wrote:
> 
> 
> On Thu, Sep 11, 2003 at 01:08:37PM +0200, Till Kamppeter wrote:
> 
>>Monty wrote:
>>
>>>LiDE20 and liDE30 never worked well; the autocal code was broken.  I
>>>know, I have them.  I spent several days debugging the plustek backend
>>>for exactly these scanners last month; see CVS (gerhard integrated my
>>>patches I believe), or contact me for a Canoscan LiDE patch against
>>>vanilla 1.0.12.
>>
>>Can you send me the patch?
> 
> 
> Gerhard has, I believe, integrated it into his latest Plustek backend
> download.  He may have added additional improvements since I made the
> patch; you should probably ask him... specifically, I only enabled the
> patch for CanoScan LiDE (as that's all I had to test) but it likely
> fixes autocal for a wider array of scanners if you turn it on in the
> conf file.  Gerhard may have correctly identified a larger default
> set.
> 
> Regardless, the patch as I originally constructed it is attached. It
> applies to vanilla 1.0.12.
> 
> The patch has nothing whatsoever to do with USB communication issues;
> like I said, I use 1.0.12 happily with USB, but I need a hub to make
> most USB devices work properly with my box and Linux.  It's not
> specific to SANE; most of my USB devices when not on the hub will work
> perfectly one week, not at all the next...
> 
> Monty
> 
> 
> ------------------------------------------------------------------------
> 
> diff -ruNw sane-backends-1.0.12/backend/canoscan-calibrate.c sane-backends-1.0.12-canoscan/backend/canoscan-calibrate.c
> --- sane-backends-1.0.12/backend/canoscan-calibrate.c	1969-12-31 19:00:00.000000000 -0500
> +++ sane-backends-1.0.12-canoscan/backend/canoscan-calibrate.c	2003-08-15 01:54:37.000000000 -0400
> @@ -0,0 +1,982 @@
> +/*.............................................................................
> + * Project : SANE library for Plustek flatbed scanners; canoscan calibration
> + *.............................................................................
> + */
> +
> +/** @file canoscan-calibrate.c
> + *  @brief Calibration routines.
> + *
> + * Based on sources acquired from Plustek Inc. and Gerhard Jaeger
> + * <gerhard@gjaeger.de>
> + *
> + * Current rehash by Monty <monty@xiph.org>
> + *
> + * The basic premise: The stock Plustek-usbshading.c in the plustek
> + * driver is effectively nonfunctional for Canon CanoScan scanners.
> + * These scanners rely heavily on all calibration steps, especially
> + * fine white, to produce acceptible scan results.  However, to make
> + * autocalibration work and make it work well involves some
> + * substantial mucking aobut in code that supports thirty other
> + * scanners with widely varying characteristics... none of which I own
> + * or can test.
> + *
> + * Therefore, I'm splitting out a few calibration functions I need
> + * to modify for the CanoScan which allows me to simplify things 
> + * greatly for the CanoScan without worrying about breaking other
> + * scanners, as well as reuse the vast majority of the Plustek
> + * driver infrastructure without forking.
> + *
> + * History:
> + * - 0.45m - birth of the file; tested extensively with the LiDE 20
> + * 
> + * This file is part of the SANE package.
> + *
> + * This program 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 2 of the
> + * License, or (at your option) any later version.
> + *
> + * This program 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, write to the Free Software
> + * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
> + * MA 02111-1307, USA.
> + *
> + * As a special exception, the authors of SANE give permission for
> + * additional uses of the libraries contained in this release of SANE.
> + *
> + * The exception is that, if you link a SANE library with other files
> + * to produce an executable, this does not by itself cause the
> + * resulting executable to be covered by the GNU General Public
> + * License.  Your use of that executable is in no way restricted on
> + * account of linking the SANE library code into it.
> + *
> + * This exception does not, however, invalidate any other reasons why
> + * the executable file might be covered by the GNU General Public
> + * License.
> + *
> + * If you submit changes to SANE to the maintainers to be included in
> + * a subsequent release, you agree by submitting the changes that
> + * those changes may be distributed with this exception intact.
> + *
> + * If you write modifications of your own for SANE, it is your choice
> + * whether to permit this exception to apply to your modifications.
> + * If you do not wish that, delete this exception notice.
> + * <hr> */
> + 
> +static SANE_Bool cano_HostSwap_p(void){
> +  /* the NatSemi 983x is a big endian chip, and the line protocol data
> +     all arrives big-endian.  This determines if we need to swap to
> +     host-order */
> +  u_short pattern = 0xfeed; /* deadbeef */
> +  unsigned char *bytewise = (unsigned char *)&pattern;
> +  DBG( _DBG_INFO2, "cano_HostSwap_p()\n" );
> +  if (bytewise[0] == 0xfe){
> +    DBG( _DBG_INFO2, "We're big-endian!  No need to swap!\n" );
> +    DBG( _DBG_INFO2, "cano_HostSwap_p() done\n" );
> +    return 0;
> +  }
> +  DBG( _DBG_INFO2, "We're little-endian!  NatSemi LM9833 is big!  Must swap calibration data!\n" );
> +  DBG( _DBG_INFO2, "cano_HostSwap_p() done\n" );
> +  return 1;
> +}
> +
> +static int strip_state=0; /* 0 for not ready,  1 pos white lamp on,  2 lamp off */
> +
> +static int cano_PrepareToReadWhiteCal(pPlustek_Device dev){
> +  pHWDef    hw       = &dev->usbDev.HwSetting;
> +  switch (strip_state){
> +  case 0: {
> +    if(!usb_ModuleToHome( dev, SANE_TRUE )){
> +      DBG( _DBG_ERROR, "cano_PrepareToReadWhiteCal() failed\n" );
> +      return _E_LAMP_NOT_IN_POS;
> +    }
> +    if( !usb_ModuleMove(dev, MOVE_Forward,
> +			(u_long)dev->usbDev.pSource->ShadingOriginY)) {
> +      DBG( _DBG_ERROR, "cano_PrepareToReadWhiteCal() failed\n" );
> +      return _E_LAMP_NOT_IN_POS;
> +    }
> +    break;
> +  }    
> +  case 2:
> +    
> +    a_bRegs[0x29] = hw->bReg_0x29;
> +    usb_switchLamp( dev, SANE_TRUE );
> +    if( !usbio_WriteReg( dev->fd, 0x29, a_bRegs[0x29])) {
> +      DBG( _DBG_ERROR, "cano_PrepareToReadWhiteCal() failed\n" );
> +      return _E_LAMP_NOT_IN_POS;
> +    }
> +    break;
> +  }
> +  
> +  strip_state=1;
> +  return 0;
> +}
> +
> +static int cano_PrepareToReadBlackCal(pPlustek_Device dev){
> +  if(strip_state==0)
> +    if(cano_PrepareToReadWhiteCal(dev))return SANE_FALSE;
> +  if(strip_state!=2){    
> +    /* switch lamp off to read dark data... */
> +    a_bRegs[0x29] = 0;
> +    usb_switchLamp( dev, SANE_FALSE );
> +    strip_state=2;
> +  }
> +  return 0;
> +}
> +
> +static int cano_LampOnAfterCalibration(pPlustek_Device dev){
> +  pHWDef    hw       = &dev->usbDev.HwSetting;
> +  switch (strip_state){
> +  case 2:
> +    
> +    a_bRegs[0x29] = hw->bReg_0x29;
> +    usb_switchLamp( dev, SANE_TRUE );
> +    if( !usbio_WriteReg( dev->fd, 0x29, a_bRegs[0x29])) {
> +      DBG( _DBG_ERROR, "cano_LampOnAfterCalibration() failed\n" );
> +      return _E_LAMP_NOT_IN_POS;
> +    }
> +    strip_state=1;
> +    break;
> +  }
> +  return 0;
> +}
> +
> +static SANE_Bool cano_adjLampSetting( u_short *min, u_short *max, 
> +				      u_short *off,u_short val){
> +  
> +  u_long newoff = *off;
> +  if(val<IDEAL_GainNormal && val>IDEAL_GainNormal-8000) return SANE_FALSE;
> +
> +  if(val > IDEAL_GainNormal-4000){
> +    *max   = newoff;
> +    *off = ((newoff + *min)>>1);
> +  }else{
> +    u_short bisect = (newoff + *max)>>1;
> +    u_short twice  =  newoff*2;
> +    *min   = newoff;
> +    *off= twice<bisect?twice:bisect;
> +  }
> +
> +  if(*min+1 >= *max) return SANE_FALSE;
> +  return SANE_TRUE;
> +}
> +
> +/** cano_AdjustLightsource
> + * coarse calibration step 0
> + * [Monty changes]: On the CanoScan at least, the default lamp
> + * settings are several *hundred* percent too high and vary from
> + * scanner-to-scanner by 20-50%. This is only for CIS devices 
> + * where the lamp_off parameter is adjustable; I'd make it more general, 
> + * but I only have the CIS hardware to test.
> + */
> +
> +static int cano_AdjustLightsource( pPlustek_Device dev)
> +{
> +  char      tmp[40];
> +  int       i=0;
> +  pDCapsDef scaps      = &dev->usbDev.Caps;
> +  pHWDef    hw         = &dev->usbDev.HwSetting;
> +  u_long    dw, bytes2get;
> +
> +  RGBUShortDef max_rgb, min_rgb;
> +
> +  if( ! (hw->bReg_0x26 & _ONE_CH_COLOR)) return SANE_FALSE;
> +  if(  _IS_PLUSTEKMOTOR(hw->motorModel)) return SANE_FALSE;
> +  if( usb_IsEscPressed())  return SANE_FALSE;
> +
> +
> +  DBG( _DBG_INFO2, "cano_AdjustLightsource()\n" );
> +
> +  /* define the strip to scan for coarse calibration;  done at 300dpi */
> +  m_ScanParam.Size.dwLines  = 1;
> +  m_ScanParam.Size.dwPixels = scaps->Normal.Size.x *
> +    scaps->OpticDpi.x / 300UL;
> +  
> +  m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels * 2;
> +
> +  if( m_ScanParam.bDataType == SCANDATATYPE_Color )
> +    m_ScanParam.Size.dwBytes *=3;
> +  
> +  m_ScanParam.Origin.x = (u_short)((u_long) hw->wActivePixelsStart *
> +				   300UL / scaps->OpticDpi.x);
> +  m_ScanParam.bCalibration = PARAM_Gain;
> +  
> +  DBG( _DBG_INFO2, "Coarse Calibration Strip:\n" );
> +  DBG( _DBG_INFO2, "Lines    = %lu\n", m_ScanParam.Size.dwLines  );
> +  DBG( _DBG_INFO2, "Pixels   = %lu\n", m_ScanParam.Size.dwPixels );
> +  DBG( _DBG_INFO2, "Bytes    = %lu\n", m_ScanParam.Size.dwBytes  );
> +  DBG( _DBG_INFO2, "Origin.X = %u\n",  m_ScanParam.Origin.x );
> +
> +  max_rgb.Red   = max_rgb.Green = max_rgb.Blue = 0xffff;
> +  min_rgb.Red   = hw->red_lamp_on;
> +  min_rgb.Green = hw->green_lamp_on;
> +  min_rgb.Blue  = hw->blue_lamp_on;
> +
> +  while(1){
> +  
> +    m_ScanParam.dMCLK = dMCLK;
> +    if( !usb_SetScanParameters( dev, &m_ScanParam )){
> +      return SANE_FALSE;
> +    }
> +    
> +    bytes2get = m_ScanParam.Size.dwPhyBytes;
> +    if( m_ScanParam.bDataType == SCANDATATYPE_Color )
> +      bytes2get *=3;
> +    
> +    if(	!usb_ScanBegin( dev, SANE_FALSE) ||
> +	!usb_ScanReadImage( dev, pScanBuffer, bytes2get ) ||
> +	!usb_ScanEnd( dev )) {
> +      DBG( _DBG_ERROR, "cano_AdjustLightsource() failed\n" );
> +      return SANE_FALSE;
> +    }
> +    
> +    DBG( _DBG_INFO2, "PhyBytes  = %lu\n",  m_ScanParam.Size.dwPhyBytes  );
> +    DBG( _DBG_INFO2, "PhyPixels = %lu\n",  m_ScanParam.Size.dwPhyPixels );
> +    
> +    sprintf( tmp, "/tmp/coarse-lamp-%u.raw", i );
> +    
> +    dumpPic( tmp, NULL, 0 );
> +    dumpPic( tmp, pScanBuffer, bytes2get );
> +    
> +    if(cano_HostSwap_p())
> +      usb_Swap((u_short *)pScanBuffer, bytes2get );
> +
> +    sprintf( tmp, "/tmp/coarse-lamp-swap%u.raw", i++ );
> +    
> +    dumpPic( tmp, NULL, 0 );
> +    dumpPic( tmp, pScanBuffer, bytes2get );
> +    
> +    
> +    {  
> +      RGBUShortDef tmp;
> +      u_long       dwR, dwG, dwB;
> +      u_long       dwDiv = 10;
> +      u_long       dwLoop1 = m_ScanParam.Size.dwPhyPixels / dwDiv, dwLoop2;
> +      SANE_Bool    adj = SANE_FALSE;
> +      
> +      tmp.Red = tmp.Green = tmp.Blue = 0;
> +      
> +      /* find out the max pixel value for R, G, B */
> +      for( dw = 0; dwLoop1; dwLoop1-- ) {
> +	
> +	/* do some averaging... */
> +	for (dwLoop2 = dwDiv, dwR = dwG = dwB = 0; dwLoop2; dwLoop2--, dw++)
> +	  if( m_ScanParam.bDataType == SCANDATATYPE_Color )
> +	    {
> +	      dwR += ((u_short*)pScanBuffer)[dw];
> +	      dwG += ((u_short*)pScanBuffer)[dw+m_ScanParam.Size.dwPhyPixels+1];
> +	      dwB += ((u_short*)pScanBuffer)[dw+(m_ScanParam.Size.dwPhyPixels+1)*2];
> +	    }else{
> +	      dwG += ((u_short*)pScanBuffer)[dw];
> +	    }
> +
> +	dwR = dwR / dwDiv;
> +	dwG = dwG / dwDiv;
> +	dwB = dwB / dwDiv;
> +	
> +	if(tmp.Red < dwR)
> +	  tmp.Red = dwR;
> +	if(tmp.Green < dwG)
> +	  tmp.Green = dwG;
> +	if(tmp.Blue < dwB)
> +	  tmp.Blue = dwB;
> +	
> +      }
> +      
> +      if( m_ScanParam.bDataType == SCANDATATYPE_Color )
> +	DBG( _DBG_INFO2, "red_lamp_off  = %u/%u/%u\n", min_rgb.Red  ,hw->red_lamp_off  ,max_rgb.Red  );
> +      DBG( _DBG_INFO2, "green_lamp_on = %u/%u/%u\n", min_rgb.Green,hw->green_lamp_off,max_rgb.Green);
> +      if( m_ScanParam.bDataType == SCANDATATYPE_Color )
> +	DBG( _DBG_INFO2, "blue_lamp_off = %u/%u/%u\n", min_rgb.Blue ,hw->blue_lamp_off ,max_rgb.Blue );
> +      
> +      DBG(_DBG_INFO2, "CUR(R,G,B)= 0x%04x(%u), 0x%04x(%u), 0x%04x(%u)\n",
> +	  tmp.Red, tmp.Red, tmp.Green, tmp.Green, tmp.Blue, tmp.Blue);
> +      
> +      /* bisect */
> +      adj=0;
> +      if( m_ScanParam.bDataType == SCANDATATYPE_Color )
> +	adj += cano_adjLampSetting(&min_rgb.Red,&max_rgb.Red,&hw->red_lamp_off,tmp.Red);
> +      adj   += cano_adjLampSetting(&min_rgb.Green,&max_rgb.Green,&hw->green_lamp_off,tmp.Green);
> +      if( m_ScanParam.bDataType == SCANDATATYPE_Color )
> +	adj += cano_adjLampSetting(&min_rgb.Blue,&max_rgb.Blue,&hw->blue_lamp_off,tmp.Blue);
> +      
> +      if( !adj ) break;
> +      usb_AdjustLamps(dev);
> +    }
> +  }
> +
> +  DBG( _DBG_INFO2, "red_lamp_on    = %u\n", hw->red_lamp_on  );
> +  DBG( _DBG_INFO2, "red_lamp_off   = %u\n", hw->red_lamp_off );
> +  DBG( _DBG_INFO2, "green_lamp_on  = %u\n", hw->green_lamp_on  );
> +  DBG( _DBG_INFO2, "green_lamp_off = %u\n", hw->green_lamp_off );
> +  DBG( _DBG_INFO2, "blue_lamp_on   = %u\n", hw->blue_lamp_on   );
> +  DBG( _DBG_INFO2, "blue_lamp_off  = %u\n", hw->blue_lamp_off  );
> +    
> +  DBG( _DBG_INFO2, "cano_AdjustLightsource() done.\n" );
> +
> +  return SANE_TRUE;
> +}
> +
> +
> +static SANE_Bool cano_adjGainSetting( u_char *min, u_char *max, 
> +				 u_char *gain,u_long val){
> +
> +  u_long newgain = *gain;
> +
> +  if(val<IDEAL_GainNormal && val>IDEAL_GainNormal-8000) return SANE_FALSE;
> +
> +  if(val > IDEAL_GainNormal-4000){
> +    *max   = newgain;
> +    *gain  = (newgain + *min)>>1;
> +  }else{
> +    *min   = newgain;
> +    *gain  = (newgain + *max)>>1;
> +  }
> +
> +  if(*min+1 >= *max) return SANE_FALSE;
> +  return SANE_TRUE;
> +}
> +
> +/** cano_AdjustGain
> + * function to perform the "coarse calibration step" part 1.
> + * We scan reference image pixels to determine the optimum coarse gain settings
> + * for R, G, B. (Analog gain and offset prior to ADC). These coefficients are
> + * applied at the line rate during normal scanning.
> + * The scanned line should contain a white strip with some black at the
> + * beginning. The function searches for the maximum value which corresponds to
> + * the maximum white value.
> + * Affects register 0x3b, 0x3c and 0x3d
> + *
> + * adjLightsource, above, steals most of this function's thunder.
> + */
> +
> +static SANE_Bool cano_AdjustGain( pPlustek_Device dev ) {
> +  char      tmp[40];
> +  int       i=0,adj=1;
> +  pDCapsDef scaps    = &dev->usbDev.Caps;
> +  pHWDef    hw       = &dev->usbDev.HwSetting;
> +  u_long    dw, bytes2get;
> +
> +  unsigned char max[3], min[3];
> +  
> +  if( usb_IsEscPressed()) return SANE_FALSE;
> +  
> +  bMaxITA = 0xff;
> +
> +  max[0]  = max[1] = max[2] = 0x3f;
> +  min[0]  = min[1] = min[2] = 1;
> +
> +  DBG( _DBG_INFO2, "cano_AdjustGain()\n" );
> +  
> +  /*
> +   * define the strip to scan for coarse calibration
> +   * done at 300dpi
> +   */
> +
> +  m_ScanParam.Size.dwLines  = 1;				/* for gain */
> +  m_ScanParam.Size.dwPixels = scaps->Normal.Size.x *
> +    scaps->OpticDpi.x / 300UL;
> +  
> +  m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels * 2;
> +  if(m_ScanParam.bDataType == SCANDATATYPE_Color ) 
> +    m_ScanParam.Size.dwBytes *=3;
> +  
> +  m_ScanParam.Origin.x = (u_short)((u_long) hw->wActivePixelsStart *
> +				   300UL / scaps->OpticDpi.x);
> +  m_ScanParam.bCalibration = PARAM_Gain;
> +  
> +  DBG( _DBG_INFO2, "Coarse Calibration Strip:\n" );
> +  DBG( _DBG_INFO2, "Lines    = %lu\n", m_ScanParam.Size.dwLines  );
> +  DBG( _DBG_INFO2, "Pixels   = %lu\n", m_ScanParam.Size.dwPixels );
> +  DBG( _DBG_INFO2, "Bytes    = %lu\n", m_ScanParam.Size.dwBytes  );
> +  DBG( _DBG_INFO2, "Origin.X = %u\n",  m_ScanParam.Origin.x );
> +  
> +  while(adj){
> +    m_ScanParam.dMCLK = dMCLK;
> +    
> +    if( !usb_SetScanParameters( dev, &m_ScanParam ))
> +      return SANE_FALSE;
> +    
> +    bytes2get = m_ScanParam.Size.dwPhyBytes;
> +    if( m_ScanParam.bDataType == SCANDATATYPE_Color ) 
> +      bytes2get *=3;
> +    
> +    if(	!usb_ScanBegin( dev, SANE_FALSE) ||
> +	!usb_ScanReadImage( dev, pScanBuffer, bytes2get ) ||
> +	!usb_ScanEnd( dev )) {
> +      DBG( _DBG_ERROR, "cano_AdjustGain() failed\n" );
> +      return SANE_FALSE;
> +    }
> +    
> +    DBG( _DBG_INFO2, "PhyBytes  = %lu\n",  m_ScanParam.Size.dwPhyBytes  );
> +    DBG( _DBG_INFO2, "PhyPixels = %lu\n",  m_ScanParam.Size.dwPhyPixels );
> +    
> +    sprintf( tmp, "/tmp/coarse-gain-%u.raw", i++ );
> +    
> +    dumpPic( tmp, NULL, 0 );
> +    dumpPic( tmp, pScanBuffer, bytes2get );
> +    
> +    if(cano_HostSwap_p())
> +      usb_Swap((u_short *)pScanBuffer, bytes2get );
> +    
> +    if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {
> +      
> +      RGBUShortDef max_rgb;
> +      u_long       dwR, dwG, dwB;
> +      u_long       dwDiv = 10;
> +      u_long       dwLoop1 = m_ScanParam.Size.dwPhyPixels / dwDiv, dwLoop2;
> +      
> +      max_rgb.Red = max_rgb.Green = max_rgb.Blue = 0;
> +      
> +      /* find out the max pixel value for R, G, B */
> +      for( dw = 0; dwLoop1; dwLoop1-- ) {
> +	
> +	/* do some averaging... */
> +	for (dwLoop2 = dwDiv, dwR = dwG = dwB = 0; dwLoop2; dwLoop2--, dw++) {
> +	  dwR += ((u_short*)pScanBuffer)[dw];
> +	  dwG += ((u_short*)pScanBuffer)[dw+m_ScanParam.Size.dwPhyPixels+1];
> +	  dwB += ((u_short*)pScanBuffer)[dw+(m_ScanParam.Size.dwPhyPixels+1)*2];
> +	}
> +	dwR = dwR / dwDiv;
> +	dwG = dwG / dwDiv;
> +	dwB = dwB / dwDiv;
> +	
> +	if(max_rgb.Red < dwR)
> +	  max_rgb.Red = dwR;
> +	if(max_rgb.Green < dwG)
> +	  max_rgb.Green = dwG;
> +	if(max_rgb.Blue < dwB)
> +	  max_rgb.Blue = dwB;
> +      }
> +      
> +      DBG(_DBG_INFO2, "MAX(R,G,B)= 0x%04x(%u), 0x%04x(%u), 0x%04x(%u)\n",
> +	  max_rgb.Red, max_rgb.Red, max_rgb.Green,
> +	  max_rgb.Green, max_rgb.Blue, max_rgb.Blue );
> +      
> +      adj  = cano_adjGainSetting(min  , max  , a_bRegs+0x3b,max_rgb.Red  );
> +      adj |= cano_adjGainSetting(min+1, max+1, a_bRegs+0x3c,max_rgb.Green);
> +      adj |= cano_adjGainSetting(min+2, max+2, a_bRegs+0x3d,max_rgb.Blue );
> +      
> +    } else {
> +      
> +      u_short	w_max = 0;
> +      
> +      for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++ ) {
> +	if( w_max < ((u_short*)pScanBuffer)[dw])
> +	  w_max = ((u_short*)pScanBuffer)[dw];
> +      }
> +      
> +      adj = cano_adjGainSetting(min,max,a_bRegs+0x3c,w_max);
> +      a_bRegs[0x3a] = (a_bRegs[0x3d] = a_bRegs[0x3c]);
> +      
> +      DBG(_DBG_INFO2, "MAX(G)= 0x%04x(%u)\n", w_max, w_max );
> +      
> +    }
> +    DBG( _DBG_INFO2, "REG[0x3b] = %u\n", a_bRegs[0x3b] );
> +    DBG( _DBG_INFO2, "REG[0x3c] = %u\n", a_bRegs[0x3c] );
> +    DBG( _DBG_INFO2, "REG[0x3d] = %u\n", a_bRegs[0x3d] );
> +  
> +  }
> +  
> +  DBG( _DBG_INFO2, "cano_AdjustGain() done.\n" );
> +
> +  return SANE_TRUE;
> +}
> +
> +
> +static int cano_GetNewOffset(u_long *val, int channel, signed char *low, 
> +			    signed char *now, signed char *high ){
> +
> +  /* if we're too black, we're likely off the low end */
> +  if(val[channel]<=16){
> +    low[channel]=now[channel];
> +    now[channel]=(now[channel]+high[channel])/2;
> +
> +    a_bRegs[0x38+channel]= (now[channel]&0x3f);
> +
> +    if(low[channel]+1>=high[channel])return 0;
> +    return 1;
> +  }else if (val[channel]>=2048){
> +    high[channel]=now[channel];
> +    now[channel]=(now[channel]+low[channel])/2;
> +
> +    a_bRegs[0x38+channel]= (now[channel]&0x3f);
> +
> +    if(low[channel]+1>=high[channel])return 0;
> +    return 1;
> +  }
> +  return 0;
> +}
> +
> +/** cano_AdjustOffset
> + * function to perform the "coarse calibration step" part 2.
> + * We scan reference image pixels to determine the optimum coarse offset settings
> + * for R, G, B. (Analog gain and offset prior to ADC). These coefficients are
> + * applied at the line rate during normal scanning.
> + * On CIS based devices, we switch the light off, on CCD devices, we use the optical
> + * black pixels.
> + * Affects register 0x38, 0x39 and 0x3a
> + */
> +
> +/* Move this to a bisection-based algo and correct some fenceposts;
> +   Plustek's example code disagrees with NatSemi's docs; going by the
> +   docs works better, I will assume the docs are correct. --Monty */
> +
> +static int cano_AdjustOffset( pPlustek_Device dev )
> +{
> +  pDCapsDef scaps      = &dev->usbDev.Caps;
> +  char          tmp[40];
> +  int           adj=1;
> +  int           i;
> +  u_long        dw, dwPixels, bytes2get;
> +  u_long        dwSum[3];
> +  
> +  signed char low[3]={-32,-32,-32};
> +  signed char now[3]={0,0,0};
> +  signed char high[3]={31,31,31};
> +
> +  pHWDef hw = &dev->usbDev.HwSetting;
> +  if( usb_IsEscPressed()) return SANE_FALSE;
> +  
> +  DBG( _DBG_INFO2, "cano_AdjustOffset()\n" );
> +  
> +  m_ScanParam.Size.dwLines  = 1;
> +  m_ScanParam.Size.dwPixels = scaps->Normal.Size.x *
> +    scaps->OpticDpi.x / 300UL;
> +  
> +  dwPixels = m_ScanParam.Size.dwPixels;
> +  
> +  m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels * 2;
> +  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) 
> +    m_ScanParam.Size.dwBytes *= 3;
> +  
> +  m_ScanParam.Origin.x = (u_short)((u_long)hw->bOpticBlackStart * 300UL /
> +				   dev->usbDev.Caps.OpticDpi.x);
> +  m_ScanParam.bCalibration = PARAM_Offset;
> +  m_ScanParam.dMCLK        = dMCLK;
> +  
> +  
> +  if( !usb_SetScanParameters( dev, &m_ScanParam )) {
> +    DBG( _DBG_ERROR, "cano_AdjustOffset() failed\n" );
> +    return SANE_FALSE;
> +  }
> +  
> +  i = 0;
> +  
> +  bytes2get = m_ScanParam.Size.dwPhyBytes;
> +  if( m_ScanParam.bDataType == SCANDATATYPE_Color )
> +    bytes2get *= 3;
> +  
> +  DBG( _DBG_INFO2, "S.dwPixels  = %lu\n", m_ScanParam.Size.dwPixels );		
> +  DBG( _DBG_INFO2, "dwPixels    = %lu\n", dwPixels );
> +  DBG( _DBG_INFO2, "dwPhyBytes  = %lu\n", m_ScanParam.Size.dwPhyBytes );
> +  DBG( _DBG_INFO2, "dwPhyPixels = %lu\n", m_ScanParam.Size.dwPhyPixels );
> +  DBG( _DBG_INFO2, "bytes2get   = %lu\n", bytes2get );
> +  
> +  while( adj ) {
> +    
> +    if((!usb_ScanBegin(dev, SANE_FALSE)) ||
> +       (!usb_ScanReadImage(dev,pScanBuffer,bytes2get)) ||
> +       !usb_ScanEnd( dev )) {
> +      DBG( _DBG_ERROR, "cano_AdjustOffset() failed\n" );
> +      return SANE_FALSE;
> +    }
> +    
> +    sprintf( tmp, "/tmp/coarse-off-%u.raw", i++ );
> +    
> +    dumpPic( tmp, NULL, 0 );
> +    dumpPic( tmp, pScanBuffer, bytes2get );
> +    
> +    if(cano_HostSwap_p())
> +      usb_Swap((u_short *)pScanBuffer, bytes2get );
> +    
> +    if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {
> +      
> +      dwSum[0] = dwSum[1] = dwSum[2] = 0;
> +      
> +      for (dw = 0; dw < dwPixels; dw++) {
> +	
> +	dwSum[0] += ((u_short*)pScanBuffer)[dw];
> +	dwSum[1] += ((u_short*)pScanBuffer)[dw+m_ScanParam.Size.dwPhyPixels+1];
> +	dwSum[2] += ((u_short*)pScanBuffer)[dw+(m_ScanParam.Size.dwPhyPixels+1)*2];
> +
> +      }
> +      
> +      DBG( _DBG_INFO2, "RedSum   = %lu, ave = %lu\n",
> +	   dwSum[0], dwSum[0] /dwPixels );
> +      DBG( _DBG_INFO2, "GreenSum = %lu, ave = %lu\n",
> +	   dwSum[1], dwSum[1] /dwPixels );
> +      DBG( _DBG_INFO2, "BlueSum  = %lu, ave = %lu\n",
> +	   dwSum[2], dwSum[2] /dwPixels );
> +      
> +      /* do averaging for each channel */
> +      dwSum[0] /= dwPixels;
> +      dwSum[1] /= dwPixels;
> +      dwSum[2] /= dwPixels;
> +      
> +      adj  = cano_GetNewOffset( dwSum, 0, low, now, high );
> +      adj |= cano_GetNewOffset( dwSum, 1, low, now, high );
> +      adj |= cano_GetNewOffset( dwSum, 2, low, now, high );
> +      
> +      DBG( _DBG_INFO2, "RedOff   = %d/%d/%d\n", (int)low[0],(int)now[0],(int)high[0]);
> +      DBG( _DBG_INFO2, "GreenOff = %d/%d/%d\n", (int)low[1],(int)now[1],(int)high[1]);
> +      DBG( _DBG_INFO2, "BlueOff  = %d/%d/%d\n", (int)low[2],(int)now[2],(int)high[2]);
> +      
> +    } else {
> +      dwSum[0] = 0;
> +      
> +      for( dw = 0; dw < dwPixels; dw++ )
> +	dwSum[0] += ((u_short*)pScanBuffer)[dw];
> +      dwSum [0] /= dwPixels;
> +      
> +      DBG( _DBG_INFO2, "Sum   = %lu, ave = %lu\n",
> +	   dwSum[0], dwSum[0] /dwPixels );
> +      
> +      adj = cano_GetNewOffset( dwSum, 0, low, now, high );
> +
> +      a_bRegs[0x3a] = a_bRegs[0x39] = a_bRegs[0x38];
> +      
> +      DBG( _DBG_INFO2, "GrayOff = %d/%d/%d\n", (int)low[0],(int)now[0],(int)high[0]);
> +
> +    }
> +    
> +    DBG( _DBG_INFO2, "REG[0x38] = %u\n", a_bRegs[0x38] );
> +    DBG( _DBG_INFO2, "REG[0x39] = %u\n", a_bRegs[0x39] );
> +    DBG( _DBG_INFO2, "REG[0x3a] = %u\n", a_bRegs[0x3a] );
> +
> +    _UIO(sanei_lm983x_write(dev->fd, 0x38, &a_bRegs[0x38], 3, SANE_TRUE));
> +
> +  }
> +  
> +  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {
> +    a_bRegs[0x38] = now[0];	
> +    a_bRegs[0x39] = now[1];	
> +    a_bRegs[0x3a] = now[2];
> +  } else {
> +    
> +    a_bRegs[0x38] = a_bRegs[0x39] = a_bRegs[0x3a] = now[0];	
> +  }
> +  
> +  DBG( _DBG_INFO2, "cano_AdjustOffset() done.\n" );
> +  
> +  return SANE_TRUE;
> +}
> +
> +/** usb_AdjustDarkShading
> + * fine calibration part 1
> + *
> + */
> +static SANE_Bool cano_AdjustDarkShading( pPlustek_Device dev )
> +{
> +  char         tmp[40];
> +  pScanDef     scanning = &dev->scanning;
> +  pDCapsDef    scaps    = &dev->usbDev.Caps;
> +  unsigned int i,j;
> +  
> +  DBG( _DBG_INFO2, "cano_AdjustDarkShading()\n" );
> +  if( usb_IsEscPressed())
> +    return SANE_FALSE;
> +   
> +  m_ScanParam = scanning->sParam;
> +
> +  if( m_ScanParam.PhyDpi.x > 75)
> +    m_ScanParam.Size.dwLines = 64;
> +  else
> +    m_ScanParam.Size.dwLines = 32;
> +
> +  m_ScanParam.Origin.y  = 0;
> +  m_ScanParam.bBitDepth = 16;
> +  
> +  m_ScanParam.UserDpi.y = scaps->OpticDpi.y;
> +  m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels * 2;
> +  
> +  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) 
> +    m_ScanParam.Size.dwBytes *= 3;
> +  
> +  m_ScanParam.bCalibration = PARAM_DarkShading;
> +  m_ScanParam.dMCLK        = dMCLK;
> +  
> +  sprintf( tmp, "/tmp/fine-dark.raw" );
> +  dumpPic( tmp, NULL, 0 );
> +  
> +  usb_SetScanParameters( dev, &m_ScanParam );
> +  if( usb_ScanBegin( dev, SANE_FALSE ) &&
> +      usb_ScanReadImage( dev, pScanBuffer, m_ScanParam.Size.dwTotalBytes)) {
> +    
> +    dumpPic( tmp, pScanBuffer, m_ScanParam.Size.dwTotalBytes );
> +    
> +    if(cano_HostSwap_p())
> +      usb_Swap((u_short *)pScanBuffer, m_ScanParam.Size.dwTotalBytes);
> +  }
> +  if (!usb_ScanEnd( dev )){
> +    DBG( _DBG_ERROR, "cano_AdjustDarkShading() failed\n" );
> +    return SANE_FALSE;
> +  }
> +  
> +  /* average the n lines, compute reg values */
> +  if( scanning->sParam.bDataType == SCANDATATYPE_Color ) {
> +    int      step   = m_ScanParam.Size.dwPhyPixels + 1;
> +    int      stepW  = m_ScanParam.Size.dwPhyPixels;
> +    for(i=0;i<m_ScanParam.Size.dwPhyPixels;i++){
> +      u_long red=0,green=0,blue=0;
> +      u_short *bufp   = ((u_short *)pScanBuffer)+i;
> +
> +      for(j=0;j<m_ScanParam.Size.dwPhyLines;j++){
> +	red   += *bufp;   bufp+=step;
> +	green += *bufp;   bufp+=step;
> +	blue  += *bufp;   bufp+=step;
> +      }
> +
> +      a_wDarkShading[i]         = red/j   +1000;
> +      a_wDarkShading[i+stepW]   = green/j +1000;
> +      a_wDarkShading[i+stepW*2] = blue/j  +1000;
> +
> +    }
> +    
> +    if(cano_HostSwap_p())
> +      usb_Swap(a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2 * 3 );
> +
> +  }else{
> +    int      step   = m_ScanParam.Size.dwPhyPixels + 1;
> +    for(i=0;i<m_ScanParam.Size.dwPhyPixels;i++){
> +      u_long gray=0;
> +      u_short *bufp   = ((u_short *)pScanBuffer)+i;
> +
> +      for(j=0;j<m_ScanParam.Size.dwPhyLines;j++){
> +	gray   += *bufp;   bufp+=step;
> +      }
> +      
> +      a_wDarkShading[i]= gray/j;
> +      
> +    }
> +    if(cano_HostSwap_p())
> +      usb_Swap(a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2 );
> +    memcpy(a_wDarkShading+ m_ScanParam.Size.dwPhyPixels * 2,
> +	   a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2);
> +    memcpy(a_wDarkShading+ m_ScanParam.Size.dwPhyPixels * 4,
> +	   a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2);
> +    
> +  }
> +    
> +  DBG( _DBG_INFO2, "cano_AdjustDarkShading() done\n" );
> +  return SANE_TRUE;
> +}
> +
> +/** usb_AdjustWhiteShading
> + * fine calibration part 2 - read the white calibration area and calculate
> + * the gain coefficient for each pixel
> + *
> + */
> +static SANE_Bool cano_AdjustWhiteShading( pPlustek_Device dev )
> +{
> +  char         tmp[40];
> +  pScanDef     scanning = &dev->scanning;
> +  pDCapsDef    scaps    = &dev->usbDev.Caps;
> +  pHWDef       hw       = &dev->usbDev.HwSetting;
> +  unsigned int i,j;
> +  
> +  DBG( _DBG_INFO2, "cano_AdjustWhiteShading()\n" );
> +  if( usb_IsEscPressed())
> +    return SANE_FALSE;
> +  
> +  m_ScanParam = scanning->sParam;
> +  if( m_ScanParam.PhyDpi.x > 75)
> +    m_ScanParam.Size.dwLines = 64;
> +  else
> +    m_ScanParam.Size.dwLines = 32;
> +  
> +  m_ScanParam.Origin.y  = 0;
> +  m_ScanParam.bBitDepth = 16;
> +  
> +  m_ScanParam.UserDpi.y = scaps->OpticDpi.y;
> +  m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels * 2;
> +  
> +  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) 
> +    m_ScanParam.Size.dwBytes *= 3;
> +  
> +  m_ScanParam.bCalibration = PARAM_WhiteShading;
> +  m_ScanParam.dMCLK        = dMCLK;
> +  
> +  m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels * 2;
> +  if( hw->bReg_0x26 & _ONE_CH_COLOR &&
> +      m_ScanParam.bDataType == SCANDATATYPE_Color ) {
> +    m_ScanParam.Size.dwBytes *= 3;
> +  }
> +  
> +  sprintf( tmp, "/tmp/fine-white.raw" );
> +  dumpPic( tmp, NULL, 0 );
> +  
> +  if( usb_SetScanParameters( dev, &m_ScanParam ) &&
> +      usb_ScanBegin( dev, SANE_FALSE ) &&
> +      usb_ScanReadImage( dev, pScanBuffer, m_ScanParam.Size.dwTotalBytes)) {
> +    
> +    dumpPic( tmp, pScanBuffer, m_ScanParam.Size.dwTotalBytes );
> +    
> +    if(cano_HostSwap_p())
> +      usb_Swap((u_short *)pScanBuffer, m_ScanParam.Size.dwTotalBytes);
> +    if (!usb_ScanEnd( dev )){
> +      DBG( _DBG_ERROR, "cano_AdjustWhiteShading() failed\n" );
> +      return SANE_FALSE;
> +    }
> +  }else{
> +    DBG( _DBG_ERROR, "cano_AdjustWhiteShading() failed\n" );
> +    return SANE_FALSE;
> +  }
> +  
> +  /* average the n lines, compute reg values */
> +  if( scanning->sParam.bDataType == SCANDATATYPE_Color ) {
> +    int      step   = m_ScanParam.Size.dwPhyPixels + 1;
> +    int      stepW  = m_ScanParam.Size.dwPhyPixels;
> +    for(i=0;i<m_ScanParam.Size.dwPhyPixels;i++){
> +      u_long red=0,green=0,blue=0;
> +      u_short *bufp   = ((u_short *)pScanBuffer)+i;
> +
> +      for(j=0;j<m_ScanParam.Size.dwPhyLines;j++){
> +	red   += *bufp;   bufp+=step;
> +	green += *bufp;   bufp+=step;
> +	blue  += *bufp;   bufp+=step;
> +      }
> +
> +      /* an extra 5% on the top */
> +      red   = 68811.*16384.*j/red;
> +      green = 68811.*16384.*j/green;
> +      blue  = 68811.*16384.*j/blue;
> +
> +      a_wWhiteShading[i]         = (red>65535?65535:red);
> +      a_wWhiteShading[i+stepW]   = (green>65535?65535:green);
> +      a_wWhiteShading[i+stepW*2] = (blue>65535?65535:blue);
> +
> +
> +    }
> +
> +    if(cano_HostSwap_p())
> +      usb_Swap(a_wWhiteShading, m_ScanParam.Size.dwPhyPixels * 2 * 3 );
> +    
> +  }else{
> +    int      step   = m_ScanParam.Size.dwPhyPixels + 1;
> +    for(i=0;i<m_ScanParam.Size.dwPhyPixels;i++){
> +      u_long gray=0;
> +      u_short *bufp   = ((u_short *)pScanBuffer)+i;
> +
> +      for(j=0;j<m_ScanParam.Size.dwPhyLines;j++){
> +	gray   += *bufp;   bufp+=step;
> +      }
> +      
> +      gray = 65535.*16384.*j/gray;
> +      a_wWhiteShading[i]= (gray>65535?65535:gray);
> +      
> +    }
> +    if(cano_HostSwap_p())
> +      usb_Swap(a_wWhiteShading, m_ScanParam.Size.dwPhyPixels * 2 );
> +    memcpy(a_wWhiteShading+ m_ScanParam.Size.dwPhyPixels * 2,
> +	   a_wWhiteShading, m_ScanParam.Size.dwPhyPixels * 2);
> +    memcpy(a_wWhiteShading+ m_ScanParam.Size.dwPhyPixels * 4,
> +	   a_wWhiteShading, m_ScanParam.Size.dwPhyPixels * 2);
> +
> +  }
> +    
> +  DBG( _DBG_INFO2, "cano_AdjustWhiteShading() done\n" );
> +  return SANE_TRUE;
> +}
> +
> +static int cano_DoCalibration( pPlustek_Device dev )
> +{
> +    pScanDef  scanning = &dev->scanning;
> +    pHWDef    hw       = &dev->usbDev.HwSetting;
> +    pDCapsDef scaps    = &dev->usbDev.Caps;
> +    
> +
> +    if( SANE_TRUE == scanning->fCalibrated )
> +      return SANE_TRUE;
> +
> +    DBG( _DBG_INFO2, "cano_DoCalibration()\n" );
> +
> +    if(	!( hw->bReg_0x26 & _ONE_CH_COLOR)){
> +      DBG( _DBG_ERROR, "altCalibration can't work with non-line-rate CIS devices\n" );
> +      return SANE_FALSE; // can't cal this  
> +    }
> +    if( _IS_PLUSTEKMOTOR(hw->motorModel)){
> +      DBG( _DBG_ERROR, "altCalibration can't work with this Plustek motor control setup\n" );
> +      return SANE_FALSE; // can't cal this
> +    }
> +    if( scanning->sParam.bChannels != 1){
> +      DBG( _DBG_ERROR, "altCalibration can't work with non-line-rate CIS devices\n" );
> +      return SANE_FALSE; // can't cal this
> +    }
> +
> +
> +    /* Don't allow calibration settings from the other driver to confuse our use of
> +       a few of its functions */
> +    scaps->workaroundFlag &= ~_WAF_SKIP_WHITEFINE; 
> +    scaps->workaroundFlag &= ~_WAF_SKIP_FINE; 
> +    scaps->workaroundFlag &= ~_WAF_BYPASS_CALIBRATION; 
> +
> +    /* Set the shading position to undefined */
> +    strip_state=0;
> +    usb_PrepareCalibration( dev );
> +    
> +    /* You *really* don't want to skip calibration for CanoScan devices. I promise. */
> +    usb_SetMCLK( dev, &scanning->sParam );
> +	
> +    DBG( _DBG_INFO2, "###### ADJUST LAMP (COARSE)#######\n" );
> +    if(cano_PrepareToReadWhiteCal(dev))return SANE_FALSE;
> +    a_bRegs[0x45] &= ~0x10;
> +    if( !cano_AdjustLightsource(dev)) {
> +      DBG( _DBG_ERROR, "Coarse Calibration failed!!!\n" );
> +      return _E_INTERNAL;
> +    }
> +
> +    DBG( _DBG_INFO2, "###### ADJUST OFFSET (COARSE) ####\n" );
> +    if(cano_PrepareToReadBlackCal(dev))return SANE_FALSE;
> +    if( !cano_AdjustOffset(dev)) {
> +      DBG( _DBG_ERROR, "Coarse Calibration failed!!!\n" );
> +      return _E_INTERNAL;
> +    }
> +
> +    DBG( _DBG_INFO2, "###### ADJUST GAIN (COARSE)#######\n" );
> +    if(cano_PrepareToReadWhiteCal(dev))return SANE_FALSE;
> +    if( !cano_AdjustGain(dev)) {
> +      DBG( _DBG_ERROR, "Coarse Calibration failed!!!\n" );
> +      return _E_INTERNAL;
> +    }
> +
> +    DBG( _DBG_INFO2, "###### ADJUST DARK (FINE) ########\n" );
> +    if(cano_PrepareToReadBlackCal(dev))return SANE_FALSE;
> +    a_bRegs[0x45] |= 0x10;
> +    if( !cano_AdjustDarkShading(dev)) {
> +      DBG( _DBG_ERROR, "Fine Calibration failed!!!\n" );
> +      return _E_INTERNAL;
> +    }
> +
> +    DBG( _DBG_INFO2, "###### ADJUST WHITE (FINE) #######\n" );
> +    if(cano_PrepareToReadWhiteCal(dev))return SANE_FALSE;
> +    
> +    if(!usb_ModuleToHome( dev, SANE_TRUE )) return SANE_FALSE;
> +    if( !usb_ModuleMove(dev, MOVE_Forward,
> +    		(u_long)dev->usbDev.pSource->ShadingOriginY)) {
> +      return _E_INTERNAL;
> +    }
> +    if( !cano_AdjustWhiteShading(dev)) {
> +      DBG( _DBG_ERROR, "Fine Calibration failed!!!\n" );
> +      return _E_INTERNAL;
> +    }
> +
> +    /* Lamp on if it's not */
> +    cano_LampOnAfterCalibration(dev);
> +    strip_state=0;
> +    
> +    /*
> +     * home the sensor after calibration
> +     */
> +    usb_ModuleToHome( dev, SANE_TRUE );
> +    scanning->fCalibrated = SANE_TRUE;
> +
> +    DBG( _DBG_INFO2, "cano_DoCalibration() done\n" );
> +    return SANE_TRUE;
> +}
> +
> diff -ruNw sane-backends-1.0.12/backend/plustek-share.h sane-backends-1.0.12-canoscan/backend/plustek-share.h
> --- sane-backends-1.0.12/backend/plustek-share.h	2003-05-16 16:59:43.000000000 -0400
> +++ sane-backends-1.0.12-canoscan/backend/plustek-share.h	2003-08-15 00:40:34.000000000 -0400
> @@ -258,6 +258,10 @@
>   * for adjusting the usb stuff
>   */
>  typedef struct {
> +        int     altCalibrate;  /* force use of the alternate canoscan
> +                                  autocal; perhaps other Canon
> +                                  scanners require the alternate
> +                                  autocalibration as well */
>  	int     lampOff;
>  	int     lampOffOnEnd;
>  	int     warmup;
> diff -ruNw sane-backends-1.0.12/backend/plustek-usb.c sane-backends-1.0.12-canoscan/backend/plustek-usb.c
> --- sane-backends-1.0.12/backend/plustek-usb.c	2003-05-16 16:59:43.000000000 -0400
> +++ sane-backends-1.0.12-canoscan/backend/plustek-usb.c	2003-08-15 00:40:34.000000000 -0400
> @@ -963,7 +963,19 @@
>  	 */
>  	usb_ModuleStatus( dev );
>  
> +        if(dev->usbDev.vendor==0x04A9 &&    /* canoscan ----------*/
> +           (dev->usbDev.product==0x220D  || /* LiDE20 and related */
> +            dev->usbDev.product==0x220E)    /* LiDE30 and related */
> +           ){
> +          result = cano_DoCalibration( dev );
> +        }else{
> +          if(dev->adj.altCalibrate)
> +            result = cano_DoCalibration( dev );
> +          else
>      result = usb_DoCalibration( dev );
> +        }
> +
> +
>      if( SANE_TRUE != result ) {
>  		DBG( _DBG_INFO, "calibration failed!!!\n" );
>          return result;
> diff -ruNw sane-backends-1.0.12/backend/plustek.c sane-backends-1.0.12-canoscan/backend/plustek.c
> --- sane-backends-1.0.12/backend/plustek.c	2003-05-16 16:59:44.000000000 -0400
> +++ sane-backends-1.0.12-canoscan/backend/plustek.c	2003-08-15 02:22:09.000000000 -0400
> @@ -175,6 +175,7 @@
>  # include "plustek-usbscan.c"
>  # include "plustek-usbimg.c"
>  # include "plustek-usbshading.c"
> +# include "canoscan-calibrate.c"
>  # include "plustek-usb.c"
>  #endif
>  
> @@ -299,6 +300,7 @@
>  	DBG( _DBG_SANE_INIT, "warmup       : %ds\n",  cnf->adj.warmup          );
>  	DBG( _DBG_SANE_INIT, "lampOff      : %d\n",   cnf->adj.lampOff         );
>  	DBG( _DBG_SANE_INIT, "lampOffOnEnd : %d\n",   cnf->adj.lampOffOnEnd    );
> +        DBG( _DBG_SANE_INIT, "altCalibrate : >%s<\n", cnf->adj.altCalibrate?"yes":"no"   );
>  	DBG( _DBG_SANE_INIT, "skipCalibr.  : %d\n",   cnf->adj.skipCalibration );
>  	DBG( _DBG_SANE_INIT, "skipFine     : %d\n",   cnf->adj.skipFine        );
>  	DBG( _DBG_SANE_INIT, "skipFineWhite: %d\n",   cnf->adj.skipFineWhite   );
> @@ -1434,6 +1436,8 @@
>  			
>  			ival = 0;
>     			decodeVal( str, "enableTPA", _INT, &config.adj.enableTpa, &ival);
> +			decodeVal( str, "altCalibrate",
> +									  _INT, &config.adj.altCalibrate,&ival);
>  			decodeVal( str, "skipCalibration",
>  									  _INT, &config.adj.skipCalibration,&ival);
>  			decodeVal( str, "skipFine",