Solved

Save signature to file

Posted on 2016-10-25
7
121 Views
Last Modified: 2016-10-26
I found code online to allow a user to sign with a mouse. It's working fine except that it doesn't save the file. (I've set the permissions on the server to read/write for the folder.)  Any ideas on why it's not saving?

Thanks,

Steve

The page is here: http://testregister.cenational.org/test/sig/SignApp/Default.php

Default.php
    <script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-2.0.3.min.js"></script>
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
    <script src="../../../support/bootstrap.min.js"></script> 

    <script src="Scripts/signature_pad.js"></script>


    <script>
        $(document).ready(function () {
            // Handler for .ready() called.

            var wrapper = document.getElementById("signature-pad"),
            clearButton = wrapper.querySelector("[data-action=clear]"),
            saveButton = wrapper.querySelector("[data-action=save]"),
            canvas = wrapper.querySelector("canvas"),
            signaturePad;

            // Adjust canvas coordinate space taking into account pixel ratio,
            // to make it look crisp on mobile devices.
            // This also causes canvas to be cleared.
            function resizeCanvas() {
                var ratio = window.devicePixelRatio || 1;
                canvas.width = canvas.offsetWidth * ratio;
                canvas.height = canvas.offsetHeight * ratio;
                canvas.getContext("2d").scale(ratio, ratio);
            }

            window.onresize = resizeCanvas;
            resizeCanvas();

            signaturePad = new SignaturePad(canvas);

            clearButton.addEventListener("click", function (event) {
                signaturePad.clear();
            });

            saveButton.addEventListener("click", function (event) {
                if (signaturePad.isEmpty()) {
                    alert("Please provide signature first.");
                } else {
                    SaveImage(signaturePad.toDataURL());
                }
            });
        });

        var uri = 'api/signatures';

        function SaveImage(dataURL) {
            dataURL = dataURL.replace('data:image/png;base64,', '');
            var data = JSON.stringify(
                               {
                                   value: dataURL
                               });

            $.ajax({
                type: "POST",
                url: "/images/signature",
                contentType: false,
                processData: false,
                data: data,
                contentType: "application/json; charset=utf-8",
                success: function (msg) {
                    alert("Done!");
                },
                error: onWebServiceFailed
            });
        }

        function onWebServiceFailed(result, status, error) {
            var errormsg = eval("(" + result.responseText + ")");
            alert(errormsg.Message);
        }
    </script>

    <div class="page-header">
        <h1>Signature App demonstration using SignaturePad and Web API</h1>
    </div>

    <div class="panel panel-default">
        <div class="panel-body" id="signature-pad">
            <div>
                <canvas style="width: 400px; height: 200px;"></canvas>
            </div>
            <div>
                <div class="alert alert-info">Sign above</div>
                <button data-action="clear" class="btn btn-info">Clear</button>
                <button data-action="save" class="btn btn-success">Save</button>
            </div>
        </div>
    </div>

Open in new window


signature_pad.js
var SignaturePad = (function (document) {
    "use strict";

    var SignaturePad = function (canvas, options) {
        var self = this,
            opts = options || {};

        this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
        this.minWidth = opts.minWidth || 0.5;
        this.maxWidth = opts.maxWidth || 2.5;
        this.dotSize = opts.dotSize || function () {
            return (this.minWidth + this.maxWidth) / 2;
        };
        this.penColor = opts.penColor || "black";
        this.backgroundColor = opts.backgroundColor || "rgba(0,0,0,0)";
        this.onEnd = opts.onEnd;
        this.onBegin = opts.onBegin;

        this._canvas = canvas;
        this._ctx = canvas.getContext("2d");
        this.clear();

        this._handleMouseEvents();
        this._handleTouchEvents();
    };

    SignaturePad.prototype.clear = function () {
        var ctx = this._ctx,
            canvas = this._canvas;

        ctx.fillStyle = this.backgroundColor;
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        this._reset();
    };

    SignaturePad.prototype.toDataURL = function (imageType, quality) {
        var canvas = this._canvas;
        return canvas.toDataURL.apply(canvas, arguments);
    };

    SignaturePad.prototype.fromDataURL = function (dataUrl) {
        var self = this,
            image = new Image();

        this._reset();
        image.src = dataUrl;
        image.onload = function () {
            self._ctx.drawImage(image, 0, 0, self._canvas.width, self._canvas.height);
        };
        this._isEmpty = false;
    };

    SignaturePad.prototype._strokeUpdate = function (event) {
        var point = this._createPoint(event);
        this._addPoint(point);
    };

    SignaturePad.prototype._strokeBegin = function (event) {
        this._reset();
        this._strokeUpdate(event);
        if (typeof this.onBegin === 'function') {
            this.onBegin(event);
        }
    };

    SignaturePad.prototype._strokeDraw = function (point) {
        var ctx = this._ctx,
            dotSize = typeof(this.dotSize) === 'function' ? this.dotSize() : this.dotSize;

        ctx.beginPath();
        this._drawPoint(point.x, point.y, dotSize);
        ctx.closePath();
        ctx.fill();
    };

    SignaturePad.prototype._strokeEnd = function (event) {
        var canDrawCurve = this.points.length > 2,
            point = this.points[0];

        if (!canDrawCurve && point) {
            this._strokeDraw(point);
        }
        if (typeof this.onEnd === 'function') {
            this.onEnd(event);
        }
    };

    SignaturePad.prototype._handleMouseEvents = function () {
        var self = this;
        this._mouseButtonDown = false;

        this._canvas.addEventListener("mousedown", function (event) {
            if (event.which === 1) {
                self._mouseButtonDown = true;
                self._strokeBegin(event);
            }
        });

        this._canvas.addEventListener("mousemove", function (event) {
            if (self._mouseButtonDown) {
                self._strokeUpdate(event);
            }
        });

        document.addEventListener("mouseup", function (event) {
            if (event.which === 1 && self._mouseButtonDown) {
                self._mouseButtonDown = false;
                self._strokeEnd(event);
            }
        });
    };

    SignaturePad.prototype._handleTouchEvents = function () {
        var self = this;

        // Pass touch events to canvas element on mobile IE.
        this._canvas.style.msTouchAction = 'none';

        this._canvas.addEventListener("touchstart", function (event) {
            var touch = event.changedTouches[0];
            self._strokeBegin(touch);
        });

        this._canvas.addEventListener("touchmove", function (event) {
            // Prevent scrolling.
            event.preventDefault();

            var touch = event.changedTouches[0];
            self._strokeUpdate(touch);
        });

        document.addEventListener("touchend", function (event) {
            var wasCanvasTouched = event.target === self._canvas;
            if (wasCanvasTouched) {
                self._strokeEnd(event);
            }
        });
    };

    SignaturePad.prototype.isEmpty = function () {
        return this._isEmpty;
    };

    SignaturePad.prototype._reset = function () {
        this.points = [];
        this._lastVelocity = 0;
        this._lastWidth = (this.minWidth + this.maxWidth) / 2;
        this._isEmpty = true;
        this._ctx.fillStyle = this.penColor;
    };

    SignaturePad.prototype._createPoint = function (event) {
        var rect = this._canvas.getBoundingClientRect();
        return new Point(
            event.clientX - rect.left,
            event.clientY - rect.top
        );
    };

    SignaturePad.prototype._addPoint = function (point) {
        var points = this.points,
            c2, c3,
            curve, tmp;

        points.push(point);

        if (points.length > 2) {
            // To reduce the initial lag make it work with 3 points
            // by copying the first point to the beginning.
            if (points.length === 3) points.unshift(points[0]);

            tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
            c2 = tmp.c2;
            tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
            c3 = tmp.c1;
            curve = new Bezier(points[1], c2, c3, points[2]);
            this._addCurve(curve);

            // Remove the first element from the list,
            // so that we always have no more than 4 points in points array.
            points.shift();
        }
    };

    SignaturePad.prototype._calculateCurveControlPoints = function (s1, s2, s3) {
        var dx1 = s1.x - s2.x, dy1 = s1.y - s2.y,
            dx2 = s2.x - s3.x, dy2 = s2.y - s3.y,

            m1 = {x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0},
            m2 = {x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0},

            l1 = Math.sqrt(dx1*dx1 + dy1*dy1),
            l2 = Math.sqrt(dx2*dx2 + dy2*dy2),

            dxm = (m1.x - m2.x),
            dym = (m1.y - m2.y),

            k = l2 / (l1 + l2),
            cm = {x: m2.x + dxm*k, y: m2.y + dym*k},

            tx = s2.x - cm.x,
            ty = s2.y - cm.y;

        return {
            c1: new Point(m1.x + tx, m1.y + ty),
            c2: new Point(m2.x + tx, m2.y + ty)
        };
    };

    SignaturePad.prototype._addCurve = function (curve) {
        var startPoint = curve.startPoint,
            endPoint = curve.endPoint,
            velocity, newWidth;

        velocity = endPoint.velocityFrom(startPoint);
        velocity = this.velocityFilterWeight * velocity
            + (1 - this.velocityFilterWeight) * this._lastVelocity;

        newWidth = this._strokeWidth(velocity);
        this._drawCurve(curve, this._lastWidth, newWidth);

        this._lastVelocity = velocity;
        this._lastWidth = newWidth;
    };

    SignaturePad.prototype._drawPoint = function (x, y, size) {
        var ctx = this._ctx;

        ctx.moveTo(x, y);
        ctx.arc(x, y, size, 0, 2 * Math.PI, false);
        this._isEmpty = false;
    };

    SignaturePad.prototype._drawCurve = function (curve, startWidth, endWidth) {
        var ctx = this._ctx,
            widthDelta = endWidth - startWidth,
            drawSteps, width, i, t, tt, ttt, u, uu, uuu, x, y;

        drawSteps = Math.floor(curve.length());
        ctx.beginPath();
        for (i = 0; i < drawSteps; i++) {
            // Calculate the Bezier (x, y) coordinate for this step.
            t = i / drawSteps;
            tt = t * t;
            ttt = tt * t;
            u = 1 - t;
            uu = u * u;
            uuu = uu * u;

            x = uuu * curve.startPoint.x;
            x += 3 * uu * t * curve.control1.x;
            x += 3 * u * tt * curve.control2.x;
            x += ttt * curve.endPoint.x;

            y = uuu * curve.startPoint.y;
            y += 3 * uu * t * curve.control1.y;
            y += 3 * u * tt * curve.control2.y;
            y += ttt * curve.endPoint.y;

            width = startWidth + ttt * widthDelta;
            this._drawPoint(x, y, width);
        }
        ctx.closePath();
        ctx.fill();
    };

    SignaturePad.prototype._strokeWidth = function (velocity) {
        return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
    };


    var Point = function (x, y, time) {
        this.x = x;
        this.y = y;
        this.time = time || new Date().getTime();
    };

    Point.prototype.velocityFrom = function (start) {
        return (this.time !== start.time) ? this.distanceTo(start) / (this.time - start.time) : 1;
    };

    Point.prototype.distanceTo = function (start) {
        return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
    };

    var Bezier = function (startPoint, control1, control2, endPoint) {
        this.startPoint = startPoint;
        this.control1 = control1;
        this.control2 = control2;
        this.endPoint = endPoint;
    };

    // Returns approximated length.
    Bezier.prototype.length = function () {
        var steps = 10,
            length = 0,
            i, t, cx, cy, px, py, xdiff, ydiff;

        for (i = 0; i <= steps; i++) {
            t = i / steps;
            cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
            cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
            if (i > 0) {
                xdiff = cx - px;
                ydiff = cy - py;
                length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
            }
            px = cx;
            py = cy;
        }
        return length;
    };

    Bezier.prototype._point = function (t, start, c1, c2, end) {
        return          start * (1.0 - t) * (1.0 - t)  * (1.0 - t)
               + 3.0 *  c1    * (1.0 - t) * (1.0 - t)  * t
               + 3.0 *  c2    * (1.0 - t) * t          * t
               +        end   * t         * t          * t;
    };

    return SignaturePad;
})(document);

Open in new window

0
Comment
Question by:stkoontz
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
  • 2
  • 2
  • +1
7 Comments
 
LVL 9

Expert Comment

by:Moussa Mokhtari
ID: 41859406
@stkoontz
You need to make sure that URL /images/signature is on your server
create on your root folder a folder named images within in it a folder named signature

Cheers
0
 
LVL 2

Author Comment

by:stkoontz
ID: 41859419
Thanks, Moussa for coming on to help.  The url is on my server.

Steve
0
 
LVL 9

Expert Comment

by:Moussa Mokhtari
ID: 41859424
at line 71 in Default file
var errormsg = eval("(" + result.responseText + ")");
remove  "(" and ")"
to see the error message and tell me what the out put
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 110

Accepted Solution

by:
Ray Paseur earned 500 total points
ID: 41859474
This seems to have suddenly become a popular topic.  Maybe I'll have to write an article about it!

Here is the server-side script that responds to the POST-method request that sends the signature.  It stores the signature in an image file.
<?php // demo/signature_capture_server.php
/**
 * https://www.experts-exchange.com/questions/28977073/Issues-with-signature.html#a41848213
 */
error_reporting(E_ALL);

// REVERSE THE URL ENCODING
$img = rawurldecode($_POST['q']);

// REMOVE HEADER data:image/png;base64,
$img = str_replace('data:image/png;base64,', NULL, $img);

// REVERSE BASE-64 ENCODING
$img = base64_decode($img);

// STORE THE IMAGE
file_put_contents('storage/signature.png', $img);

// ACTIVATE SOMETHING LIKE THIS TO SHOW THE IMAGE
header('Content-type: image/png');
echo $img;

Open in new window

And here is the client-side script that captures the signature
<?php // demo/signature_capture_client.php
/**
 * https://www.experts-exchange.com/questions/28977073/Issues-with-signature.html#a41848213
 *
 * To capture the PNG image on the server, take the string in $_POST[q]
 *   1. rawurldecode()
 *   2. str_replace('data:image/png;base64,', NULL)
 *   3. base64_decode()
 * The resulting string variable is the PNG image from this script
 */
error_reporting(E_ALL);


// CREATE OUR WEB PAGE IN HTML5 FORMAT
$htm = <<<HTML5
<!DOCTYPE html>
<html dir="ltr" lang="en-US">
<head>
<meta charset="utf-8" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<style type="text/css">
fieldset {
    width:420px;
    padding:10px;
    text-align:center;
}
#signaturePad {
    margin:0 auto;
    border:1px solid #ccc;
    width:400px;
}
</style>

