Coder Social home page Coder Social logo

Comments (66)

jaredatch avatar jaredatch commented on May 22, 2024 26

Here's what we ended up using.

The big difference and thing to note is that running this does NOT modify the primary canvas. It duplicates it, then trims the signature on the duplicate, and returns the data url. This is because I didn't want to modify the canvas the user sees.

Our use case is we have the signature field and a hidden field, which is for the data URL. Every time onEnd fires, we run the signature through the crop function (which returns the trimmed data) and then insert it/update the hidden field.

Works very well. Hopefully it helps someone, took a bit of fiddling to get worked out :)

        /**
         * Crop signature canvas to only contain the signature and no whitespace.
         *
         * @since 1.0.0
         */
        cropSignatureCanvas: function(canvas) {

            // First duplicate the canvas to not alter the original
            var croppedCanvas = document.createElement('canvas'),
                croppedCtx    = croppedCanvas.getContext('2d');

                croppedCanvas.width  = canvas.width;
                croppedCanvas.height = canvas.height;
                croppedCtx.drawImage(canvas, 0, 0);

            // Next do the actual cropping
            var w         = croppedCanvas.width,
                h         = croppedCanvas.height,
                pix       = {x:[], y:[]},
                imageData = croppedCtx.getImageData(0,0,croppedCanvas.width,croppedCanvas.height),
                x, y, index;

            for (y = 0; y < h; y++) {
                for (x = 0; x < w; x++) {
                    index = (y * w + x) * 4;
                    if (imageData.data[index+3] > 0) {
                        pix.x.push(x);
                        pix.y.push(y);

                    }
                }
            }
            pix.x.sort(function(a,b){return a-b});
            pix.y.sort(function(a,b){return a-b});
            var n = pix.x.length-1;

            w = pix.x[n] - pix.x[0];
            h = pix.y[n] - pix.y[0];
            var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h);

            croppedCanvas.width = w;
            croppedCanvas.height = h;
            croppedCtx.putImageData(cut, 0, 0);

            return croppedCanvas.toDataURL();
        },

from signature_pad.

rodrigovallades avatar rodrigovallades commented on May 22, 2024 16

I disagree that this funcionality should be handled by a separate code/library/component. Don't get me wrong, please. But the signature pad exists to capture a users' signature, and the blank space around it is not part of the signature. Also, in many development teams this is a job for a frontend developer, and most times a frontend developer doesn't have access to backend funcionality to trim the imagem server-side. Signature-pad should handle it, in my opinion.

As I said, please don't get me wrong!

from signature_pad.

moritzgloeckl avatar moritzgloeckl commented on May 22, 2024 10

I translated @jaredatch's solution to Angular 2 / Typescript:

let canvas: HTMLCanvasElement = document.getElementsByTagName("canvas")[0];
let croppedCanvas:HTMLCanvasElement = document.createElement('canvas');
let croppedCtx:CanvasRenderingContext2D = croppedCanvas.getContext("2d");

croppedCanvas.width = canvas.width;
croppedCanvas.height = canvas.height;
croppedCtx.drawImage(canvas, 0, 0);

let w = croppedCanvas.width;
let h = croppedCanvas.height;
let pix = {x:[], y:[]};
let imageData = croppedCtx.getImageData(0,0,w,h);
let index = 0;

for (let y = 0; y < h; y++) {
	for (let x = 0; x < w; x++) {
		index = (y * w + x) * 4;
		if (imageData.data[index+3] > 0) {
			pix.x.push(x);
			pix.y.push(y);
		}
	}
}

pix.x.sort((a,b) => a-b);
pix.y.sort((a,b) => a-b);
let n = pix.x.length-1;

w = pix.x[n] - pix.x[0];
h = pix.y[n] - pix.y[0];
var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h);

croppedCanvas.width = w;
croppedCanvas.height = h;
croppedCtx.putImageData(cut, 0, 0);

return croppedCanvas.toDataURL();

Works great!

from signature_pad.

Riba-Kit avatar Riba-Kit commented on May 22, 2024 4

jaredatch's code with small modifications becomes 3-4x faster

function getCroppedCanvasImage(canvas: HTMLCanvasElement) {

	let originalCtx = canvas.getContext('2d');

	let originalWidth = canvas.width;
	let originalHeight = canvas.height;
	let imageData = originalCtx.getImageData(0,0, originalWidth, originalHeight);

	let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;

	for (y = 0; y < originalHeight; y++) {
		for (x = 0; x < originalWidth; x++) {
			currentPixelColorValueIndex = (y * originalWidth + x) * 4;
			let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
			if (currentPixelAlphaValue > 0) {
				if (minX > x) minX = x;
				if (maxX < x) maxX = x;
				if (minY > y) minY = y;
				if (maxY < y) maxY = y;
			}
		}
	}

	let croppedWidth = maxX - minX;
	let croppedHeight = maxY - minY;
	if (croppedWidth < 0 || croppedHeight < 0) return null;
	let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);

	let croppedCanvas = document.createElement('canvas'),
		croppedCtx    = croppedCanvas.getContext('2d');

	croppedCanvas.width = croppedWidth;
	croppedCanvas.height = croppedHeight;
	croppedCtx.putImageData(cuttedImageData, 0, 0);

	return croppedCanvas.toDataURL();
}

