/// <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.");
            }
        }