Logo Search packages:      
Sourcecode: sane-backends version File versions

plustek-usbshading.c

Go to the documentation of this file.
/*.............................................................................
 * Project : SANE library for Plustek flatbed scanners.
 *.............................................................................
 */

/** @file plustek-usbshading.c
 *  @brief Calibration routines.
 *
 * Based on sources acquired from Plustek Inc.<br>
 * Copyright (C) 2001-2004 Gerhard Jaeger <gerhard@gjaeger.de>
 *
 * History:
 * - 0.40 - starting version of the USB support
 * - 0.41 - minor fixes
 *        - added workaround stuff for EPSON1250
 * - 0.42 - added workaround stuff for UMAX 3400
 * - 0.43 - added call to usb_switchLamp before reading dark data
 * - 0.44 - added more debug output and bypass calibration
 *        - added dump of shading data
 * - 0.45 - added coarse calibration for CIS devices
 *        - added _WAF_SKIP_FINE to skip the results of fine calibration
 *        - CanoScan fixes and fine-tuning
 * - 0.46 - CanoScan will now be calibrated by code in plustek-usbcal.c
 *        - added functions to save and restore calibration data from a file
 *        - fixed some TPA issues
 * - 0.47 - made calibration work on big-endian machines
 * - 0.48 - added more debug output
 *        - added usb_AutoWarmup()
 * .
 * <hr>
 * 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>
 */
 
/************** global stuff - I hate it, but we need it... ******************/

00073 #define _MAX_AUTO_WARMUP 60  /**< number of loops                            */
00074 #define _AUTO_SLEEP       1  /**< time to sleep, when lamp is not stable     */
00075 #define _AUTO_THRESH     60  /**< threshold for stop waiting (normal lamp)   */
00076 #define _AUTO_TPA_THRESH 40  /**< threshold for stop waiting (TPA lamp)      */

00078 #define _MAX_GAIN_LOOPS  10  /**< max number of loops for coarse calibration */

#define _MAX_SHAD       0x4000
00081 #define _SHADING_BUF    (_MAX_SHAD*3)    /**< max size of the shading buffer */

#define SWAP_COARSE
#define SWAP_FINE

static u_short a_wWhiteShading[_SHADING_BUF] = {0};
static u_short a_wDarkShading[_SHADING_BUF]  = {0};

/************************** static variables *********************************/

static RGBUShortDef Gain_Hilight;
static RGBUShortDef Gain_NegHilight;
static RGBByteDef   Gain_Reg;

static u_long     m_dwPixels;

static u_char    *pScanBuffer;
static pScanParam pParam;
static ScanParam  m_ScanParam;
static u_long       m_dwIdealGain;

static pRGBUShortDef  m_pAvColor;
static u_short       *m_pAvMono;
static u_long        *m_pSum;

static double dMCLK, dExpect, dMax;
static double dMCLK_ADF;
static double dRed, dGreen, dBlue;

static u_short m_wHilight = 4 /*0*/;  /* check the windows registry... */
static u_short m_wShadow  = 4 /*0*/;  /* check the windows registry... */

/**
 */
static void usb_line_statistics( char *cmt, u_short* buf,
                                 u_long dim_x, SANE_Bool color )
{
      int          i, end;
      u_long       dw, imad, imid, alld, cld, cud;
      u_short      mid, mad, aved, lbd, ubd, tmp;
      pMonoWordDef pvd, pvd2;

      pvd = pvd2 = (pMonoWordDef)buf;

      if( color )
            end = 3;
      else
        end = 1;

      for( i = 0; i < end; i++ ) {

            mid  = 0xFFFF;
            mad  = 0;
            imid = 0;
            imad = 0;
            alld = 0;
            cld  = 0;
            cud  = 0;

            for( dw = 0; dw < dim_x; pvd++, dw++ ) {

#ifdef SWAP_FINE
                  tmp = pvd->Mono;
#else
                  tmp = _LOBYTE(pvd->Mono) * 256 + _HIBYTE(pvd->Mono);
#endif

                  if( tmp > mad ) {
                        mad  = tmp;
                        imad = dw;
                  }

                  if( tmp < mid ) {
                        mid  = tmp;
                        imid = dw;
                  }

                  alld += tmp;
            }

            aved = (u_short)(alld/dim_x);
            lbd  = aved - 0.05*aved;
            ubd  = aved + 0.05*aved;

            for( dw = 0; dw < dim_x; pvd2++, dw++ ) {

#ifdef SWAP_FINE
                  tmp = pvd2->Mono;
#else
                  tmp = _LOBYTE(pvd2->Mono) * 256 + _HIBYTE(pvd2->Mono);
#endif

                  if( tmp > ubd ) {
                        cud++;
                  } else if( tmp < lbd ) {
                        cld++;
                  }
            }

            DBG( _DBG_INFO2, "Color[%u] (%s) : "
                                      "min=%u(%lu) max=%u(%lu) ave=%u\n",
                                                            i, cmt, mid, imid, mad, imad, aved);
            DBG( _DBG_INFO2, "5%%: %u (%lu), %u (%lu)\n", lbd, cld,ubd,cud);
      }
}

/** usb_SetMCLK
 * get the MCLK out of our table
 */
00190 static void usb_SetMCLK( Plustek_Device *dev, pScanParam pParam )
{
      int          idx, i;
      pClkMotorDef clk;
      pHWDef       hw = &dev->usbDev.HwSetting;

      clk = usb_GetMotorSet( hw->motorModel );
      idx = 0;
      for( i = 0; i < _MAX_CLK; i++ ) {
            if( pParam->PhyDpi.x <= dpi_ranges[i] )
                  break;
            idx++;
      }
      if( idx >= _MAX_CLK )
            idx = _MAX_CLK - 1;

      if( pParam->bDataType != SCANDATATYPE_Color ) {

            if( pParam->bBitDepth > 8 )
                  dMCLK = clk->gray_mclk_16[idx];
            else
                  dMCLK = clk->gray_mclk_8[idx];
      } else {
            if( pParam->bBitDepth > 8 )
                  dMCLK = clk->color_mclk_16[idx];
            else
                  dMCLK = clk->color_mclk_8[idx];
      }

      pParam->dMCLK = dMCLK;

      DBG( _DBG_INFO, "SETMCLK[%u/%u], using entry %u: %f, %u\n",
           hw->motorModel, pParam->bDataType, idx, dMCLK, pParam->PhyDpi.x );
}

/** usb_SetDarkShading
 * download the dark shading data to Merlins' DRAM
 */
00228 static SANE_Bool usb_SetDarkShading( int fd, u_char channel,
                                     void *coeff_buffer, u_short wCount )
{
      int res;

      a_bRegs[0x03] = 0;
      if( channel == CHANNEL_green )
            a_bRegs[0x03] |= 4;
      else
            if( channel == CHANNEL_blue )
                  a_bRegs[0x03] |= 8;

      if( usbio_WriteReg( fd, 0x03, a_bRegs[0x03] )) {

            /* Dataport address is always 0 for setting offset coefficient */
            a_bRegs[0x04] = 0;
            a_bRegs[0x05] = 0;

            res = sanei_lm983x_write( fd, 0x04, &a_bRegs[0x04], 2, SANE_TRUE );

            /* Download offset coefficients */
            if( SANE_STATUS_GOOD == res ) {
                  
                  res = sanei_lm983x_write( fd, 0x06,
                                            (u_char*)coeff_buffer, wCount, SANE_FALSE );
                  if( SANE_STATUS_GOOD == res )
                        return SANE_TRUE;
            }
      }
      
      DBG( _DBG_ERROR, "usb_SetDarkShading() failed\n" );
      return SANE_FALSE;
}

/** usb_SetWhiteShading
 * download the white shading data to Merlins' DRAM
 */
00265 static SANE_Bool usb_SetWhiteShading( int fd, u_char channel,
                                      void *data_buffer, u_short wCount )
{
      int res;

      a_bRegs [0x03] = 1;
      if (channel == CHANNEL_green)
            a_bRegs [0x03] |= 4;
      else
            if (channel == CHANNEL_blue)
                  a_bRegs [0x03] |= 8;

      if( usbio_WriteReg( fd, 0x03, a_bRegs[0x03] )) {

            /* Dataport address is always 0 for setting offset coefficient */
            a_bRegs[0x04] = 0;
            a_bRegs[0x05] = 0;

            res = sanei_lm983x_write( fd, 0x04, &a_bRegs[0x04], 2, SANE_TRUE );

            /* Download offset coefficients */
            if( SANE_STATUS_GOOD == res ) {
                  res = sanei_lm983x_write(fd, 0x06,
                                          (u_char*)data_buffer, wCount, SANE_FALSE );
                  if( SANE_STATUS_GOOD == res )
                        return SANE_TRUE;
            }
      }
      
      DBG( _DBG_ERROR, "usb_SetWhiteShading() failed\n" );
      return SANE_FALSE;
}

/** usb_GetSWOffsetGain4TPA
 *  preset the offset and gain parameter for fine calibration for
 *  TPA/ADF scanning
 */
00302 static void usb_GetSWOffsetGain4TPA( Plustek_Device *dev )
{
      pScanParam pParam = &dev->scanning.sParam;
      pDCapsDef  sCaps  = &dev->usbDev.Caps;

      switch( sCaps->bCCD ) {
            case kEPSON:
                  DBG( _DBG_INFO2, "kEPSON TPA adjustments\n" );
                  pParam->swGain[0] = 1000;
                  pParam->swGain[1] = 1000;
                  pParam->swGain[2] = 1000;
            break;
      }
}

/** usb_GetSWOffsetGain
 * preset the offset and gain parameter for fine calibration for normal
 * scanning
 */
