Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
jQuery Signature Pad Compression/Decompression algorithm
/** Reinflates a compressed signature string:
resolution = a representation of the resolution in
pixels of the canvas which this signature will be drawn
e.g. {x:800,y:200}
*/
var inflateToJsonSignature = function (deflatedSig, resolution) {
var components = [],
modifier = 1,
compressWithResolution = /^(?:\[(\d+)x(\d+)\])?([\w\W]*)/,
parsedSigString = deflatedSig.match(compressWithResolution),
widthModifier, heightModifier, i,
sigString, deflatedLen,
componentsLength;
// If the previously compressed signature had a resolution,
// attempt to scale to fit the new canvas
if (parsedSigString && resolution) {
widthModifier = resolution.x / (parsedSigString[1] || 1);
heightModifier = resolution.y / (parsedSigString[2] || 1);
modifier = widthModifier < heightModifier ? widthModifier : heightModifier;
deflatedSig = parsedSigString[3];
}
// Get each byte of the deflated signature as a unicode char
// and convert to a decimal coordinate value.
// e.g. '}' => 125
// Stuff the result in our output array
deflatedLen = deflatedSig.length;
for (i = 0; i < deflatedSig.length; i++) {
components.push((deflatedSig[i].charCodeAt()).toString());
}
// Rebuild the signature string from the result array above.
// Every 4 chars represent the two sets of x,y coordinates
componentsLength = components.length;
sigString = "[";
for (i = 0; i < componentsLength; i = i + 4) {
sigString += (
'{"lx":' + Math.round(components[i] * modifier) +
',"ly":' + Math.round(components[i + 1] * modifier) +
',"mx":' + Math.round(components[i + 2] * modifier) +
',"my":' + Math.round(components[i + 3] * modifier) + '},');
}
return sigString.substring(0, (sigString.length - 1)) + "]";
};
/** Deflates(compresses) a json signature string:
resolution = a representation of the resolution in
pixels of the canvas which this signature came from
e.g. {x:800,y:200}
*/
var deflateFromJsonSignature = function (jsonSig, resolution) {
var replacedSig,
compressString = "",
components,
componentsLength, i;
// Grab only the digits from the string
components = jsonSig.match(/\d+/g);
componentsLength = components.length
for (i = 0; i < componentsLength; i = i + 1) {
// don't save lines drawn outside the canvas. just draw to the edge.
components[i] = components[i] < 0 ? 0 : components[i];
// convert coordinate to a unicode value
// e.g. 125 => '}'
// Append the result to the compressed string
compressString += String.fromCharCode(parseInt(components[i].toString()));
}
// if a resolution was specified add it to the front of the compression string to allow
// better scaling if the canvas changes size in the future
if (resolution) {
compressString = "[" + resolution.x + "x" + resolution.y + "]" + compressString;
}
return compressString;
}
Owner

jaboc83 commented Apr 30, 2012

A quick little compression algorithm I threw together to compress the JSON string output.

jerodg commented Nov 21, 2012

Could you maybe offer a quick write up on how to implement this? My javascript skills are weak sauce and I can't figure out how to pass the output to this before it's submitted to the database.

Here's how I integrated this code into my app (code distilled from what I use in my app):

$(document).ready(function() {
// Decompress the saved JSON from the server data
  var saved_json = inflateToJsonSignature( $( "#signature_json_text" ).val() ); // Save the JSON text in a var to use below

  var sig_api = $('.sigPad').signaturePad();

  if ( saved_json.length >  0 ) {
    sig_api.regenerate( saved_json ); // Regenerate the signature from the now uncompressed server data
  }

  $( "#signature_form" ).submit( function( event ) {
    // Compress the saved JSON upon submit
    var result = deflateFromJsonSignature( $( "#signature_json_text" ).val() );
    $( "#signature_json_text" ).val( result );
    return true;
  } );
});

Thanks @jaboc83 for writing these compression routines!

Owner

jaboc83 commented Apr 28, 2013

finally got around to cleaning these routines up a little bit.

Owner

jaboc83 commented Apr 28, 2013

also here's a quick jsfiddle of using the algorithms to see it in action http://jsfiddle.net/jaboc83/KAY29/17/

nice jsfiddle jaboc!

For the compression algorithm, this is what I opted for.
var pattern = /\d+/g; // extract numbers only
var sig=document.getElementById('signature').value;
var result=sig.match( pattern );
alert(result);

Owner

jaboc83 commented Apr 30, 2013

Good call on that regex jaggedsoft! That should save a few computations for sure :) Updated the fiddle with the suggested regex.

