Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Signature capturing library for Windows 10 8

Status
Not open for further replies.

Rajesh Karunakaran

Programmer
Sep 29, 2016
542
MU
Hi friends,

Any library which can incorporate Signature Capturing onto Windows application?

I need to write an application for a Windows PC (it's in fact a small laptop kind of, say like a tablet). In a form, there will be some text at top portion for the user to read and the at bottom a signature panel. The laptop supports touch screen and hence the user may user finger to draw the signature. Otherwise also, a stylus can be used I hope.

By the way, I would certainly go for a free/opensource one. But, paid also would be an option (if not very costly).

Rajesh
 
Rajesh,

Since Windows 7, the system has a built-in capability to support your requirement.

For convenience, I'm copying the following code from a post I dropped at Foxite.

Code:
LOCAL SP AS SignaturePad

m.SP = CREATEOBJECT("SignaturePad")
m.SP.Show(1)

DEFINE CLASS SignaturePad AS Form

	Height = 250
	Width = 400
	Caption = "Signature Pad"

	ADD OBJECT shpPad AS Shape WITH ;
		Top = 16, ;
		Left = 16, ;
		Height = 201, ;
		Width = 265, ;
		BackColor = RGB(255,255,255)

	ADD OBJECT cmdClear AS CommandButton WITH ;
		Top = 32, ;
		Left = 304, ;
		Height = 27, ;
		Width = 84, ;
		Caption = "Clear"

	ADD OBJECT cmdSave AS CommandButton WITH ;
		Top = 72, ;
		Left = 304, ;
		Height = 27, ;
		Width = 84, ;
		Caption = "Save"

	PROCEDURE shpPad.Init

		* prepare a ink pad
		LOCAL InkCollector AS MSInkAut.InkCollector
		LOCAL InkRectangle AS MSInkAut.InkRectangle

		m.InkCollector = CREATEOBJECT("MSInkAut.InkCollector.1")
		m.InkCollector.hWnd = Thisform.HWnd

		WITH m.InkCollector.defaultDrawingAttributes

			.Color = This.FillColor
			.Width = 100
			.PenTip = 0
			.AntiAliased = .T.
			.FitToCurve = .F.

		ENDWITH

		m.InkRectangle = CREATEOBJECT("MSInkAut.InkRectangle")

		* place the pad over the shape
		WITH m.InkRectangle

			.Left = This.Left
			.Top = This.Top
			.Right = This.Left + This.Width
			.Bottom = This.Top + This.Height

		ENDWITH

		m.InkCollector.SetWindowInputRectangle(m.InkRectangle)

		m.InkCollector.Enabled = .T.

		* and control it via the shape control
		This.AddProperty("InkCollector", m.InkCollector)

	ENDPROC

	PROCEDURE cmdClear.Click

		* delete stored ink strokes
		WITH Thisform.shpPad

			.InkCollector.Ink.DeleteStrokes()
			.Refresh()

		ENDWITH

	ENDPROC

	PROCEDURE cmdSave.Click

		LOCAL GIFContents AS String
		LOCAL SafetyStatus AS String

		m.SafetyStatus = SET("Safety")
		SET SAFETY OFF
		TRY
			m.GIFContents = Thisform.shpPad.InkCollector.Ink.Save(2)
			STRTOFILE(m.GIFContents, PUTFILE("Save", "Signature", "gif"))
		CATCH
		ENDTRY

		IF m.SafetyStatus == "ON"
			SET SAFETY ON
		ENDIF

	ENDPROC

ENDDEFINE
 
Dear atlopes

I just had a straight run and it simply works!!!
Awesome!!!

Thank you so much

Rajesh


 
atlopes beat me to it.

Besides InkCollector there are InkEdit, InkOVerlay and InkPicture.

Besides Save(2), which results in a GIF with embedded ISF (ink serialized format) there are further things you can save:
Save: PersistenceFormat: CompressionMode:
Also, the GIF you create will be automatically clipped to the size of the drawn strokes.
inkgif_h0pzjn.png


The Ink overlay on the left, an image control displaying the result GIF on the right, the border shows the GIF area.
Other formats are smaller, also as GIF image without embedded strokes.



Chriss
 
Hi Chriss,

1. All the link are giving "404 - Page not found" error!

2. I am wondering is there a way (a method call or similar) to know if there is actually any strokes in the inkPad/Collector.
The idea is that, if saving without actually drawing anything, I can alert "Nothing to save!"

Rajesh
 
Hello Rajesh,

the forum seems to remove the closing bracket at the end of the links. So here they are again:

Save
PersistenceFormat
Compressionmode

Rajesh said:
if there is actually any strokes in the inkPad/Collector

I think there are several ways. The InkOverlay or InkCollector has an Ink object member that has a strokes collection. It has neither a length or count property, but a foreach with no stroke objects would indicate an empty signature.

I would work with the InkCollector.Ink.Strokes.GetBoundingBox() method. That returns an InkRectangel object, which has left,right,top, and bottom properties. If they all are 0 nothing has been drawn.
I think you could also get a 0 width and height Gif from save or a 0 byte ISF file, if saving in that format, or it would just have a ISF header that says there are 0 strokes.

Chriss
 
Hi Chriss,

Regarding the links, I didn't notice those brackets!
The new ones are working.

Regarding checking the drawing status, that was indeed accurate and precise information.
I would try to get it in PNG format (possible?). Anyway, for time being, let me play with the defaults.
Will check all those options.

Thanks a lot.

Rajesh
 
Chriss,

I have created a form and added a shape and then added all those from your sample code.
Now, one thing I am noticing is that when the ShpPad is kept inside a Container, it doesn't work.
When I moved the shpPad directly inside the form, it's okay.
Is that because the InkCollector needs to be directly within a WindowHandle ?

For me, it's not necessary to keep the shape inside a Container. I was just experimenting because
if things are in a Container, it will be easy for me to do responsiveness of controls on ReSize.

Rajesh
 
That was not my sample code, but atlopes.

The Ink controls (also InkOverlay) work based on a rectangle you define relative to form coordinates, whether the control is in a container or not, so you better know top and bottom, left and right (not width and height) in form coordinates, also when a form is resized.

The simple solution is to make the whole form collect ink, just like a real contract paper could be written on, the customer will be asked to sign in the signature pad and the save routine only saves a rectangle bounding the ink anyway. It's not impossible to add resizing capabilities but anchoring alone won't do it, as you need to disable the ink control to set a new rectangle, and then enable it back. And ink resizing is another thing, depends a bit on aspect ratio, too.

At best you have the form resized before you add the InkCollector and define its rectangle by shape coordinates relative to the form with OBJTOCLIENT().


Chriss
 
Rajesh, maybe you forgot to update the references.

Here is a revision of the code with the signature pad moved into a container.

Code:
LOCAL SP AS SignaturePad

m.SP = CREATEOBJECT("SignaturePad")
m.SP.Show(1)

DEFINE CLASS SignaturePad AS Form

	Height = 250
	Width = 400
	Caption = "Signature Pad"

	ADD OBJECT cntPad AS PadContainer WITH ;
		Top = 16, ;
		Left = 16

	ADD OBJECT cmdClear AS CommandButton WITH ;
		Top = 32, ;
		Left = 304, ;
		Height = 27, ;
		Width = 84, ;
		Caption = "Clear"

	ADD OBJECT cmdSave AS CommandButton WITH ;
		Top = 72, ;
		Left = 304, ;
		Height = 27, ;
		Width = 84, ;
		Caption = "Save"

	PROCEDURE cmdClear.Click

		Thisform.cntPad.Clear()

	ENDPROC

	PROCEDURE cmdSave.Click

		Thisform.cntPad.Save()

	ENDPROC

ENDDEFINE

DEFINE CLASS PadContainer AS Container

	Height = 201
	Width = 265

	ADD OBJECT shpPad AS Shape WITH ;
		Top = 0, ;
		Left = 0, ;
		Height = 201, ;
		Width = 265, ;
		BackColor = RGB(255,255,255)

	PROCEDURE shpPad.Init

		* prepare a ink pad
		LOCAL InkCollector AS MSInkAut.InkCollector
		LOCAL InkRectangle AS MSInkAut.InkRectangle

		m.InkCollector = CREATEOBJECT("MSInkAut.InkCollector.1")
		m.InkCollector.hWnd = Thisform.HWnd

		WITH m.InkCollector.defaultDrawingAttributes

			.Color = This.FillColor
			.Width = 100
			.PenTip = 0
			.AntiAliased = .T.
			.FitToCurve = .F.

		ENDWITH

		m.InkRectangle = CREATEOBJECT("MSInkAut.InkRectangle")

		* place the pad over the shape
		WITH m.InkRectangle

			.Left = This.Left
			.Top = This.Top
			.Right = This.Left + This.Width
			.Bottom = This.Top + This.Height

		ENDWITH

		m.InkCollector.SetWindowInputRectangle(m.InkRectangle)

		m.InkCollector.Enabled = .T.

		* and control it via the shape control
		This.AddProperty("InkCollector", m.InkCollector)

	ENDPROC

	* clear the signature pad
	PROCEDURE Clear

		WITH This.shpPad

			.InkCollector.Ink.DeleteStrokes()
			.Refresh()

		ENDWITH

	ENDPROC

	* save the contents of the signature pad
	PROCEDURE Save

		LOCAL GIFContents AS String
		LOCAL SafetyStatus AS String

		m.SafetyStatus = SET("Safety")
		SET SAFETY OFF
		TRY
			m.GIFContents = This.shpPad.InkCollector.Ink.Save(2)
			STRTOFILE(m.GIFContents, PUTFILE("Save", "Signature", "gif"))
		CATCH
		ENDTRY

		IF m.SafetyStatus == "ON"
			SET SAFETY ON
		ENDIF

	ENDPROC

ENDDEFINE
 
This would really be a good case for using the control base class and program resize behavior into it, too. I just guess you don't sign half, then resize and then continue signing. In a fullscreen application you usually will have forms to adapt to screen size once when they are started. And a container, or the control base class can do that phase by anchoring. The Ink objects should then be added at runtime after the form has become visible, so a typical thing for the form.activate only done at the first activate.

Regarding PNG: The PersistenceFormat enumeration clearly says what you can get from the control. Nobody hinders you to convert GIF To PNG, but if you think you get better quality output when initially creating a PNG you'd have to write code that does draw the strokes on a gdiplus bitmap object, for example and save that as PNG. It'll still be a bitmap image, not a vector format. The vector format is easiest saved as the default persistence format. Only an ink control can redraw that, though.

I think of a step of complaints in a software that will then send the acquired signature as proof and there a bitmap graphic is usually easiest to put into a mail, for example.

PNG is made as GIF enhancement and thought of ideal for line graphics, that's surely why you would like to get a PNG, but MS wasn't adopting this, perhaps because now GIF is old enough the patents are gone and so that made it free to use in the ink implementation and good enough with its single transparent color.

Ink controls actually also have methods to get the stroke, their coordinates and draw them, but that's only on the ink controls, not on a graphics/bitmap object. You can go into details of the strokes and get coordinates. Each stroke object has GetPoints().



Chriss
 
Chriss said:
That was not my sample code, but atlopes.
Chriss, that was my mistake to mention your name! :-(

Atlopes said:
Rajesh, maybe you forgot to update the references.
I had created a separate form and added controls as per your program. I put the shape inside a container. I had updated the references as well, say, from 'thisform.shpPad' to 'thisform.cntBottom.shpPad' etc etc'. Didn't work. I think, the position of Ink Rectangle was wrong.

Anyway, now I decided to remove the container and everything is directly in the form itself. It works fine. I have added the responsiveness as well.

Chriss,
I think, I will stay with the defaults. GIF is okay for me.

Now, I am just trying to see if I can convert the GIF to base64Binary and save to a memo field, instead of keeping the GIF file as it is in the disk. Need to check which will be appropriate in various aspects, in terms of table size etc. Btw, the GIF size is very small.

Rajesh
 
Well, Rajesh,

if you would have read the reference about the PersistenceFormats, you'd know Base64 encoded Gif is one of the possible formats. Base64Gif is 4th on the list, so cBase64Gif = ...Ink.Save(3) will give you that (beccause Enumerations are 0-based, ie first element is 0, then 1, etc.).

It will just be a waste of memo/fpt file space, as you have either Blob or a binary memo field type to store binary data without the need to convert it to base64 code, which needs 133% of the size of the binary data.

I personally would still store the files. data is data and anything you can keep away from a memo helps to minimize bloat effects, no matter if the single field values are long or short. You store data in files within a database folder, gifs are just other files in there, users will need write permission in the folder to write into dbfs, cdx, etc anyway. The only reason to not just keep the GIfs there is anybody knowing the database folder can grab a lot of signatures.

Out of data privacy concerns you would also need to protect data in memo fields, anyway. It doesn't take that much knowledge to get data from dbfs, too. DBF viewers are software available to anybody.

Chriss
 
Rajesh,

Rajesh said:
I put the shape inside a container. I had updated the references as well,...
again you didn't get what I already said: Coordinates for the Ink control need to be relative to the form, not to a container.

Code:
			.Left = This.Left
			.Top = This.Top
			.Right = This.Left + This.Width
			.Bottom = This.Top + This.Height

This part of atlopes code only works for a shape directly on the form, not within a container. You could still get the necessary coordinates by OBJTOCLIENT().

The Ink control is bound to the window by its HWND. It has no knowledge of VFPs control coordinates and containers, it only orients itslef in the rectangle given in form coordinates. A shape within a container will have left/top=0 (or low) but the inkrectangle must start at the container coordinates and that get's even more complicated if you have the pad inside a page of a pageframe in a container inside another container, perhaps. See OBBJTOCLIENT and please read what's suggested to you as reference.

Chriss
 
Chriss,

Your explanations are indeed convincing! Thanks

Regarding Save() formats, yes I had gone through your text. Just wanted to test how it's going. That's what I wanted to write :).
Now, as per your suggestion, I am convinced that it's more appropriate to keep the GIF files themselves and link their name with the corresponding table record. Let me study both options with my colleague also before finalising it.

The only reason thinking to save the graphic in the table itself was that I thought you don't have to copy the GIF files separately if there is a need to copy the database to another location/pc etc. Too, the GIF size found to be very small (as it's a two colour signature). So, now, if we're to GIF files as it is, somebody who would do a database copy must know that GIF also need to be taken along. That's rather coming under the application maintenance administration policies, isn't it? So, no probs.

For the container matter, yes, I think the problem was because of the relative coordinates issue. Anyway, I am not using container now. The controls are all in the form itself.

Atlopes,

Your code was to the point, complete and perfect for my start. Thanks once again dear!

Rajesh
 
Rajhesh,

as Mike Lewis suggested in another thread or two, it's a good idea to save GIFs with the name like the primary key of the record. That way you only need to have a standard path to the GIFs and can generate their filename as Transform(primarykeyfield)+'.gif'.

As said I'd consider the database folder as all files belonging to the database, so moving data, backup of data, etc is always copying/backing up of a folder, that would then simply include all gifs, but also any other things you might keep separate like a separate dbc events program file or documentation or even tools, dlls, flls, exes, apps.

Just don't put the gifs in the root database folder, give them at least a subfolder and if there will be many you need to keep for long, then better have a directory tree and not just one directory, as file access can become slow with many files in one directory. If you have an integer id, a possible way would be to transform an id of 435232, for example, to databasefolder/signatures/4/3/5/2/3/2.gif, this way any directory has at max 10 subdirectories and 10 gifs. Transorm can do that by

Code:
STRTRAN(TRANSFORM(intid,"signatures/9/9/9/9/9/9/9/9/9/9.gif"),'/ ','')

You should create directories as needed and as the id increments by 1 you will only need 1 new directory if at all, so you can check for Justpath(giffilenname) to exist and if not create the directory with md/mkdir.

Chriss
 
Chriss,

Yes, I will be saving the GIF in sub folders only, not in application root. The files will be separate for each record. Also, sometimes, there may be separate sign for each purpose in a single visit.

So, I think, I can have the filename as <<the rec id>>_<<purpose id>>_<<customer id>>.GIF
like in 11012521_001_80125488.GIF
Here, to link a sign to a visit and purpose, I need only the first 2 parts, the customer id is just for an information and maybe I can avoid it too.

By the way, that STRTRAN code looks interesting to me!
I never had a thought like that.

Rajesh
 
TRANSFORM is doing the heavy work, STRTRAN just remove "/ " (slash space) for shorter numbers.

I was never expecting you to save gifs in the application folder. It is still data, it belongs to the data folder. And that's usually in the network on a network share. The nature of a device like a tablet for taking the signature could be offline, so data is local, but once that tablet checks in its data those gifs should be put into a central database related folder structure.

What you planned seems not thought to the end. If you need an image for just 2 parts like the visit and purpose, then you can't create the full file name from that, also if you look for a signature of a customer but don't know for which visit or purpose, well, that's again not working. The gifs need to be named by a primary key only.

Say you have a visits table and each visit record has the customer that's visited and the purpose in it, that should have a visitid that's unique in itself and become the only key you use for naming the gif. I don't know your database structure, so take this with a grain of salt the essential part is signatures should be 1:1 relatable to one record only and if there is no such thing then it would be a sign you need a new table that combines all the foreign keys pointing to all the detail data that's related to that signature.

Say you do a care service, then I'd think the visits would be one candidate for a signature id, and details related to the visit might be multiple care services done at that visit, which are all signed with the one visit signature, they will point to the visit record in their foreign key(s) and be related to the signature that way. The visit will be to one patient, so there is no need to store a customer/patient id with the signature name. Any redundant info you put into the signature might only be helpful to be able to validate from which person the signature is. I'd still don't put two or more ids into the signature image file name, you could solve that extra information thought into creating a dedicated signature record that points out the visit and the signer in two separate foreign keys, which could cover the case not the patient signed, but an authorized representative like a direct relative of the patient.

And what you could do to ensure the files are not rearranged or altered in any way is to store a cryptographic signature or hash of the full gif data plus the path its stored in. That way any manipulation of a file or any file move to a wrong folder could be detected.

Chriss
 
Chriss,

Oh, I mainly meant to refer the TRANSFORM part inside the STRTRAN!

I am not saving the images in the application folder. They will be in a separate sub folder inside the application folder, most probably the decision will be to put inside our 'Data\' folder.

Also, yes, after capturing the sign, the image will be written to a central system.

Moreover, as I said, for each visit, there may be multiple services/purposes for which the customers are supposed to sign if they agree to each of them. Here, I will capture separate signature for each service/purpose (very much similar to if we physically sign on separate papers stating separate agreements on individual services). They customer may agree to one and sign and disagree to another and don't sign. So, there will be separate sign images for each service he agreed, which are linked to a single visit made by him. This is the scenario.

Also, I maybe thinking of making the GIF filename kind of encrypted form so that someone picking up the image file cannot easily identify whose signature it is. Does that make sense ?

Rajesh
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top