00321 static void usb_GetSWOffsetGain( Plustek_Device *dev )
{
      pScanParam pParam = &dev->scanning.sParam;
      pDCapsDef  sCaps  = &dev->usbDev.Caps;
      pHWDef     hw     = &dev->usbDev.HwSetting;

      pParam->swOffset[0] = 0;
      pParam->swOffset[1] = 0;
      pParam->swOffset[2] = 0;

      pParam->swGain[0] = 1000;
      pParam->swGain[1] = 1000;
      pParam->swGain[2] = 1000;

      if( pParam->bSource != SOURCE_Reflection ) {
            usb_GetSWOffsetGain4TPA( dev );
            return;
      }

      /* only valid for normal scanning... */
      switch( sCaps->bCCD ) {

      case kEPSON:
            DBG( _DBG_INFO2, "kEPSON adjustments\n" );
#if 0
            pParam->swGain[0] = 800;
            pParam->swGain[1] = 800;
            pParam->swGain[2] = 800;
#endif
            break;
      
      case kNECSLIM:
            DBG( _DBG_INFO2, "kNECSLIM adjustments\n" );
            if( pParam->PhyDpi.x <= 150 ) {
                  pParam->swOffset[0] = 600;
                  pParam->swOffset[1] = 500;
                  pParam->swOffset[2] = 300;
                  pParam->swGain[0] = 960;
                  pParam->swGain[1] = 970;
                  pParam->swGain[2] = 1000;
            } else if (pParam->PhyDpi.x <= 300) {
                  pParam->swOffset[0] = 700;
                  pParam->swOffset[1] = 600;
                  pParam->swOffset[2] = 400;
                  pParam->swGain[0] = 967;
                  pParam->swGain[1] = 980;
                  pParam->swGain[2] = 1000;
            } else {
                  pParam->swOffset[0] = 900;
                  pParam->swOffset[1] = 850;
                  pParam->swOffset[2] = 620;
                  pParam->swGain[0] = 965;
                  pParam->swGain[1] = 980;
                  pParam->swGain[2] = 1000;
            }
            break;

      case kNEC8861:
            DBG( _DBG_INFO2, "kNEC8861 adjustments\n" );
            break;
#if 0
      case kCIS650:
      case kCIS670:
      case kCIS1220:

            DBG( _DBG_INFO2, "kCIS adjustments\n" );
            if(pParam->bDataType == SCANDATATYPE_Color) {

                  pParam->swGain[0] =
                  pParam->swGain[1] =
                  pParam->swGain[2] = 952;

                  pParam->swOffset[0] =
                  pParam->swOffset[1] =
                  pParam->swOffset[2] = 1000;
            }
            break;
      case kCIS1240:
            DBG( _DBG_INFO2, "kCIS1240 adjustments\n" );
            if(pParam->bDataType == SCANDATATYPE_Color) {

                  pParam->swGain[0] = 950;
                  pParam->swGain[1] = 950;
                  pParam->swGain[2] = 900;

                  pParam->swOffset[0] =
                  pParam->swOffset[1] =
                  pParam->swOffset[2] = 0; /*1000;*/
            }
            break;
#endif

      case kNEC3799:
            DBG( _DBG_INFO2, "kNEC3799 adjustments\n" );
            if( sCaps->bPCB == 2 ) {
                  if( pParam->PhyDpi.x <= 150 ) {
                        pParam->swOffset[0] = 600;
                        pParam->swOffset[1] = 500;
                        pParam->swOffset[2] = 300;
                        pParam->swGain[0] = 960;
                        pParam->swGain[1] = 970;
                        pParam->swGain[2] = 1000;
                  } else if (pParam->PhyDpi.x <= 300) {
                        pParam->swOffset[0] = 700;
                        pParam->swOffset[1] = 600;
                        pParam->swOffset[2] = 400;
                        pParam->swGain[0] = 967;
                        pParam->swGain[1] = 980;
                        pParam->swGain[2] = 1000;
                  } else {
                        pParam->swOffset[0] = 900;
                        pParam->swOffset[1] = 850;
                        pParam->swOffset[2] = 620;
                        pParam->swGain[0] = 965;
                        pParam->swGain[1] = 980;
                        pParam->swGain[2] = 1000;
                  }
            } else if( hw->motorModel == MODEL_KaoHsiung ) {
                  pParam->swOffset[0] = 1950;
                  pParam->swOffset[1] = 1700;
                  pParam->swOffset[2] = 1250;
                  pParam->swGain[0] = 955;
                  pParam->swGain[1] = 950;
                  pParam->swGain[2] = 1000;
            } else { /* MODEL_Hualien */
                  if( pParam->PhyDpi.x <= 300 ) {
                        if( pParam->bBitDepth > 8 ) {
                              pParam->swOffset[0] = 0;
                              pParam->swOffset[1] = 0;
                              pParam->swOffset[2] = -300;
                              pParam->swGain[0] = 970;
                              pParam->swGain[1] = 985;
                              pParam->swGain[2] = 1050;
                        } else {
                              pParam->swOffset[0] = -485;
                              pParam->swOffset[1] = -375;
                              pParam->swOffset[2] = -628;
                              pParam->swGain[0] = 970;
                              pParam->swGain[1] = 980;
                              pParam->swGain[2] = 1050;
                        }
                  } else {
                        if( pParam->bBitDepth > 8 ) {
                              pParam->swOffset[0] = 1150;
                              pParam->swOffset[1] = 1000;
                              pParam->swOffset[2] = 700;
                              pParam->swGain[0] = 990;
                              pParam->swGain[1] = 1000;
                              pParam->swGain[2] = 1050;
                        } else {
                              pParam->swOffset[0] = -30;
                              pParam->swOffset[1] = 0;
                              pParam->swOffset[2] = -250;
                              pParam->swGain[0] = 985;
                              pParam->swGain[1] = 995;
                              pParam->swGain[2] = 1050;
                        }
                  }
            }
            break;

      case kSONY548:
            DBG( _DBG_INFO2, "kSONY548 adjustments\n" );
            if(pParam->bDataType == SCANDATATYPE_Color)
            {
                  if(pParam->PhyDpi.x <= 75)
                  {
                        pParam->swOffset[0] = 650;
                        pParam->swOffset[1] = 850;
                        pParam->swOffset[2] = 500;
                        pParam->swGain[0] = 980;
                        pParam->swGain[1] = 1004;
                        pParam->swGain[2] = 1036;
                  }
                  else if(pParam->PhyDpi.x <= 300)
                  {
                        pParam->swOffset[0] = 700;
                        pParam->swOffset[1] = 900;
                        pParam->swOffset[2] = 550;
                        pParam->swGain[0] = 970;
                        pParam->swGain[1] = 995;
                        pParam->swGain[2] = 1020;
                  }
                  else if(pParam->PhyDpi.x <= 400)
                  {
                        pParam->swOffset[0] = 770;
                        pParam->swOffset[1] = 1010;
                        pParam->swOffset[2] = 600;
                        pParam->swGain[0] = 970;
                        pParam->swGain[1] = 993;
                        pParam->swGain[2] = 1023;
                  }
                  else
                  {
                        pParam->swOffset[0] = 380;
                        pParam->swOffset[1] = 920;
                        pParam->swOffset[2] = 450;
                        pParam->swGain[0] = 957;
                        pParam->swGain[1] = 980;
                        pParam->swGain[2] = 1008;
                  }
            }
            else
            {
                  if(pParam->PhyDpi.x <= 75)
                  {
                        pParam->swOffset[1] = 1250;
                        pParam->swGain[1] = 950;
                  }
                  else if(pParam->PhyDpi.x <= 300)
                  {
                        pParam->swOffset[1] = 1250;
                        pParam->swGain[1] = 950;
                  }
                  else if(pParam->PhyDpi.x <= 400)
                  {
                        pParam->swOffset[1] = 1250;
                        pParam->swGain[1] = 950;
                  }
                  else
                  {
                        pParam->swOffset[1] = 1250;
                        pParam->swGain[1] = 950;
                  }
            }
            break;

      case kNEC3778:
            DBG( _DBG_INFO2, "kNEC3778 adjustments\n" );
            if((_LM9831 == hw->chip) && (pParam->PhyDpi.x <= 300))
            {
                  pParam->swOffset[0] = 0;
                  pParam->swOffset[1] = 0;
                  pParam->swOffset[2] = 0;
                  pParam->swGain[0] = 900;
                  pParam->swGain[1] = 920;
                  pParam->swGain[2] = 980;
            }
            else if( hw->motorModel == MODEL_HuaLien && pParam->PhyDpi.x > 800)
            {
                  pParam->swOffset[0] = 0;
                  pParam->swOffset[1] = 0;
                  pParam->swOffset[2] = -200;
                  pParam->swGain[0] = 980;
                  pParam->swGain[1] = 930;
                  pParam->swGain[2] = 1080;
            }
            else
            {
                  pParam->swOffset[0] = -304;
                  pParam->swOffset[1] = -304;
                  pParam->swOffset[2] = -304;
                  pParam->swGain[0] = 910;      
                  pParam->swGain[1] = 920;      
                  pParam->swGain[2] = 975;
            }
            
            if(pParam->bDataType == SCANDATATYPE_BW && pParam->PhyDpi.x <= 300)
            {
                  pParam->swOffset[1] = 1000;
                  pParam->swGain[1] = 1000;
            }
            break;
      }
}

/** according to the pixel values,
 */
00589 static u_char usb_GetNewGain( u_short wMax, int channel )
{
      double dRatio, dAmp;
      u_long dwInc, dwDec;
      u_char bGain;

      if( !wMax )
            wMax = 1;

      dAmp = 0.93 + 0.067 * a_bRegs[0x3b+channel];

      if((m_dwIdealGain / (wMax / dAmp)) < 3) {

            dRatio = ((double) m_dwIdealGain * dAmp / wMax - 0.93) / 0.067;
            if(ceil(dRatio) > 0x3f)
                  return 0x3f;

            dwInc = (u_long)((0.93 + ceil (dRatio) * 0.067) * wMax / dAmp);
            dwDec = (u_long)((0.93 + floor (dRatio) * 0.067) * wMax / dAmp);
            if((dwInc >= 0xff00) ||
               (labs (dwInc - m_dwIdealGain) > labs(dwDec - m_dwIdealGain))) {
                  bGain = (u_char)floor(dRatio);
            } else {
                  bGain = (u_char)ceil(dRatio);
            }

      } else {

            dRatio = m_dwIdealGain / (wMax / dAmp);
            dAmp   = floor((dRatio / 3 - 0.93)/0.067);

            if( dAmp > 31 )
                  dAmp = 31;

            bGain = (u_char)dAmp + 32;
      }
      
      if( bGain > 0x3f ) {
            DBG( _DBG_INFO, "* GAIN Overflow!!!\n" );
            bGain = 0x3f;
      }
      return bGain;
}

/** limit and set register given by address
 * @param gain -
 * @param reg  -
 */
00637 static void setAdjGain( int gain, u_char *reg )
{
      if( gain >= 0 ) {

            if( gain > 0x3f )
                  *reg = 0x3f;
            else
                  *reg = gain;
      }
}

/**
 * @param channel - 0 = red, 1 = green, 2 = blue
 * @param max     -
 * @param ideal   -
 * @param l_on    -
 * @param l_off   -
 * @return
 */
00656 static SANE_Bool adjLampSetting( int channel, u_long  max,  u_long   ideal,
                                                 u_short l_on, u_short *l_off )
{
      SANE_Bool adj = SANE_FALSE;
      u_long    lamp_on;

      /* so if the image was too bright, we dim the lamps by 3% */
      if( max > ideal ) {

            lamp_on = (*l_off) - l_on;
          lamp_on = (lamp_on * 97)/100;
            *l_off  = l_on + lamp_on;
        DBG( _DBG_INFO2,
                      "lamp(%u) adjust (-3%%): %i %i\n", channel, l_on, *l_off );
            adj = SANE_TRUE;            
      }

    /* if the image was too dull, increase lamp by 1% */
      if( a_bRegs[0x3b + channel] == 63 ) {

            lamp_on  = (*l_off) - l_on;
          lamp_on += (lamp_on/100);
            *l_off   = l_on + lamp_on;
        DBG( _DBG_INFO2,
                      "lamp(%u) adjust (+1%%): %i %i\n", channel, l_on, *l_off );
            adj = SANE_TRUE;
    }

    return adj;
}    

/** usb_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
 *
 */