Thank you @jaboc83 for this - saves a ton of space! A slight suggestion based on a problem I ran into. By adding 65 to the base decimal on compression you're able to avoid all of the nasty Unicode characters that usually throw up flags when submitting client data to databases. When you decompress, simply subtract 65 from the character code. I had been converting signatures in an asp.net app and was unable to use this routine until I altered it due to possible XSS vulnerabilities within asp.net.

/** Reinflates a compressed signature string:
    resolution = a representation of the resolution in 
  pixels of the canvas which this signature will be drawn 
    e.g. {x:800,y:200}
*/
var inflateToJsonSignature = function (deflatedSig, resolution) {
    var components = [],
        modifier = 1,
        compressWithResolution = /^(?:\[(\d+)x(\d+)\])?([\w\W]*)/,
        parsedSigString = deflatedSig.match(compressWithResolution),
        widthModifier, heightModifier, i,
        sigString, deflatedLen,
        componentsLength;

    // If the previously compressed signature had a resolution, 
    // attempt to scale to fit the new canvas
    if (parsedSigString && resolution) {
        widthModifier = resolution.x / (parsedSigString[1] || 1);
        heightModifier = resolution.y / (parsedSigString[2] || 1);
        modifier = widthModifier < heightModifier ? widthModifier : heightModifier;
        deflatedSig = parsedSigString[3];
    }

    // Get each byte of the deflated signature as a unicode char
    // and convert to a decimal coordinate value. 
    // e.g. '}' => 125 
    // Stuff the result in our output array
    deflatedLen = deflatedSig.length;
    for (i = 0; i < deflatedSig.length; i++) {
        components.push(((deflatedSig[i].charCodeAt())-65).toString());
    }

    // Rebuild the signature string from the result array above.
    // Every 4 chars represent the two sets of x,y coordinates
    componentsLength = components.length;
    sigString = "[";
    for (i = 0; i < componentsLength; i = i + 4) {
        sigString += (
            '{"lx":' + Math.round(components[i] * modifier) +
                ',"ly":' + Math.round(components[i + 1] * modifier) +
                    ',"mx":' + Math.round(components[i + 2] * modifier) +
                        ',"my":' + Math.round(components[i + 3] * modifier) + '},');
    }
    return sigString.substring(0, (sigString.length - 1)) + "]";
};

/** Deflates(compresses) a json signature string:
    resolution = a representation of the resolution in 
    pixels of the canvas which this signature came from  
    e.g. {x:800,y:200}
*/
var deflateFromJsonSignature = function (jsonSig, resolution) {
    var replacedSig,
        compressString = "",
        components,
        componentsLength, i;

    // Grab only the digits from the string
    components = jsonSig.match(/\d+/g);

    componentsLength = components.length
    for (i = 0; i < componentsLength; i = i + 1) {
        // don't save lines drawn outside the canvas. just draw to the edge.
        components[i] = components[i] < 0 ? 0 : components[i];
        // convert coordinate to a unicode value 
        // e.g. 125 => '}'
        // Append the result to the compressed string
        compressString += String.fromCharCode(parseInt(components[i].toString())+65);
    }
    // if a resolution was specified add it to the front of the compression string to allow
    // better scaling if the canvas changes size in the future
    if (resolution) {
        compressString = "[" + resolution.x + "x" + resolution.y + "]" + compressString;
    }
    return compressString;
}

This is a nice script. One thing that would make it even cooler would be if it didn't crash when trying to deflate a null value. That would serve as an extra protection against validation mistakes.

It's not pretty, but this is what I added in the script for dealing with null values. This is at the end of inflateToJsonSignature:

/** Replace NaN values with 0 - should not occur, but in case they show up...*/
sigString = sigString.replace(/NaN/g, "0");

return sigString.substring(0, (sigString.length - 1)) + "]";

Of course it's going to butcher part of the signature, but it's better than the resulting write failing out in the console.

srrsparky commented Jun 14, 2016 edited

For those interested in a PHP script for this to compress data already stored in a database:

function compressSig($sig) {
    $components = preg_match_all("/\d+/",$sig,$preg_out);
    $compressString="";
    $componentsLength = count($preg_out[0]);
    $j=0;
    while($j<$componentsLength) {
        if ($preg_out[0][$j]<0) {
            $preg_out[0][$j]=0;
        }
        $compressString .= mb_convert_encoding('&#'.intval($preg_out[0][$j]+100).';', 'UTF-8', 'HTML-ENTITIES');
        $j++;
    }
    return $compressString;
}

I added 100 to the char value to miss all the "bad" characters that cause issues with PHP, mySQL, Java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment