|
I really appreciate what you did to wrap the twainlib class. I have a question, I would like to know how to call TransferPictures asynchronously so that the class that contains TwainMan doesn't have to wait on the scanner to acquire picture, we would just wait for the ImageScanned event. I've been playing around with it and can't figure out how to do it. The TransferPictures fails on:
rc = DSiinf( appid, srcds, TwDG.Image, TwDAT.ImageInfo, TwMSG.Get, iinf );
Any help would be appreciated,
Jose
|
|
|
|
|
Hi
Well Actually that's the way this class should work...
you call Scan() , the scanner scans, and when it's done you get the event...
how do you use the class?
greets
m@u
|
|
|
|
|
I call Scan(), but if the TransferPictures function(that is called in WndProc) is not on another thread the UI is not updated during transfer process so the form "locks up" during image scanning, which is something I would like to avoid aesthetically. I tried some type of Multi-threading but I couldn't get it to work. I think it has to do with the windows handle of the calling application(or thread).
Any ideas would help.
|
|
|
|
|
ok..
i think you could get it to work with something like this:
<br />
using System;<br />
using System.Threading;<br />
using System.Collections<br />
public class MTScan<br />
{<br />
private Thread scanThread;<br />
TwainMan scanner;<br />
public MTScan()<br />
{<br />
scanThread = new Thread(startScanApp);<br />
}<br />
private void startScanApp()<br />
{<br />
Application.Idle = new EventHandler(Application_Idle);<br />
Application.Start();<br />
}<br />
private void Application_Idle(object sender, EventArgs e)<br />
{<br />
scanner = new TwainMan();<br />
scanner.ImageScanned += new ImageScannedEventHandler(scanner_ImageScanned);<br />
}<br />
private void scanner_ImageScanned(object sender, ImageScannedEventArgs e)<br />
{<br />
onImageScanned(e);<br />
}<br />
protected virtual void onImageScanned(ImageScannedEventArgs e)<br />
{<br />
if (ImageScanned != null)<br />
{<br />
ImageScanned(this, e);<br />
}<br />
}<br />
public void Select()<br />
{<br />
scanner.Invoke(new ThreadStart(scanner.Select));<br />
}<br />
public void Scan()<br />
{<br />
scanner.Invoke(new ThreadStart(scanner.Scan));<br />
}<br />
public void Dispose()<br />
{<br />
scanner.Invoke(new ThreadStart(dispose));<br />
}<br />
private void dispose()<br />
{<br />
scanner.Dispose();<br />
Application.ExitThread();<br />
}<br />
}<br />
public class ImageScannedEventArgs:EventArgs<br />
{<br />
private Bitmap image;<br />
public Bitmap Image<br />
{<br />
get<br />
{<br />
return image;<br />
}<br />
}<br />
public ImageScannedEventArgs(Bitmap Image)<br />
{<br />
image = Image;<br />
}<br />
}<br />
public delegate void ImageScannedEventHandler(object sender, ImageScannedEventArgs e);<br />
[StructLayout(LayoutKind.Sequential, Pack=2)]<br />
internal class BITMAPINFOHEADER<br />
{<br />
public int biSize;<br />
public int biWidth;<br />
public int biHeight;<br />
public short biPlanes;<br />
public short biBitCount;<br />
public int biCompression;<br />
public int biSizeImage;<br />
public int biXPelsPerMeter;<br />
public int biYPelsPerMeter;<br />
public int biClrUsed;<br />
public int biClrImportant;<br />
}<br />
public class TwainMan:NativeWindow,IDisposable<br />
{<br />
private class InvokeHolder<br />
{<br />
private Delegate target;<br />
private object[] args;<br />
public InvokeHolder(Delegate Target, object[] Args)<br />
{<br />
target = Target;<br />
args = Args;<br />
}<br />
public void Run()<br />
{<br />
target.DynamicInvoke(args);<br />
}<br />
}<br />
[DllImport("kernel32.dll", ExactSpelling=true)]<br />
internal static extern IntPtr GlobalLock( IntPtr handle );<br />
[DllImport("kernel32.dll", ExactSpelling=true)]<br />
internal static extern IntPtr GlobalFree( IntPtr handle );<br />
private const int WM_USER = 0x400;<br />
private const int WM_Invoke = WM_USER+5;<br />
[DllImport("user32.dll", EntryPoint="PostThreadMessageA")]<br />
public static extern int PostThreadMessage ( <br />
int idThread,<br />
int msg,<br />
int wParam,<br />
int lParam);<br />
private Twain tw;<br />
private BITMAPINFOHEADER bmi;<br />
private Rectangle bmprect;<br />
private bool ready = false;<br />
public event ImageScannedEventHandler ImageScanned;<br />
private int threadID;<br />
private Queue delegateQueue;<br />
public bool Ready<br />
{<br />
get<br />
{<br />
return ready;<br />
}<br />
}<br />
public TwainMan()<br />
{<br />
CreateParams cp = new CreateParams();<br />
cp.Parent = new IntPtr(-3);<br />
cp.Caption = "";<br />
this.CreateHandle(cp);<br />
tw = new Twain();<br />
tw.Init(Handle);<br />
threadID = AppDomain.GetCurrentThreadId();<br />
delegateQueue = new Queue();<br />
}<br />
public void Invoke(Delegate Target, params object[] args)<br />
{<br />
delegateQueue.Enqueue(new InvokeHolder(Target, args);<br />
PostThreadMessage(threadID, WM_Invoke,0,0);<br />
}<br />
public void Select()<br />
{<br />
ready = tw.Select();<br />
}<br />
public void Scan()<br />
{<br />
if (ready)<br />
{<br />
tw.Acquire();<br />
}<br />
}<br />
protected IntPtr GetPixelInfo( IntPtr bmpptr )<br />
{<br />
bmi = new BITMAPINFOHEADER();<br />
Marshal.PtrToStructure( bmpptr, bmi );<br />
bmprect = Rectangle.Empty;<br />
bmprect.X = bmprect.Y = 0;<br />
bmprect.Width = bmi.biWidth;<br />
bmprect.Height = bmi.biHeight;<br />
<br />
if( bmi.biSizeImage == 0 )<br />
bmi.biSizeImage = ((((bmi.biWidth * bmi.biBitCount) + 31) & ~31) >> 3) * bmi.biHeight;<br />
<br />
int p = bmi.biClrUsed;<br />
if( (p == 0) && (bmi.biBitCount <= 8) )<br />
p = 1 << bmi.biBitCount;<br />
p = (p * 4) + bmi.biSize + (int) bmpptr;<br />
return (IntPtr) p;<br />
}<br />
protected virtual void onImageScanned(ImageScannedEventArgs e)<br />
{<br />
if (ImageScanned != null)<br />
{<br />
ImageScanned(this,e);<br />
}<br />
}<br />
protected override void WndProc(ref Message m)<br />
{<br />
if (tw != null)<br />
{<br />
TwainCommand cmd = tw.PassMessage( ref m );<br />
if( cmd != TwainCommand.Not )<br />
{<br />
switch( cmd )<br />
{<br />
case TwainCommand.CloseRequest:<br />
{<br />
tw.CloseSrc();<br />
ready = false;<br />
break;<br />
}<br />
case TwainCommand.CloseOk:<br />
{<br />
tw.CloseSrc();<br />
ready = false;<br />
break;<br />
}<br />
case TwainCommand.DeviceEvent:<br />
{<br />
break;<br />
}<br />
case TwainCommand.TransferReady:<br />
{<br />
ArrayList pics = tw.TransferPictures();<br />
tw.CloseSrc();<br />
for( int i = 0; i < pics.Count; i++ )<br />
{<br />
IntPtr img = (IntPtr) pics[ i ];<br />
IntPtr bmpt = GlobalLock(img);<br />
IntPtr pi = GetPixelInfo(bmpt);<br />
IntPtr imgH = IntPtr.Zero;<br />
int Ret = GdiPlusLib.Gdip.GdipCreateBitmapFromGdiDib(bmpt,pi,ref imgH);<br />
if (img != IntPtr.Zero)<br />
{<br />
Bitmap bmp = (Bitmap)typeof(Bitmap).InvokeMember("FromGDIplus", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, null, new object[] { imgH });<br />
onImageScanned(new ImageScannedEventArgs(bmp.Clone() as Bitmap));<br />
}<br />
}<br />
break;<br />
}<br />
case WM_Invoke:<br />
{<br />
if (invokeQueue.Count != 0)<br />
{<br />
do<br />
{<br />
InvokeHolder h = invokeQueue.Dequeue() as InvokeHolder;<br />
h.Run();<br />
}while(invokeQueue.Count != 0);<br />
}<br />
break;<br />
}<br />
}<br />
}<br />
}<br />
base.WndProc(ref m);<br />
}<br />
#region IDisposable Member<br />
<br />
public void Dispose()<br />
{<br />
try<br />
{<br />
tw.CloseSrc();<br />
}<br />
catch{}<br />
tw = null;<br />
}<br />
<br />
#endregion<br />
}<br />
this code should theoretically start a different thread where the whole scanning should be handled.
it's not testet, so maybe it contains some syntax / logical errors. but i think the solution for scanning in a different thread is something like that..
greets
m@u
|
|
|
|
|
Hello,
I have used this class this is a very good class it works as you said. then I have tried to create a user control out of it that also works on windows applications. but I was trying to put on web application using object tag there i get security error. I think native window call back message loop dose not works here. Do you have any solution for this.
The Error I got is as follows:
<b>
Stack Trace:</b>
System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed.
at UserControlScan.UserControlScan.button2_Click(Object sender, EventArgs e)
at System.Windows.Forms.Control.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnClick(EventArgs e)
at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ButtonBase.WndProc(Message& m)
at System.Windows.Forms.Button.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
The action that failed was:
InheritanceDemand
The type of the first permission that failed was:
System.Security.Permissions.SecurityPermission
The Zone of the assembly that failed was:
Intranet
Error text
Application attempted to perform an operation not allowed by the security policy. To grant this application the required permission, contact your system administrator or use the Microsoft .NET Framework Configuration tool
Please give some alternative for this
|
|
|
|
|
Dear Sir,
I used above code to scan documents
however i can't select the device(which is printer+scanner) after tw.select().
what should i do to select the device to scan..
thank you,
Bibek
|
|
|
|
|
Hi. First of all I want to apologyze for my English in advance. I'm from Argentina, and I'm working with a HP scanner. The problem is that the driver displays a splash screen with the HP logo every time I need to make a scan and i want to remove it. Do you know if that's possible programatically?. Thanks!!!
|
|
|
|
|
I've been asked to create a custom scanning app that will capture a US-Letter (8.5"x11") that saves to server w/ a specific filename format.
I've been successful with 8" width, and the scanner bed is almost 9" wide.
Attempts to set PaperSize fail (not supported for Canon CanoScan 4200F?)
capPS As TwCapability = _
New TwCapability(TwCap.SupportedSizes, TwSS.TwSS_USLetter, TwType.Int16)
Setting the Frame to 8" (or imageWidth) succeeds:
Dim myLayout As New TwImageLayout
With myLayout.Frame
.Right = 8
'other properties here..
End With
rc = Me.DSilayout(appid, srcds, TwDG.Image, TwDAT.ImageLayout, TwMSG.Set, myLayout) 'returns checkStatus
rc = Me.DSstatus(appid, srcds, TwDG.Image, TwDAT.ImageLayout, TwMSG.Get, s) 'returns success
<structlayout(layoutkind.sequential, pack:="2)"> Friend Structure TwFrame
Public Left As Int32
Public Top As Int32
Public Right As Int32
Public Bottom As Int32
End Structure
I need a frame structure that will accept decimal values?
Using the structure below (from other posts), I'm running into problems:
<structlayout(layoutkind.sequential, pack:="2)"> Friend Structure TwFrame
Public Left As TwFix32
Public Top As TwFix32
Public Right As TwFix32
Public Bottom As TwFix32
End Structure
Setting the frame using:
Frame.Bottom.FromFloat(11.0)
Frame.Right.FromFloat(8.5)
'*don't set top & left as they are 0*
returns (after settings have successfully been sent to scanner)
Frame.Bottom.Whole=11
Frame.Bottom.Frac=786431
Frame.Left.Whole=0
Frame.Left.Frac=0
Frame.Right.Whole=8
Frame.Right.Frac=589823
Frame.Top.Whole=0
Frame.Top.Frac=3579117570 *why isn't this 0? perhaps structure isn't what scanner is eexpeting?*
The scanned area is not even close to what is desired.
length starts ~2" from edge and is ~9"
width starts at edge but is ~2.8"
I'd appreciate any help as I (foolishly) promised this app for the users by mid-December.
Thanks!
Paul Elstone, Queensway-Carleton Hospital, Ottawa, Canada [pElstone@qch.on.ca]
|
|
|
|
|
How would I get the program to close (using Close(); ) after you have saved the images... I'm trying to automate the thing so it runs, scans, saves and exits... very difficult without throwing loaderlock exceptions!
Please make sure you have a solution (i.e. you tried it and it worked) before posting back to me.
Qjay
|
|
|
|
|
Hi,
First of all congratulations on writing this article. Recently I have been working on creating an ActiveX DLL which could be invoked using a Javascript call from an HTML page and the scanned image could be displayed within the page. I got most of the things but when I invoke the TWAIN driver, it displays the scanning control, I can perform New Scan multiple times within it but when I click at Accept nothing happens, the same code works perfectly when I invoke it from a Windows Form.
I trapped Windows Messages, it seems that when I do a call to the ActiveX from HTML it does not send any message back when I click Accept in the Scanning control.
Help would be highly appreciated.
Regards,
|
|
|
|
|
Use the code m@u created in thread "Usage of Twain without a Form [modified]". Your problem is that IE is not a .NET Application, so System.Windows.Forms.Application.AddMessageFilter(...) doesn't work. I haven't tried m@u's code but using the NativeWindow to handle the message sounds like it will work just fine.
Breaking your theories
Sully
|
|
|
|
|
Thanks a lot finally I have successfully addressed the issue.
|
|
|
|
|
How exactly did you solve it? I have the same problem, but am having trouble getting the code to be recognized as ActiveX
|
|
|
|
|
Hi,
Can you please let me know whether this works as web application. If so can u please instruct me on the same..
I'm struggling a lot sir from many days of night outs.. Please do help me.
Thanks
Regards.....
Bharath.
|
|
|
|
|
Yep, having a very similar issue here and would be VERY gratefull if anyone could help.
In fact my problem is that it works well until I call the code from an ActiveX wrapper class, i.e., when Internet Explorer instantiates the ActiveXObject which calls the MainFrame Form. I do get to the scanner's Twain GUI, but when I click the "Scan" button, no message is ever caught in the PreFilterMessage method.
Thank you in advance for helping.
|
|
|
|
|
Oh, I missed your hints. Will try the examples mentioned and will get back with the solution if I succeed.
|
|
|
|
|
Hi to all!
I work with digital photocamera. I want to get preview image stream without using standart window. How can I do this? Or, if it is imposible, how can I disable setting buttons on standart window?
Thanks for any answer.
-- modified at 9:42 Monday 6th November, 2006
|
|
|
|
|
This is an amazing example, thank you for posting it!
Would it be possible to programmatically capture the image coming in from the twain device? Your code demonstrates that you can set the guif.ShowUI to be set to 0.
Gratefully yours,
Alex
http://www.lifesabirch.org
|
|
|
|
|
Thank you, this is all very helpful!
One quiestion: How can I display the scanner settings dialog without starting the scan process?
|
|
|
|
|
|
Hey love the code, but I have to crop the images I'm uploading. I'm definetly a newbie to TWAIN, and am using the VB.Net version. Right now Im just saving it and doing it in another program, is there anyway to crop it rogramatically... right now I just need any black space to be cropped out. Thanks for any suggestions.
|
|
|
|
|
FYI, the twain driver that came with my panasonic scanner has that feature. You might be able to set that programmatically using the capabilities get/set methods. However, since the twain dialog pops up anyway, I just set it then.
Good luck!
|
|
|
|
|
Ya I figure there has to be a way, but I'm kinda running on a deadline so my solution was to convert it to a bmp, and display in a picturebox, then have the user simlpy draw a rectangle and crop it. (I had to have the default gui disabled) But if anyone finds a way to do it, I'd still be very interested. Thanks for the info though.
|
|
|
|
|
Imports System
Imports System.Text
Imports System.Runtime.InteropServices
Imports System.Drawing
Imports System.Collections
Imports System.ComponentModel
Namespace ScanSoft
<StructLayout(LayoutKind.Sequential, Pack:=2)> Friend Class BITMAPINFOHEADER
Public biSize As Integer
Public biWidth As Integer
Public biHeight As Integer
Public biPlanes As Short
Public biBitCount As Short
Public biCompression As Integer
Public biSizeImage As Integer
Public biXPelsPerMeter As Integer
Public biYPelsPerMeter As Integer
Public biClrUsed As Integer
Public biClrImportant As Integer
End Class
Public Class scanToImage
<DllImport("gdi32.dll", ExactSpelling:=True)> Friend Shared Function SetDIBitsToDevice(ByVal hdc As IntPtr, ByVal xdst As Integer, ByVal ydst As Integer, ByVal width As Integer, ByVal height As Integer, ByVal xsrc As Integer, ByVal ysrc As Integer, ByVal start As Integer, ByVal lines As Integer, ByVal bitsptr As IntPtr, ByVal bmiptr As IntPtr, ByVal color As Integer) As Integer
End Function
<DllImport("kernel32.dll", ExactSpelling:=True)> Friend Shared Function GlobalLock(ByVal handle As IntPtr) As IntPtr
End Function
<DllImport("kernel32.dll", ExactSpelling:=True)> Friend Shared Function GlobalFree(ByVal handle As IntPtr) As IntPtr
End Function
<DllImport("kernel32.dll", CharSet:=CharSet.Auto)> Public Shared Sub OutputDebugString(ByVal outstr As String)
End Sub
Dim bmi As BITMAPINFOHEADER
Dim bmprect As Rectangle
Dim dibhand As IntPtr
Dim bmpptr As IntPtr
Dim pixptr As IntPtr
Public Sub New(ByVal dibhandp As IntPtr)
bmprect = New Rectangle(0, 0, 0, 0)
dibhand = dibhandp
bmpptr = GlobalLock(dibhand)
pixptr = GetPixelInfo(bmpptr)
End Sub
Protected Function GetPixelInfo(ByVal bmpptr As IntPtr) As IntPtr
bmi = New BITMAPINFOHEADER
Marshal.PtrToStructure(bmpptr, bmi)
bmprect.X = bmprect.Y = 0
bmprect.Width = bmi.biWidth
bmprect.Height = bmi.biHeight
If (bmi.biSizeImage = 0) Then
bmi.biSizeImage = Int((((bmi.biWidth * bmi.biBitCount) + 31) & Hex(Not (31))) / 2 ^ 3) * bmi.biHeight
End If
Dim p As Integer = bmi.biClrUsed
If ((p = 0) And (bmi.biBitCount <= 8)) Then
p = Int(1 * 2 ^ bmi.biBitCount)
End If
p = (p * 4) + bmi.biSize + CType(bmpptr.ToInt32, Integer)
Return New IntPtr(p)
End Function
Public Function ImgToBitmap(ByVal dibhandp As IntPtr) As Bitmap
bmprect = New Rectangle(0, 0, 0, 0)
dibhand = dibhandp
bmpptr = GlobalLock(dibhand)
pixptr = GetPixelInfo(bmpptr)
Dim TempBMP As Bitmap = New Bitmap(bmprect.Width, bmprect.Height)
Dim TempGrap As Graphics = Graphics.FromImage(TempBMP)
Dim hdc As IntPtr = TempGrap.GetHdc
SetDIBitsToDevice(hdc, 0, 0, bmprect.Width, bmprect.Height, 0, 0, 0, bmprect.Height, pixptr, bmpptr, 0)
TempGrap.ReleaseHdc(hdc)
TempGrap.Dispose()
GlobalFree(dibhand)
dibhand = IntPtr.Zero
Return (TempBMP)
End Function
End Class
End Namespace
'Put this code in a new class. Change the following code in the mainform
Public Function PreFilterMessage(ByRef m As Message) As Boolean Implements IMessageFilter.PreFilterMessage
Dim cmd As TwainCommand = tw.PassMessage(m)
If (cmd = TwainCommand.Not) Then
Return False
End If
Select Case cmd
Case TwainCommand.CloseRequest
EndingScan()
tw.CloseSrc()
Case TwainCommand.CloseOk
EndingScan()
tw.CloseSrc()
Case TwainCommand.DeviceEvent
Case TwainCommand.Failure
EndingScan()
tw.CloseSrc()
Case TwainCommand.TransferReady
Dim pics As ArrayList = tw.TransferPictures()
EndingScan()
tw.CloseSrc()
picnumber += 1
Dim i As Integer
For i = 0 To pics.Count - 1 Step 1
Dim img As IntPtr = CType(pics(i), IntPtr)
Dim scan As New scanToImage(img)
PictureBox1.Image = scan.ImgToBitmap(img)
GetPictureInfo()
Next
End Select
Return True
End Function
|
|
|
|
|
it is not working for me.
|
|
|
|
|