00698 static SANE_Bool usb_AdjustGain( Plustek_Device *dev, int fNegative )
{
      char      tmp[40];
      int       i;
      pScanDef  scanning = &dev->scanning;
      pDCapsDef scaps    = &dev->usbDev.Caps;
      pHWDef    hw       = &dev->usbDev.HwSetting;
      u_long    dw, start, end, len;
      SANE_Bool fRepeatITA = SANE_TRUE;

      if( usb_IsEscPressed())
            return SANE_FALSE;

      bMaxITA = 0xff;

      DBG( _DBG_INFO, "#########################\n" );
      DBG( _DBG_INFO, "usb_AdjustGain()\n" );
      if((dev->adj.rgain != -1) && 
         (dev->adj.ggain != -1) && (dev->adj.bgain != -1)) {
            setAdjGain( dev->adj.rgain, &a_bRegs[0x3b] );
            setAdjGain( dev->adj.ggain, &a_bRegs[0x3c] );
            setAdjGain( dev->adj.bgain, &a_bRegs[0x3d] );
            DBG( _DBG_INFO, "- function skipped, using frontend values!\n" );
            return SANE_TRUE;
      }

      /* define the strip to scan for coarse calibration */
      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 * m_ScanParam.bChannels;

      if( hw->bReg_0x26 & _ONE_CH_COLOR &&
            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;

      start = 0;
      len   = m_ScanParam.Size.dwPixels;

      if( scanning->sParam.bSource == SOURCE_Transparency ) {
            start = scaps->Positive.DataOrigin.x * scaps->OpticDpi.x / 300UL;
            len   = scaps->Positive.Size.x * scaps->OpticDpi.x / 300UL;
      }
      else if( scanning->sParam.bSource == SOURCE_Negative ) {
            start = scaps->Negative.DataOrigin.x * scaps->OpticDpi.x / 300UL;
            len   = scaps->Negative.Size.x * scaps->OpticDpi.x / 300UL;
      }
      end = start + len;

      start = ((u_long)dev->usbDev.pSource->DataOrigin.x*scaps->OpticDpi.x/300UL);
      len   = ((u_long)dev->usbDev.pSource->Size.x * scaps->OpticDpi.x / 300UL);

      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 );
      DBG( _DBG_INFO2, "Start    = %lu\n", start );
      DBG( _DBG_INFO2, "Len      = %lu\n", len   );
      DBG( _DBG_INFO2, "End      = %lu\n", end   );

      i = 0;
TOGAIN:
      m_ScanParam.dMCLK = dMCLK;

      if( !usb_SetScanParameters( dev, &m_ScanParam ))
            return SANE_FALSE;

      if( !usb_ScanBegin( dev, SANE_FALSE) ||
            !usb_ScanReadImage( dev, pScanBuffer, m_ScanParam.Size.dwPhyBytes ) ||
            !usb_ScanEnd( dev )) {
            DBG( _DBG_ERROR, "usb_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 );

      if( end > m_ScanParam.Size.dwPhyPixels )
            end = m_ScanParam.Size.dwPhyPixels;

      sprintf( tmp, "coarse-gain-%u.raw", i++ );

      dumpPicInit( &m_ScanParam, tmp );
      dumpPic( tmp, pScanBuffer, m_ScanParam.Size.dwPhyBytes );
            
#ifdef SWAP_COARSE
      if(usb_HostSwap())
#endif
            usb_Swap((u_short *)pScanBuffer, m_ScanParam.Size.dwPhyBytes );

      if( fNegative ) {

            if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {

                  RGBULongDef rgb, rgbSum;
                  u_long      dwLoop = len / 20 * 20;
                  u_long      dw10, dwGray, dwGrayMax;

                  rgb.Red = rgb.Green = rgb.Blue = dwGrayMax = 0;

                  for( dw = start; dwLoop; dwLoop-- ) {

                        rgbSum.Red = rgbSum.Green = rgbSum.Blue = 0;
                        for( dw10 = 20; dw10--; dw++ ) {
                              rgbSum.Red   += (u_long)(((pRGBULongDef)pScanBuffer)[dw].Red);
                              rgbSum.Green += (u_long)(((pRGBULongDef)pScanBuffer)[dw].Green);
                              rgbSum.Blue  += (u_long)(((pRGBULongDef)pScanBuffer)[dw].Blue);
                        }

                        /* do some weighting of the color planes for negatives */
                        dwGray = (rgbSum.Red * 30UL + rgbSum.Green * 59UL + rgbSum.Blue * 11UL) / 100UL;

                        if( fNegative == 1 || rgbSum.Red > rgbSum.Green) {
                              if( dwGray > dwGrayMax ) {
                                    dwGrayMax = dwGray;
                                    rgb.Red   = rgbSum.Red;
                                    rgb.Green = rgbSum.Green;
                                    rgb.Blue  = rgbSum.Blue;
                              }
                        }
                  }

                  Gain_Hilight.Red   = (u_short)(rgb.Red / 20UL);
                  Gain_Hilight.Green = (u_short)(rgb.Green / 20UL);
                  Gain_Hilight.Blue  = (u_short)(rgb.Blue / 20UL);
                  DBG(_DBG_INFO2, "MAX(R,G,B)= 0x%04x(%u), 0x%04x(%u), 0x%04x(%u)\n",
                      Gain_Hilight.Red, Gain_Hilight.Red, Gain_Hilight.Green, 
                      Gain_Hilight.Green, Gain_Hilight.Blue, Gain_Hilight.Blue );

                  a_bRegs[0x3b] = usb_GetNewGain(Gain_Hilight.Red,   0 );
                  a_bRegs[0x3c] = usb_GetNewGain(Gain_Hilight.Green, 1 );
                  a_bRegs[0x3d] = usb_GetNewGain(Gain_Hilight.Blue,  2 );

            } else {

                  u_long dwMax  = 0, dwSum;
                  u_long dwLoop = len / 20 * 20;
                  u_long dw10;

                  for( dw = start; dwLoop; dwLoop-- ) {

                        dwSum = 0;
                        for( dw10 = 20; dw10--; dw++ )
                              dwSum += (u_long)((u_short*)pScanBuffer)[dw];

                        if((fNegative == 1) || (dwSum < 0x6000 * 20)) {
                              if( dwMax < dwSum )
                                    dwMax = dwSum;
                        }
                  }
                  Gain_Hilight.Red  = Gain_Hilight.Green =
                  Gain_Hilight.Blue = (u_short)(dwMax / 20UL);

                  Gain_Reg.Red  = Gain_Reg.Green =
                  Gain_Reg.Blue = a_bRegs[0x3b] =
                  a_bRegs[0x3c] = a_bRegs[0x3d] = usb_GetNewGain(Gain_Hilight.Green,1);
            }
      } else {
      
            if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {

                  RGBUShortDef max_rgb, min_rgb, tmp_rgb;
                  u_long       dwR, dwG, dwB;
                  u_long       dwDiv   = 10;
                  u_long       dwLoop1 = len / dwDiv, dwLoop2;

                  max_rgb.Red = max_rgb.Green = max_rgb.Blue = 0;
                  min_rgb.Red = min_rgb.Green = min_rgb.Blue = 0xffff;

                  /* find out the max pixel value for R, G, B */
                  for( dw = start; dwLoop1; dwLoop1-- ) {

                        /* do some averaging... */
                        for (dwLoop2 = dwDiv, dwR = dwG = dwB = 0;
                                                                              dwLoop2; dwLoop2--, dw++) {
                              if( hw->bReg_0x26 & _ONE_CH_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 {
                                    dwR += ((pRGBUShortDef)pScanBuffer)[dw].Red;
                                    dwG += ((pRGBUShortDef)pScanBuffer)[dw].Green;
                                    dwB += ((pRGBUShortDef)pScanBuffer)[dw].Blue;
                              }
                        }
                        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;

                        if(min_rgb.Red > dwR)
                              min_rgb.Red = dwR;
                        if(min_rgb.Green > dwG)
                              min_rgb.Green = dwG;
                        if(min_rgb.Blue > dwB)
                              min_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 );
                  DBG(_DBG_INFO2, "MIN(R,G,B)= 0x%04x(%u), 0x%04x(%u), 0x%04x(%u)\n",
                              min_rgb.Red, min_rgb.Red, min_rgb.Green,
                              min_rgb.Green, min_rgb.Blue, min_rgb.Blue );

                  /* on CIS scanner, we use the min value, on CCD the max value
                   * for adjusting the gain
                   */
                  tmp_rgb = max_rgb;
                  if( hw->bReg_0x26 & _ONE_CH_COLOR ) 
                        tmp_rgb = min_rgb;

                  DBG(_DBG_INFO2, "CUR(R,G,B)= 0x%04x(%u), 0x%04x(%u), 0x%04x(%u)\n",
                              tmp_rgb.Red, tmp_rgb.Red, tmp_rgb.Green,
                            tmp_rgb.Green, tmp_rgb.Blue, tmp_rgb.Blue);

/*                m_dwIdealGain = IDEAL_GainNormal;
*/                                   /* min(min(rgb.wRed, rgb.wGreen), rgb.wBlue) */

                  a_bRegs[0x3b] = usb_GetNewGain( tmp_rgb.Red,   0 );
                  a_bRegs[0x3c] = usb_GetNewGain( tmp_rgb.Green, 1 );
                  a_bRegs[0x3d] = usb_GetNewGain( tmp_rgb.Blue,  2 );

                  if( !_IS_PLUSTEKMOTOR(hw->motorModel)) {

                        SANE_Bool adj = SANE_FALSE;

                        /* on CIS devices, we can control the lamp off settings */
                        if( hw->bReg_0x26 & _ONE_CH_COLOR ) {

/*                            m_dwIdealGain = IDEAL_GainNormal;
 */
                              if( adjLampSetting( CHANNEL_red, tmp_rgb.Red, m_dwIdealGain,
                                                  hw->red_lamp_on, &hw->red_lamp_off )) {
                                    adj = SANE_TRUE;
                              }

                              if( adjLampSetting( CHANNEL_green, tmp_rgb.Green, m_dwIdealGain,
                                                    hw->green_lamp_on, &hw->green_lamp_off )) {
                                    adj = SANE_TRUE;
                              }

                              if( adjLampSetting( CHANNEL_blue, tmp_rgb.Blue, m_dwIdealGain,
                                                          hw->blue_lamp_on, &hw->blue_lamp_off)){
                                    adj = SANE_TRUE;
                              }

                              /* on any adjustment, set the registers... */
                              if( adj ) {
                                    usb_AdjustLamps( dev );

                                    if( i < _MAX_GAIN_LOOPS )
                                          goto TOGAIN;
                              }
                   
                        } else {

                              if((!a_bRegs[0x3b] ||
                                  !a_bRegs[0x3c] || !a_bRegs[0x3d]) && dMCLK > 3.0) {

                                    scanning->sParam.dMCLK = dMCLK = dMCLK - 0.5;
                                    a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;
                                    
                                    adj = SANE_TRUE;

                              } else if(((a_bRegs[0x3b] == 63) || (a_bRegs[0x3c] == 63) ||
                                                        (a_bRegs[0x3d] == 63)) && (dMCLK < 10)) {

                                    scanning->sParam.dMCLK = dMCLK = dMCLK + 0.5;
                                    a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;

                                    adj = SANE_TRUE;
                              }

                              if( adj ) {
                                    if( i < _MAX_GAIN_LOOPS )
                                          goto TOGAIN;
                              }
                        }
                        
                  } else {

                        /* for MODEL KaoHsiung 1200 scanner multi-straight-line bug at
                         * 1200 dpi color mode
                         */
                        if( hw->motorModel == MODEL_KaoHsiung &&
                            scaps->bCCD == kNEC3778 && dMCLK>= 5.5 && !a_bRegs[0x3c]){

                              a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;
                              scanning->sParam.dMCLK = dMCLK = dMCLK - 1.5;
                              goto TOGAIN;

                        } else if( hw->motorModel == MODEL_HuaLien &&
                                     scaps->bCCD == kNEC3799 && fRepeatITA ) {

                              if((!a_bRegs[0x3b] ||
                                  !a_bRegs[0x3c] || !a_bRegs[0x3d]) && dMCLK > 3.0) {

                                    scanning->sParam.dMCLK = dMCLK = dMCLK - 0.5;
                                    a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;
                                    goto TOGAIN;

                              } else if(((a_bRegs[0x3b] == 63) || (a_bRegs[0x3c] == 63) ||
                                                              (a_bRegs[0x3d] == 63)) && (dMCLK < 10)) {

                                    scanning->sParam.dMCLK = dMCLK = dMCLK + 0.5;
                                    a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;
                                    goto TOGAIN;
                              }
                              bMaxITA = (u_char)floor((dMCLK + 1) / 2);
                              fRepeatITA = SANE_FALSE;
                        }
                  }

            } else {

                  u_short     w_max = 0, w_min = 0xffff, w_tmp;

                  for( dw = start; dw < end; dw++ ) {
                        if( w_max < ((u_short*)pScanBuffer)[dw])
                              w_max = ((u_short*)pScanBuffer)[dw];
                        if( w_min > ((u_short*)pScanBuffer)[dw])
                              w_min = ((u_short*)pScanBuffer)[dw];
                  }

                  w_tmp = w_max;
                  if( hw->bReg_0x26 & _ONE_CH_COLOR )
                        w_tmp = w_min;

                  a_bRegs[0x3b] =
                  a_bRegs[0x3c] =
                  a_bRegs[0x3d] = usb_GetNewGain(w_tmp, 0);

                  DBG(_DBG_INFO2, "MAX(G)= 0x%04x(%u)\n", w_max, w_max );
                  DBG(_DBG_INFO2, "MIN(G)= 0x%04x(%u)\n", w_min, w_min );
                  DBG(_DBG_INFO2, "CUR(G)= 0x%04x(%u)\n", w_tmp, w_tmp );

/*                m_dwIdealGain = IDEAL_GainNormal;
 */
                  if( !_IS_PLUSTEKMOTOR(hw->motorModel)) {

                        SANE_Bool adj = SANE_FALSE;

                        /* on CIS devices, we can control the lamp off settings */
                        if( hw->bReg_0x26 & _ONE_CH_COLOR ) {

                              if( adjLampSetting( CHANNEL_green, w_tmp, m_dwIdealGain,
                                                  hw->green_lamp_on, &hw->green_lamp_off )) {
                                    adj = SANE_TRUE;
                              }

                              /* on any adjustment, set the registers... */
                              if( adj ) {
                                    usb_AdjustLamps( dev );

                                    if( i < _MAX_GAIN_LOOPS )
                                          goto TOGAIN;
                              }

                        } else {

                              if( !a_bRegs[0x3b] && (dMCLK > 6.0)) {

                                    scanning->sParam.dMCLK = dMCLK = dMCLK - 0.5;
                                    a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;

                                    adj = SANE_TRUE;

                              } else if((a_bRegs[0x3b] == 63) && (dMCLK < 20)) {

                                    scanning->sParam.dMCLK = dMCLK = dMCLK + 0.5;
                                    a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;

                                    adj = SANE_TRUE;
                              }

                              if( adj ) {
                                    if( i < _MAX_GAIN_LOOPS )
                                          goto TOGAIN;
                              }
                        }
                  }
            }
      }

      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, "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_INFO, "usb_AdjustGain() done.\n" );

/* return SANE_FALSE;*/
      return SANE_TRUE;
}

/** usb_GetNewOffset
 * @param pdwSum   -
 * @param pdwDiff  -
 * @param pcOffset -
 * @param pIdeal   -
 * @param channel  -
 * @param cAdjust  -
 */
01121 static void usb_GetNewOffset( u_long *pdwSum, u_long *pdwDiff,
                                                signed char *pcOffset, u_char *pIdeal,
                                                            u_long channel, signed char cAdjust )
{
      /* IDEAL_Offset is currently set to 0x1000 = 4096 */
      u_long dwIdealOffset = IDEAL_Offset;

      if( pdwSum[channel] > dwIdealOffset ) {

            /* Over ideal value */
            pdwSum[channel] -= dwIdealOffset;
            if( pdwSum[channel] < pdwDiff[channel] ) {
                  /* New offset is better than old one */
                  pdwDiff[channel] = pdwSum[channel];
                  pIdeal[channel]  = a_bRegs[0x38 + channel];
            }
            pcOffset[channel] -= cAdjust;

      } else      {

            /* Below than ideal value */
            pdwSum[channel] = dwIdealOffset - pdwSum [channel];
            if( pdwSum[channel] < pdwDiff[channel] ) {
                  /* New offset is better than old one */
                  pdwDiff[channel] = pdwSum[channel];
                  pIdeal[channel]  = a_bRegs[0x38 + channel];
            }
            pcOffset[channel] += cAdjust;
      }

      if( pcOffset[channel] >= 0 )
            a_bRegs[0x38 + channel] = pcOffset[channel];
      else
            a_bRegs[0x38 + channel] = (u_char)(32 - pcOffset[channel]);
}

/** usb_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
 */
01166 static SANE_Bool usb_AdjustOffset( Plustek_Device *dev )
{
      char          tmp[40];
      signed char   cAdjust = 16;
      signed char   cOffset[3];
      u_char        bExpect[3];
      int           i;
      u_long        dw, dwPixels;
    u_long        dwDiff[3], dwSum[3];

      pHWDef hw = &dev->usbDev.HwSetting;

      if( usb_IsEscPressed())
            return SANE_FALSE;

      DBG( _DBG_INFO, "#########################\n" );
      DBG( _DBG_INFO, "usb_AdjustOffset()\n" );
      if((dev->adj.rofs != -1) && 
         (dev->adj.gofs != -1) && (dev->adj.bofs != -1)) {
            a_bRegs[0x38] = (dev->adj.rofs & 0x3f);
            a_bRegs[0x39] = (dev->adj.gofs & 0x3f);
            a_bRegs[0x3a] = (dev->adj.bofs & 0x3f);
            DBG( _DBG_INFO, "- function skipped, using frontend values!\n" );
            return SANE_TRUE;
      }

      m_ScanParam.Size.dwLines  = 1;
      m_ScanParam.Size.dwPixels = 2550;

      if( hw->bReg_0x26 & _ONE_CH_COLOR ) 
            dwPixels = m_ScanParam.Size.dwPixels;
      else
            dwPixels = (u_long)(hw->bOpticBlackEnd - hw->bOpticBlackStart );

      m_ScanParam.Size.dwPixels = 2550;
      m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels * 2 *
                                                           m_ScanParam.bChannels;
      if( hw->bReg_0x26 & _ONE_CH_COLOR &&
            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;

      dwDiff[0]  = dwDiff[1]  = dwDiff[2]  = 0xffff;
      cOffset[0] = cOffset[1] = cOffset[2] = 0;
      bExpect[0] = bExpect[1] = bExpect[2] = 0;

      a_bRegs[0x38] = a_bRegs [0x39] = a_bRegs [0x3a] = 0;

      if( hw->bReg_0x26 & _ONE_CH_COLOR ) {
          /*
             * if we have dark shading strip, there's no need to switch
           * the lamp off
             */
            if( dev->usbDev.pSource->DarkShadOrgY >= 0 ) {

                  usb_ModuleToHome( dev, SANE_TRUE );
                  usb_ModuleMove  ( dev, MOVE_Forward,
                                                (u_long)dev->usbDev.pSource->DarkShadOrgY );

                  a_bRegs[0x45] &= ~0x10;

            } else {

                  /* switch lamp off to read dark data... */
                  a_bRegs[0x29] = 0;
                  usb_switchLamp( dev, SANE_FALSE );
            }
      }

      if( 0 == dwPixels ) {
            DBG( _DBG_ERROR, "OpticBlackEnd = OpticBlackStart!!!\n" );
            return SANE_FALSE;
      }

      if( !usb_SetScanParameters( dev, &m_ScanParam )) {
            DBG( _DBG_ERROR, "usb_AdjustOffset() failed\n" );
            return SANE_FALSE;
      }
            
      i = 0;

      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 );

      while( cAdjust ) {

            /*
             * read data (a white calibration strip - hopefully ;-)
             */
            if((!usb_ScanBegin(dev, SANE_FALSE)) ||
               (!usb_ScanReadImage(dev,pScanBuffer,m_ScanParam.Size.dwPhyBytes)) ||
                  !usb_ScanEnd( dev )) {
                  DBG( _DBG_ERROR, "usb_AdjustOffset() failed\n" );
                  return SANE_FALSE;
            }

            sprintf( tmp, "coarse-off-%u.raw", i++ );
            
#ifdef SWAP_COARSE
            if(usb_HostSwap())
                  usb_Swap((u_short *)pScanBuffer, m_ScanParam.Size.dwPhyBytes );
#endif
            dumpPicInit( &m_ScanParam, tmp );
            dumpPic( tmp, pScanBuffer, m_ScanParam.Size.dwPhyBytes );

            if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {

                  dwSum[0] = dwSum[1] = dwSum[2] = 0;

                  for (dw = 0; dw < dwPixels; dw++) {
#ifndef SWAP_COARSE
                        dwSum[0] += (u_long)_HILO2WORD(((pColorWordDef)pScanBuffer)[dw].HiLo[0]);
                        dwSum[1] += (u_long)_HILO2WORD(((pColorWordDef)pScanBuffer)[dw].HiLo[1]);
                        dwSum[2] += (u_long)_HILO2WORD(((pColorWordDef)pScanBuffer)[dw].HiLo[2]);
#else
                        dwSum[0] += ((pRGBUShortDef)pScanBuffer)[dw].Red;
                        dwSum[1] += ((pRGBUShortDef)pScanBuffer)[dw].Green;
                        dwSum[2] += ((pRGBUShortDef)pScanBuffer)[dw].Blue;
#endif
                  }

            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;

                  usb_GetNewOffset( dwSum, dwDiff, cOffset, bExpect, 0, cAdjust );
                  usb_GetNewOffset( dwSum, dwDiff, cOffset, bExpect, 1, cAdjust );
                  usb_GetNewOffset( dwSum, dwDiff, cOffset, bExpect, 2, cAdjust );

            DBG( _DBG_INFO2, "RedExpect   = %u\n", bExpect[0] );
            DBG( _DBG_INFO2, "GreenExpect = %u\n", bExpect[1] );
            DBG( _DBG_INFO2, "BlueExpect  = %u\n", bExpect[2] );

            } else {
                  dwSum[0] = 0;

                  for( dw = 0; dw < dwPixels; dw++ ) {
#ifndef SWAP_COARSE
                        dwSum[0] += (u_long)_HILO2WORD(((pHiLoDef)pScanBuffer)[dw]);
#else
                        dwSum[0] += ((u_short*)pScanBuffer)[dw];
#endif
                  }
                  dwSum [0] /= dwPixels;
                  usb_GetNewOffset( dwSum, dwDiff, cOffset, bExpect, 0, cAdjust );
                  a_bRegs[0x3a] = a_bRegs[0x39] = a_bRegs[0x38];

                  DBG(_DBG_INFO2,"Sum = %lu, ave = %lu\n",dwSum[0],dwSum[0]/dwPixels);
                  DBG(_DBG_INFO2,"Expect = %u\n", bExpect[0]);
            }

            _UIO(sanei_lm983x_write(dev->fd, 0x38, &a_bRegs[0x38], 3, SANE_TRUE));
            cAdjust >>= 1;
      }

      if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {
            a_bRegs[0x38] = bExpect[0];
            a_bRegs[0x39] = bExpect[1];
            a_bRegs[0x3a] = bExpect[2];
      } else {

            a_bRegs[0x38] = a_bRegs[0x39] = a_bRegs[0x3a] = bExpect[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] );
      DBG( _DBG_INFO, "usb_AdjustOffset() done.\n" );

      /* switch it on again on CIS based scanners */
      if( hw->bReg_0x26 & _ONE_CH_COLOR ) {

            if( dev->usbDev.pSource->DarkShadOrgY < 0 ) {
                  a_bRegs[0x29] = hw->bReg_0x29;
                  usb_switchLamp( dev, SANE_TRUE );
                  usbio_WriteReg( dev->fd, 0x29, a_bRegs[0x29]);
            }
      }

      return SANE_TRUE;
}

/** this function tries to find out some suitable values for the dark
 *  fine calibration. If the device owns a black calibration strip
 *  (never saw one yet - _WAF_BLACKFINE is set then), the data is simply
 *  copied. If not, then the white strip is read with the lamp switched
 *  off...
 */
01369 static void usb_GetDarkShading( Plustek_Device *dev, u_short *pwDest,
                                pHiLoDef pSrce, u_long dwPixels,
                                u_long dwAdd, int iOffset )
{
      u_long    dw;
      u_long    dwSum[2];
      pDCapsDef scaps = &dev->usbDev.Caps;
      pHWDef    hw    = &dev->usbDev.HwSetting;

      if( scaps->workaroundFlag & _WAF_BLACKFINE )
      {
            u_short w;
            int     wtmp;

            /* here we use the source  buffer + a static offset */
            for (dw = 0; dw < dwPixels; dw++, pSrce += dwAdd)
            {
#ifndef SWAP_FINE
                  wtmp = ((int)_PHILO2WORD(pSrce) + iOffset);
#else
                  wtmp = (*((int*)pSrce) + iOffset);
#endif
            if( wtmp < 0 )
                  wtmp = 0;

                  if( wtmp > 0xffff )
                        wtmp = 0xffff;
                                     
            w = (u_short)wtmp;
                  
#ifndef SWAP_FINE
                  pwDest[dw] = _LOBYTE(w) * 256 + _HIBYTE(w);
#else
                  pwDest[dw] = w;
#endif
            }
      }
      else
      {
            dwSum[0] = dwSum[1] = 0;
            if( hw->bSensorConfiguration & 0x04 ) {

                  /* Even/Odd CCD */
                  for( dw = 0; dw < dwPixels; dw++, pSrce += dwAdd ) {
#ifndef SWAP_FINE
                        dwSum[dw & 1] += (u_long)_PHILO2WORD(pSrce);
#else
                        dwSum[dw & 1] += (u_long)(*(u_short*)pSrce);
#endif
                  }
                  dwSum[0] /= ((dwPixels + 1UL) >> 1);
                  dwSum[1] /= (dwPixels >> 1);

                  if( /*Registry.GetEvenOdd() == 1 ||*/ scaps->bPCB == 2)
                  {
                        dwSum[0] = dwSum[1] = (dwSum[0] + dwSum[1]) / 2;
                  }

                  dwSum[0] = (int)dwSum[0] + iOffset;
                  dwSum[1] = (int)dwSum[1] + iOffset;

                  if((int)dwSum[0] < 0)
                        dwSum[0] = 0;

                  if((int)dwSum[1] < 0)
                        dwSum[1] = 0;
#ifndef SWAP_FINE
                  dwSum[0] = (u_long)_LOBYTE(_LOWORD(dwSum[0])) * 256UL +
                                                                              _HIBYTE(_LOWORD(dwSum[0]));
                  dwSum[1] = (u_long)_LOBYTE(_LOWORD(dwSum[1])) * 256UL +
                                                                              _HIBYTE(_LOWORD(dwSum[1]));
#else
                  dwSum[0] = (u_long)_LOWORD(dwSum[0]);
                  dwSum[1] = (u_long)_LOWORD(dwSum[1]);
#endif

                  for( dw = 0; dw < dwPixels; dw++ )
                        pwDest[dw] = (u_short)dwSum[dw & 1];
            } else {
                  
                  /* Standard CCD */

                  /* do some averaging on the line */
                  for( dw = 0; dw < dwPixels; dw++, pSrce += dwAdd ) {
#ifndef SWAP_FINE
                        dwSum[0] += (u_long)_PHILO2WORD(pSrce);
#else
                        dwSum[0] += (u_long)(*(u_short*)pSrce);
#endif
                  }

                  dwSum[0] /= dwPixels;

                  /* add our offset... */
                  dwSum[0] = (int)dwSum[0] + iOffset;
                  if((int)dwSum[0] < 0)
                        dwSum[0] = 0;
#ifndef SWAP_FINE
                  dwSum[0] = (u_long)_LOBYTE(_LOWORD(dwSum[0])) * 256UL +
                                                                              _HIBYTE(_LOWORD(dwSum[0]));
#else
                  dwSum[0] = (u_long)_LOWORD(dwSum[0]);
#endif

                  /* fill the shading data */
                  for( dw = 0; dw < dwPixels; dw++ )
                        pwDest[dw] = (u_short)dwSum[0];
            }
      }
#ifdef SWAP_FINE
      if(usb_HostSwap())
            usb_Swap( pwDest, dwPixels *2 );
#endif
}

/** usb_AdjustDarkShading
 * fine calibration part 1 - read the black calibration area and write
 * the black line data to the offset coefficient data in Merlins' DRAM
 * If there's no black line available, we can use the min pixel value
 * from coarse calibration...
 */
01490 static SANE_Bool usb_AdjustDarkShading( Plustek_Device *dev )
{
      char      tmp[40];
    pScanDef  scanning = &dev->scanning;
      pDCapsDef scaps    = &dev->usbDev.Caps;
      pHWDef    hw       = &dev->usbDev.HwSetting;

      if( usb_IsEscPressed())
            return SANE_FALSE;

      if( scaps->workaroundFlag & _WAF_SKIP_FINE )
            return SANE_TRUE;

      DBG( _DBG_INFO, "#########################\n" );
      DBG( _DBG_INFO, "usb_AdjustDarkShading()\n" );
      DBG( _DBG_INFO2, "* MCLK = %f (scanparam-MCLK=%f)\n",
                                    dMCLK, scanning->sParam.dMCLK );

      m_ScanParam              = scanning->sParam;
      m_ScanParam.Origin.y     = 0;
      m_ScanParam.UserDpi.y    = scaps->OpticDpi.y;
      m_ScanParam.Size.dwLines = 1;                   /* for gain */
      m_ScanParam.bBitDepth    = 16;
      m_ScanParam.bCalibration = PARAM_DarkShading;
      m_ScanParam.dMCLK        = dMCLK;   

      if( _LM9831 == hw->chip ) {

            m_ScanParam.UserDpi.x = usb_SetAsicDpiX( dev, m_ScanParam.UserDpi.x);
            if( m_ScanParam.UserDpi.x < 100)
                  m_ScanParam.UserDpi.x = 150;
                  
            /* Now DPI X is physical */
            m_ScanParam.Origin.x      = m_ScanParam.Origin.x %
                                        (u_short)m_dHDPIDivider;
            m_ScanParam.Size.dwPixels = (u_long)scaps->Normal.Size.x *
                                                m_ScanParam.UserDpi.x / 300UL;
            m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels *
                                        2UL * m_ScanParam.bChannels;
            m_dwPixels = scanning->sParam.Size.dwPixels *
                         m_ScanParam.UserDpi.x / scanning->sParam.UserDpi.x;
      } else {
            m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels *
                                                               2 * m_ScanParam.bChannels;
      }

      if( hw->bReg_0x26 & _ONE_CH_COLOR &&
            m_ScanParam.bDataType == SCANDATATYPE_Color ) {
            m_ScanParam.Size.dwBytes *= 3;
    }

    /*
       * if we have dark shading strip, there's no need to switch
     * the lamp off
       */
      if( dev->usbDev.pSource->DarkShadOrgY >= 0 ) {

            usb_ModuleToHome( dev, SANE_TRUE );
            usb_ModuleMove  ( dev, MOVE_Forward,
                                                (u_long)dev->usbDev.pSource->DarkShadOrgY );
      } else {

            /* switch lamp off to read dark data... */
            a_bRegs[0x29] = 0;
            usb_switchLamp( dev, SANE_FALSE );
      }

      usb_SetScanParameters( dev, &m_ScanParam );

      if((!usb_ScanBegin(dev, SANE_FALSE)) ||
         (!usb_ScanReadImage(dev,pScanBuffer, m_ScanParam.Size.dwPhyBytes)) ||
         (!usb_ScanEnd( dev ))) {

            /* on error, reset the lamp settings*/
            
            a_bRegs[0x29] = hw->bReg_0x29;
            usb_switchLamp( dev, SANE_TRUE );
            usbio_WriteReg( dev->fd, 0x29, a_bRegs[0x29] );
            
            DBG( _DBG_ERROR, "usb_AdjustDarkShading() failed\n" );
            return SANE_FALSE;
      }
      
      /*
       * set illumination mode and switch lamp on again
       */
      a_bRegs[0x29] = hw->bReg_0x29;
      usb_switchLamp( dev, SANE_TRUE );

      if( !usbio_WriteReg( dev->fd, 0x29, a_bRegs[0x29])) {
            DBG( _DBG_ERROR, "usb_AdjustDarkShading() failed\n" );
            return SANE_FALSE;
      }     

#ifdef SWAP_FINE
      if(usb_HostSwap())
            usb_Swap((u_short *)pScanBuffer, m_ScanParam.Size.dwPhyBytes );
#endif

      sprintf( tmp, "fine-black.raw" );

      dumpPicInit( &m_ScanParam, tmp );
      dumpPic( tmp, pScanBuffer, m_ScanParam.Size.dwPhyBytes );

      usleep(500 * 1000);    /* Warm up lamp again */

      if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {

            if( hw->bReg_0x26 & _ONE_CH_COLOR ) {

                  usb_GetDarkShading( dev, a_wDarkShading, (pHiLoDef)pScanBuffer,
                                                m_ScanParam.Size.dwPhyPixels, 1,
                                                                        scanning->sParam.swOffset[0]);

                  usb_GetDarkShading( dev, a_wDarkShading + m_ScanParam.Size.dwPhyPixels,
                                          (pHiLoDef)pScanBuffer + m_ScanParam.Size.dwPhyPixels,
                              m_ScanParam.Size.dwPhyPixels, 1, scanning->sParam.swOffset[1]);

                  usb_GetDarkShading( dev, a_wDarkShading + m_ScanParam.Size.dwPhyPixels * 2,
                                 (pHiLoDef)pScanBuffer + m_ScanParam.Size.dwPhyPixels * 2,
                                          m_ScanParam.Size.dwPhyPixels, 1, scanning->sParam.swOffset[2]);

            } else {

                  usb_GetDarkShading( dev, a_wDarkShading, (pHiLoDef)pScanBuffer,
                                          m_ScanParam.Size.dwPhyPixels, 3,
                                                                        scanning->sParam.swOffset[0]);

                  usb_GetDarkShading( dev, a_wDarkShading + m_ScanParam.Size.dwPhyPixels,
                                          (pHiLoDef)pScanBuffer + 1, m_ScanParam.Size.dwPhyPixels,
                                          3, scanning->sParam.swOffset[1]);
                  usb_GetDarkShading( dev, a_wDarkShading + m_ScanParam.Size.dwPhyPixels * 2,
                                 (pHiLoDef)pScanBuffer + 2, m_ScanParam.Size.dwPhyPixels,
                                          3, scanning->sParam.swOffset[2]);
            }
      } else {

            usb_GetDarkShading( dev, a_wDarkShading, (pHiLoDef)pScanBuffer,
                            m_ScanParam.Size.dwPhyPixels, 1,
                                scanning->sParam.swOffset[1]);

            memcpy( a_wDarkShading + m_ScanParam.Size.dwPhyPixels,
                        a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2 );
            memcpy( a_wDarkShading + m_ScanParam.Size.dwPhyPixels * 2,
                        a_wDarkShading, m_ScanParam.Size.dwPhyPixels * 2 );
      }

      a_bRegs[0x45] |= 0x10;

      usb_line_statistics( "Dark", a_wDarkShading, m_ScanParam.Size.dwPhyPixels,
                          scanning->sParam.bDataType == SCANDATATYPE_Color?1:0);
      return SANE_TRUE;       
}

/** usb_AdjustWhiteShading
 * fine calibration part 2 - read the white calibration area and calculate
 * the gain coefficient for each pixel
 */
01648 static SANE_Bool usb_AdjustWhiteShading( Plustek_Device *dev )
{
      char         tmp[40];
      pScanDef     scanning = &dev->scanning;
      pDCapsDef    scaps    = &dev->usbDev.Caps;
      pHWDef       hw       = &dev->usbDev.HwSetting;
      u_char      *pBuf     = pScanBuffer;
      u_long       dw, dwLines, dwRead;
      u_long       dwShadingLines;
      pMonoWordDef pValue;
      u_long*      pdw;
      int          i;
      SANE_Bool    swap = usb_HostSwap();
      
      if( scaps->workaroundFlag & _WAF_SKIP_FINE )
            return SANE_TRUE;

      DBG( _DBG_INFO, "#########################\n" );
      DBG( _DBG_INFO, "usb_AdjustWhiteShading()\n" );
      
      m_pAvColor = (pRGBUShortDef)pScanBuffer;
      m_pAvMono  = (u_short*)pScanBuffer;

      if( usb_IsEscPressed())
            return SANE_FALSE;

      if( m_ScanParam.PhyDpi.x > 75)
            dwShadingLines = 64;
      else
            dwShadingLines = 32;

      /* NOTE: m_wHilight + m_wShadow < dwShadingLines */
      m_wHilight = 4;
      m_wShadow  = 4;

      m_ScanParam           = scanning->sParam;
      m_ScanParam.Origin.y  = 0;
      m_ScanParam.bBitDepth = 16;

/* HEINER: check ADF stuff... */
#if 0
      if( ScanInf.m_fADF ) {
            /* avoid ADF WhiteShading big noise */
            m_ScanParam.UserDpi.y = Device.Caps.OpticDpi.y - 200;
      } else {
#endif
            m_ScanParam.UserDpi.y = scaps->OpticDpi.y;
/*    } */
      m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels * 2 *
                                                                                    m_ScanParam.bChannels;

      if( hw->bReg_0x26 & _ONE_CH_COLOR &&
            m_ScanParam.bDataType == SCANDATATYPE_Color ) {
            m_ScanParam.Size.dwBytes *= 3;
      }

      m_ScanParam.bCalibration = PARAM_WhiteShading;
      m_ScanParam.dMCLK        = dMCLK;

      if( _LM9831 == hw->chip ) {

            m_ScanParam.UserDpi.x = usb_SetAsicDpiX( dev, m_ScanParam.UserDpi.x);
            if( m_ScanParam.UserDpi.x < 100 )
                  m_ScanParam.UserDpi.x = 150;

            /* Now DPI X is physical */
            m_ScanParam.Origin.x      = m_ScanParam.Origin.x % (u_short)m_dHDPIDivider;
            m_ScanParam.Size.dwPixels = (u_long)scaps->Normal.Size.x * m_ScanParam.UserDpi.x / 300UL;
            m_ScanParam.Size.dwBytes  = m_ScanParam.Size.dwPixels * 2UL * m_ScanParam.bChannels;
            if( hw->bReg_0x26 & _ONE_CH_COLOR &&
                  m_ScanParam.bDataType == SCANDATATYPE_Color ) {
                  m_ScanParam.Size.dwBytes *= 3;
            }
            m_dwPixels = scanning->sParam.Size.dwPixels * m_ScanParam.UserDpi.x / scanning->sParam.UserDpi.x;

            dw = (u_long)(hw->wDRAMSize - DRAM_UsedByAsic16BitMode) * 1024UL;
            m_ScanParam.Size.dwLines = dwShadingLines;      /* SHADING_Lines */
            for( dwLines = dw / m_ScanParam.Size.dwBytes;
                   dwLines < m_ScanParam.Size.dwLines; m_ScanParam.Size.dwLines>>=1);
      } else {
            m_ScanParam.Size.dwBytes = m_ScanParam.Size.dwPixels * 2 * m_ScanParam.bChannels;
            if( hw->bReg_0x26 & _ONE_CH_COLOR &&
                  m_ScanParam.bDataType == SCANDATATYPE_Color ) {
                  m_ScanParam.Size.dwBytes *= 3;
            }
            m_ScanParam.Size.dwLines = dwShadingLines;      /* SHADING_Lines */
      }

      /* goto the correct position again... */
      if( dev->usbDev.pSource->DarkShadOrgY >= 0 ) {

            usb_ModuleToHome( dev, SANE_TRUE );
            usb_ModuleMove  ( dev, MOVE_Forward,
                              (u_long)dev->usbDev.pSource->ShadingOriginY );
      }

      sprintf( tmp, "fine-white.raw" );
      DBG( _DBG_INFO2, "FINE WHITE Calibration Strip: %s\n", tmp );
      DBG( _DBG_INFO2, "Shad.-Lines = %lu\n", dwShadingLines  );
      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 );

      for( dw = dwShadingLines /*SHADING_Lines*/,
           dwRead = 0; dw; dw -= m_ScanParam.Size.dwLines ) {

            if( usb_SetScanParameters( dev, &m_ScanParam ) &&
                usb_ScanBegin( dev, SANE_FALSE )) {

                  DBG(_DBG_INFO2,"TotalBytes = %lu\n",m_ScanParam.Size.dwTotalBytes);

                  if( _LM9831 == hw->chip ) {
                        /* Delay for white shading hold for 9831-1200 scanner */
                        usleep(250 * 10000);
                  }

                  if( usb_ScanReadImage( dev, pBuf + dwRead,
                                                   m_ScanParam.Size.dwTotalBytes)) {

                        if( _LM9831 == hw->chip ) {
                              /* Delay for white shading hold for 9831-1200 scanner */
                              usleep(10 * 1000);
                        }

                        if( 0 == dwRead ) {
                              dumpPicInit( &m_ScanParam, tmp );
                        }
                        
                        dumpPic( tmp, pBuf + dwRead, m_ScanParam.Size.dwTotalBytes );

                        if( usb_ScanEnd( dev )) {
                              dwRead += m_ScanParam.Size.dwTotalBytes;
                              continue;
                        }
                  }
            }

            DBG( _DBG_ERROR, "usb_AdjustWhiteShading() failed\n" );
            return SANE_FALSE;
      }

      m_pSum = (u_long*)(pBuf + m_ScanParam.Size.dwPhyBytes * dwShadingLines);
      
      /*
       * do some reordering on CIS based devices:
     * from RRRRRRR.... GGGGGGGG.... BBBBBBBBB, create RGB RGB RGB ...
     * to use the following code, originally written for CCD devices...
       */
      if( hw->bReg_0x26 & _ONE_CH_COLOR ) {

      u_short *dest, *src;
            u_long   dww;

            src = (u_short*)pBuf;

            DBG( _DBG_INFO2, "PhyBytes  = %lu\n", m_ScanParam.Size.dwPhyBytes );
            DBG( _DBG_INFO2, "PhyPixels = %lu\n", m_ScanParam.Size.dwPhyPixels );
            DBG( _DBG_INFO2, "Pixels    = %lu\n", m_ScanParam.Size.dwPixels );
            DBG( _DBG_INFO2, "Bytes     = %lu\n", m_ScanParam.Size.dwBytes  );
            DBG( _DBG_INFO2, "Channels  = %u\n",  m_ScanParam.bChannels );

            for( dwLines = dwShadingLines; dwLines; dwLines-- ) {

                  dest = a_wWhiteShading;

                  for( dw=dww=0; dw < m_ScanParam.Size.dwPhyPixels; dw++, dww+=3 ) {

                        dest[dww]     = src[dw];
                        dest[dww + 1] = src[m_ScanParam.Size.dwPhyPixels + dw];
                        dest[dww + 2] = src[m_ScanParam.Size.dwPhyPixels * 2 + dw];
                  }

                  /* copy line back ... */
                  memcpy( src, dest, m_ScanParam.Size.dwPhyPixels * 3 * 2 );
                  src = &src[m_ScanParam.Size.dwPhyPixels * 3];
            }

            m_ScanParam.bChannels = 3;
      }

      if( _LM9831 == hw->chip ) {

            u_short *pwDest = (u_short*)pBuf;
            pHiLoDef pwSrce = (pHiLoDef)pBuf;

            pwSrce  += ((u_long)(scanning->sParam.Origin.x-m_ScanParam.Origin.x) /
                              (u_short)m_dHDPIDivider) *
                                    (scaps->OpticDpi.x / 300UL) * m_ScanParam.bChannels;

            for( dwLines = dwShadingLines /*SHADING_Lines*/; dwLines; dwLines--) {

#ifdef SWAP_FINE
                  if(usb_HostSwap()) {
#endif
                        for( dw = 0; dw < m_dwPixels * m_ScanParam.bChannels; dw++ ) 
                              pwDest[dw] = _HILO2WORD( pwSrce[dw] );
#ifdef SWAP_FINE
                  } else {
                        for( dw = 0; dw < m_dwPixels * m_ScanParam.bChannels; dw++ )
                              pwDest[dw] = ((u_short*)pwSrce)[dw];
                  }
#endif
                  pwDest += (u_long)m_dwPixels * m_ScanParam.bChannels;
                  pwSrce  = (pHiLoDef)((u_char*)pwSrce + m_ScanParam.Size.dwPhyBytes);
            }

            _SWAP(m_ScanParam.Size.dwPhyPixels, m_dwPixels);
      } else {
            /* Discard the status word and conv. the hi-lo order to intel format */
            u_short *pwDest = (u_short*)pBuf;
            pHiLoDef pwSrce = (pHiLoDef)pBuf;

            for( dwLines = dwShadingLines/*SHADING_Lines*/; dwLines; dwLines-- ) {

#ifdef SWAP_FINE
                  if(usb_HostSwap()) {
#endif
                        for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels *
                                                      m_ScanParam.bChannels; dw++) {
                              pwDest[dw] = _HILO2WORD( pwSrce[dw] );
                        }
#ifdef SWAP_FINE
                  } else {
                        for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels *
                                                      m_ScanParam.bChannels; dw++) {
                              pwDest[dw] = ((u_short*)pwSrce)[dw];
                        }
                  }
#endif
                  pwDest += m_ScanParam.Size.dwPhyPixels * m_ScanParam.bChannels;
                  pwSrce = (pHiLoDef)((u_char*)pwSrce + m_ScanParam.Size.dwPhyBytes);
            }
      }

      if( scanning->sParam.bDataType == SCANDATATYPE_Color ) {

            u_long*       pdwR = (u_long*)m_pSum;
            u_long*       pdwG = pdwR + m_ScanParam.Size.dwPhyPixels;
            u_long*       pdwB = pdwG + m_ScanParam.Size.dwPhyPixels;
            pRGBUShortDef pw, pwRGB;
            u_short       w, wR, wG, wB;

            memset(m_pSum, 0, m_ScanParam.Size.dwPhyPixels * 4UL * 3UL);

            /* Sort hilight */
            if( m_wHilight ) {

                  for( dwLines = m_wHilight,
                         pwRGB = m_pAvColor + m_ScanParam.Size.dwPhyPixels * dwLines;
                         dwLines < dwShadingLines/*SHADING_Lines*/;
                                       dwLines++, pwRGB += m_ScanParam.Size.dwPhyPixels ) {
                        for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++ ) {
                              pw = m_pAvColor;
                              wR = pwRGB[dw].Red;
                              wG = pwRGB[dw].Green;
                              wB = pwRGB[dw].Blue;

                              for( w = 0; w < m_wHilight;
                                     w++, pw += m_ScanParam.Size.dwPhyPixels ) {
                                    if( wR > pw[dw].Red )
                                          _SWAP( wR, pw[dw].Red );

                                    if( wG > pw[dw].Green )
                                          _SWAP( wG, pw[dw].Green );

                                    if( wB > pw[dw].Blue )
                                          _SWAP( wB, pw[dw].Blue );
                              }
                              pwRGB[dw].Red   = wR;
                              pwRGB[dw].Green = wG;
                              pwRGB[dw].Blue  = wB;
                        }
                  }
            }

            /* Sort shadow */
            if( m_wShadow ) {

                  for( dwLines = m_wHilight,
                         pwRGB = m_pAvColor + m_ScanParam.Size.dwPhyPixels * dwLines;
                         dwLines < (dwShadingLines/*SHADING_Lines*/ - m_wShadow);
                                          dwLines++, pwRGB += m_ScanParam.Size.dwPhyPixels) {

                        for (dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++) {
                        
                              pw = m_pAvColor + (dwShadingLines/*SHADING_Lines*/ -
                                                      m_wShadow) * m_ScanParam.Size.dwPhyPixels;
                              wR = pwRGB[dw].Red;
                              wG = pwRGB[dw].Green;
                              wB = pwRGB[dw].Blue;
                              
                              for( w = 0; w < m_wShadow;
                                     w++, pw += m_ScanParam.Size.dwPhyPixels ) {
                                    if( wR < pw[dw].Red )
                                          _SWAP( wR, pw[dw].Red );
                                    if( wG < pw[dw].Green )
                                          _SWAP( wG, pw [dw].Green );
                                    if( wB > pw[dw].Blue )
                                          _SWAP( wB, pw[dw].Blue );
                              }
                              pwRGB [dw].Red   = wR;
                              pwRGB [dw].Green = wG;
                              pwRGB [dw].Blue  = wB;
                        }
                  }
            }

            /* Sum */
            for( dwLines = m_wHilight,
                   pwRGB = (pRGBUShortDef)
                                    (m_pAvColor + m_ScanParam.Size.dwPhyPixels * dwLines);
                   dwLines < (dwShadingLines/*SHADING_Lines*/ - m_wShadow);
                   dwLines++, pwRGB += m_ScanParam.Size.dwPhyPixels ) {

                  for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++ ) {
                        pdwR[dw] += pwRGB[dw].Red;
                        pdwG[dw] += pwRGB[dw].Green;
                        pdwB[dw] += pwRGB[dw].Blue;
                  }
            }

            /* simulate old program */
            pValue = (pMonoWordDef)a_wWhiteShading;
            pdw    = pdwR;

            /* Software gain */
            if( scanning->sParam.bSource != SOURCE_Negative ) {

                  for( i = 0; i < 3; i++ ) {

                        for( dw = m_ScanParam.Size.dwPhyPixels;
                                                                        dw; dw--, pValue++, pdw++) {
                              *pdw = *pdw * 1000 / ((dwShadingLines/*SHADING_Lines*/ -
                                          m_wHilight-m_wShadow) * scanning->sParam.swGain[i]);
                              if(*pdw > 65535U)
                                    pValue->Mono = 65535U;
                              else
                                    pValue->Mono = (u_short)*pdw;
                              
                              if (pValue->Mono > 16384U)
                                    pValue->Mono = (u_short)(GAIN_Target * 16384U / pValue->Mono);
                              else
                                    pValue->Mono = GAIN_Target;

#ifdef SWAP_FINE
                              if( swap ) 
#endif
                                    _SWAP(pValue->HiLo.bHi, pValue->HiLo.bLo);
                        }
                  }
            } else {
                  for( dw = m_ScanParam.Size.dwPhyPixels * 3; dw; dw--, pValue++, pdw++)
                        pValue->Mono = (u_short)(*pdw / (dwShadingLines - m_wHilight - m_wShadow));
            }                 
      } else {

            /* gray mode */
            u_short *pwAv, *pw;
            u_short  w, wV;

            memset( m_pSum, 0, m_ScanParam.Size.dwPhyPixels << 2 );
            if( m_wHilight ) {
                  for( dwLines = m_wHilight,
                         pwAv = m_pAvMono + m_ScanParam.Size.dwPhyPixels * dwLines;
                         dwLines < dwShadingLines/*SHADING_Lines*/;
                                     dwLines++, pwAv += m_ScanParam.Size.dwPhyPixels) {

                        for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++ ) {

                              pw = m_pAvMono;
                              wV = pwAv [dw];
                              for( w = 0; w < m_wHilight; w++,
                                     pw += m_ScanParam.Size.dwPhyPixels ) {
                                    if( wV > pw[dw] )
                                          _SWAP( wV, pw[dw] );
                              }
                              pwAv[dw] = wV;
                        }
                  }
            }

            /* Sort shadow */
            if (m_wShadow)
            {
                  for (dwLines = m_wHilight, pwAv = m_pAvMono + m_ScanParam.Size.dwPhyPixels * dwLines;
                         dwLines < (dwShadingLines/*SHADING_Lines*/ - m_wShadow); dwLines++, pwAv += m_ScanParam.Size.dwPhyPixels)
                        for (dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++)
                        {
                              pw = m_pAvMono + (dwShadingLines/*SHADING_Lines*/ - m_wShadow) * m_ScanParam.Size.dwPhyPixels;
                              wV = pwAv [dw];
                              for (w = 0; w < m_wShadow; w++, pw += m_ScanParam.Size.dwPhyPixels)
                                    if (wV < pw [dw])
                                          _SWAP (wV, pw[dw]);
                              pwAv [dw] = wV;
                        }
            }

            /* Sum */
            pdw = (u_long*)m_pSum; /* HEINER: check this */

            for (dwLines = m_wHilight,
                   pwAv = m_pAvMono + m_ScanParam.Size.dwPhyPixels * dwLines;
                   dwLines < (dwShadingLines/*SHADING_Lines*/ - m_wShadow);
                   dwLines++, pwAv += m_ScanParam.Size.dwPhyPixels) {
                  for (dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++)
                        pdw[dw] += pwAv[dw];
            }

            /* Software gain */
            pValue = (pMonoWordDef)a_wWhiteShading;
            if( scanning->sParam.bSource != SOURCE_Negative ) {

                  for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++) {
                        
                        pdw[dw] = pdw [dw] * 1000 / ((dwShadingLines/*SHADING_Lines*/ -
                                     m_wHilight - m_wShadow) * scanning->sParam.swGain[1]);
                                     
                        if( pdw[dw] > 65535U )
                              pValue[dw].Mono = 65535;
                        else
                              pValue[dw].Mono = (u_short)pdw[dw];

                        if( pValue[dw].Mono > 16384U ) {
                              pValue[dw].Mono = (u_short)(GAIN_Target * 16384U / pValue[dw].Mono);
                        } else {
                              pValue[dw].Mono = GAIN_Target;
                        }

#ifdef SWAP_FINE
                        if( swap )
#endif
                              _SWAP(pValue[dw].HiLo.bHi, pValue[dw].HiLo.bLo);
                  }
                  
            } else{
                  
                  for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++ ) {
                        pValue[dw].Mono = (u_short)(pdw[dw] /
                           (dwShadingLines - m_wHilight - m_wShadow));
                           
                  /* swapping will be done later... */
                  }
            }
      }

      usb_line_statistics( "White", a_wWhiteShading, m_ScanParam.Size.dwPhyPixels,
                          scanning->sParam.bDataType == SCANDATATYPE_Color?1:0);
      return SANE_TRUE;
}

