?
Solved

Save signature to file

Posted on 2016-10-25
7
Medium Priority
?
240 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
WordPress Tutorial 4: Recommended Plugins

Now that you have WordPress installed, understand the interface, and know how to install new parts, let’s take a look at our recommended plugins.

 
LVL 111

Accepted Solution

by:
Ray Paseur earned 2000 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 111

Expert Comment

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

Expert Comment

by:Julian Hansen
ID: 41861628
0

Featured Post

The Orion Papers

Are you interested in becoming an AWS Certified Solutions Architect?

Discover a new interactive way of training for the exam.

Question has a verified solution.

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

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.
The well known Cerber ransomware continues to spread this summer through spear phishing email campaigns targeting enterprises. Learn how it easily bypasses traditional defenses - and what you can do to protect your data.
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.
Suggested Courses

770 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