from signature_pad.

larrymcp avatar larrymcp commented on May 22, 2024 4

Fantastic @Riba-Kit : many thanks!

One question, should the line:

if (croppedWidth < 0 || croppedHeight < 0) return null;

...include the zero also? I modified it to:

if (croppedWidth <= 0 || croppedHeight <= 0) return null;

...because I found documentation which says that getImageData will throw an exception if either of those is zero.

from signature_pad.

tylerhsu avatar tylerhsu commented on May 22, 2024 4

As others have mentioned, the solutions earlier in this thread produce bitmap images. For those looking to obtain a cropped SVG, here's the function I use. First call signaturePad.toSVG(), then pass the resulting string to this function, which returns a cropped version.

It manipulates the svg's viewBox attribute to "zoom in" on the appropriate area. It determines the appropriate area by calling the builtin getBBox() function on the SVG element.

function cropSVG(svgText: string): string {
  // First convert the svg string into an html element so we can
  // call getBBox() on it.
  const tempElement = document.createElement('div');
  tempElement.setAttribute('style', 'visibility: hidden');
  // getBBox() only works if the element is rendered into the page.
  document.body.appendChild(tempElement);
  tempElement.innerHTML = svgText;
  const svgElement = tempElement.getElementsByTagName('svg')[0]
  // This gets the bounding box around the signature.
  const bbox = svgElement.getBBox();
  // Use the bounding box's coordinates as the svg's viewBox.
  // This eliminates the whitespace by "zooming in" on the
  // bounding box.  Include 5px extra, as the signature's
  // edges get slightly clipped otherwise.
  const viewBox = [bbox.x - 5, bbox.y - 5, bbox.width + 10, bbox.height + 10].join(" ");
  svgElement.setAttribute("viewBox", viewBox);
  // Let the svg have the size and aspect ratio of the
  // cropped result, not the original canvas.
  svgElement.removeAttribute("width");
  svgElement.removeAttribute("height");
  const croppedSVG = svgElement.outerHTML;
  document.body.removeChild(tempElement);
  return croppedSVG;
}

Use as follows:

const croppedSVGString = cropSVG(signaturePad.toSVG());

from signature_pad.

hariomgoyal64 avatar hariomgoyal64 commented on May 22, 2024 3

@agilgur5 I tried many solutions for this but none worked. I don't know how it works for some people. Still the best solution which works for me :
trimCanvas(c) { const ctx = c.getContext('2d'); const copy = document.createElement('canvas').getContext('2d'); const pixels = ctx.getImageData(0, 0, c.width, c.height); const l = pixels.data.length; let i; const bound = { top: null, left: null, right: null, bottom: null }; let x; let y; // Iterate over every pixel to find the highest // and where it ends on every axis () for (i = 0; i < l; i += 4) { if (pixels.data[i + 3] !== 0) { x = (i / 4) % c.width; // tslint:disable-next-line: no-bitwise y = ~~((i / 4) / c.width); if (bound.top === null) { bound.top = y; } if (bound.left === null) { bound.left = x; } else if (x < bound.left) { bound.left = x; } if (bound.right === null) { bound.right = x; } else if (bound.right < x) { bound.right = x; } if (bound.bottom === null) { bound.bottom = y; } else if (bound.bottom < y) { bound.bottom = y; } } } // Calculate the height and width of the content const trimHeight = bound.bottom - bound.top; const trimWidth = bound.right - bound.left; const trimmed = ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight); copy.canvas.width = trimWidth; copy.canvas.height = trimHeight; copy.putImageData(trimmed, 0, 0); // Return trimmed canvas return copy.canvas; }

from signature_pad.

tillifywebb avatar tillifywebb commented on May 22, 2024 2

I agree. Since this is for a signature in some respects it makes sense that the white space should be trimmed, or added as a flag: trimWhiteSpace: true.
Just seems so sensible to do it client-side and within the library.

from signature_pad.

marmangarcia avatar marmangarcia commented on May 22, 2024 2

this save my life thank you @efc

signaturePad.removeBlanks();
$('#base64Data').val(signaturePad.toDataURL());

from signature_pad.

leonetosoft avatar leonetosoft commented on May 22, 2024 2

I translated @jaredatch's solution to Angular 2 / Typescript:

let canvas: HTMLCanvasElement = document.getElementsByTagName("canvas")[0];
let croppedCanvas:HTMLCanvasElement = document.createElement('canvas');
let croppedCtx:CanvasRenderingContext2D = croppedCanvas.getContext("2d");

croppedCanvas.width = canvas.width;
croppedCanvas.height = canvas.height;
croppedCtx.drawImage(canvas, 0, 0);

let w = croppedCanvas.width;
let h = croppedCanvas.height;
let pix = {x:[], y:[]};
let imageData = croppedCtx.getImageData(0,0,w,h);
let index = 0;

for (let y = 0; y < h; y++) {
	for (let x = 0; x < w; x++) {
		index = (y * w + x) * 4;
		if (imageData.data[index+3] > 0) {
			pix.x.push(x);
			pix.y.push(y);
		}
	}
}

pix.x.sort((a,b) => a-b);
pix.y.sort((a,b) => a-b);
let n = pix.x.length-1;

w = pix.x[n] - pix.x[0];
h = pix.y[n] - pix.y[0];
var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h);