/** for negative film only
 * we need to resize the gain to obtain bright white...
 */
02102 static void usb_ResizeWhiteShading( double dAmp, u_short *pwShading, int iGain )
{
      u_long  dw, dwAmp;
      u_short w;
      
      DBG( _DBG_INFO2, "ResizeWhiteShading: dAmp=%.3f, iGain=%i\n", dAmp, iGain );

      for( dw = 0; dw < m_ScanParam.Size.dwPhyPixels; dw++ ) {
            
            dwAmp = (u_long)(GAIN_Target * 0x4000 /
                                        (pwShading[dw] + 1) * dAmp) * iGain / 1000;

            if( dwAmp <= GAIN_Target)
                  w = (u_short)dwAmp;
            else 
                  w = GAIN_Target;

#ifndef SWAP_FINE
            pwShading[dw] = (u_short)_LOBYTE(w) * 256 + _HIBYTE(w);
#else
            pwShading[dw] = w;
#endif
      }

#ifdef SWAP_FINE
      if( usb_HostSwap()) {
            usb_Swap( pwShading, m_ScanParam.Size.dwPhyPixels );
      }
#endif
}

/** do the base settings for calibration scans
 */
02135 static void usb_PrepareCalibration( Plustek_Device *dev )
{
    pScanDef  scanning = &dev->scanning;
      pDCapsDef scaps    = &dev->usbDev.Caps;

      usb_GetSWOffsetGain( dev );

      pScanBuffer = scanning->pScanBuffer;
      pParam      = &scanning->sParam;

      memset( &m_ScanParam, 0, sizeof(ScanParam));

      m_ScanParam.UserDpi   = scaps->OpticDpi;
      m_ScanParam.PhyDpi    = scaps->OpticDpi;
      m_ScanParam.bChannels = scanning->sParam.bChannels;
      m_ScanParam.bBitDepth = 16;
      m_ScanParam.bSource   = scanning->sParam.bSource;
      m_ScanParam.Origin.y  = 0;

      if( scanning->sParam.bDataType == SCANDATATYPE_Color )
            m_ScanParam.bDataType = SCANDATATYPE_Color;
      else
            m_ScanParam.bDataType = SCANDATATYPE_Gray;

      usb_SetMCLK( dev, &m_ScanParam );
 
      /* preset these registers offset/gain */
      a_bRegs[0x38] = a_bRegs[0x39] = a_bRegs[0x3a] = 0;
      a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;
      a_bRegs[0x45] &= ~0x10;

      memset( a_wWhiteShading, 0, _SHADING_BUF );
      memset( a_wDarkShading,  0, _SHADING_BUF );

      scanning->skipCoarseCalib = SANE_FALSE;

      if( dev->adj.cacheCalData )
            if( usb_ReadAndSetCalData( dev ))
                  scanning->skipCoarseCalib = SANE_TRUE;
}

