text: 
  #include "stdafx.h"
  #include "resource.h"
  
  #include <process.h>
  
  #include "bar.h"
  #include "pbrush.h"
  #include "imaging.h"
  
  //////////////////////////////////////////////////////////////////////////
  //
  // Trace
  //
  
  #ifdef DBG
  
  void AFX_CDECL Trace(PCTSTR pszFormat, ...)
  {
      va_list argList;
      va_start(argList, pszFormat);
  
      CString strMessage;
      strMessage.FormatV(pszFormat, argList);
  
      OutputDebugString(strMessage);
  
      va_end(argList);
  }
  
  #else //DBG
  
  inline void AFX_CDECL Trace(PCTSTR pszFormat, ...)
  {
  }
  
  #endif DBG
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  CImagingMgr::~CImagingMgr()
  {
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  CWIAMgr::CWIAMgr()
  {
      HRESULT hr;
  
      m_pEventCallback = new CEventCallback();
  
      if (m_pEventCallback)
      {
          hr = m_pEventCallback->Register();
  
          if (hr != S_OK)
          {
              m_pEventCallback.Release();
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT
  CWIAMgr::SelectSource(
      HWND hWndParent,
      LONG lFlags
  )
  {
      HRESULT hr = S_FALSE;
  
      // Create a connection to the local WIA device manager
  
      CComPtr<IWiaDevMgr> pWiaDevMgr;
  
      hr = pWiaDevMgr.CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_ALL|CLSCTX_NO_FAILURE_LOG);
  
      if (!SUCCEEDED(hr))
      {
          return hr;
      }
  
      // clear the current selection (if any)
  
      m_bstrDeviceID.Empty();
  
      // display the device selection dialog
  
      hr = pWiaDevMgr->SelectDeviceDlgID(
          hWndParent,
          StiDeviceTypeDefault,
          lFlags,
          &m_bstrDeviceID
      );
  
      theApp.RestoreWaitCursor();
  
      if (hr != S_OK)
      {
          Trace(_T("SelectDeviceDlgID HRESULT=%08x\n"), hr);
          return hr;
      }
  
      return S_OK;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT
  CWIAMgr::Select(
      LPCTSTR pDeviceId
  )
  {
      m_bstrDeviceID = pDeviceId;
  
      return S_OK;
  }
  
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT ReadPropertyLong(IWiaItem *pWiaItem, PROPID propid, LONG *lResult)
  {
      if (!lResult)
      {
          return E_POINTER;
      }
  
      HRESULT hr = S_FALSE;
  
      CComQIPtr<IWiaPropertyStorage> pWiaPropertyStorage(pWiaItem);
  
      if (pWiaPropertyStorage == 0)
      {
          return E_NOINTERFACE;
      }
  
      PROPSPEC PropSpec;
  
      PropSpec.ulKind = PRSPEC_PROPID;
      PropSpec.propid = propid;
  
      PROPVARIANT PropVariant;
  
      PropVariantInit(&PropVariant);
  
      hr = pWiaPropertyStorage->ReadMultiple(1, &PropSpec, &PropVariant);
  
      if (hr != S_OK)
      {
          Trace(_T("ReadMultiple HRESULT=%08x\n"), hr);
          return hr;
      }
  
      switch (PropVariant.vt)
      {
          case VT_I1:   *lResult = (LONG) PropVariant.cVal;    break;
          case VT_UI1:  *lResult = (LONG) PropVariant.bVal;    break;
          case VT_I2:   *lResult = (LONG) PropVariant.iVal;    break;
          case VT_UI2:  *lResult = (LONG) PropVariant.uiVal;   break;
          case VT_I4:   *lResult = (LONG) PropVariant.lVal;    break;
          case VT_UI4:  *lResult = (LONG) PropVariant.ulVal;   break;
          case VT_INT:  *lResult = (LONG) PropVariant.intVal;  break;
          case VT_UINT: *lResult = (LONG) PropVariant.uintVal; break;
          case VT_R4:   *lResult = (LONG) (PropVariant.fltVal + 0.5); break;
          case VT_R8:   *lResult = (LONG) (PropVariant.dblVal + 0.5); break;
          default:      hr = S_FALSE; break;
      }
  
      PropVariantClear(&PropVariant);
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT
  CWIAMgr::Acquire(
      HWND     hWndParent,
      HGLOBAL *phDib
  )
  {
      ASSERT(phDib != 0);
  
      HRESULT hr;
  
      // Create a connection to the local WIA device manager
  
      CComPtr<IWiaDevMgr> pWiaDevMgr;
  
      hr = pWiaDevMgr.CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_ALL|CLSCTX_NO_FAILURE_LOG);
  
      if (!SUCCEEDED(hr))
      {
          return hr;
      }
  
      // Create the device object.
      // Select the device first if
      //   no device has been selected before or
      //   we fail to create the device with the selected ID
  
      CComPtr<IWiaItem> pRootItem;
  
      if (!m_bstrDeviceID ||
          !SUCCEEDED(pWiaDevMgr->CreateDevice(m_bstrDeviceID, &pRootItem)))
      {
          // clear the current selection (if any)
  
          m_bstrDeviceID.Empty();
  
          // display the device selection dialog
  
          hr = pWiaDevMgr->SelectDeviceDlg(
              hWndParent,
              StiDeviceTypeDefault,
              0,
              &m_bstrDeviceID,
              &pRootItem
          );
  
          theApp.RestoreWaitCursor();
  
          if (hr != S_OK)
          {
              Trace(_T("SelectDeviceDlg HRESULT=%08x\n"), hr);
              return hr;
          }
  
  #ifndef USE_SELECTSOURCE_MENUITEM
  
          // forget the current selection
  
          m_bstrDeviceID.Empty();
  
  #endif //!USE_SELECTSOURCE_MENUITEM
  
          if (!SUCCEEDED(hr)) 
          {
              Trace(_T("CreateDevice HRESULT=%08x\n"), hr);
              return hr;
          }
      }
  
      // display the image selection dialog and let the user
      // select the item to be transferred
  
      CComPtrArray<IWiaItem> ppIWiaItem;
  
      hr = pRootItem->DeviceDlg(
          hWndParent,
          WIA_DEVICE_DIALOG_SINGLE_IMAGE,
          WIA_INTENT_NONE,
          &ppIWiaItem.ItemCount(),
          &ppIWiaItem
      );
  
      theApp.RestoreWaitCursor();
  
      if (hr != S_OK)
      {
          Trace(_T("DeviceDlg HRESULT=%08x\n"), hr);
          return hr;
      }
  
      if (ppIWiaItem.ItemCount() == 0)
      {
          Trace(_T("DeviceDlg returned 0 items\n"));
          return E_FAIL;
      }
  
      // set the image transfer properties; we want a DIB memory transfer
  
      TYMED tymed      = (TYMED) TYMED_CALLBACK;
      GUID  guidFormat = WiaImgFmt_MEMORYBMP;
  
      PROPSPEC    PropSpec[2]    = { 0 };
      PROPVARIANT PropVariant[2] = { 0 };
  
      PropSpec[0].ulKind   = PRSPEC_PROPID;
      PropSpec[0].propid   = WIA_IPA_TYMED;
      PropVariant[0].vt    = VT_I4;
      PropVariant[0].lVal  = tymed;
  
      PropSpec[1].ulKind   = PRSPEC_PROPID;
      PropSpec[1].propid   = WIA_IPA_FORMAT;
      PropVariant[1].vt    = VT_CLSID;
      PropVariant[1].puuid = &guidFormat;
  
      CComQIPtr<IWiaPropertyStorage> pWiaPropertyStorage(ppIWiaItem[0]);
  
      if (pWiaPropertyStorage == 0)
      {
          return E_NOINTERFACE;
      }
  
      hr = pWiaPropertyStorage->WriteMultiple(
          1,
          &(PropSpec[0]),
          &(PropVariant[0]),
          WIA_IPA_FIRST
      );
  
      if (hr != S_OK)
      {
          Trace(_T("WriteMultiple HRESULT=%08x\n"), hr);
          return hr;
      }
  
      hr = pWiaPropertyStorage->WriteMultiple(
          1,
          &(PropSpec[1]),
          &(PropVariant[1]),
          WIA_IPA_FIRST
      );
  
      if (hr != S_OK)
      {
          Trace(_T("WriteMultiple HRESULT=%08x\n"), hr);
          return hr;
      }
  
      // now, determine the transfer buffer size
  
      // 64k transfer size and double buffering seem to work fine;
      // a smaller buffer considerably slows down the memory transfer
      // and a larger buffer doesn't give much speed increase.
      // If the device minimum is larger than 64k though, use that size...
  
      LONG lBufferSize;
  
      hr = ReadPropertyLong(ppIWiaItem[0], WIA_IPA_MIN_BUFFER_SIZE, &lBufferSize);
  
      if (hr != S_OK || lBufferSize < 64*1024)
      {
          lBufferSize = 64*1024;
      }
  
      // setup the progress dialog 
  
      CComPtr<IWiaProgressDialog> pProgress;
  
      hr = CoCreateInstance( 
          CLSID_WiaDefaultUi, 
          0, 
          CLSCTX_INPROC_SERVER, 
          IID_IWiaProgressDialog, 
          (void**) &pProgress
      );
  
      if (hr != S_OK)
      {
          pProgress = new CProgressDialog;
      }
  
      LONG nDeviceType;
  
      hr = ReadPropertyLong(pRootItem, WIA_DIP_DEV_TYPE, &nDeviceType);
      
      if (hr != S_OK)
      {
          nDeviceType = 0;
      }
  
      LONG lAnimFlag;
  
      switch (GET_STIDEVICE_TYPE(nDeviceType))
      {
          case StiDeviceTypeScanner:
              lAnimFlag = WIA_PROGRESSDLG_ANIM_SCANNER_ACQUIRE;
              break;
  
          case StiDeviceTypeDigitalCamera:
              lAnimFlag = WIA_PROGRESSDLG_ANIM_CAMERA_ACQUIRE;
              break;
  
          case StiDeviceTypeStreamingVideo:
              lAnimFlag = WIA_PROGRESSDLG_ANIM_VIDEO_ACQUIRE;
              break;
  
          default:
              lAnimFlag = WIA_PROGRESSDLG_NO_ANIM;
              break;
      }
  
      pProgress->Create(hWndParent, lAnimFlag);
  
      CString strDownloading;
      strDownloading.LoadString(IDS_DOWNLOAD_IMAGE);
  
      USES_CONVERSION;
      pProgress->SetTitle(T2CW(strDownloading));
  
      pProgress->SetMessage(L"");
  
      pProgress->Show();
  
      // init the data callback interface
  
      CDataCallback *pDataCallback = new CDataCallback(pProgress);
  
      if (!pDataCallback)
      {
          theApp.SetMemoryEmergency(TRUE);
          return E_OUTOFMEMORY;
      }
  
      CComQIPtr<IWiaDataCallback> pIWiaDataCallback(pDataCallback);
  
      ASSERT(pIWiaDataCallback != 0);
  
      // initiate the transfer
  
      CComQIPtr<IWiaDataTransfer> pIWiaDataTransfer(ppIWiaItem[0]);
  
      if (pIWiaDataTransfer == 0)
      {
          return E_NOINTERFACE;
      }
  
      WIA_DATA_TRANSFER_INFO WiaDataTransferInfo = { 0 };
  
      WiaDataTransferInfo.ulSize        = sizeof(WIA_DATA_TRANSFER_INFO);
      WiaDataTransferInfo.ulBufferSize  = 2 * lBufferSize;
      WiaDataTransferInfo.bDoubleBuffer = TRUE;
  
      // This *easy* solution will cause the mspaint UI to freeze during
      // image transfer; this is possibly too long time to remain frozen.
      // So we will create a worker thread to do the data transfer.
      //
      //hr = pIWiaDataTransfer->idtGetBandedData(
      //    &WiaDataTransferInfo,
      //    pIWiaDataCallback
      //);
  
      EnableWindow(hWndParent, FALSE);
  
      hr = GetBandedData(CGetBandedDataThreadData(
          pIWiaDataTransfer,
          &WiaDataTransferInfo,
          pIWiaDataCallback
      ));
  
      EnableWindow(hWndParent, TRUE);
  
      // check if the user has pressed cancel
  
      if (pProgress)
      {
          BOOL bCancelled;
  
          if (pProgress->Cancelled(&bCancelled) == S_OK && bCancelled)
          {
              hr = S_FALSE;
          }
  
          pProgress->Destroy();
      }
  
      if (hr != S_OK)
      {
          Trace(_T("idtGetBandedData HRESULT=%08x\n"), hr);
          return hr;
      }
  
      // return the results
  
      pDataCallback->PrintTimes();
  
      *phDib = pDataCallback->GetBuffer();
  
      return S_OK;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  CWIAMgr::CGetBandedDataThreadData::CGetBandedDataThreadData(
      IWiaDataTransfer       *pIWiaDataTransfer,
      WIA_DATA_TRANSFER_INFO *pWiaDataTransferInfo,
      IWiaDataCallback       *pIWiaDataCallback
  ) :
      m_pIWiaDataTransfer(pIWiaDataTransfer),
      m_pWiaDataTransferInfo(pWiaDataTransferInfo),
      m_pIWiaDataCallback(pIWiaDataCallback)
  {
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT CWIAMgr::CGetBandedDataThreadData::Marshal()
  {
      HRESULT hr;
  
      // marshal the IWiaDataTransfer interface
  
      ASSERT(m_pIWiaDataTransfer != 0);
  
      hr = CoMarshalInterThreadInterfaceInStream(
          IID_IWiaDataTransfer,
          m_pIWiaDataTransfer,
          &m_pIWiaDataTransferStream
      );
  
      if (hr != S_OK)
      {
          Trace(_T("CoMarshalInterThreadInterfaceInStream HRESULT=%08x\n"), hr);
          return hr;
      }
  
      m_pIWiaDataTransfer.Release();
  
      // marshal the IWiaDataCallback interface
  
      ASSERT(m_pIWiaDataCallback != 0);
  
      hr = CoMarshalInterThreadInterfaceInStream(
          IID_IWiaDataCallback,
          m_pIWiaDataCallback,
          &m_pIWiaDataCallbackStream
      );
  
      if (hr != S_OK)
      {
          Trace(_T("CoMarshalInterThreadInterfaceInStream HRESULT=%08x\n"), hr);
          return hr;
      }
  
      m_pIWiaDataCallback.Release();
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT CWIAMgr::CGetBandedDataThreadData::Unmarshal()
  {
      HRESULT hr;
  
      // unmarshal the IWiaDataTransfer interface
  
      ASSERT(m_pIWiaDataTransferStream != 0);
  
      hr = CoGetInterfaceAndReleaseStream(
          m_pIWiaDataTransferStream,
          IID_IWiaDataTransfer,
          (void **) &m_pIWiaDataTransfer
      );
  
      // CoGetInterfaceAndReleaseStream should already have
      // released the stream pointer, so set it to zero so that
      // ~CGetBandedDataThreadData will not try to release it again
  
      m_pIWiaDataTransferStream.Detach();
  
      if (hr != S_OK)
      {
          Trace(_T("CoGetInterfaceAndReleaseStream HRESULT=%08x\n"), hr);
          return hr;
      }
  
      // unmarshal the IWiaDataCallback interface
  
      ASSERT(m_pIWiaDataCallbackStream != 0);
  
      hr = CoGetInterfaceAndReleaseStream(
          m_pIWiaDataCallbackStream,
          IID_IWiaDataCallback,
          (void **) &m_pIWiaDataCallback
      );
  
      m_pIWiaDataCallbackStream.Detach();
  
      if (hr != S_OK)
      {
          Trace(_T("CoGetInterfaceAndReleaseStream HRESULT=%08x\n"), hr);
          return hr;
      }
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT CWIAMgr::GetBandedData(CGetBandedDataThreadData &ThreadData)
  {
      // marshal the interface pointers before passing them to another thread
  
      HRESULT hr = ThreadData.Marshal();
  
      if (hr != S_OK)
      {
          return hr;
      }
  
      // fire up the new thread
  
      unsigned nThreadId;
  
      HANDLE hThread = (HANDLE) _beginthreadex(
          0,
          0,
          GetBandedDataThread,
          &ThreadData,
          0,
          &nThreadId
      );
  
      if (hThread == 0)
      {
          Trace(_T("CreateThread LastError=%08x\n"), GetLastError());
          return HRESULT_FROM_WIN32(GetLastError());
      }
  
      // enter a msg loop while waiting for the thread to complete;
      // this will keep the mspaint UI alive
  
      while (MsgWaitForMultipleObjects(1, &hThread, FALSE, INFINITE, QS_ALLINPUT) == WAIT_OBJECT_0+1)
      {
          MSG msg;
  
          while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
          {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
          }
  
          theApp.RestoreWaitCursor();
      }
  
      // if we reach here, the thread must have ended; get the result
  
      DWORD dwExitCode = S_FALSE;
  
      GetExitCodeThread(hThread, &dwExitCode);
  
      CloseHandle(hThread);
  
      ASSERT(sizeof(DWORD) >= sizeof(HRESULT));
      return (HRESULT) dwExitCode;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  unsigned WINAPI CWIAMgr::GetBandedDataThread(PVOID pVoid)
  {
      // Init COM for this thread
  
      HRESULT hr = CoInitialize(0);
  
      if (hr != S_OK)
      {
          Trace(_T("CoInitialize HRESULT=%08x\n"), hr);
          return (unsigned) hr;
      }
  
      CGetBandedDataThreadData *pThreadData = (CGetBandedDataThreadData *) pVoid;
  
      ASSERT(pThreadData != 0);
  
      if (pThreadData != 0)
      {
          // unmarshal the interface pointers before calling idtGetBandedData
  
          hr = pThreadData->Unmarshal();
  
          if (hr == S_OK)
          {
              hr = pThreadData->m_pIWiaDataTransfer->idtGetBandedData(
                  pThreadData->m_pWiaDataTransferInfo,
                  pThreadData->m_pIWiaDataCallback
              );
          }
      }
  
      CoUninitialize();
  
      ASSERT(sizeof(unsigned) >= sizeof(HRESULT));
      return (unsigned) hr;
  }
  
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  int CWIAMgr::NumDevices(HWND /*hWndParent*/)
  {
      return m_pEventCallback ? m_pEventCallback->GetNumDevices() : 0;
  }
  
  #ifdef USE_TWAIN
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  CTwainMgr::CTwainMgr()
  {
      m_TwainState = State_1_Pre_Session;
  
      // fill in the m_AppId struct with defaults
  
      m_AppId.Id = 0;
      m_AppId.Version.MajorNum = 1;
      m_AppId.Version.MinorNum = 0;
      m_AppId.Version.Language = TWLG_USA;
      m_AppId.Version.Country  = TWCY_USA;
      strcpy(m_AppId.Version.Info,  "FileDescription");
      m_AppId.ProtocolMajor   = TWON_PROTOCOLMAJOR;
      m_AppId.ProtocolMinor   = TWON_PROTOCOLMINOR;
      m_AppId.SupportedGroups = DG_IMAGE | DG_CONTROL;
      strcpy(m_AppId.Manufacturer,  "CompanyName");
      strcpy(m_AppId.ProductFamily, "ProductVersion");
      strcpy(m_AppId.ProductName,   "ProductName");
  
      // reset m_SrcId
  
      m_SrcId.Id = 0;
      m_SrcId.ProductName[0] = '\0';
  
      // Load TWAIN DLL
  
      m_hTwainDll = LoadLibrary(_T("TWAIN_32.DLL"));
  
      if (m_hTwainDll)
      {
          // Get the entry point
  
          m_DSM_Entry = (DSMENTRYPROC) GetProcAddress(m_hTwainDll, "DSM_Entry");
  
          if (m_DSM_Entry)
          {
              m_TwainState = State_2_Source_Manager_Loaded;
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  CTwainMgr::~CTwainMgr()
  {
      // Free the library if loaded
  
      if (m_TwainState >= State_1_Pre_Session)
      {
          FreeLibrary(m_hTwainDll);
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT
  CTwainMgr::SelectSource(
      HWND hWndParent,
      LONG /*lFlags*/
  )
  {
      HRESULT   hr = S_FALSE;
      TW_UINT16 rc = TWRC_FAILURE;
  
      if (m_TwainState >= State_2_Source_Manager_Loaded)
      {
          __try
          {
              if (m_TwainState == State_2_Source_Manager_Loaded)
              {
                  // Open the data source manager
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_PARENT,
                      MSG_OPENDSM,
                      (TW_MEMREF) &hWndParent
                  );
  
                  if (rc != TWRC_SUCCESS)
                  {
                      __leave;
                  }
  
                  m_TwainState = State_3_Source_Manager_Open;
              }
  
              // pop up the selection dialog
  
              rc = m_DSM_Entry(
                  &m_AppId,
                  0,
                  DG_CONTROL,
                  DAT_IDENTITY,
                  MSG_USERSELECT,
                  (TW_MEMREF) &m_SrcId
              );
  
              ASSERT(rc == TWRC_SUCCESS || rc == TWRC_CANCEL);
  
              if (rc == TWRC_SUCCESS)
              {
                  hr = S_OK;
              }
          }
          __finally
          {
              if (m_TwainState == State_3_Source_Manager_Open)
              {
                  // Close the data source manager
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_PARENT,
                      MSG_CLOSEDSM,
                      (TW_MEMREF) &hWndParent
                  );
  
                  ASSERT(rc == TWRC_SUCCESS);
  
                  m_TwainState = State_2_Source_Manager_Loaded;
              }
          }
      }
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT
  CTwainMgr::Select(
      LPCTSTR pDeviceId
  )
  {
  #ifdef UNICODE
      WideCharToMultiByte(CP_ACP, 0, pDeviceId, -1,
          m_SrcId.ProductName, sizeof(m_SrcId.ProductName), 0, 0);
  #else //UNICODE
      lstrcpyn(m_SrcId.ProductName, pDeviceId, sizeof(m_SrcId.ProductName));
  #endif //UNICODE
  
      return S_OK;
  }
  
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT
  CTwainMgr::Acquire(
      HWND     hWndParent,
      HGLOBAL *phDib
  )
  {
      ASSERT(phDib);
  
      HRESULT   hr = S_FALSE;
      TW_UINT16 rc = TWRC_FAILURE;
  
      if (m_TwainState >= State_2_Source_Manager_Loaded)
      {
          __try
          {
              if (m_TwainState == State_2_Source_Manager_Loaded)
              {
                  // Open the data source manager
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_PARENT,
                      MSG_OPENDSM,
                      (TW_MEMREF) &hWndParent
                  );
  
                  if (rc != TWRC_SUCCESS)
                  {
                      __leave;
                  }
  
                  m_TwainState = State_3_Source_Manager_Open;
              }
  
  #ifdef USE_SELECTSOURCE_MENUITEM
  
              if (m_SrcId.ProductName[0] == '\0')
              {
                  // if no data source is selected yet, get the default
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_IDENTITY,
                      MSG_GETDEFAULT,
                      (TW_MEMREF) &m_SrcId
                  );
  
                  if (rc != TWRC_SUCCESS)
                  {
                      __leave;
                  }
              }
  
  #else //USE_SELECTSOURCE_MENUITEM
  
              rc = m_DSM_Entry(
                  &m_AppId,
                  0,
                  DG_CONTROL,
                  DAT_IDENTITY,
                  MSG_USERSELECT,
                  (TW_MEMREF) &m_SrcId
              );
  
              ASSERT(rc == TWRC_SUCCESS || rc == TWRC_CANCEL);
  
              if (rc != TWRC_SUCCESS)
              {
                  __leave;
              }
  
  #endif //USE_SELECTSOURCE_MENUITEM
  
              if (m_TwainState == State_3_Source_Manager_Open)
              {
                  // open the data source
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_IDENTITY,
                      MSG_OPENDS,
                      (TW_MEMREF) &m_SrcId
                  );
  
                  if (rc != TWRC_SUCCESS)
                  {
                      __leave;
                  }
  
                  m_TwainState = State_4_Source_Open;
              }
  
              // set the desired transfer options;
              // we want to transfer a single 8-bit RGB image
  
              SetCapability(CAP_XFERCOUNT, TWTY_INT16, 1);
              SetCapability(ICAP_PIXELTYPE, TWTY_UINT32, TWPT_RGB);
              SetCapability(ICAP_BITDEPTH, TWTY_UINT32, 8);
  
              if (m_TwainState == State_4_Source_Open)
              {
                  // enable the data source
  
                  TW_USERINTERFACE twUI;
  
                  twUI.ShowUI  = TRUE;
                  twUI.hParent = hWndParent;
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      &m_SrcId,
                      DG_CONTROL,
                      DAT_USERINTERFACE,
                      MSG_ENABLEDS,
                      (TW_MEMREF) &twUI
                  );
  
                  theApp.RestoreWaitCursor();
  
                  if (rc != TWRC_SUCCESS)
                  {
                      __leave;
                  }
  
                  m_TwainState = State_5_Source_Enabled;
              }
  
              if (m_TwainState == State_5_Source_Enabled)
              {
                  // Disable the parent window
  
                  EnableWindow(hWndParent, FALSE);
  
                  // Enter the message loop to transfer the image
  
                  MSG   msg;
                  BOOL  bDone = FALSE;
  
                  while (!bDone && GetMessage(&msg, 0, 0, 0))
                  {
                      // process the event through TWAIN
  
                      TW_EVENT twEvent;
  
                      twEvent.pEvent    = &msg;
                      twEvent.TWMessage = MSG_NULL;
  
                      rc = m_DSM_Entry(
                          &m_AppId,
                          &m_SrcId,
                          DG_CONTROL,
                          DAT_EVENT,
                          MSG_PROCESSEVENT,
                          (TW_MEMREF) &twEvent
                      );
  
                      if (twEvent.TWMessage == MSG_CLOSEDSREQ)
                      {
                          bDone = TRUE;
                          hr = S_FALSE;
                      }
                      else if (twEvent.TWMessage == MSG_XFERREADY)
                      {
  
                          m_TwainState = State_6_Transfer_Ready;
  
                          TW_PENDINGXFERS twPendingXfers;
  
                          do
                          {
                              m_TwainState = State_7_Transferring;
  
                              rc = m_DSM_Entry(
                                  &m_AppId,
                                  &m_SrcId,
                                  DG_IMAGE,
                                  DAT_IMAGENATIVEXFER,
                                  MSG_GET,
                                  (TW_MEMREF) phDib
                              );
  
                              if (rc != TWRC_XFERDONE)
                              {
                                  if (*phDib)
                                  {
                                      GlobalFree(*phDib);
                                  }
  
                                  __leave;
                              }
  
                              hr = S_OK;
  
                              // End the transfer
  
                              rc = m_DSM_Entry(
                                  &m_AppId,
                                  &m_SrcId,
                                  DG_CONTROL,
                                  DAT_PENDINGXFERS,
                                  MSG_ENDXFER,
                                  (TW_MEMREF) &twPendingXfers
                              );
  
                              if (rc != TWRC_SUCCESS)
                              {
                                  __leave;
                              }
  
                              m_TwainState = State_6_Transfer_Ready;
  
                          } while (twPendingXfers.Count != 0);
  
                          m_TwainState = State_5_Source_Enabled;
  
                          //exit after a single image transfer
  
                          bDone = TRUE;
                      }
  
                      if (rc == TWRC_NOTDSEVENT)
                      {
                          TranslateMessage(&msg);
                          DispatchMessage(&msg);
                      }
                  }
              }
          }
          __finally
          {
              // enable the parent window upon exiting the message loop
  
              EnableWindow(hWndParent, TRUE);
  
              ASSERT(m_TwainState <= State_6_Transfer_Ready);
  
              if (m_TwainState == State_6_Transfer_Ready)
              {
                  TW_PENDINGXFERS twPendingXfers;
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      &m_SrcId,
                      DG_CONTROL,
                      DAT_PENDINGXFERS,
                      MSG_RESET,
                      (TW_MEMREF) &twPendingXfers
                  );
  
                  ASSERT(rc == TWRC_SUCCESS);
  
                  m_TwainState = State_5_Source_Enabled;
              }
  
              if (m_TwainState == State_5_Source_Enabled)
              {
                  TW_USERINTERFACE twUI;
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      &m_SrcId,
                      DG_CONTROL,
                      DAT_USERINTERFACE,
                      MSG_DISABLEDS,
                      (TW_MEMREF) &twUI
                  );
  
                  ASSERT(rc == TWRC_SUCCESS);
  
                  m_TwainState = State_4_Source_Open;
              }
  
              if (m_TwainState == State_4_Source_Open)
              {
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_IDENTITY,
                      MSG_CLOSEDS,
                      (TW_MEMREF) &m_SrcId
                  );
  
                  ASSERT(rc == TWRC_SUCCESS);
  
                  m_TwainState = State_3_Source_Manager_Open;
              }
  
              if (m_TwainState == State_3_Source_Manager_Open)
              {
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_PARENT,
                      MSG_CLOSEDSM,
                      (TW_MEMREF) &hWndParent
                  );
  
                  ASSERT(rc == TWRC_SUCCESS);
  
                  m_TwainState = State_2_Source_Manager_Loaded;
              }
          }
      }
  
      return hr;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  int CTwainMgr::NumDevices(HWND hWndParent)
  {
      return 1; // this is too slow, better lie...
  
      int nNumDevices = 0;
  
      TW_UINT16 rc = TWRC_FAILURE;
  
      // m_TwainState >= State_2 guarantees m_DSM_Entry != 0
  
      if (m_TwainState >= State_2_Source_Manager_Loaded)
      {
          __try
          {
              if (m_TwainState == State_2_Source_Manager_Loaded)
              {
                  // Open the data source manager
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_PARENT,
                      MSG_OPENDSM,
                      (TW_MEMREF) &hWndParent
                  );
  
                  if (rc != TWRC_SUCCESS)
                  {
                      __leave;
                  }
  
                  m_TwainState = State_3_Source_Manager_Open;
              }
  
              // Enumerate the devices one by one
  
              TW_IDENTITY SrcId;
  
              rc = m_DSM_Entry(
                  &m_AppId,
                  0,
                  DG_CONTROL,
                  DAT_IDENTITY,
                  MSG_GETFIRST,
                  (TW_MEMREF) &SrcId
              );
  
              while (rc == TWRC_SUCCESS)
              {
                  ++nNumDevices;
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_IDENTITY,
                      MSG_GETNEXT,
                      (TW_MEMREF) &SrcId
                  );
              }
          }
          __finally
          {
              if (m_TwainState == State_3_Source_Manager_Open)
              {
                  // Close the data source manager
  
                  rc = m_DSM_Entry(
                      &m_AppId,
                      0,
                      DG_CONTROL,
                      DAT_PARENT,
                      MSG_CLOSEDSM,
                      (TW_MEMREF) &hWndParent
                  );
  
                  ASSERT(rc == TWRC_SUCCESS);
  
                  m_TwainState = State_2_Source_Manager_Loaded;
              }
          }
      }
  
      return nNumDevices;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  TW_UINT16
  CTwainMgr::SetCapability(
      TW_UINT16 Cap,
      TW_UINT16 ItemType,
      TW_UINT32 Item
  )
  {
      TW_UINT16 rc = TWRC_FAILURE;
  
      TW_CAPABILITY twCapability;
  
      twCapability.Cap        = Cap;
      twCapability.ConType    = TWON_ONEVALUE;
      twCapability.hContainer = 0;
  
      twCapability.hContainer = GlobalAlloc(
          GMEM_MOVEABLE | GMEM_ZEROINIT,
          sizeof(TW_ONEVALUE)
      );
  
      if (twCapability.hContainer) 
      {
          pTW_ONEVALUE pVal = (pTW_ONEVALUE) GlobalLock(twCapability.hContainer);
  
          if (pVal) 
          {
              pVal->ItemType = ItemType;
              pVal->Item     = Item;
  
              GlobalUnlock(twCapability.hContainer);
  
              rc = m_DSM_Entry(
                  &m_AppId,
                  &m_SrcId,
                  DG_CONTROL,
                  DAT_CAPABILITY,
                  MSG_SET,
                  (TW_MEMREF) &twCapability
              );
          }
  
          GlobalFree(twCapability.hContainer);
      }
  
      return rc;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  TW_UINT16
  CTwainMgr::GetCapability(
      TW_UINT16   Cap,
      pTW_UINT16  pItemType,
      pTW_UINT32  pItem
  )
  {
      TW_CAPABILITY twCapability;
  
      twCapability.Cap        = Cap;
      twCapability.ConType    = TWON_DONTCARE16;
      twCapability.hContainer = 0;
  
      TW_UINT16 rc = m_DSM_Entry(
          &m_AppId,
          &m_SrcId,
          DG_CONTROL,
          DAT_CAPABILITY,
          MSG_GET,
          (TW_MEMREF) &twCapability
      );
  
      if (twCapability.hContainer) 
      {
          pTW_ONEVALUE pVal = (pTW_ONEVALUE) GlobalLock(twCapability.hContainer);
  
          if (pVal) 
          {
              if (pItemType) 
              {
                  *pItemType = pVal->ItemType;
              }
  
              if (pItem) 
              {
                  *pItem = pVal->Item;
              }
          }
  
          GlobalFree(twCapability.hContainer);
      }
  
      return rc;
  }
  
  #endif //USE_TWAIN
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT 
  WiaGetNumDevices(
      IWiaDevMgr *_pWiaDevMgr,
      ULONG      *pulNumDevices
  )
  {
      HRESULT hr;
  
      // Validate and initialize output parameters
  
      if (pulNumDevices == 0)
      {
          return E_POINTER;
      }
  
      *pulNumDevices = 0;
  
      // Create a connection to the local WIA device manager
  
      CComPtr<IWiaDevMgr> pWiaDevMgr = _pWiaDevMgr;
  
      if (pWiaDevMgr == 0)
      {
          hr = pWiaDevMgr.CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_ALL|CLSCTX_NO_FAILURE_LOG);
  
          if (!SUCCEEDED(hr))
          {
              return hr;
          }
      }
  
      // Get a list of all the WIA devices on the system
  
      CComPtr<IEnumWIA_DEV_INFO> pIEnumWIA_DEV_INFO;
  
      hr = pWiaDevMgr->EnumDeviceInfo(
          0,
          &pIEnumWIA_DEV_INFO
      );
  
      if (!SUCCEEDED(hr))
      {
          return hr;
      }
  
      // Get the number of WIA devices
  
      ULONG celt;
  
      hr = pIEnumWIA_DEV_INFO->GetCount(&celt);
  
      if (!SUCCEEDED(hr))
      {
          return hr;
      }
  
      *pulNumDevices = celt;
  
      return S_OK;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  // 
  //
  
  CEventCallback::CEventCallback()
  {
      m_cRef = 0;
      m_nNumDevices = 0;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  // 
  //
  
  HRESULT CEventCallback::Register()
  {
      HRESULT hr;
  
      // Create a connection to the local WIA device manager
  
      CComPtr<IWiaDevMgr> pWiaDevMgr;
  
      hr = pWiaDevMgr.CoCreateInstance(CLSID_WiaDevMgr, NULL, CLSCTX_ALL|CLSCTX_NO_FAILURE_LOG);
  
      if (!SUCCEEDED(hr))
      {
          return hr;
      }
  
      // Get the count of all the WIA devices on the system
  
      hr = WiaGetNumDevices(pWiaDevMgr, &m_nNumDevices);
  
      if (!SUCCEEDED(hr))
      {
          return hr;
      }
  
      // Register the callback interface
  
      hr = pWiaDevMgr->RegisterEventCallbackInterface(
          0,
          0,
          &WIA_EVENT_DEVICE_CONNECTED,
          this,
          &m_pConnectEventObject
      );
  
      if (!SUCCEEDED(hr))
      {
          return hr;
      }
  
      hr = pWiaDevMgr->RegisterEventCallbackInterface(
          0,
          0,
          &WIA_EVENT_DEVICE_DISCONNECTED,
          this,
          &m_pDisconnectEventObject
      );
  
      if (!SUCCEEDED(hr))
      {
          return hr;
      }
  
      return S_OK;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  ULONG CEventCallback::GetNumDevices() const
  {
      return m_nNumDevices;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  STDMETHODIMP CEventCallback::QueryInterface(REFIID iid, LPVOID *ppvObj)
  {
      if (ppvObj == 0)
      {
          return E_POINTER;
      }
  
      if (iid == IID_IUnknown)
      {
          AddRef();
          *ppvObj = (IUnknown *) this;
          return S_OK;
      }
  
      if (iid == IID_IWiaEventCallback)
      {
          AddRef();
          *ppvObj = (IWiaEventCallback *) this;
          return S_OK;
      }
  
      *ppvObj = 0;
      return E_NOINTERFACE;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  STDMETHODIMP_(ULONG) CEventCallback::AddRef()
  {
      return InterlockedIncrement(&m_cRef);
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  STDMETHODIMP_(ULONG) CEventCallback::Release()
  {
      ASSERT( 0 != m_cRef );
      ULONG cRef = InterlockedDecrement(&m_cRef);
      if (cRef == 0)
      {
          delete this;
      }
      return cRef;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  STDMETHODIMP CEventCallback::ImageEventCallback(
      LPCGUID pEventGuid,
      BSTR    bstrEventDescription,
      BSTR    bstrDeviceID,
      BSTR    bstrDeviceDescription,
      DWORD   dwDeviceType,
      BSTR    bstrFullItemName,
      ULONG  *pulEventType,
      ULONG   ulReserved
  )
  {
      return WiaGetNumDevices(0, &m_nNumDevices);
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  CDataCallback::CDataCallback(IWiaProgressDialog *pProgress)
  {
      m_cRef        = 0;
      m_hBuffer     = 0;
      m_lBufferSize = 0;
      m_lDataSize   = 0;
      m_pProgress   = pProgress;
  
  #ifdef DBG
      m_hDumpFile = CreateFile(_T("wiadump.bin"), GENERIC_WRITE, 
          FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
      m_TimeDeviceBegin.QuadPart  = 0;
      m_TimeDeviceEnd.QuadPart    = 0;
      m_TimeProcessBegin.QuadPart = 0;
      m_TimeProcessEnd.QuadPart   = 0;
      m_TimeClientBegin.QuadPart  = 0;
      m_TimeClientEnd.QuadPart    = 0;
  #endif //DBG
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  CDataCallback::~CDataCallback()
  {
      if (m_hBuffer)
      {
          GlobalFree(m_hBuffer);
      }
  
  #ifdef DBG
      CloseHandle(m_hDumpFile);
  #endif //DBG
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HGLOBAL CDataCallback::GetBuffer()
  {
      HGLOBAL hBuffer = m_hBuffer;
  
      m_hBuffer     = 0;
      m_lBufferSize = 0;
      m_lDataSize   = 0;
  
      return hBuffer;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  STDMETHODIMP CDataCallback::QueryInterface(REFIID iid, LPVOID *ppvObj)
  {
      if (ppvObj == 0)
      {
          return E_POINTER;
      }
  
      if (iid == IID_IUnknown)
      {
          AddRef();
          *ppvObj = (IUnknown*) this;
          return S_OK;
      }
  
      if (iid == IID_IWiaDataCallback)
      {
          AddRef();
          *ppvObj = (IWiaDataCallback *) this;
          return S_OK;
      }
  
      *ppvObj = 0;
      return E_NOINTERFACE;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  STDMETHODIMP_(ULONG) CDataCallback::AddRef()
  {
      return InterlockedIncrement(&m_cRef);
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  STDMETHODIMP_(ULONG) CDataCallback::Release()
  {
      ASSERT( 0 != m_cRef );
      ULONG cRef = InterlockedDecrement(&m_cRef);
  
      if (cRef == 0)
      {
          delete this;
      }
  
      return cRef;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  STDMETHODIMP CDataCallback::BandedDataCallback(
      LONG  lReason,
      LONG  lStatus,
      LONG  lPercentComplete,
      LONG  lOffset,
      LONG  lLength,
      LONG  lReserved,
      LONG  lResLength,
      PBYTE pbBuffer
  )
  {
      HRESULT hr;
  
      Trace(
          _T("DataCallback: Reason=%d Stat=%d %%=%d Offset=%d Length=%d Buf=%p\n"),
          lReason, lStatus, lPercentComplete, lOffset, lLength, pbBuffer
      );
  
      // check if the user has pressed cancel
  
      BOOL bCancelled;
  
      if (m_pProgress && m_pProgress->Cancelled(&bCancelled) == S_OK && bCancelled)
      {
          return S_FALSE;
      }
  
      switch (lReason)
      {
          case IT_MSG_DATA_HEADER:
          {
              // allocate memory for the image if the size is given in the header
  
              PWIA_DATA_CALLBACK_HEADER pHeader = (PWIA_DATA_CALLBACK_HEADER) pbBuffer;
  
              if (pHeader && pHeader->lBufferSize)
              {
                  hr = ReAllocBuffer(pHeader->lBufferSize);
  
                  if (hr != S_OK)
                  {
                      return S_FALSE;
                  }
              }
          
              break;
          }
  
          case IT_MSG_DATA:
          {
              QueryStartTimes(lStatus, lPercentComplete);
  
              UpdateStatus(lStatus, lPercentComplete);
  
              // if the buffer is not allocated yet and this is the first block, 
              // try to allocate a buffer according to the bitmap header info
  
              if (m_lBufferSize == 0 && lOffset == 0)
              {
                  LONG lBufferSize = FindDibSize(pbBuffer);
  
                  if (lBufferSize)
                  {
                      hr = ReAllocBuffer(lBufferSize);
  
                      if (hr != S_OK)
                      {
                          return S_FALSE;
                      }
                  }
              }
  
              // if the transfer goes past the buffer, try to expand it
  
              if (lOffset + lLength > m_lBufferSize)
              {
                  hr = ReAllocBuffer(lOffset + 2*lLength);
  
                  if (hr != S_OK)
                  {
                      return S_FALSE;
                  }
              }
  
              // keep track of data size
  
              if (lOffset + lLength > m_lDataSize)
              {
                  m_lDataSize = lOffset + lLength;
              }
  
              // copy the transfer buffer
  
              PBYTE pBuffer = (PBYTE) GlobalLock(m_hBuffer);
  
              if (pBuffer)
              {
                  CopyMemory(pBuffer + lOffset, pbBuffer, lLength);
  
                  GlobalUnlock(m_hBuffer);
              }
  
  #ifdef DBG
              DWORD nWritten;
              SetFilePointer(m_hDumpFile, lOffset, 0, FILE_BEGIN);
              WriteFile(m_hDumpFile, pbBuffer, lLength, &nWritten, 0);
  #endif //DBG
  
              QueryStopTimes(lStatus, lPercentComplete);
  
              break;
          }
  
          case IT_MSG_STATUS:
          {
              // update the progress bar position
  
              QueryStartTimes(lStatus, lPercentComplete);
  
              UpdateStatus(lStatus, lPercentComplete);
  
              QueryStopTimes(lStatus, lPercentComplete);
  
              break;
          }
  
          case IT_MSG_TERMINATION:
          {
              PVOID pBuffer = GlobalLock(m_hBuffer);
  
              if (pBuffer)
              {
                  FixDibHeader(pBuffer, m_lDataSize);
  
                  GlobalUnlock(m_hBuffer);
              }
  
              break;
          }
  
          case IT_MSG_NEW_PAGE:
          {
              // mspaint should not get this message, but...
  
              PVOID pBuffer = GlobalLock(m_hBuffer);
  
              if (pBuffer)
              {
                  FixDibHeader(pBuffer, m_lDataSize);
  
                  GlobalUnlock(m_hBuffer);
              }
  
              break;
          }
      }
  
      return S_OK;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  HRESULT CDataCallback::ReAllocBuffer(LONG lBufferSize)
  {
      // try to allocate the new buffer
  
      Trace(_T("Allocating %d bytes for image data\n"), lBufferSize);
  
      HGLOBAL hBuffer;
  
      if (m_hBuffer == 0)
      {
          hBuffer = (PBYTE) GlobalAlloc(GMEM_MOVEABLE, lBufferSize);
      }
      else
      {
          hBuffer = (PBYTE) GlobalReAlloc(m_hBuffer, lBufferSize, 0);
      }
  
      if (hBuffer == 0)
      {
          theApp.SetMemoryEmergency(TRUE);
          return S_FALSE;
      }
  
      // store this new buffer
  
      m_hBuffer = hBuffer;
  
      m_lBufferSize = lBufferSize;
  
      return S_OK;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  inline ULONG LineWidth(ULONG nWidth, ULONG nBitCount)
  {
      return (((nWidth * nBitCount) + 31) & ~31) >> 3;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  ULONG FindDibSize(LPCVOID pDib)
  {
      ULONG nHeaderSize = *(PDWORD)pDib;
  
      // Do we recognize the header?
  
      if (nHeaderSize != sizeof(BITMAPCOREHEADER) &&
          nHeaderSize != sizeof(BITMAPINFOHEADER) &&
          nHeaderSize != sizeof(BITMAPV4HEADER)   &&
          nHeaderSize != sizeof(BITMAPV5HEADER))
      {
          return 0;
      }
  
      // Start the calculation with the header size
  
      ULONG nDibSize = nHeaderSize;
  
      // is this an old style BITMAPCOREHEADER?
  
      if (nHeaderSize == sizeof(BITMAPCOREHEADER))
      {
          PBITMAPCOREHEADER pbmch = (PBITMAPCOREHEADER) pDib;
  
          // Add the color table size
  
          if (pbmch->bcBitCount <= 8)
          {
              nDibSize += sizeof(RGBTRIPLE) * (1 << pbmch->bcBitCount);
          }
  
          // Add the bitmap size
  
          nDibSize += LineWidth(pbmch->bcWidth, pbmch->bcBitCount) * pbmch->bcHeight;
      }
      else
      {
          // this is at least a BITMAPINFOHEADER
  
          PBITMAPINFOHEADER pbmih = (PBITMAPINFOHEADER) pDib;
  
          // Add the color table size
  
          if (pbmih->biClrUsed != 0)
          {
              nDibSize += sizeof(RGBQUAD) * pbmih->biClrUsed;
          }
          else if (pbmih->biBitCount <= 8)
          {
              nDibSize += sizeof(RGBQUAD) * (1 << pbmih->biBitCount);
          }
  
          // Add the bitmap size
  
          if (pbmih->biSizeImage != 0)
          {
              nDibSize += pbmih->biSizeImage;
          }
          else
          {
              // biSizeImage must be specified for compressed bitmaps
  
              if (pbmih->biCompression != BI_RGB &&
                  pbmih->biCompression != BI_BITFIELDS)
              {
                  return 0;
              }
  
              nDibSize += LineWidth(pbmih->biWidth, pbmih->biBitCount) * abs(pbmih->biHeight);
          }
  
          // Consider special cases
  
          if (nHeaderSize == sizeof(BITMAPINFOHEADER))
          {     
              // If this is a 16 or 32 bit bitmap and BI_BITFIELDS is used, 
              // bmiColors member contains three DWORD color masks.
              // For V4 or V5 headers, this info is included the header
  
              if (pbmih->biCompression == BI_BITFIELDS)
              {
                  nDibSize += 3 * sizeof(DWORD);
              }
          }
          else if (nHeaderSize >= sizeof(BITMAPV5HEADER))
          {
              // If this is a V5 header and an ICM profile is specified,
              // we need to consider the profile data size
              
              PBITMAPV5HEADER pbV5h = (PBITMAPV5HEADER) pDib;
  
              // if there is some padding before the profile data, add it
  
              if (pbV5h->bV5ProfileData > nDibSize)
              {
                  nDibSize = pbV5h->bV5ProfileData;
              }
  
              // add the profile data size
  
              nDibSize += pbV5h->bV5ProfileSize;
          }
      }
  
      return nDibSize;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  ULONG FindDibOffBits(LPCVOID pDib)
  {
      ULONG nHeaderSize = *(PDWORD)pDib;
  
      // Do we recognize the header?
  
      if (nHeaderSize != sizeof(BITMAPCOREHEADER) &&
          nHeaderSize != sizeof(BITMAPINFOHEADER) &&
          nHeaderSize != sizeof(BITMAPV4HEADER)   &&
          nHeaderSize != sizeof(BITMAPV5HEADER))
      {
          return 0;
      }
  
      // Start the calculation with the header size
  
      ULONG nOffBits = nHeaderSize;
  
      // is this an old style BITMAPCOREHEADER?
  
      if (nHeaderSize == sizeof(BITMAPCOREHEADER))
      {
          PBITMAPCOREHEADER pbmch = (PBITMAPCOREHEADER) pDib;
  
          // Add the color table size
  
          if (pbmch->bcBitCount <= 8)
          {
              nOffBits += sizeof(RGBTRIPLE) * (1 << pbmch->bcBitCount);
          }
      }
      else
      {
          // this is at least a BITMAPINFOHEADER
  
          PBITMAPINFOHEADER pbmih = (PBITMAPINFOHEADER) pDib;
  
          // Add the color table size
  
          if (pbmih->biClrUsed != 0)
          {
              nOffBits += sizeof(RGBQUAD) * pbmih->biClrUsed;
          }
          else if (pbmih->biBitCount <= 8)
          {
              nOffBits += sizeof(RGBQUAD) * (1 << pbmih->biBitCount);
          }
  
          // Consider special cases
  
          if (nHeaderSize == sizeof(BITMAPINFOHEADER))
          {     
              // If this is a 16 or 32 bit bitmap and BI_BITFIELDS is used, 
              // bmiColors member contains three DWORD color masks.
              // For V4 or V5 headers, this info is included the header
  
              if (pbmih->biCompression == BI_BITFIELDS)
              {
                  nOffBits += 3 * sizeof(DWORD);
              }
          }
          else if (nHeaderSize >= sizeof(BITMAPV5HEADER))
          {
              // If this is a V5 header and an ICM profile is specified,
              // we need to consider the profile data size
              
              PBITMAPV5HEADER pbV5h = (PBITMAPV5HEADER) pDib;
  
              // if the profile data comes before the pixel data, add it
  
              if (pbV5h->bV5ProfileData <= nOffBits)
              {
                  nOffBits += pbV5h->bV5ProfileSize;
              }
          }
      }
  
      return nOffBits;
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  void FixDibHeader(LPVOID pDib, DWORD dwSize)
  {
      ULONG nHeaderSize = *(PDWORD)pDib;
  
      // Do we recognize the header?
  
      if (nHeaderSize != sizeof(BITMAPCOREHEADER) &&
          nHeaderSize != sizeof(BITMAPINFOHEADER) &&
          nHeaderSize != sizeof(BITMAPV4HEADER)   &&
          nHeaderSize != sizeof(BITMAPV5HEADER))
      {
          return;
      }
  
      // is this an old style BITMAPCOREHEADER?
  
      if (nHeaderSize == sizeof(BITMAPCOREHEADER))
      {
          PBITMAPCOREHEADER pbmch = (PBITMAPCOREHEADER) pDib;
  
          // fix the height value if necessary
  
          if (pbmch->bcHeight == 0)
          {
              // start the calculation with the header size
  
              DWORD dwSizeImage = dwSize - nHeaderSize;
  
              // subtract the color table size
  
              if (pbmch->bcBitCount <= 8)
              {
                  dwSizeImage -= sizeof(RGBTRIPLE) * (1 << pbmch->bcBitCount);
              }
  
              // calculate the height
  
              pbmch->bcHeight = (WORD) (dwSizeImage / LineWidth(pbmch->bcWidth, pbmch->bcBitCount));
          }
      }
      else
      {
          // this is at least a BITMAPINFOHEADER
  
          PBITMAPINFOHEADER pbmih = (PBITMAPINFOHEADER) pDib;
  
          // fix the height value if necessary
  
          if (pbmih->biHeight == 0)
          {
              // find the size of the image data
  
              DWORD dwSizeImage;
  
              if (pbmih->biSizeImage != 0)
              {
                  // if the size is specified in the header, take it
  
                  dwSizeImage = pbmih->biSizeImage;
              }
              else
              {
                  // start the calculation with the header size
  
                  dwSizeImage = dwSize - nHeaderSize;
  
                  // subtract the color table size
  
                  if (pbmih->biClrUsed != 0)
                  {
                      dwSizeImage -= sizeof(RGBQUAD) * pbmih->biClrUsed;
                  }
                  else if (pbmih->biBitCount <= 8)
                  {
                      dwSizeImage -= sizeof(RGBQUAD) * (1 << pbmih->biBitCount);
                  }
  
                  // Consider special cases
  
                  if (nHeaderSize == sizeof(BITMAPINFOHEADER))
                  {     
                      // If this is a 16 or 32 bit bitmap and BI_BITFIELDS is used, 
                      // bmiColors member contains three DWORD color masks.
                      // For V4 or V5 headers, this info is included the header
  
                      if (pbmih->biCompression == BI_BITFIELDS)
                      {
                          dwSizeImage -= 3 * sizeof(DWORD);
                      }
                  }
                  else if (nHeaderSize >= sizeof(BITMAPV5HEADER))
                  {
                      // If this is a V5 header and an ICM profile is specified,
                      // we need to consider the profile data size
              
                      PBITMAPV5HEADER pbV5h = (PBITMAPV5HEADER) pDib;
  
                      // add the profile data size
  
                      dwSizeImage -= pbV5h->bV5ProfileSize;
                  }
  
                  // store the image size
  
                  pbmih->biSizeImage = dwSizeImage;
              }
  
              // finally, calculate the height
  
              pbmih->biHeight = -(LONG) (dwSizeImage / LineWidth(pbmih->biWidth, pbmih->biBitCount));
          }
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  void CDataCallback::UpdateStatus(LONG lStatus, LONG lPercentComplete)
  {
      if (m_pProgress)
      {
          m_pProgress->SetPercentComplete(lPercentComplete);
  
          CString strFormat;
  
          switch (lStatus)
          {
              case IT_STATUS_TRANSFER_FROM_DEVICE:
                  strFormat.LoadString(IDS_STATUS_TRANSFER_FROM_DEVICE);
                  break;
  
              case IT_STATUS_PROCESSING_DATA:
                  strFormat.LoadString(IDS_STATUS_PROCESSING_DATA);
                  break;
  
              case IT_STATUS_TRANSFER_TO_CLIENT:
                  strFormat.LoadString(IDS_STATUS_TRANSFER_TO_CLIENT);
                  break;
          }
  
          CString strStatusText;
          strStatusText.Format(strFormat, lPercentComplete);
  
          USES_CONVERSION;
          m_pProgress->SetMessage(T2CW(strStatusText));
      }
  }
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  #ifdef DBG
  
  void CDataCallback::QueryStartTimes(LONG lStatus, LONG  lPercentComplete)
  {
      if (lStatus & IT_STATUS_TRANSFER_FROM_DEVICE &&
          (lPercentComplete == 0 || m_TimeDeviceBegin.QuadPart == 0))
      {
          QueryPerformanceCounter(&m_TimeDeviceBegin);
      }
  
      if (lStatus & IT_STATUS_PROCESSING_DATA &&
          (lPercentComplete == 0 || m_TimeProcessBegin.QuadPart == 0))
      {
          QueryPerformanceCounter(&m_TimeProcessBegin);
      }
  
      if (lStatus & IT_STATUS_TRANSFER_TO_CLIENT &&
          (lPercentComplete == 0 || m_TimeClientBegin.QuadPart == 0))
      {
          QueryPerformanceCounter(&m_TimeClientBegin);
      }
  }
  
  void CDataCallback::QueryStopTimes(LONG lStatus, LONG  lPercentComplete)
  {
      if (lStatus & IT_STATUS_TRANSFER_FROM_DEVICE && lPercentComplete == 100)
      {
          QueryPerformanceCounter(&m_TimeDeviceEnd);
      }
  
      if (lStatus & IT_STATUS_PROCESSING_DATA && lPercentComplete == 100)
      {
          QueryPerformanceCounter(&m_TimeProcessEnd);
      }
  
      if (lStatus & IT_STATUS_TRANSFER_TO_CLIENT && lPercentComplete == 100)
      {
          QueryPerformanceCounter(&m_TimeClientEnd);
      }
  }
  
  void CDataCallback::PrintTimes()
  {
      LARGE_INTEGER Freq;
      QueryPerformanceFrequency(&Freq);
  
      double nTimeDevice =
          (double) (m_TimeDeviceEnd.QuadPart - m_TimeDeviceBegin.QuadPart) /
          (double) Freq.QuadPart;
  
      double nTimeProcess =
          (double) (m_TimeProcessEnd.QuadPart - m_TimeProcessBegin.QuadPart) /
          (double) Freq.QuadPart;
  
      double nTimeClient =
          (double) (m_TimeClientEnd.QuadPart - m_TimeClientBegin.QuadPart) /
          (double) Freq.QuadPart;
  
      Trace(
          _T("TRANSFER_FROM_DEVICE = %.02lf secs\n")
          _T("PROCESSING_DATA      = %.02lf secs\n")
          _T("TRANSFER_TO_CLIENT   = %.02lf secs\n")
          _T("\n"),
          nTimeDevice,
          nTimeProcess,
          nTimeClient
      );
  }
  
  #else //DBG
  
  inline void CDataCallback::QueryStartTimes(LONG, LONG)
  {
  }
  
  inline void CDataCallback::QueryStopTimes(LONG, LONG)
  {
  }
  
  inline void CDataCallback::PrintTimes()
  {
  }
  
  #endif //DBG
  
  //////////////////////////////////////////////////////////////////////////
  //
  //
  //
  
  CProgressDialog::CProgressDialog()
  {
      m_cRef = 0;
  }
  
  CProgressDialog::~CProgressDialog()
  {
      // remove the "downloading..." message from the status bar
  
      if (g_pStatBarWnd)
      {
          g_pStatBarWnd->SetPaneText(0, _T(""));
      }
  }
  
  STDMETHODIMP CProgressDialog::QueryInterface(REFIID iid, LPVOID *ppvObj)
  {
      if (ppvObj == 0)
      {
          return E_POINTER;
      }
  
      if (iid == IID_IUnknown)
      {
          AddRef();
          *ppvObj = (IUnknown*) this;
          return S_OK;
      }
  
      if (iid == IID_IWiaProgressDialog)
      {
          AddRef();
          *ppvObj = (IWiaProgressDialog *) this;
          return S_OK;
      }
  
      *ppvObj = 0;
      return E_NOINTERFACE;
  }
  
  STDMETHODIMP_(ULONG) CProgressDialog::AddRef()
  {
      return InterlockedIncrement(&m_cRef);
  }
  
  STDMETHODIMP_(ULONG) CProgressDialog::Release()
  {
      ASSERT( 0 != m_cRef );
      LONG cRef = InterlockedDecrement(&m_cRef);
  
      if (cRef == 0)
      {
          delete this;
      }
  
      return cRef;
  }
  
  STDMETHODIMP CProgressDialog::Create(HWND hwndParent, LONG lFlags)
  {
      if (g_pStatBarWnd)
      {
          RECT r;
          g_pStatBarWnd->GetItemRect(1, &r);
  
          m_ProgressCtrl.Create(WS_CHILD | WS_VISIBLE, r, g_pStatBarWnd, 1);
          m_ProgressCtrl.SetRange(0, 100);
      }
  
      return S_OK;
  }
  
  STDMETHODIMP CProgressDialog::Show()
  {
      m_ProgressCtrl.UpdateWindow();
  
      return S_OK;
  }
  
  STDMETHODIMP CProgressDialog::Hide()
  {
      return S_OK;
  }
  
  STDMETHODIMP CProgressDialog::Cancelled(BOOL *pbCancelled)
  {
      *pbCancelled = FALSE;
  
      return S_OK;
  }
  
  STDMETHODIMP CProgressDialog::SetTitle(LPCWSTR pszMessage)
  {
      return S_OK;
  }
  
  STDMETHODIMP CProgressDialog::SetMessage(LPCWSTR pszTitle)
  {
      if (g_pStatBarWnd)
      {
          USES_CONVERSION;
          g_pStatBarWnd->SetPaneText(0, W2CT(pszTitle));
      }
  
      return S_OK;
  }
  
  STDMETHODIMP CProgressDialog::SetPercentComplete(UINT nPercent)
  {
      m_ProgressCtrl.SetPos(nPercent);
  
      return S_OK;
  }
  
  STDMETHODIMP CProgressDialog::Destroy()
  {
      return S_OK;
  }
  
  