croppedCanvas.width = w;
croppedCanvas.height = h;
croppedCtx.putImageData(cut, 0, 0);

return croppedCanvas.toDataURL();

Works great!

PERFECT!!!!

from signature_pad.

ustincameron avatar ustincameron commented on May 22, 2024 2

Please consider merging this in since the whitespace is not part of a signature. Trying to use this within React client-side pdf generation. Third-party packages like trim-canvas are outdated and fail to load @babel/core correctly.

from signature_pad.

vivdroid avatar vivdroid commented on May 22, 2024 2

In case someone wants to have this with white background, just add this little change inside for loop.
Also I have added some extra transparent padding to the final image.

Final Code

const signPad = new SignaturePad(canvasRef.current, {
  backgroundColor: 'rgb(255, 255, 255)' // adding background color to signature pad
});

// function to remove blanks
export function removeBlanks(canvas) {
  let croppedCanvas = document.createElement('canvas');
  let croppedCtx = croppedCanvas.getContext("2d");

  croppedCanvas.width = canvas.width;
  croppedCanvas.height = canvas.height;
  croppedCtx.drawImage(canvas, 0, 0);

  let w = croppedCanvas.width;
  let h = croppedCanvas.height;
  let pix = { x: [], y: [] };
  let imageData = croppedCtx.getImageData(0, 0, w, h);
  let index = 0;

  for (let y = 0; y < h; y++) {
    for (let x = 0; x < w; x++) {
      index = (y * w + x) * 4;
      // if (imageData.data[index + 3] > 0) {
      if (imageData.data[index] === 255 
        && imageData.data[index + 1] === 255 
        && imageData.data[index + 2] === 255 
        && imageData.data[index + 3] === 255 
        ) {
        continue;
      }
      if (imageData.data[index + 3] > 0) {
        pix.x.push(x);
        pix.y.push(y);
      }
    }
  }

  pix.x.sort((a, b) => a - b);
  pix.y.sort((a, b) => a - b);
  let n = pix.x.length - 1;

  w = pix.x[n] - pix.x[0];
  h = pix.y[n] - pix.y[0];
  var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h);

  croppedCanvas.width = w + 40; // extra width
  croppedCanvas.height = h + 40; // extra height
  croppedCtx.putImageData(cut, 20, 20); // extra height/width

  return croppedCanvas.toDataURL();
}

from signature_pad.

efc avatar efc commented on May 22, 2024 1

Thanks, @agilgur5. That's great!

from signature_pad.

mochsner avatar mochsner commented on May 22, 2024 1

Running into issues with tests for all non-angular versions of this program... is it still up-to-date? Been a late night, so sorry for any potentially major oversights...

For all the images, the loops don't seem to be catching where "text" is -- anything that's white seems to be text. It's simply cropping the canvas by 1 pixel for each iteration. Any help would be appreciated :)

from signature_pad.

agilgur5 avatar agilgur5 commented on May 22, 2024 1

@ustincameron hi, dude who made trim-canvas here, which was built from this thread (see the comments). All of the comments in this thread are virtually identical to it and each other.

Third-party packages like trim-canvas are outdated

Just because a package (like trim-canvas) hasn't published a new version in years, doesn't mean it's outdated. trim-canvas gets ~37k downloads every single week and is used inside of react-signature-canvas (also made by me) and vue-signature-canvas among others, which are actively used as well. It has 100% test coverage as does react-signature-canvas, which also has a few live, working examples.

and fail to load @babel/core correctly.

Well trim-canvas was built before @babel/core (Babel 7) existed, so it doesn't even use it. babel-core (Babel 6) is a dev dependency and there are no (prod) dependencies. Can look at the build on UNPKG which doesn't make any imports/requires

If you've got a problem with trim-canvas, I'd recommend filing an issue in that repo with a detailed reproduction or failing test case.

from signature_pad.

the-hotmann avatar the-hotmann commented on May 22, 2024 1

Here's what we ended up using.

The big difference and thing to note is that running this does NOT modify the primary canvas. It duplicates it, then trims the signature on the duplicate, and returns the data url. This is because I didn't want to modify the canvas the user sees.

Our use case is we have the signature field and a hidden field, which is for the data URL. Every time onEnd fires, we run the signature through the crop function (which returns the trimmed data) and then insert it/update the hidden field.