/** read the white calibration strip until the lamp seems to be stable
 * the timed warmup will be used, when the warmup time is set to -1
 */
02179 static SANE_Bool usb_AutoWarmup( Plustek_Device *dev )
{
      int       i, stable_count;
      pScanDef  scanning = &dev->scanning;
      pDCapsDef scaps    = &dev->usbDev.Caps;
      pHWDef    hw       = &dev->usbDev.HwSetting;
      u_long    dw, start, end, len;
      u_long    curR,   curG,  curB;
      u_long    lastR, lastG, lastB;
      long      diffR, diffG, diffB;
      long      thresh = _AUTO_THRESH;

      if( usb_IsEscPressed())
            return SANE_FALSE;

      bMaxITA = 0xff;

      DBG( _DBG_INFO, "#########################\n" );
      DBG( _DBG_INFO, "usb_AutoWarmup()\n" );

      if( dev->adj.warmup >= 0 ) {
            DBG( _DBG_INFO, "... using timed warmup: %ds\n", dev->adj.warmup );
            if( !usb_Wait4Warmup( dev )) {
                  DBG( _DBG_ERROR, "usb_AutoWarmup() - CANCEL detected\n" );
                  return SANE_FALSE;
            }
            DBG( _DBG_INFO, "usb_AutoWarmup() done.\n" );
            return SANE_TRUE;
      }

      usb_PrepareCalibration( dev );
      a_bRegs[0x38] = a_bRegs[0x39] = a_bRegs[0x3a] = 0;
      a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;

      /* define the strip to scan for warming up the lamp, in the end
       * we always scan the full line, even for TPA
       */
      m_ScanParam.bDataType     = SCANDATATYPE_Color;
      m_ScanParam.bCalibration  = PARAM_Gain;
      m_ScanParam.dMCLK         = dMCLK;
      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 * m_ScanParam.bChannels;

      if( hw->bReg_0x26 & _ONE_CH_COLOR )
            m_ScanParam.Size.dwBytes *= 3;

      m_ScanParam.Origin.x = (u_short)((u_long) hw->wActivePixelsStart *
                                                      300UL / scaps->OpticDpi.x);

      stable_count = 0;
      start = 500;
      len   = m_ScanParam.Size.dwPixels;

      if( scanning->sParam.bSource == SOURCE_Transparency ) {
            start  = scaps->Positive.DataOrigin.x * scaps->OpticDpi.x / 300UL;
            len    = scaps->Positive.Size.x * scaps->OpticDpi.x / 300UL;
            thresh = _AUTO_TPA_THRESH;
      }
      else if( scanning->sParam.bSource == SOURCE_Negative ) {
            start  = scaps->Negative.DataOrigin.x * scaps->OpticDpi.x / 300UL;
            len    = scaps->Negative.Size.x * scaps->OpticDpi.x / 300UL;
            thresh = _AUTO_TPA_THRESH;
      }
      end = start + len;
      DBG( _DBG_INFO2, "Start=%lu, End=%lu, Len=%lu, Thresh=%li\n", 
                       start, end, len, thresh );

      lastR = lastG = lastB = 0;
      for( i = 0; i < _MAX_AUTO_WARMUP + 1 ; i++ ) {

            if( !usb_SetScanParameters( dev, &m_ScanParam ))
                  return SANE_FALSE;

            if( !usb_ScanBegin( dev, SANE_FALSE) ||
                !usb_ScanReadImage( dev, pScanBuffer, m_ScanParam.Size.dwPhyBytes ) ||
                !usb_ScanEnd( dev )) {
                  DBG( _DBG_ERROR, "usb_AutoWarmup() failed\n" );
                  return SANE_FALSE;
            }

#ifdef SWAP_COARSE
            if(usb_HostSwap())
#endif
                  usb_Swap((u_short *)pScanBuffer, m_ScanParam.Size.dwPhyBytes );

            if( end > m_ScanParam.Size.dwPhyPixels )
                  end = m_ScanParam.Size.dwPhyPixels;

            curR = curG = curB = 0;
            for( dw = start; dw < end; dw++ ) {
                  
                  if( hw->bReg_0x26 & _ONE_CH_COLOR ) {
                        curR += ((u_short*)pScanBuffer)[dw];
                        curG += ((u_short*)pScanBuffer)[dw+m_ScanParam.Size.dwPhyPixels+1];
                        curB += ((u_short*)pScanBuffer)
                                               [dw+(m_ScanParam.Size.dwPhyPixels+1)*2];
                  } else {
                        curR += ((pRGBUShortDef)pScanBuffer)[dw].Red;
                        curG += ((pRGBUShortDef)pScanBuffer)[dw].Green;
                        curB += ((pRGBUShortDef)pScanBuffer)[dw].Blue;
                  }
            }
            curR /= len;
            curG /= len;
            curB /= len;

            diffR = curR - lastR; lastR = curR;
            diffG = curG - lastG; lastG = curG;
            diffB = curB - lastB; lastB = curB;
            DBG( _DBG_INFO2, "%i/%i-AVE(R,G,B)= %lu(%ld), %lu(%ld), %lu(%ld)\n", 
                          i, stable_count, curR, diffR, curG, diffG, curB, diffB );

            /* we consider the lamp to be stable, 
             * when the diffs are less than thresh for at least 3 loops
             */
            if((diffR < thresh) && (diffG < thresh) && (diffB < thresh)) {
                  if( stable_count > 3 )
                        break;
                  stable_count++;
            } else {
                  stable_count = 0;
            }

            /* no need to sleep in the first loop */
            if((i != 0) && (stable_count == 0))
                  sleep( _AUTO_SLEEP );
      }

      DBG( _DBG_INFO, "usb_AutoWarmup() done - %u loops\n", i+1 );
      DBG( _DBG_INFO, "* AVE(R,G,B)= %lu(%ld), %lu(%ld), %lu(%ld)\n", 
                             curR, diffR, curG, diffG, curB, diffB );
      return SANE_TRUE;
}

