/// <summary> /// This operation is used to initiate the transfer of an image from the Source to the application via the /// Buffered Memory transfer mechanism. /// This operation supports the transfer of successive blocks of image data (in strips or,optionally, /// tiles) from the Source into one or more main memory transfer buffers. These buffers(for strips) /// are allocated and owned by the application. For tiled transfers, the source allocates the buffers. /// The application should repeatedly invoke this operation while TWRC_SUCCESS is returned by the Source. /// </summary> /// <param name="length">The length.</param> /// <param name="isMemFile">If set to <c>true</c> that transfer a MemFile.</param> /// <returns> /// Information about transmitting data. /// </returns> protected override ImageMemXfer OnImageMemXfer(long length, bool isMemFile) { // return new image 100x100 if (this._imageData == null || this._imageData.IsXferDone) { this._imageData = new ImageMemXfer { Columns = 100, Rows = 5, BytesPerRow = 3 * 100, XOffset = 0, YOffset = 0, Compression = TwCompression.None, ImageData = new byte[3 * 100 * 5], IsXferDone = false }; } else { this._imageData.YOffset += 5; this._imageData.IsXferDone = this._imageData.YOffset + this._imageData.Rows == this.XferEnvironment.ImageInfo.ImageLength; } return(this._imageData); }
/// <summary> /// 把imageMemXfer.Memory內的記憶體向右移動 /// </summary> /// <param name="imageMemXfer"></param> /// <param name="bytePerPixel"></param> private byte[] ShiftPixels(ref ImageMemXfer imageMemXfer, int bytePerPixel) { /// 參考: /// https://stackoverflow.com/questions/2185944/why-must-stride-in-the-system-drawing-bitmap-constructor-be-a-multiple-of-4 /// https://stackoverflow.com/questions/47918451/creating-a-bitmap-from-rgb-array-in-c-result-image-difference/ byte[] managedArray = new byte[(int)imageMemXfer.Memory.Length]; Marshal.Copy(imageMemXfer.Memory.TheMem, managedArray, 0, (int)imageMemXfer.Memory.Length); int bytesPerRow = (int)imageMemXfer.BytesPerRow; byte[] newArray = null; int? skipByte = null; if (bytesPerRow % 4 != 0) { skipByte = 4 - (bytesPerRow % 4); int newArrayLength = (int)imageMemXfer.Memory.Length + skipByte.Value * (int)imageMemXfer.Rows; newArray = new byte[newArrayLength]; } //for (int i = 0; i < imageMemXfer.Rows; i++) //{ // managedArray[0 + i * bytesPerRow] = 0; // managedArray[1 + i * bytesPerRow] = 255; // managedArray[2 + i * bytesPerRow] = 0; // managedArray[bytesPerRow - 3 + i * bytesPerRow] = 255; // managedArray[bytesPerRow - 2 + i * bytesPerRow] = 0; // managedArray[bytesPerRow - 1 + i * bytesPerRow] = 255; //} //Marshal.Copy(managedArray, 0, imageMemXfer.Memory.TheMem, (int)imageMemXfer.Memory.Length); //return managedArray; for (int i = 0; i < imageMemXfer.Rows; i++) { // get a single row byte[] rowArray = new byte[bytesPerRow]; Buffer.BlockCopy(managedArray, i * bytesPerRow, rowArray, 0, bytesPerRow); // right shift 3 bytes by i byte[] tempArray = new byte[bytesPerRow]; int shiftAmount = i * bytePerPixel; //for (int j = 0; j < bytesPerRow; j++) //{ // each byte in rowArray[] is moved by i*bytePerPixel //tempArray[(j + shiftAmount) % bytesPerRow] = rowArray[j]; //} /*// copy the last shiftAmount pixels from previous line, to this line's tail * if (i > 0) * { * int srcOffset = i * bytesPerRow - shiftAmount; * int destOffset = bytesPerRow - shiftAmount; * Buffer.BlockCopy(managedArray, srcOffset, tempArray, destOffset, shiftAmount); * }*/ //// fill with a color //for (int j = 0; j < bytesPerRow; j++) //{ // int color1 = (i * 2) % 255; // tempArray[j] = (byte)color1; //} // copy back if (skipByte.HasValue) { Buffer.BlockCopy(rowArray, 0, newArray, i * (bytesPerRow + skipByte.Value), bytesPerRow); } else { Buffer.BlockCopy(rowArray, 0, managedArray, i * bytesPerRow, bytesPerRow); } } /*// test the border * int rows = (int)imageMemXfer.Rows; * // 左上角 * managedArray[0] = 0; * managedArray[1] = 255; * managedArray[2] = 0; * // 第二排第一個 * managedArray[0+ bytesPerRow] = 255; * managedArray[1+ bytesPerRow] = 255; * managedArray[2+ bytesPerRow] = 255; * // 第二排第二個 * managedArray[3 + bytesPerRow] = 0; * managedArray[4 + bytesPerRow] = 0; * managedArray[5 + bytesPerRow] = 0; * // 第一排最後一個 * managedArray[bytesPerRow - 1] = 255; * managedArray[bytesPerRow - 2] = 0; * managedArray[bytesPerRow - 3] = 0; * // 最後一排第一個 * managedArray[0 + bytesPerRow * (rows - 1)] = 0; * managedArray[1 + bytesPerRow * (rows - 1)] = 0; * managedArray[2 + bytesPerRow * (rows - 1)] = 255; * // 最後一排最後一個 red * managedArray[(int)imageMemXfer.Memory.Length-1] = 0; * managedArray[(int)imageMemXfer.Memory.Length-2] = 0; * managedArray[(int)imageMemXfer.Memory.Length-3] = 255;*/ if (skipByte.HasValue) { Marshal.Copy(newArray, 0, imageMemXfer.Memory.TheMem, newArray.Length); } else { Marshal.Copy(managedArray, 0, imageMemXfer.Memory.TheMem, (int)imageMemXfer.Memory.Length); } //for (int i = 0; i < imageMemXfer.Rows; i++) //{ // // fill with a color // //managedArray[i] = (byte)((i * 2) % 255); // managedArray[0 + i * bytesPerRow] = 0; // managedArray[1 + i * bytesPerRow] = 255; // managedArray[2 + i * bytesPerRow] = 0; // managedArray[bytesPerRow - 3 + i * bytesPerRow] = 255; // managedArray[bytesPerRow - 2 + i * bytesPerRow] = 0; // managedArray[bytesPerRow - 1 + i * bytesPerRow] = 255; //} if (skipByte.HasValue) { return(newArray); } else { return(managedArray); } }
protected void TransferPicturesIncremental() { // see http://www.twain.org/wp-content/uploads/2017/03/TWAIN-2.4-Specification.pdf // page 4-20 Console.WriteLine("TransferPicturesIncremental..."); Logger.WriteLog(LOG_LEVEL.LL_NORMAL_LOG, "TransferPicturesIncremental..."); if (DataSource.SourceId.Id == 0) { return; } PendingXfers pendingTransfer = new PendingXfers(); TwainResult result; try { int recievedBlockCount = 1; do { pendingTransfer.Count = 0; // the Twain source will fill this in during DsPendingTransfer Console.WriteLine("Get the image info..."); // Get the image info ImageInfo imageInfo = new ImageInfo(); result = Twain32Native.DsImageInfo( ApplicationId, DataSource.SourceId, DataGroup.Image, DataArgumentType.ImageInfo, Message.Get, imageInfo); if (result != TwainResult.Success) { DataSource.Close(); break; } /*Console.WriteLine("Get the image layout..."); * ImageLayout imageLayout = new ImageLayout(); * result = Twain32Native.DsImageLayout( * ApplicationId, * DataSource.SourceId, * DataGroup.Image, * DataArgumentType.ImageLayout, * Message.GetCurrent, * imageLayout); * * if (result != TwainResult.Success) * { * DataSource.Close(); * break; * }*/ // Setup Destination Bitmap Bitmap bitmap = BitmapRenderer.NewBitmapForImageInfo(imageInfo); Console.WriteLine("Setup incremental Memory XFer..."); // Setup incremental Memory XFer SetupMemXfer setupMemXfer = new SetupMemXfer(); result = Twain32Native.DsSetupMemXfer( ApplicationId, DataSource.SourceId, DataGroup.Control, DataArgumentType.SetupMemXfer, Message.Get, setupMemXfer ); if (result != TwainResult.Success) { DataSource.Close(); break; } Console.WriteLine("allocate the preferred buffer size..."); // allocate the preferred buffer size // see twain spec pdf, page 4-21 ImageMemXfer imageMemXfer = new ImageMemXfer(); try { imageMemXfer.Memory.Flags = MemoryFlags.AppOwns | MemoryFlags.Pointer; imageMemXfer.Memory.Length = setupMemXfer.MinBufSize; // 對於A8 scanner,Preferred = MaxBufSize,太大了,所以我們選小一點的 imageMemXfer.Memory.TheMem = Kernel32Native.GlobalAlloc(GlobalAllocFlags.MemFixed, (int)setupMemXfer.MinBufSize * 2); // 不知道為什麼原本她size寫要*2倍 imageMemXfer.Compression = Compression.None; if (imageMemXfer.Memory.TheMem == IntPtr.Zero) { Logger.WriteLog(LOG_LEVEL.LL_SERIOUS_ERROR, "error allocating buffer for memory transfer"); throw new TwainException("error allocating buffer for memory transfer"); } long pixels_written = 0; long total_pixels = imageInfo.ImageWidth * imageInfo.ImageLength; do { // perform a transfer result = Twain32Native.DsImageMemXfer( ApplicationId, DataSource.SourceId, DataGroup.Image, DataArgumentType.ImageMemXfer, Message.Get, imageMemXfer ); //string savePath = @"C:\Users\Tenny\Pictures\TwainTest\tempBitmap_"; //savePath += i.ToString() + @".bmp"; if (result == TwainResult.Success || result == TwainResult.XferDone) { // dibArray是這次Buffer的RGB陣列 byte[] dibArray = ShiftPixels(ref imageMemXfer, imageInfo.BitsPerPixel / 8); BitmapRenderer.TransferPixels(bitmap, imageInfo, imageMemXfer); pixels_written += (imageMemXfer.BytesWritten * 8) / imageInfo.BitsPerPixel; double percent_complete = (double)pixels_written / (double)total_pixels; if (result == TwainResult.XferDone) { percent_complete = 1.0; // 算出空白區域的高度,裁切尾端部分 int blankHeight = GetCropHeight(bitmap); if (blankHeight > 0 && blankHeight < imageInfo.ImageLength) { bitmap = cropImage(bitmap, blankHeight); } } // fire the transfer event TransferImageEventArgs args = new TransferImageEventArgs(bitmap, result != TwainResult.XferDone, (float)percent_complete); TransferImage(this, args); if (!args.ContinueScanning) { result = TwainResult.XferDone; } } recievedBlockCount++; } while (result == TwainResult.Success); } finally { if (imageMemXfer.Memory.TheMem != IntPtr.Zero) { Kernel32Native.GlobalFree(imageMemXfer.Memory.TheMem); imageMemXfer.Memory.TheMem = IntPtr.Zero; } } // End pending transfers result = Twain32Native.DsPendingTransfer( ApplicationId, DataSource.SourceId, DataGroup.Control, DataArgumentType.PendingXfers, Message.EndXfer, pendingTransfer); if (result != TwainResult.Success) { DataSource.Close(); break; } }while (pendingTransfer.Count != 0); } finally { // Reset any pending transfers result = Twain32Native.DsPendingTransfer( ApplicationId, DataSource.SourceId, DataGroup.Control, DataArgumentType.PendingXfers, Message.Reset, pendingTransfer); Logger.WriteLog(LOG_LEVEL.LL_NORMAL_LOG, "TransferPicturesIncremental...done."); } }