Works very well. Hopefully it helps someone, took a bit of fiddling to get worked out :)

        /**
         * Crop signature canvas to only contain the signature and no whitespace.
         *
         * @since 1.0.0
         */
        cropSignatureCanvas: function(canvas) {

            // First duplicate the canvas to not alter the original
            var croppedCanvas = document.createElement('canvas'),
                croppedCtx    = croppedCanvas.getContext('2d');

                croppedCanvas.width  = canvas.width;
                croppedCanvas.height = canvas.height;
                croppedCtx.drawImage(canvas, 0, 0);

            // Next do the actual cropping
            var w         = croppedCanvas.width,
                h         = croppedCanvas.height,
                pix       = {x:[], y:[]},
                imageData = croppedCtx.getImageData(0,0,croppedCanvas.width,croppedCanvas.height),
                x, y, index;

            for (y = 0; y < h; y++) {
                for (x = 0; x < w; x++) {
                    index = (y * w + x) * 4;
                    if (imageData.data[index+3] > 0) {
                        pix.x.push(x);
                        pix.y.push(y);

                    }
                }
            }
            pix.x.sort(function(a,b){return a-b});
            pix.y.sort(function(a,b){return a-b});
            var n = pix.x.length-1;

            w = pix.x[n] - pix.x[0];
            h = pix.y[n] - pix.y[0];
            var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h);

            croppedCanvas.width = w;
            croppedCanvas.height = h;
            croppedCtx.putImageData(cut, 0, 0);

            return croppedCanvas.toDataURL();
        },

Sorry for this late reply, but is there an option to use this for SVG?
For me this always exports a PNG even if I replace the last line with:

return croppedCanvas.toDataURL('image/svg+xml');

Really searching for a way to trim a Canvas and export it as SVG and not as any bitmap

from signature_pad.

cnuarin avatar cnuarin commented on May 22, 2024 1

Thanks.
To center the signature without cropping (works for my need):

Replace the code:

    this._canvas.width = cropRight-cropLeft;
    this._canvas.height = cropBottom-cropTop;
    this._ctx.clearRect(0, 0, cropRight-cropLeft, cropBottom-cropTop);
    this._ctx.putImageData(relevantData, 0, 0);

with:

    this._ctx.clearRect(0, 0, imgWidth, imgHeight);
    this._ctx.putImageData(relevantData, (imgWidth - (cropRight-cropLeft))/2, (imgHeight - (cropBottom-cropTop))/2);

from signature_pad.

Riba-Kit avatar Riba-Kit commented on May 22, 2024 1

@larrymcp Thanks! You found a bug in my production)

from signature_pad.

efc avatar efc commented on May 22, 2024 1

@merbin2012, yes, this requires a transparent background. But instead of coloring the background of this element, why not try wrapping it in another div to which you apply your background color?

from signature_pad.

szimek avatar szimek commented on May 22, 2024

Thanks! While it might be useful to be able to trim it on the client side (to display it later to the user, or to upload less data), if it's not actually needed there, it's simpler to trim it on the server side using e.g. ImageMagick - convert -trim input.jpg output.jpg.

As it's not specific to the library, but rather a generic image processing operation, I'd like to avoid merging it. However, I'll leave it opened for now, so that it's easier to find for others.

from signature_pad.

efc avatar efc commented on May 22, 2024

I agree that if the server-side is accessible, that is a better place to put trimming. However, I am working with a server that does not (and will not) have ImageMagick installed. Still, even in this situation, the trimming could be placed in javascript outside the library, so you are probably right to keep it out of the library itself. (Apologies, flailing a bit with git comments!)

from signature_pad.

nathanbertram avatar nathanbertram commented on May 22, 2024

@efc super useful, thanks! I like the idea of doing it client side and saving the server from having to process this.

from signature_pad.

contactmatts avatar contactmatts commented on May 22, 2024

Not only is it useful, but it's needed. I'm working with Salesforce (cloud based) and have no access to perform image trimming on the server.

from signature_pad.

fmp777 avatar fmp777 commented on May 22, 2024

I agree, this should be merged into the project. THANK YOU MUCH EFC!

from signature_pad.

szimek avatar szimek commented on May 22, 2024

I don't think I'll ever merge it into this library. Like I mentioned before, removing white space around an image can be moved into a separate library, because it's not specific to signature pad library and doesn't use any part of it.

Feel free to create a small library with the code posted by @efc and I'll add info about it to readme file. The code would probably still need to be modified to support e.g. non-transparent background colors.

from signature_pad.

mikemclin avatar mikemclin commented on May 22, 2024

This can also be done easily in PHP using the Intervention library... http://image.intervention.io/api/trim

UPDATE - While this works, I found it to be VERY processor intensive, especially when submitting a large signature pad image (I was using GD library). I am now trimming in the browser, which seems "instant", and doesn't use up server resources.

from signature_pad.

BasitAli avatar BasitAli commented on May 22, 2024

Could this be merged in? It's not a bad idea to have an additional utility function. If someone needs client side trimming (as I do), they can use this method. Currently I rely on a fork, but having it in the main repository makes upgrading straightforward.

from signature_pad.

szimek avatar szimek commented on May 22, 2024

@BasitAli It won't be merged. You don't have to rely on a fork - you can simply use the function provided by @efc. It doesn't access any data from the library - only the canvas, so it doesn't even have to be on SignaturePad class. You can simply add it as e.g. window.imageUtils.trim or something similar and use that.

from signature_pad.