/**
 */
static int usb_DoIt( Plustek_Device *dev )
{
      pScanDef  scanning = &dev->scanning;

      DBG( _DBG_INFO, "Settings done, so start...\n" );
      if( !scanning->skipCoarseCalib ) {
            DBG( _DBG_INFO2, "###### ADJUST GAIN (COARSE)#######\n" );
            if( !usb_AdjustGain(dev, 0)) {
                  DBG( _DBG_ERROR, "Coarse Calibration failed!!!\n" );
                  return _E_INTERNAL;
            }
            DBG( _DBG_INFO2, "###### ADJUST OFFSET (COARSE) ####\n" );
            if( !usb_AdjustOffset(dev)) {
                  DBG( _DBG_ERROR, "Coarse Calibration failed!!!\n" );
                  return _E_INTERNAL;
            }
      } else {
            DBG( _DBG_INFO2, "Coarse Calibration skipped, using saved data\n" );
      }
      DBG( _DBG_INFO2, "###### ADJUST DARK (FINE) ########\n" );
      if( !usb_AdjustDarkShading(dev)) {
            DBG( _DBG_ERROR, "Fine Calibration failed!!!\n" );
            return _E_INTERNAL;
      }
      DBG( _DBG_INFO2, "###### ADJUST WHITE (FINE) #######\n" );
      if( !usb_AdjustWhiteShading(dev)) {
            DBG( _DBG_ERROR, "Fine Calibration failed!!!\n" );
            return _E_INTERNAL;
      }
      return 0;
}

