diff --git a/NTwain.Net35/NTwain.Net35.csproj b/NTwain.Net35/NTwain.Net35.csproj index f54b7a1..7e75245 100644 --- a/NTwain.Net35/NTwain.Net35.csproj +++ b/NTwain.Net35/NTwain.Net35.csproj @@ -41,6 +41,7 @@ + @@ -87,6 +88,9 @@ IDataSource.cs + + ImageTools.cs + IMemoryManager.cs @@ -105,18 +109,12 @@ Internals\IWinMessageFilter.cs - - Internals\MESSAGE.cs - Internals\TentativeStateCommitable.cs Internals\TransferLogic.cs - - Internals\UnsafeNativeMethods.cs - Internals\WindowsHook.cs @@ -126,6 +124,21 @@ Internals\WrappedManualResetEvent.cs + + Interop\BITMAPINFO.cs + + + Interop\BITMAPINFOHEADER.cs + + + Interop\MESSAGE.cs + + + Interop\NativeMethods.cs + + + Interop\UnsafeNativeMethods.cs + IPlatformInfo.cs diff --git a/NTwain/DataTransferredEventArgs.cs b/NTwain/DataTransferredEventArgs.cs index cb61889..f3e93dd 100644 --- a/NTwain/DataTransferredEventArgs.cs +++ b/NTwain/DataTransferredEventArgs.cs @@ -1,8 +1,14 @@ using NTwain.Data; +using NTwain.Internals; +using NTwain.Interop; using System; using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; using System.Globalization; using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Permissions; namespace NTwain { @@ -131,5 +137,29 @@ namespace NTwain } return Enumerable.Empty(); } + + + /// + /// Gets the bitmap from the if it's an image. + /// + /// + public Bitmap GetNativeBitmap() + { + Bitmap image = null; + if (NativeData != IntPtr.Zero) + { + if (PlatformInfo.Current.IsWindows) + { + image = ImageTools.ReadBitmapImage(NativeData); + } + else if (PlatformInfo.Current.IsLinux) + { + image = ImageTools.ReadTiffImage(NativeData); + } + } + return image; + } + + } } \ No newline at end of file diff --git a/NTwain/ImageTools.cs b/NTwain/ImageTools.cs new file mode 100644 index 0000000..d45c46b --- /dev/null +++ b/NTwain/ImageTools.cs @@ -0,0 +1,119 @@ +using Microsoft.Win32.SafeHandles; +using NTwain.Interop; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using System.Windows.Media.Imaging; + +namespace NTwain +{ + public static class ImageTools + { + internal static Bitmap ReadBitmapImage(IntPtr dibBitmap) + { + Bitmap finalImg = null; + Bitmap tempImg = null; + if (IsDib(dibBitmap)) + { + try + { + var header = (BITMAPINFOHEADER)Marshal.PtrToStructure(dibBitmap, typeof(BITMAPINFOHEADER)); + + if (header.Validate()) + { + PixelFormat format = header.GetDrawingPixelFormat(); + tempImg = new Bitmap(header.biWidth, Math.Abs(header.biHeight), header.GetStride(), format, header.GetScan0(dibBitmap)); + ColorPalette pal = header.GetDrawingPalette(dibBitmap); + if (pal != null) + { + tempImg.Palette = pal; + } + float xdpi = header.GetXDpi(); + float ydpi = header.GetYDpi(); + if (xdpi != 0 && ydpi == 0) + { + ydpi = xdpi; + } + else if (ydpi != 0 && xdpi == 0) + { + xdpi = ydpi; + } + if (xdpi != 0) + { + tempImg.SetResolution(xdpi, ydpi); + } + if (header.IsBottomUpImage) + { + tempImg.RotateFlip(RotateFlipType.RotateNoneFlipY); + } + finalImg = tempImg; + tempImg = null; + } + } + finally + { + if (tempImg != null) + { + tempImg.Dispose(); + } + } + } + return finalImg; + } + + static bool IsDib(IntPtr dibBitmap) + { + // a quick check not guaranteed correct, + // compare first byte to size of struct (which is also the first field) + var test = Marshal.ReadInt32(dibBitmap); + // should be 40 + return test == BITMAPINFOHEADER.GetByteSize(); + } + + /// + /// Converts a to WPF . + /// + /// The image to convert. + /// + public static BitmapSource ConvertToWpfBitmap(this Bitmap image) + { + if (image != null) + { + using (var hbm = new SafeHBitmapHandle(image.GetHbitmap(), true)) + { + return System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap( + hbm.DangerousGetHandle(), + IntPtr.Zero, + System.Windows.Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + } + } + return null; + } + + class SafeHBitmapHandle : SafeHandleZeroOrMinusOneIsInvalid + { + [SecurityCritical] + public SafeHBitmapHandle(IntPtr preexistingHandle, bool ownsHandle) + : base(ownsHandle) + { + SetHandle(preexistingHandle); + } + + protected override bool ReleaseHandle() + { + return NativeMethods.DeleteObject(handle); + } + } + + internal static Bitmap ReadTiffImage(IntPtr data) + { + return null; + } + } +} diff --git a/NTwain/Internals/WinMemoryManager.cs b/NTwain/Internals/WinMemoryManager.cs index 8f0df2e..50b1ae9 100644 --- a/NTwain/Internals/WinMemoryManager.cs +++ b/NTwain/Internals/WinMemoryManager.cs @@ -1,4 +1,5 @@ using NTwain.Data; +using NTwain.Interop; using System; using System.ComponentModel; diff --git a/NTwain/Internals/WindowsHook.cs b/NTwain/Internals/WindowsHook.cs index 51c55c8..ed686c5 100644 --- a/NTwain/Internals/WindowsHook.cs +++ b/NTwain/Internals/WindowsHook.cs @@ -1,4 +1,5 @@ -using System; +using NTwain.Interop; +using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Windows.Interop; diff --git a/NTwain/Interop/BITMAPINFO.cs b/NTwain/Interop/BITMAPINFO.cs new file mode 100644 index 0000000..2969996 --- /dev/null +++ b/NTwain/Interop/BITMAPINFO.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Runtime.InteropServices; +using System.Drawing.Imaging; +using System.Drawing; + +namespace NTwain.Interop +{ + // this is a good read + // http://atlc.sourceforge.net/bmp.html + + /// + /// Defines the dimensions and color information for a DIB. + /// + [StructLayout(LayoutKind.Sequential)] + public struct BITMAPINFO + { + /// + /// Structure that contains information about the dimensions of color format. + /// + public BITMAPINFOHEADER bmiHeader; + /// + /// This contains one of the following: + /// 1. An array of RGBQUAD. The elements of the array that make up the color table. + /// 2. An array of 16-bit unsigned integers that specifies indexes into the currently realized logical palette. This use of bmiColors is allowed for functions that use DIBs. + /// The number of entries in the array depends on the values of the biBitCount and biClrUsed members of the BITMAPINFOHEADER structure. + /// + public IntPtr bmiColors; + + }; +} diff --git a/NTwain/Interop/BITMAPINFOHEADER.cs b/NTwain/Interop/BITMAPINFOHEADER.cs new file mode 100644 index 0000000..c742438 --- /dev/null +++ b/NTwain/Interop/BITMAPINFOHEADER.cs @@ -0,0 +1,310 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; +using System.Text; + +namespace NTwain.Interop +{ + /// + /// Structure that contains information about the dimensions and color format of a DIB. + /// + [StructLayout(LayoutKind.Sequential)] + public struct BITMAPINFOHEADER + { + #region fields + /// + /// The number of bytes required by the structure. + /// + public uint biSize; + /// + /// The width of the bitmap, in pixels. + /// If Compression is JPEG or PNG, the Width member specifies the width of the decompressed + /// JPEG or PNG image file, respectively. + /// + public int biWidth; + /// + /// The height of the bitmap, in pixels. If Height is positive, + /// the bitmap is a bottom-up DIB and its origin is the lower-left corner. + /// If Height is negative, the bitmap is a top-down DIB and its origin is the upper-left corner. + /// If Height is negative, indicating a top-down DIB, Compression must be either RGB or BITFIELDS. Top-down DIBs cannot be compressed. + /// If Compression is JPEG or PNG, the Height member specifies the height of the decompressed JPEG or PNG image file, respectively. + /// + public int biHeight; + /// + /// The number of planes for the target device. This value must be set to 1. + /// + public ushort biPlanes; + /// + /// The number of bits-per-pixel. The BitCount member + /// determines the number of bits that define each pixel and the maximum number of colors in the bitmap. + /// + public ushort biBitCount; + /// + /// The type of compression for a compressed bottom-up bitmap (top-down DIBs cannot be compressed). + /// + public CompressionType biCompression; + /// + /// The size, in bytes, of the image. This may be set to zero for RGB bitmaps. + /// If Compression is JPEG or PNG, SizeImage indicates the size of the JPEG or PNG image buffer, respectively. + /// + public uint biSizeImage; + /// + /// The horizontal resolution, in pixels-per-meter, of the target device for the bitmap. + /// An application can use this value to select a bitmap from a resource group that + /// best matches the characteristics of the current device. + /// + public int biXPelsPerMeter; + /// + /// The vertical resolution, in pixels-per-meter, of the target device for the bitmap. + /// + public int biYPelsPerMeter; + /// + /// The number of color indexes in the color table that are actually used by the bitmap. + /// If this value is zero, the bitmap uses the maximum number of colors corresponding to + /// the value of the BitCount member for the compression mode specified by Compression. + /// + public uint biClrUsed; + /// + /// The number of color indexes that are required for displaying the bitmap. + /// If this value is zero, all colors are required. + /// + public uint biClrImportant; + #endregion + + #region utilities + + const double METER_INCH_RATIO = 39.3700787; + + /// + /// Gets the horizontal dpi of the bitmap. + /// + /// + public float GetXDpi() + { + return (float)Math.Round(biXPelsPerMeter / METER_INCH_RATIO, 0); + } + /// + /// Gets the vertical dpi of the bitmap. + /// + /// + public float GetYDpi() + { + return (float)Math.Round(biYPelsPerMeter / METER_INCH_RATIO, 0); + } + /// + /// Gets the size of the structure. + /// + /// + public static uint GetByteSize() + { + return (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER)); + } + /// + /// Checks to see if this structure contain valid data. + /// It also fills in any missing pieces if possible. + /// + /// + public bool Validate() + { + if (biHeight != 0 && biWidth != 0 && biBitCount != 0) + { + if (biSize == 0) + { + biSize = GetByteSize(); + } + if (biClrUsed == 0) + { + switch (biBitCount) + { + case 1: + biClrUsed = 2; + break; + case 4: + biClrUsed = 16; + break; + case 8: + biClrUsed = 256; + break; + } + } + if (biSizeImage == 0) + { + biSizeImage = (uint)(((( + biWidth * biBitCount) + 31) & ~31) >> 3) * (uint)Math.Abs(biHeight); + } + + return true; + } + return false; + } + + /// + /// Gets the pointer to scan0 given the header pointer. + /// + /// The header PTR. + /// + public IntPtr GetScan0(IntPtr headerPtr) + { + int p = (int)biClrUsed; + if ((p == 0) && (biBitCount <= 8)) + { + p = 1 << biBitCount; + } + p = (p * 4) + (int)biSize + headerPtr.ToInt32(); + return new IntPtr(p); + } + + /// + /// Gets whether the bitmap is bottom-up or top-down format. + /// + /// + /// true if this instance is bottom up image; otherwise, false. + /// + /// + public bool IsBottomUpImage + { + get + { + return biHeight > 0; + } + } + + + /// + /// Gets the System.Drawing pixel format of current structure. + /// + /// + public PixelFormat GetDrawingPixelFormat() + { + switch (biBitCount) + { + case 1: + return PixelFormat.Format1bppIndexed; + case 4: + return PixelFormat.Format4bppIndexed; + case 8: + return PixelFormat.Format8bppIndexed; + case 16: + return PixelFormat.Format16bppRgb565; + case 24: + return PixelFormat.Format24bppRgb; + case 32: + return PixelFormat.Format32bppRgb; + case 48: + return PixelFormat.Format48bppRgb; + } + return PixelFormat.DontCare; + } + /// + /// Gets the color palette that's contained in the header. + /// Note not all images will have palette, so check if the return value + /// is null before using it. + /// + /// + public ColorPalette GetDrawingPalette(IntPtr headerPtr) + { + //if (format == PixelFormat.Format8bppIndexed) + //{ + // // update color palette to grayscale version + // ColorPalette grayPallet = bitmap.Palette; + // for (int i = 0; i < grayPallet.Entries.Length; i++) + // { + // grayPallet.Entries[i] = Color.FromArgb(i, i, i); + // } + // bitmap.Palette = grayPallet; // this is what makes the gray pallet take effect + //} + + if (biClrUsed > 0) + { + byte[] data = new byte[biClrUsed * 4]; + Marshal.Copy(new IntPtr(headerPtr.ToInt32() + biSize), data, 0, data.Length); + var dummy = new System.Drawing.Bitmap(1, 1, GetDrawingPixelFormat()); + ColorPalette pal = dummy.Palette; + dummy.Dispose(); + int index = 0; + int setCount = data.Length / 4; + for (int i = 0; i < setCount; i++) + { + index = i * 4; + pal.Entries[i] = Color.FromArgb(data[index + 2], data[index + 1], data[index]); + } + return pal; + } + return null; + } + + /// + /// Gets the stride size of this bitmap. + /// + /// + public int GetStride() + { + int bitsPerRow = (biBitCount * biWidth); + int strideTest = bitsPerRow / 8 + (bitsPerRow % 8 != 0 ? 1 : 0); + int overage = strideTest % 4; + if (overage > 0) + { + strideTest += (4 - overage); + } + return strideTest; + } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return new StringBuilder().Append("BitmapInfoHeader:") + .Append("\r\n\tSize = " + biSize) + .Append("\r\n\tWidth = " + biWidth) + .Append("\r\n\tHeight = " + biHeight) + .Append("\r\n\tPlanes = " + biPlanes) + .Append("\r\n\tBitCount = " + biBitCount) + .Append("\r\n\tCompression = " + biCompression) + .Append("\r\n\tSizeImage = " + biSizeImage) + .Append("\r\n\tXPixelsPerMeter = " + biXPelsPerMeter) + .Append("\r\n\tYPixelsPerMeter = " + biYPelsPerMeter) + .Append("\r\n\tColorUsed = " + biClrUsed) + .Append("\r\n\tColorImportant = " + biClrImportant).ToString(); + } + #endregion + + /// + /// Indicates the bitmap compression of . + /// + public enum CompressionType : uint + { + /// + /// An uncompressed format. + /// + BI_RGB = 0, + /// + /// A run-length encoded (RLE) format for bitmaps with 8 bpp. The compression format is a 2-byte format consisting of a count byte followed by a byte containing a color index. For more information, see Bitmap Compression. + /// + BI_RLE8 = 1, + /// + /// An RLE, format for bitmaps with 4 bpp. The compression format is a 2-byte format consisting of a count byte followed by two word-length color indexes. For more information, see Bitmap Compression. + /// + BI_RLE4 = 2, + /// + /// Specifies that the bitmap is not compressed and that the color table consists of three DWORD color masks that specify the red, green, and blue components of each pixel. + /// This is valid when used with 16- and 32-bpp bitmaps. + /// + BI_BITFIELDS = 3, + /// + /// Indicates that the image is a JPEG image. + /// + BI_JPEG = 4, + /// + /// Indicates that the image is a PNG image. + /// + BI_PNG = 5 + } + }; + +} diff --git a/NTwain/Internals/MESSAGE.cs b/NTwain/Interop/MESSAGE.cs similarity index 96% rename from NTwain/Internals/MESSAGE.cs rename to NTwain/Interop/MESSAGE.cs index 1facde2..5cec9f6 100644 --- a/NTwain/Internals/MESSAGE.cs +++ b/NTwain/Interop/MESSAGE.cs @@ -4,10 +4,8 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; -namespace NTwain.Internals +namespace NTwain.Interop { - - /// /// The MSG structure in Windows for TWAIN use. /// diff --git a/NTwain/Interop/NativeMethods.cs b/NTwain/Interop/NativeMethods.cs new file mode 100644 index 0000000..7743e06 --- /dev/null +++ b/NTwain/Interop/NativeMethods.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace NTwain.Interop +{ + static class NativeMethods + { + [DllImport("gdi32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool DeleteObject(IntPtr hObject); + + [DllImport("user32.dll")] + public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + } +} diff --git a/NTwain/Internals/UnsafeNativeMethods.cs b/NTwain/Interop/UnsafeNativeMethods.cs similarity index 80% rename from NTwain/Internals/UnsafeNativeMethods.cs rename to NTwain/Interop/UnsafeNativeMethods.cs index d24f772..21ea7b8 100644 --- a/NTwain/Internals/UnsafeNativeMethods.cs +++ b/NTwain/Interop/UnsafeNativeMethods.cs @@ -2,7 +2,7 @@ using System.Runtime.InteropServices; using System.Security; -namespace NTwain.Internals +namespace NTwain.Interop { [SuppressUnmanagedCodeSecurity] static class UnsafeNativeMethods @@ -24,11 +24,4 @@ namespace NTwain.Internals #endregion } - - static class NativeMethods - { - - [DllImport("user32.dll")] - public static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - } } diff --git a/NTwain/NTwain.csproj b/NTwain/NTwain.csproj index e94e8c9..413cf2d 100644 --- a/NTwain/NTwain.csproj +++ b/NTwain/NTwain.csproj @@ -50,6 +50,7 @@ + @@ -66,17 +67,21 @@ + - + + + + - + diff --git a/NTwain/TwainSessionInternal.cs b/NTwain/TwainSessionInternal.cs index b182de8..46d43d1 100644 --- a/NTwain/TwainSessionInternal.cs +++ b/NTwain/TwainSessionInternal.cs @@ -1,5 +1,6 @@ using NTwain.Data; using NTwain.Internals; +using NTwain.Interop; using NTwain.Triplets; using System; using System.Collections.Generic; @@ -231,7 +232,7 @@ namespace NTwain IntPtr msgPtr = IntPtr.Zero; try { - var winMsg = new NTwain.Internals.MESSAGE(hwnd, msg, wParam, lParam); + var winMsg = new MESSAGE(hwnd, msg, wParam, lParam); // no need to do another lock call when using marshal alloc msgPtr = Marshal.AllocHGlobal(Marshal.SizeOf(winMsg)); diff --git a/README.md b/README.md index a0ccbee..1e6523a 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,9 @@ TwainSession class provides many events, but these 2 are the most important * TransferReady - fired before a transfer occurs. You can cancel the current transfer or all subsequent transfers using the event object. * DataTransferred - fired after the transfer has occurred. The data available depends on -what you've specified using the TWAIN API before starting the transfer. +what you've specified using the TWAIN API before starting the transfer. If using image +native transfer, the event arg provides a quick GetNativeBitmap() method to convert the +data to a System.Drawing.Bitmap. NOTE: do not try to close the source/session in the handler of these 2 events or something unpredictable will happen. Either let the scan run its course or cancel the scan using the flag @@ -137,9 +139,9 @@ session.Close(); Caveats -------------------------------------- -At the moment this lib does not provide ways to parse transferred image data and require -consumers to do the conversion themselves. The winform project contains one such -example for handling DIB image pointer in native transfer using the CommonWin32 lib. +At the moment the DataTransferredEventArgs only provides conversion routine to +a Bitmap image when using native transfer. +If other transfer methods are used you'll have to deal with them yourself. If you just call session.Open() without passing a message loop hook argument, an internal message loop will be started behind the scenes. When this happens the session events may be raised from another thread. diff --git a/Tests/Tester.WPF/Tester.WPF.csproj b/Tests/Tester.WPF/Tester.WPF.csproj index fb7d17d..2963a2c 100644 --- a/Tests/Tester.WPF/Tester.WPF.csproj +++ b/Tests/Tester.WPF/Tester.WPF.csproj @@ -77,6 +77,7 @@ + ..\..\packages\MvvmLightLibs.5.0.2.0\lib\net40\System.Windows.Interactivity.dll diff --git a/Tests/Tester.WPF/ViewModels/TwainVM.cs b/Tests/Tester.WPF/ViewModels/TwainVM.cs index 26675b5..33ccf7c 100644 --- a/Tests/Tester.WPF/ViewModels/TwainVM.cs +++ b/Tests/Tester.WPF/ViewModels/TwainVM.cs @@ -289,7 +289,7 @@ namespace Tester.WPF BitmapSource img = null; if (e.NativeData != IntPtr.Zero) { - img = e.NativeData.GetWPFBitmap(); + img = e.GetNativeBitmap().ConvertToWpfBitmap(); } else if (!string.IsNullOrEmpty(e.FileDataPath)) { diff --git a/Tests/Tester.Winform/TestForm.cs b/Tests/Tester.Winform/TestForm.cs index 767aea2..cc73bc6 100644 --- a/Tests/Tester.Winform/TestForm.cs +++ b/Tests/Tester.Winform/TestForm.cs @@ -1,5 +1,4 @@ -using CommonWin32; -using NTwain; +using NTwain; using NTwain.Data; using System; using System.Diagnostics; @@ -91,7 +90,7 @@ namespace Tester.Winform Bitmap img = null; if (e.NativeData != IntPtr.Zero) { - img = e.NativeData.GetDrawingBitmap(); + img = e.GetNativeBitmap(); } else if (!string.IsNullOrEmpty(e.FileDataPath)) { diff --git a/Tests/Tester.Winform/Tester.Winform.csproj b/Tests/Tester.Winform/Tester.Winform.csproj index 125cb95..d9cf963 100644 --- a/Tests/Tester.Winform/Tester.Winform.csproj +++ b/Tests/Tester.Winform/Tester.Winform.csproj @@ -38,10 +38,6 @@ scanner.ico - - False - ..\..\packages\CommonWin32.2.0.5.5\lib\net35-Client\CommonWin32.dll - @@ -70,7 +66,6 @@ True - SettingsSingleFileGenerator Settings.Designer.cs diff --git a/Tests/Tester.Winform/packages.config b/Tests/Tester.Winform/packages.config deleted file mode 100644 index 23005ef..0000000 --- a/Tests/Tester.Winform/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file