BasitAli avatar BasitAli commented on May 22, 2024

Ah, yes, ofcourse. Thanks :).

from signature_pad.

szimek avatar szimek commented on May 22, 2024

I've just added info about trimming images to readme file and linked to the code provided by @efc (thanks!), so I'm finally closing this issue.

from signature_pad.

rodrigovallades avatar rodrigovallades commented on May 22, 2024

I was able to implement this in my project, it crops the image as it should and sends the cropped image to the server. The problem is that my canvas content gets distorted on screen. I only want to send the cropped image to my server; the changes cannot be made on screen.

I'm trying to create a temporary canvas and crop this instead, but I'm having a hard time.

Can someone help me? Maybe @efc ?

from signature_pad.

mikemclin avatar mikemclin commented on May 22, 2024

The removeBlanks method simply resizes your canvas element. So, after you call it and submit your data to wherever, you need to resize your canvas back to normal size. Here is how I accomplished it in Angular. I know you probably aren't using Angular, but maybe this will give you an idea of how to potentially tackle this problem...

In this implementation, a button triggers the submit() method, which trims the canvas, saves the data, clears the canvas, and then resizes the canvas. The same function that is used to size the canvas on page load, is re-used to size after saving the data.

Hope this helps...

Javascript (Angular Directive)

angular
    .module('app')
    .directive('psSignature', psSignature);

  function psSignature($window, $timeout) {
    return {
      restrict: 'E',
      scope: {
        onCancel: '&',
        onSubmit: '&',
        submitting: '='
      },
      templateUrl: 'signature.html',
      link: function (scope, element, attrs) {
        var canvas = element.find('canvas')[0];
        var canvasWrapper = element.find('.signature__body')[0];
        var signaturePad = new SignaturePad(canvas, {onEnd: makeDirty});
        var dirty = false;

        scope.cancel = scope.onCancel;
        scope.clear = clear;
        scope.submit = submit;
        scope.isDirty = isDirty;
        scope.showCancel = showCancel;

        activate();

        ////////////

        function activate() {
          addListeners();
          $timeout(resizeCanvas, 500);
        }

        function addListeners() {
          // Add
          angular.element($window).on('resize', resizeCanvas);
          // Clean up
          scope.$on('$destroy', function () {
            angular.element($window).off('resize', resizeCanvas);
          });
        }

        function makeDirty() {
          scope.$apply(function () {
            dirty = true;
          });
        }

        function clear() {
          signaturePad.clear();
          dirty = false;
        }

        function submit() {
          signaturePad.removeBlanks();
          scope.onSubmit({contents: signaturePad.toDataURL()});
          clear();
          resizeCanvas();
        }

        function isDirty() {
          return dirty;
        }

        function showCancel() {
          return !!(attrs.onCancel);
        }

        function resizeCanvas() {
          canvas.width = canvasWrapper.offsetWidth;
          canvas.height = canvasWrapper.offsetHeight;
        }
      }
    };
  }

signature.html HTML for directive

<div class="signature">
    <div class="signature__toolbar">
        <div class="signature__cancel">
            <button ng-click="cancel()" ng-show="showCancel()">
                <span class="icon-chevron-left-thin"></span> Cancel
            </button>
        </div>
        <div class="signature__clear">
            <button ng-click="clear()" ng-disabled="!isDirty()">
                Clear
            </button>
        </div>
        <div class="signature__submit">
            <button ng-click="submit()" ng-disabled="!isDirty()">
                <span class="icon-check"></span> Submit
            </button>
        </div>
    </div>
    <div class="signature__body">
        <div class="signature__label">Please Sign Here</div>
        <canvas class="signature__pad"></canvas>
    </div>
</div>

from signature_pad.

efc avatar efc commented on May 22, 2024

Looks like @mikemclin provided a great answer, @rodrigovallades. I don't have to bother resizing the canvas in my app since it loads a whole new HTML page with a fresh canvas, so I'm afraid I can't really improve on Mike's suggestion.

from signature_pad.

rodrigovallades avatar rodrigovallades commented on May 22, 2024

@mikemclin @efc

Actually I'm using angular in my project, indeed. I managed to accomplish what I wanted by duplicating the canvas and trimming the new one, so the canvas showing in my page stays untouched.

But I'll try your suggestion later @mikemclin , thank you!

from signature_pad.

yarnball avatar yarnball commented on May 22, 2024

@mikemclin

Can you give a few more instructions on setting this up? I presume

  1. AngularJS added above head
  2. Create a new Angular file with your sample code
  3. Add <ng-controller="app"> and your code to HTML

That's what I've done so far- and but it does not work.

  1. The buttons is not clickable
  2. If I remove ng-disabled="!isDirty()" and click the button- nothing happens.

Not sure if it is related- I have gotten the error "Cannot read property 'addEventListener' of null" when troubleshooting/

I made a plunker- https://plnkr.co/edit/NOZG5Y4dgrpCZKU5Xkja?p=preview

Is there an update/fix? Thanks

from signature_pad.

prathprabhudesai avatar prathprabhudesai commented on May 22, 2024