/** usb_DoCalibration
 */
02352 static int usb_DoCalibration( Plustek_Device *dev )
{
      int       result;
      pScanDef  scanning = &dev->scanning;
      pDCapsDef scaps    = &dev->usbDev.Caps;
      pHWDef    hw       = &dev->usbDev.HwSetting;

      DBG( _DBG_INFO, "usb_DoCalibration()\n" );

      if( SANE_TRUE == scanning->fCalibrated )
            return SANE_TRUE;

      /* Go to shading position
     */
      DBG( _DBG_INFO, "...goto shading position\n" );

      /* HEINER: Currently not clear why Plustek didn't use the ShadingOriginY
       *         for all modes
       * It should be okay to remove this and reference to the ShadingOriginY
       */   
#if 0 
      if( scanning->sParam.bSource == SOURCE_Negative ) {

            DBG( _DBG_INFO, "DataOrigin.x=%u, DataOrigin.y=%u\n",
             dev->usbDev.pSource->DataOrigin.x, dev->usbDev.pSource->DataOrigin.y);
            if(!usb_ModuleMove( dev, MOVE_Forward,
                                                        (dev->usbDev.pSource->DataOrigin.y +
                                                               dev->usbDev.pSource->Size.y / 2))) {
                  return _E_LAMP_NOT_IN_POS;
            }

      } else {
#endif
            DBG( _DBG_INFO, "ShadingOriginY=%lu\n",
                        (u_long)dev->usbDev.pSource->ShadingOriginY );

            if((hw->motorModel == MODEL_HuaLien) && (scaps->OpticDpi.x==600)) {
                  if (!usb_ModuleMove(dev, MOVE_ToShading,
                                          (u_long)dev->usbDev.pSource->ShadingOriginY)) {
                        return _E_LAMP_NOT_IN_POS;
                  }
            } else {
                  if( !usb_ModuleMove(dev, MOVE_Forward,
                                          (u_long)dev->usbDev.pSource->ShadingOriginY)) {
                        return _E_LAMP_NOT_IN_POS;
                  }
            }
/*    }*/

      DBG( _DBG_INFO, "shading position reached\n" );

      if( !usb_AutoWarmup( dev ))
            return SANE_FALSE;

      usb_PrepareCalibration( dev );

      /** this won't work for Plustek devices!!!
       */
#if 0
      if( scaps->workaroundFlag & _WAF_BYPASS_CALIBRATION ||
            !(SCANDEF_QualityScan & dev->scanning.dwFlag)) {
#else
      if( scaps->workaroundFlag & _WAF_BYPASS_CALIBRATION ) {
#endif

            DBG( _DBG_INFO, "--> BYPASS\n" );
            a_bRegs[0x38] = a_bRegs[0x39] = a_bRegs[0x3a] = 0;
            a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;

            setAdjGain( dev->adj.rgain, &a_bRegs[0x3b] );
            setAdjGain( dev->adj.ggain, &a_bRegs[0x3c] );
            setAdjGain( dev->adj.bgain, &a_bRegs[0x3d] );

            a_bRegs[0x45] |= 0x10;
            usb_SetMCLK( dev, &scanning->sParam );

          dumpregs( dev->fd, a_bRegs );
            DBG( _DBG_INFO, "<-- BYPASS\n" );

      } else {

            switch( scanning->sParam.bSource ) {

                  case SOURCE_Negative:
                        DBG( _DBG_INFO, "NEGATIVE Shading\n" );
                        m_dwIdealGain = IDEAL_GainNormal;

                        if( !_IS_PLUSTEKMOTOR(hw->motorModel)) {
                              DBG( _DBG_INFO, "No Plustek model: %udpi\n",
                                                scanning->sParam.PhyDpi.x );
                              usb_SetMCLK( dev, &scanning->sParam );
                        } else {

                              if( dev->usbDev.Caps.OpticDpi.x == 600 )
                                    dMCLK = 7;
                              else
                                    dMCLK = 8;
                        }

                        for(;;) {
                              if( usb_AdjustGain( dev, 2)) {
                                    if( a_bRegs[0x3b] && a_bRegs[0x3c] && a_bRegs[0x3d]) {
                                          break;
                                    } else {
                                          a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;
                                          dMCLK--;
                                    }
                              } else {
                                    return _E_LAMP_NOT_STABLE;
                              }
                        }
                        scanning->sParam.dMCLK = dMCLK;
                        Gain_Reg.Red    = a_bRegs[0x3b];
                        Gain_Reg.Green  = a_bRegs[0x3c];
                        Gain_Reg.Blue   = a_bRegs[0x3d];
                        Gain_NegHilight = Gain_Hilight;
                  
                        DBG( _DBG_INFO, "MCLK      = %.3f\n", dMCLK );
                        DBG( _DBG_INFO, "GainRed   = %u\n", a_bRegs[0x3b] );
                        DBG( _DBG_INFO, "GainGreen = %u\n", a_bRegs[0x3c] );
                        DBG( _DBG_INFO, "GainBlue  = %u\n", a_bRegs[0x3d] );

    #if 0
                  if( !usb_ModuleMove( dev, MOVE_Backward,
                                                 dev->usbDev.pSource->DataOrigin.y +
                                                 dev->usbDev.pSource->Size.y / 2 -
                                                 dev->usbDev.pSource->ShadingOriginY)) {
                        return _E_LAMP_NOT_IN_POS;
                  }
    #endif
                        a_bRegs[0x45] &= ~0x10;

                        a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;

                        if(!usb_AdjustGain( dev, 1 ))
                              return _E_INTERNAL;
                        
                        a_bRegs[0x3b] = a_bRegs[0x3c] = a_bRegs[0x3d] = 1;

                        DBG( _DBG_INFO, "Settings done, so start...\n" );
                        if( !usb_AdjustOffset(dev) || !usb_AdjustDarkShading(dev) ||
                                                      !usb_AdjustWhiteShading(dev)) {
                              return _E_INTERNAL;
                        }

                        dRed    = 0.93 + 0.067 * Gain_Reg.Red;
                        dGreen  = 0.93 + 0.067 * Gain_Reg.Green;
                        dBlue   = 0.93 + 0.067 * Gain_Reg.Blue;
                        dExpect = 2.85;
                        if( dBlue >= dGreen && dBlue >= dRed )
                              dMax = dBlue;
                        else
                              if( dGreen >= dRed && dGreen >= dBlue )
                                    dMax = dGreen;
                              else
                                    dMax = dRed;

                        dMax = dExpect / dMax;
                        dRed   *= dMax;
                        dGreen *= dMax;
                        dBlue  *= dMax;

                        if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {
                              usb_ResizeWhiteShading( dRed,  a_wWhiteShading,
                                                              scanning->sParam.swGain[0]);
                              usb_ResizeWhiteShading( dGreen, a_wWhiteShading +
                                                              m_ScanParam.Size.dwPhyPixels,
                                                              scanning->sParam.swGain[1]);
                              usb_ResizeWhiteShading( dBlue,  a_wWhiteShading +
                                                              m_ScanParam.Size.dwPhyPixels*2,
                                                              scanning->sParam.swGain[2]);
                        }
                        break;

                  case SOURCE_ADF:
                        DBG( _DBG_INFO, "ADF Shading\n" );
                        m_dwIdealGain = IDEAL_GainPositive;
                        if( scanning->sParam.bDataType == SCANDATATYPE_BW ) {
                              if( scanning->sParam.PhyDpi.x <= 200 ) {
                                    scanning->sParam.dMCLK = 4.5;
                                    dMCLK = 4.0;
                              } else if ( scanning->sParam.PhyDpi.x <= 300 ) {
                                    scanning->sParam.dMCLK = 4.0;
                                    dMCLK = 3.5;
                              } else if( scanning->sParam.PhyDpi.x <= 400 ) {
                                    scanning->sParam.dMCLK = 5.0;
                                    dMCLK = 4.0;
                              } else {
                                    scanning->sParam.dMCLK = 6.0;
                                    dMCLK = 4.0;
                              }
                        } else { /* Gray */

                              if( scanning->sParam.PhyDpi.x <= 400 ) {
                                    scanning->sParam.dMCLK = 6.0;
                                    dMCLK = 4.5;
                              } else {
                                    scanning->sParam.dMCLK = 9.0;
                                    dMCLK = 7.0;
                              }
                        }
                        dMCLK_ADF = dMCLK;

                        result = usb_DoIt( dev );
                        if( result != 0 )
                              return result;
                        break;

                  case SOURCE_Transparency:
                        DBG( _DBG_INFO, "TRANSPARENCY Shading\n" );
                        m_dwIdealGain = IDEAL_GainPositive;

                        if( !_IS_PLUSTEKMOTOR(hw->motorModel)) {
                              DBG( _DBG_INFO, "No Plustek model: %udpi\n",
                                                scanning->sParam.PhyDpi.x );
                              usb_SetMCLK( dev, &scanning->sParam );

                        } else {
                              if( dev->usbDev.Caps.OpticDpi.x == 600 ) {
                                    scanning->sParam.dMCLK = 8;
                                    dMCLK = 4;
                              } else {
                                    scanning->sParam.dMCLK = 8;
                                    dMCLK = 6;
                              }
                        }
                        result = usb_DoIt( dev );
                        if( result != 0 )
                              return result;
                        break;

                  default:
                        if( !_IS_PLUSTEKMOTOR(hw->motorModel)) {
                        DBG( _DBG_INFO, "No Plustek model: %udpi\n",
                                                                        scanning->sParam.PhyDpi.x );
                        usb_SetMCLK( dev, &scanning->sParam );

                  } else      if( dev->usbDev.Caps.OpticDpi.x == 600 ) {

                        DBG( _DBG_INFO, "Default Shading (600dpi)\n" );

                        if( dev->usbDev.Caps.bCCD == kSONY548 )   {

                              DBG( _DBG_INFO, "CCD - SONY548\n" );
                              if( scanning->sParam.PhyDpi.x <= 75 ) {
                                    if( scanning->sParam.bDataType  == SCANDATATYPE_Color )
                                          scanning->sParam.dMCLK = dMCLK = 2.5;
                                    else if(scanning->sParam.bDataType == SCANDATATYPE_Gray)
                                          scanning->sParam.dMCLK = dMCLK = 7.0;
                                    else
                                          scanning->sParam.dMCLK = dMCLK = 7.0;

                              } else if( scanning->sParam.PhyDpi.x <= 300 ) {
                                    if( scanning->sParam.bDataType  == SCANDATATYPE_Color )
                                          scanning->sParam.dMCLK = dMCLK = 3.0;
                                    else if(scanning->sParam.bDataType == SCANDATATYPE_Gray)
                                          scanning->sParam.dMCLK = dMCLK = 6.0;
                                    else  {
                                          if( scanning->sParam.PhyDpi.x <= 100 )
                                                scanning->sParam.dMCLK = dMCLK = 6.0;
                                          else if( scanning->sParam.PhyDpi.x <= 200 )
                                                scanning->sParam.dMCLK = dMCLK = 5.0;
                                          else
                                                scanning->sParam.dMCLK = dMCLK = 4.5;
                                    }
                              } else if( scanning->sParam.PhyDpi.x <= 400 ) {
                                    if( scanning->sParam.bDataType  == SCANDATATYPE_Color )
                                          scanning->sParam.dMCLK = dMCLK = 4.0;
                                    else if( scanning->sParam.bDataType == SCANDATATYPE_Gray )
                                          scanning->sParam.dMCLK = dMCLK = 6.0;
                                    else
                                          scanning->sParam.dMCLK = dMCLK = 4.0;
                              } else {
                                    if(scanning->sParam.bDataType  == SCANDATATYPE_Color)
                                          scanning->sParam.dMCLK = dMCLK = 6.0;
                                    else if(scanning->sParam.bDataType == SCANDATATYPE_Gray)
                                          scanning->sParam.dMCLK = dMCLK = 7.0;
                                    else
                                          scanning->sParam.dMCLK = dMCLK = 6.0;
                              }

                        } else if( dev->usbDev.Caps.bPCB == 0x02 ) {
                              DBG( _DBG_INFO, "PCB - 0x02\n" );
                              if( scanning->sParam.PhyDpi.x > 300 )
                                    scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)? 6: 16);
                              else if( scanning->sParam.PhyDpi.x > 150 )
                                    scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)?4.5: 13.5);
                              else
                                    scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)?3: 8);

                        }  else if( dev->usbDev.Caps.bButtons ) { /* with lens Shading piece (with gobo) */

                              DBG( _DBG_INFO, "CAPS - Buttons\n" );
                              scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)?3: 6);
                              if( dev->usbDev.HwSetting.motorModel == MODEL_KaoHsiung )   {
                                    if( dev->usbDev.Caps.bCCD == kNEC3799 ) {
                                          if( scanning->sParam.PhyDpi.x > 300 )
                                                scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)? 6: 13);
                                          else if(scanning->sParam.PhyDpi.x > 150)
                                                scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)?4.5:13.5);
                                          else
                                                scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)?3: 6);
                                    } else {
                                          scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)?3: 6);
                                    }
                              } else { /* motorModel == MODEL_Hualien */
                                    /*  IMPORTANT !!!!
                                     * for Hualien 600 dpi scanner big noise
                             */
                                    hw->wLineEnd = 5384;
                                    if(scanning->sParam.bDataType  == SCANDATATYPE_Color &&
                                          ((scanning->sParam.bBitDepth == 8 && 
                                                 (scanning->sParam.PhyDpi.x == 200 ||scanning->sParam.PhyDpi.x == 300))))
                                          hw->wLineEnd = 7000;
                                    a_bRegs[0x20] = _HIBYTE(hw->wLineEnd);
                                    a_bRegs[0x21] = _LOBYTE(hw->wLineEnd);

                                    if( scanning->sParam.PhyDpi.x > 300 ) {
                                          if (scanning->sParam.bBitDepth > 8)
                                                scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType  == SCANDATATYPE_Color)? 5: 13);
                                          else
                                                scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)? 6: 13);
                                    } else {
                                          if( scanning->sParam.bBitDepth > 8 )
                                                scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)? 5: 13);
                                          else
                                                scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)?3: 6);
                                    }
                              }
                        } else  { /* without lens Shading piece (without gobo) - Model U12 only */
                              DBG( _DBG_INFO, "Default trunc (U12)\n" );
                              if( scanning->sParam.PhyDpi.x > 300 )
                                    scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)? 3: 9);
                              else
                                    scanning->sParam.dMCLK = dMCLK = ((scanning->sParam.bDataType == SCANDATATYPE_Color)? 2: 6);
                        }
                  } else {    /* Device.Caps.OpticDpi.x == 1200 */

                        DBG( _DBG_INFO, "Default Shading (1200dpi)\n" );
                        if( scanning->sParam.bDataType != SCANDATATYPE_Color ) {
                              if( scanning->sParam.PhyDpi.x > 300 )
                                    scanning->sParam.dMCLK = dMCLK = 6.0;
                              else {
                                    scanning->sParam.dMCLK = dMCLK = 5.0;
                                    a_bRegs[0x0a] = 1;
                              }
                        } else {
                              if( scanning->sParam.PhyDpi.x <= 300)
                                    scanning->sParam.dMCLK = dMCLK = 2.0;
                              else if( scanning->sParam.PhyDpi.x <= 800 )
                                    scanning->sParam.dMCLK = dMCLK = 4.0;
                              else
                                    scanning->sParam.dMCLK = dMCLK = 5.5;
                        }
                  }

                        if (m_ScanParam.bSource == SOURCE_ADF)
                              m_dwIdealGain = IDEAL_GainPositive;
                        else
                              m_dwIdealGain = IDEAL_GainNormal;

                        result = usb_DoIt( dev );
                        if( result != 0 )
                              return result;
                        break;
            }
      }

      /*
       * home the sensor after calibration
       */
      if( _IS_PLUSTEKMOTOR(hw->motorModel)) {

            if( hw->motorModel != MODEL_Tokyo600 ) {
                  usb_ModuleMove  ( dev, MOVE_Forward, hw->wMotorDpi / 5 );
                  usb_ModuleToHome( dev, SANE_TRUE );
            }
      } else {

            usb_ModuleMove( dev, MOVE_Forward, 1 );
            usleep( 150 );
            usb_ModuleToHome( dev, SANE_TRUE );
      }

      if( scanning->sParam.bSource == SOURCE_ADF ) {

            if( scaps->bCCD == kNEC3778 )
                  usb_ModuleMove( dev, MOVE_Forward, 1000 );

            else /* if( scaps->bCCD == kNEC3799) */
                  usb_ModuleMove( dev, MOVE_Forward, 3 * 300 + 38 );

            usb_MotorOn( dev->fd, SANE_FALSE );
      }

      scanning->fCalibrated = SANE_TRUE;
      DBG( _DBG_INFO, "Calibration done\n" );
      DBG( _DBG_INFO, "-----------------------\n" );
      DBG( _DBG_INFO, "Static Gain:\n" );
      DBG( _DBG_INFO, "REG[0x3b] = %u\n", a_bRegs[0x3b] );
      DBG( _DBG_INFO, "REG[0x3c] = %u\n", a_bRegs[0x3c] );
      DBG( _DBG_INFO, "REG[0x3d] = %u\n", a_bRegs[0x3d] );
      DBG( _DBG_INFO, "Static Offset:\n" );
      DBG( _DBG_INFO, "REG[0x38] = %u\n", a_bRegs[0x38] );
      DBG( _DBG_INFO, "REG[0x39] = %u\n", a_bRegs[0x39] );
      DBG( _DBG_INFO, "REG[0x3a] = %u\n", a_bRegs[0x3a] );
      DBG( _DBG_INFO, "-----------------------\n" );

      return SANE_TRUE;
}