<title>Signature Thing</title>

<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script>//<![CDATA[
$(document).ready(function () {
    /** Set Canvas Size **/
    var canvasWidth = 400;
    var canvasHeight = 75;

    /** IE SUPPORT **/
    var canvasDiv = document.getElementById('signaturePad');
    canvas = document.createElement('canvas');
    canvas.setAttribute('width', canvasWidth);
    canvas.setAttribute('height', canvasHeight);
    canvas.setAttribute('id', 'canvas');
    canvasDiv.appendChild(canvas);
    if (typeof G_vmlCanvasManager != 'undefined') {
        canvas = G_vmlCanvasManager.initElement(canvas);
    }
    var context = canvas.getContext("2d");

    var clickX = new Array();
    var clickY = new Array();
    var clickDrag = new Array();
    var paint;

    /** Redraw the Canvas **/
    function redraw() {
        canvas.width = canvas.width; // Clears the canvas

        context.strokeStyle = "#000000";
        context.lineJoin = "miter";
        context.lineWidth = 2;

        for (var i = 0; i < clickX.length; i++) {
            context.beginPath();
            if (clickDrag[i] && i) {
                context.moveTo(clickX[i - 1], clickY[i - 1]);
            } else {
                context.moveTo(clickX[i] - 1, clickY[i]);
            }
            context.lineTo(clickX[i], clickY[i]);
            context.closePath();
            context.stroke();
        }
    }

    /** Save Canvas **/
    $("#saveSig").click(function saveSig() {
        var sigData = encodeURIComponent(canvas.toDataURL("image/png"));
        $("#imgData").html('Thank you! Your signature was saved');
        $.post('signature_capture_server.php', {q:sigData});
        // $('#debug').html(sigData);
    });

    /** Clear Canvas **/
    $('#clearSig').click(function clearSig() {
            clickX = new Array();
            clickY = new Array();
            clickDrag = new Array();
            context.clearRect(0, 0, canvas.width, canvas.height);
    });

    /**Draw when moving over Canvas **/
    $('#signaturePad').mousemove(function (e) {
        if (paint) {
            addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
            redraw();
        }
    });

    /**Stop Drawing on Mouseup **/
    $('#signaturePad').mouseup(function (e) {
        paint = false;
    });

    /** Starting a Click **/
    function addClick(x, y, dragging) {
        clickX.push(x);
        clickY.push(y);
        clickDrag.push(dragging);
    }

    $('#signaturePad').mousedown(function (e) {
        var mouseX = e.pageX - this.offsetLeft;
        var mouseY = e.pageY - this.offsetTop;

        paint = true;
        addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop);
        redraw();
    });

});//]]>
</script>