Just adjusted the canvas size and signature pad size in the CSS. Worked for me. :)

from signature_pad.

mikemclin avatar mikemclin commented on May 22, 2024

@yarnball I modified my answer slightly to better describe what the code snippets are for. The JavaScript is my Angular directive. The HTML provided was the HTML template for the directive.

Now I would use that directive in my code; something like this...

<ps-signature on-submit="vm.submit(contents)" submitting="vm.submitting">Please sign here...</ps-signature>

You're going to need to have a good understanding of Angular directives to actually use my code in your app. And realistically, it was built for my app's purposes and might not be very flexible for reusability.

from signature_pad.

agilgur5 avatar agilgur5 commented on May 22, 2024

For anyone who's looking for a tiny library that does this, I've created one here: https://github.com/agilgur5/trim-canvas based off @efc's code

Side note:
It's also used in my React implementation of signature_pad, in case you're looking for one of those: https://github.com/agilgur5/react-signature-canvas (based off https://github.com/blackjk3/react-signature-pad)

from signature_pad.

nathanbertram avatar nathanbertram commented on May 22, 2024

+1 @agilgur5

from signature_pad.

shawe avatar shawe commented on May 22, 2024

#49 (comment) This is exactly what I'm looking for. Thanks for sharing!

from signature_pad.

Forehon avatar Forehon commented on May 22, 2024

Hi,
I have to store signatures (base64) in a database using SignaturePad. To big ! (60ko)
This script seems perfect to solve a part of my problem but i don't know how to use it ! :o( (not a js expert)

can you explain how to integrate this ? Where should I copy this great code ? Where should I call it ? A small tuto Please

from signature_pad.

troygrosfield avatar troygrosfield commented on May 22, 2024

Thanks for the snippet @jaredatch! Very helpful!

from signature_pad.

hoangnguyen459 avatar hoangnguyen459 commented on May 22, 2024

Thank you very much for a cool solution to removeBlanks(). I have successfully saved signature without extra blanks. The problem I am encountering is re-loading the signature from database and displaying on signaturePad. The trimmed signature is auto stretching to fit the canvas. This looks not too good. Is there a way to add blanks to the trimmed signature so it will display correctly on the canvas? Thank you very much.

from signature_pad.

minhnhatspk avatar minhnhatspk commented on May 22, 2024

croppedCanvas

Here's what we ended up using.

The big difference and thing to note is that running this does NOT modify the primary canvas. It duplicates it, then trims the signature on the duplicate, and returns the data url. This is because I didn't want to modify the canvas the user sees.

Our use case is we have the signature field and a hidden field, which is for the data URL. Every time onEnd fires, we run the signature through the crop function (which returns the trimmed data) and then insert it/update the hidden field.

Works very well. Hopefully it helps someone, took a bit of fiddling to get worked out :)

        /**
         * Crop signature canvas to only contain the signature and no whitespace.
         *
         * @since 1.0.0
         */
        cropSignatureCanvas: function(canvas) {

            // First duplicate the canvas to not alter the original
            var croppedCanvas = document.createElement('canvas'),
                croppedCtx    = croppedCanvas.getContext('2d');

                croppedCanvas.width  = canvas.width;
                croppedCanvas.height = canvas.height;
                croppedCtx.drawImage(canvas, 0, 0);

            // Next do the actual cropping
            var w         = croppedCanvas.width,
                h         = croppedCanvas.height,
                pix       = {x:[], y:[]},
                imageData = croppedCtx.getImageData(0,0,croppedCanvas.width,croppedCanvas.height),
                x, y, index;

            for (y = 0; y < h; y++) {
                for (x = 0; x < w; x++) {
                    index = (y * w + x) * 4;
                    if (imageData.data[index+3] > 0) {
                        pix.x.push(x);
                        pix.y.push(y);

                    }
                }
            }
            pix.x.sort(function(a,b){return a-b});
            pix.y.sort(function(a,b){return a-b});
            var n = pix.x.length-1;

            w = pix.x[n] - pix.x[0];
            h = pix.y[n] - pix.y[0];
            var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h);

            croppedCanvas.width = w;
            croppedCanvas.height = h;
            croppedCtx.putImageData(cut, 0, 0);

            return croppedCanvas.toDataURL();
        },

It doesn't work for me.
When I draw a small signature, the base64 string is broken and can not display on the browser.
If I draw a big one, there's no problem!

from signature_pad.

 avatar commented on May 22, 2024