/** usb_DownloadShadingData
 * according to the job id, different registers or DRAM areas are set
 * in preparation for calibration or scanning
 */
02767 static SANE_Bool usb_DownloadShadingData( Plustek_Device *dev, u_char what )
{
      u_char    channel;
      pDCapsDef scaps = &dev->usbDev.Caps;
      pHWDef    hw    = &dev->usbDev.HwSetting;

      DBG( _DBG_INFO, "usb_DownloadShadingData(%u)\n", what );

      channel = CHANNEL_green;
      if( hw->bReg_0x26 & _ONE_CH_COLOR )
            channel = CHANNEL_blue;

      switch( what ) {

            case PARAM_WhiteShading:
                  if( m_ScanParam.bDataType == SCANDATATYPE_Color ) {

                        usb_SetDarkShading( dev->fd, CHANNEL_red, a_wDarkShading,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                        usb_SetDarkShading( dev->fd, CHANNEL_green, a_wDarkShading +
                                                      m_ScanParam.Size.dwPhyPixels,
                                                    (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                        usb_SetDarkShading( dev->fd, CHANNEL_blue, a_wDarkShading +
                                                      m_ScanParam.Size.dwPhyPixels * 2,
                                                    (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                  } else {
                        usb_SetDarkShading( dev->fd, channel, a_wDarkShading +
                                                      m_ScanParam.Size.dwPhyPixels,
                                                    (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                  }
                  a_bRegs[0x40] = 0x40;
                  a_bRegs[0x41] = 0x00;

                  /* set RAM configuration AND
             * Gain = Multiplier Coefficient/16384
             * CFG Register 0x40/0x41 for Multiplier Coefficient Source
             * External DRAM for Offset Coefficient Source
             */
                  a_bRegs[0x42] = (u_char)(( hw->wDRAMSize > 512)? 0x64: 0x24);

                  _UIO(sanei_lm983x_write( dev->fd, 0x40,
                                                       &a_bRegs[0x40], 0x42-0x40+1, SANE_TRUE ));
                  break;

            case PARAM_Scan:
                  {
#if 0
                        if( scaps->workaroundFlag & _WAF_BYPASS_CALIBRATION ||
                              !(SCANDEF_QualityScan & dev->scanning.dwFlag)) {
#else
                        if( scaps->workaroundFlag & _WAF_BYPASS_CALIBRATION ) {
#endif
                              DBG( _DBG_INFO, "--> BYPASS\n" );

                              /* set RAM configuration AND
                         * Bypass Multiplier
                         * CFG Register 0x40/0x41 for Multiplier Coefficient Source
                       * CFG Register 0x3e/0x3f for Offset Coefficient Source
                         */
                              a_bRegs[0x03] = 0;
                              a_bRegs[0x40] = 0x40;
                              a_bRegs[0x41] = 0x00;
                              a_bRegs[0x42] = (u_char)((hw->wDRAMSize > 512)? 0x61:0x21);
                              if( !usbio_WriteReg( dev->fd, 0x03, a_bRegs[0x03]))
                                    return SANE_FALSE;

                              _UIO(sanei_lm983x_write( dev->fd, 0x40,
                                                                   &a_bRegs[0x40], 3, SANE_TRUE));
                              break;
                        }

                        if( _LM9831 != hw->chip )
                              m_dwPixels = m_ScanParam.Size.dwPhyPixels;

                        if( scaps->workaroundFlag & _WAF_SKIP_FINE ) {
                              DBG( _DBG_INFO, "Skipping fine calibration\n" );
                        a_bRegs[0x42] = (u_char)(( hw->wDRAMSize > 512)? 0x60: 0x20);
        
                              if( !usbio_WriteReg( dev->fd, 0x42, a_bRegs[0x42]))
                                    return SANE_FALSE;

                              break;                                    
                        }                                   
                                                            
                        DBG( _DBG_INFO, "Downloading %lu pixels\n", m_dwPixels );

                        /* Download the dark & white shadings to LM983x */
                        if( pParam->bDataType == SCANDATATYPE_Color ) {
                              usb_SetDarkShading( dev->fd, CHANNEL_red, a_wDarkShading,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                              usb_SetDarkShading( dev->fd, CHANNEL_green,
                                                      a_wDarkShading + m_dwPixels,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                              usb_SetDarkShading( dev->fd, CHANNEL_blue,
                                                            a_wDarkShading + m_dwPixels * 2,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                        } else {
                              usb_SetDarkShading( dev->fd, channel,
                                                            a_wDarkShading + m_dwPixels,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                        }

                        if( pParam->bDataType == SCANDATATYPE_Color ) {
                              usb_SetWhiteShading( dev->fd, CHANNEL_red, a_wWhiteShading,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                              usb_SetWhiteShading( dev->fd, CHANNEL_green,
                                                             a_wWhiteShading +
                                                             m_ScanParam.Size.dwPhyPixels,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                              usb_SetWhiteShading( dev->fd, CHANNEL_blue,
                                                           a_wWhiteShading +
                                                             m_ScanParam.Size.dwPhyPixels * 2,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                        } else {
                              usb_SetWhiteShading( dev->fd, channel, a_wWhiteShading,
                                                      (u_short)m_ScanParam.Size.dwPhyPixels * 2);
                        }
                        
                        /* set RAM configuration AND
                         * Gain = Multiplier Coefficient/16384
                         * External DRAM for Multiplier Coefficient Source
                         * External DRAM for Offset Coefficient Source
                         */
                        a_bRegs[0x42] = (u_char)((hw->wDRAMSize > 512)? 0x66: 0x26);

                        if( scaps->workaroundFlag & _WAF_SKIP_WHITEFINE ) {
                              DBG( _DBG_INFO,"Skipping fine white calibration result\n");
                              a_bRegs[0x42] = (u_char)(( hw->wDRAMSize > 512)? 0x64: 0x24);
                        }

                        if( !usbio_WriteReg( dev->fd, 0x42, a_bRegs[0x42]))
                              return SANE_FALSE;
                  }
                  break;

            default:
                  /* for coarse calibration and "black fine" */
                  a_bRegs[0x3e] = 0;
                  a_bRegs[0x3f] = 0;
                  a_bRegs[0x40] = 0x40;
                  a_bRegs[0x41] = 0x00;

                  /* set RAM configuration AND
                   * GAIN = Multiplier Coefficient/16384
                   * CFG Register 0x40/0x41 for Multiplier Coefficient Source
                   * CFG Register 0x3e/0x3f for Offset Coefficient Source
                   */
                  a_bRegs[0x42] = (u_char)((hw->wDRAMSize > 512)? 0x60: 0x20);

                  _UIO(sanei_lm983x_write( dev->fd, 0x3e, &a_bRegs[0x3e],
                                                        0x42 - 0x3e + 1, SANE_TRUE ));
                  break;                                                      
      }

      return SANE_TRUE;
}

/* END PLUSTEK-USBSHADING.C .................................................*/

Generated by  Doxygen 1.6.0   Back to index