Skip to content

Instantly share code, notes, and snippets.

@jaboc83
Last active May 3, 2021 16:01
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save jaboc83/2559996 to your computer and use it in GitHub Desktop.
Save jaboc83/2559996 to your computer and use it in GitHub Desktop.
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;
}
@jaboc83
Copy link
Author

jaboc83 commented Apr 30, 2012

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

@jerodg
Copy link

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.

@matt-hwy1
Copy link

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!

@jaboc83
Copy link
Author

jaboc83 commented Apr 28, 2013

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

@jaboc83
Copy link
Author

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/

@jaggedsoft
Copy link

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);

@jaboc83
Copy link
Author

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.

@rgwalke2
Copy link

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;
}

@captainmorgan
Copy link

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.

@rgwalke2
Copy link

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
Copy link

srrsparky commented Jun 14, 2016

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

@dannycallaghan
Copy link

I know this was a long time ago, but I've only just discovered this, and implemented it into my AngularJS application in seconds. Really well done, it's brilliant. Thank you.

@jaboc83
Copy link
Author

jaboc83 commented Jun 5, 2020

Glad it helped!

@j-max-creator
Copy link

Hi I'm trying implement this compression for a site as the standard json sig is pushing character limits sometimes. I've managed to get the basic sigpad to save to a sql server table and reproduce the signature. So I just need to implement this compression to avoid issues with size.

Can anyone give me more info or an example on how to implement the compression? I have average jscript ability. Specifically...

  1. DoI include the sigPadCompression file as a js file or jqery?
  2. How do I pass the json string to the compression jscript? and same for the redrawing?

@j-max-creator
Copy link

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!

Can you give me an example of how to implement this with the sig output form then store in DB?

@jakemoeningspok
Copy link

@j-max-creator That entirely depends on the back-end you are using. The form submission code from @matt-hwy1 's example will make a POST back to your server and depending on the language / framework you'll have to parse the compressed form data from the request and handle the saving of the compressed sig string into whatever your database of choice is. I think that this might be a good resource to start with: https://developer.mozilla.org/en-US/docs/Learn/Forms/Sending_and_retrieving_form_data

@j-max-creator
Copy link

Hi. Sorry yeah I'm slightly beyond that and understand saving/retrieving from sql server. Sig works fine without compression but when often runs over 8000 limit for varchar. I have it compressing and saving to the database but when I try to re-inflate i'm gettting closed angle bracket sometimes within the compressed json which is then causing the redraw to fail... I am working on it and will probably get it. Thought it might have been charset or data type issue but still getting > within the compressed json. Any help/ideas would be welcome...

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