I need to use the sig image later in my app and the blank space around the sig can be a bit of a pain. I added an option to remove this blank space so that the image returned when I get the data is the cropped signature. I'm not sure if this would be useful to others, and I am not able to git at the moment, so I'll just offer it here in case it proves helpful.

    SignaturePad.prototype.removeBlanks = function () {
        var imgWidth = this._ctx.canvas.width;
        var imgHeight = this._ctx.canvas.height;
        var imageData = this._ctx.getImageData(0, 0, imgWidth, imgHeight),
        data = imageData.data,
        getAlpha = function(x, y) {
            return data[(imgWidth*y + x) * 4 + 3]
        },
        scanY = function (fromTop) {
            var offset = fromTop ? 1 : -1;

            // loop through each row
            for(var y = fromTop ? 0 : imgHeight - 1; fromTop ? (y < imgHeight) : (y > -1); y += offset) {

                // loop through each column
                for(var x = 0; x < imgWidth; x++) {
                    if (getAlpha(x, y)) {
                        return y;                        
                    }      
                }
            }
            return null; // all image is white
        },
        scanX = function (fromLeft) {
            var offset = fromLeft? 1 : -1;

            // loop through each column
            for(var x = fromLeft ? 0 : imgWidth - 1; fromLeft ? (x < imgWidth) : (x > -1); x += offset) {

                // loop through each row
                for(var y = 0; y < imgHeight; y++) {
                    if (getAlpha(x, y)) {
                        return x;                        
                    }      
                }
            }
            return null; // all image is white
        };

        var cropTop = scanY(true),
        cropBottom = scanY(false),
        cropLeft = scanX(true),
        cropRight = scanX(false);

        var relevantData = this._ctx.getImageData(cropLeft, cropTop, cropRight-cropLeft, cropBottom-cropTop);
        this._canvas.width = cropRight-cropLeft;
        this._canvas.height = cropBottom-cropTop;
        this._ctx.clearRect(0, 0, cropRight-cropLeft, cropBottom-cropTop);
        this._ctx.putImageData(relevantData, 0, 0);
    };

FYI, credit for most of this code goes to http://stackoverflow.com/questions/12175991/crop-image-white-space-automatically-using-jquery

I imagine my need for this feature also grows from my using signature_pad on a larger tablet instead of a phone-size device.

Can someone please explain how to actually call this function?

I am trying to call it this way:
var data = signaturePad.toDataURL('image/png').removeBlanks();
but it does not work. It gives me an error:

Uncaught TypeError: signaturePad.toDataURL(...).removeBlanks is not a function at

HTMLButtonElement.

How should I use it?

from signature_pad.

elegisandi avatar elegisandi commented on May 22, 2024

@Crypto789 you have to do two separate statements to achieve that:

signaturePad.removeBlanks();
var data = signaturePad.toDataURL('image/png');

NOTES: toDataURL method returns a string, so you can't chain any method/props of signaturePad prototype (though any valid String prop/method will do)

from signature_pad.

 avatar commented on May 22, 2024

@Crypto789 you have to do two separate statements to achieve that:

signaturePad.removeBlanks();
var data = signaturePad.toDataURL('image/png');

NOTES: toDataURL method returns a string, so you can't chain any method/props of signaturePad prototype (though any valid String prop/method will do)

Maybe I am not getting something...is it supposed to remove white space around the signature?

from signature_pad.

efc avatar efc commented on May 22, 2024

Yes @Crypto789, it is supposed to remove as much of the bounding rectangle as possible without removing any of the signature itself.

from signature_pad.

shaangidwani avatar shaangidwani commented on May 22, 2024

not working on ipad. is there any solution or am i miss something?

from signature_pad.

agilgur5 avatar agilgur5 commented on May 22, 2024

@shaangidwani my example demo works fine on my iPad. Uses trim-canvas under the hood.

from signature_pad.

hariomgoyal64 avatar hariomgoyal64 commented on May 22, 2024

Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The source width is 0.
I am getting this error using above code @jaredatch

from signature_pad.

agilgur5 avatar agilgur5 commented on May 22, 2024

@hariomdebut that error is a bit cryptic, but it means the canvas has 0 width. That probably means your original canvas (that is cloned) is incorrectly sized

from signature_pad.

ghwrivas avatar ghwrivas commented on May 22, 2024

@hariomdebut it work perfectly, in my code I replaced:
return this._signaturePad.toDataURL(type);
with:
return this.trimCanvas(this._canvas).toDataURL('image/png');

from signature_pad.

agilgur5 avatar agilgur5 commented on May 22, 2024

@Martinh0 that's not really possible as no browser natively supports Canvas to SVG. signature_pad (not the underlying Canvas) can only do it because it records the raw point data and has an algorithm to output SVG from that.

Can see agilgur5/react-signature-canvas#49 (comment) for more details

from signature_pad.

the-hotmann avatar the-hotmann commented on May 22, 2024

@agilgur5 hm ok, thank you. Maybe a bit offtopic, but do you know how to (in JavaScript, or PHP) trim normal SVGs?
I really cant find anything related to trim SVGs. As when I download the SVG if will always have white-borders as the SVG which gets exported was bigger of course.

This then also could (should) be implemented in signature_pad as I think this will be a good feature. But maybe just as option.

from signature_pad.

agilgur5 avatar agilgur5 commented on May 22, 2024

@Martinh0 I found a good few results in a quick search, basically all pertaining to changing the SVG's viewBox. Off the top of my head, not sure if that's the best answer as I haven't done raw SVG manipulation in like 4-6 years (Data Visualization / D3 work).

This then also could (should) be implemented in signature_pad as I think this will be a good feature. But maybe just as option.