</head>

<body>

    <fieldset>
        <div id="signaturePad"></div>
        <br/>
        <button id="clearSig" type="button">Clear Signature</button>&nbsp;
        <button id="saveSig" type="button">Save Signature</button>
        <div id="imgData"></div>
    </fieldset>

<div id="debug"></div>

</body>
</html>
HTML5;


// RENDER THE WEB PAGE
echo $htm;

Open in new window

Here's the sort of thing I found after using these scripts
signature :-)
Full disclosure:  I'm not sure where all of the CSS and JavaScript came from.  I wrote the server-side script myself, after some exploration to discern what the JavaScript was sending.

Maybe beside the point, but this technique, while kinda cool, is legally unnecessary.  Since the E-Sign Act (about 1999 or 2000) you can have an enforceable contract with an "I agree"  checkbox and a name typed into a text input field.  In many cases you do not even need the typed name.  One-touch ordering comes to mind.  I do not know of any case law that covers disputes over mouse-written signatures.

Hope that helps, ~Ray
0
 
LVL 2

Author Closing Comment

by:stkoontz
ID: 41860211
Your code works great.  Thanks!

Steve
1
 
LVL 110

Expert Comment

by:Ray Paseur
ID: 41860251
Great!  Thanks for the points and thanks for using E-E, ~Ray
0
 
LVL 56

Expert Comment

by:Julian Hansen
ID: 41861628
0

Featured Post

Salesforce Has Never Been Easier

Improve and reinforce salesforce training & adoption using WalkMe's digital adoption platform. Start saving on costly employee training by creating fast intuitive Walk-Thrus for Salesforce. Claim your Free Account Now

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

International Data Corporation (IDC) prognosticates that before the current the year gets over disbursing on IT framework products to be sent in cloud environs will be $37.1B.
This article discusses four methods for overlaying images in a container on a web page
The viewer will learn how to count occurrences of each item in an array.
The viewer will learn how to look for a specific file type in a local or remote server directory using PHP.

730 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question