The above code is only a few lines of user-land code. Theoretically I could make trim-svg as a counterpart to trim-canvas and would be supportive of either making it into signature_pad, but that's previously been rejected.
react-signature-canvas has a trimCanvas method built-in because of the frequency of these requests. Trimming SVG out-of-the-box is a bit more of a clunky API since one is Canvas => Canvas and the other SVG => SVG, but trimCanvasAsSVG or something could work.

from signature_pad.

ghwrivas avatar ghwrivas commented on May 22, 2024

I'm using that in angular component https://github.com/ghwrivas/ngx-signature-pad/blob/master/projects/ngx-signature-pad/src/lib/ngx-signature-pad.component.ts

from signature_pad.

POOLEworks avatar POOLEworks commented on May 22, 2024

When using Signature pad in a form, what is the best way to return the completed signature base64 data to an input?
Looking at a jquery on submit or maybe it's put in a hidden text input once there's a exit from the canvas. I don't want to chase my tail if that's not it. I have a ton of other information going through this form.
Sending it to a MySQL database as BLOB. This isn't going to be a database called upon daily so I'm not worried about the weight. It's more important to keep the signature with the other submitted info.

from signature_pad.

xitude avatar xitude commented on May 22, 2024

jaredatch's code with small modifications becomes 3-4x faster

function getCroppedCanvasImage(canvas: HTMLCanvasElement) {

	let originalCtx = canvas.getContext('2d');

	let originalWidth = canvas.width;
	let originalHeight = canvas.height;
	let imageData = originalCtx.getImageData(0,0, originalWidth, originalHeight);

	let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;

	for (y = 0; y < originalHeight; y++) {
		for (x = 0; x < originalWidth; x++) {
			currentPixelColorValueIndex = (y * originalWidth + x) * 4;
			let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
			if (currentPixelAlphaValue > 0) {
				if (minX > x) minX = x;
				if (maxX < x) maxX = x;
				if (minY > y) minY = y;
				if (maxY < y) maxY = y;
			}
		}
	}

	let croppedWidth = maxX - minX;
	let croppedHeight = maxY - minY;
	if (croppedWidth < 0 || croppedHeight < 0) return null;
	let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);

	let croppedCanvas = document.createElement('canvas'),
		croppedCtx    = croppedCanvas.getContext('2d');

	croppedCanvas.width = croppedWidth;
	croppedCanvas.height = croppedHeight;
	croppedCtx.putImageData(cuttedImageData, 0, 0);

	return croppedCanvas.toDataURL();
}

This returns it as .png :(

from signature_pad.

tonyalfaro avatar tonyalfaro commented on May 22, 2024

This returns it as .png :(

If you need a jpeg image, you must change :
return croppedCanvas.toDataURL();

to :
return croppedCanvas.toDataURL('image/jpeg', 1.0);

from signature_pad.

xjlin0 avatar xjlin0 commented on May 22, 2024

If exporting SVG are required, @efc 's signaturePad based solution is still the only one works without other dependencies. For signature_pad.umd.min.js v 4.0.7, it needs to modified two lines, from

this._canvas.width = cropRight-cropLeft;
this._canvas.height = cropBottom-cropTop;

to

this._ctx.canvas.width = cropRight-cropLeft;
this._ctx.canvas.height = cropBottom-cropTop;

If other file formats are allowed, one can check first which MIME types are working in browsers before using canvas based solutions such as @Riba-Kit 's. However when exporting in jpg (either signaturePad.toDataURL("image/jpeg", 0.5) or canvas.toDataURL("image/jpeg", 0.5)), none of blanks were trimmed. 😞

from signature_pad.

Andrei-Fogoros avatar Andrei-Fogoros commented on May 22, 2024

Hi Jared @jaredatch,
Could you please tell us what is the license for the above function? :)

Regards,
Andrei

from signature_pad.

ryangriggs avatar ryangriggs commented on May 22, 2024

It would be really helpful for the signature object to also return the bounding rectangle of the signature (no need to actually crop the image). Then we could do the processing ourselves. Something like signature.bounds() which returns { left, top, width, height } of the actual signature image. This could be calculated while the user is drawing the sig, thus eliminating the need to scan the entire image afterward.

from signature_pad.

merbin2012 avatar merbin2012 commented on May 22, 2024

@efc Thanks for your modification, this modification works perfectly if the background is transparent, but if we change the background color of the canvas, it doesn't work. Can you please help me?

from signature_pad.

merbin2012 avatar merbin2012 commented on May 22, 2024

@efc Thanks for your suggestion, I will try and let you know.

from signature_pad.

LucasBourgeois avatar LucasBourgeois commented on May 22, 2024

Hi,
Im using @jaredatch function and facing a weird issue..
ive this error from error reports from users but i can't really reproduce it by myself :
TypeError: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': Value is not of type 'long'.: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': Value is not of type 'long'.

from : var cut = croppedCtx.getImageData(pix.x[0], pix.y[0], w, h);.

The only thing i can find if that : either pix.x[0], pix.y[0], w or h is undefined or NaN..
but i can't find why.
Of course, i only run this function when canvas has been touched by user and has pixel on it.
Anyone had this issue before ?

thanks

from signature_pad.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.