Comments (66)
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.
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.
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.
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.
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.
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.
@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.
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.
this save my life thank you @efc
signaturePad.removeBlanks();
$('#base64Data').val(signaturePad.toDataURL());
from signature_pad.
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.
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.
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.
Thanks, @agilgur5. That's great!
from signature_pad.
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.
@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 import
s/require
s
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.
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.
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.
@larrymcp Thanks! You found a bug in my production)
from signature_pad.
@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.
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.
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.
@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.
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.
I agree, this should be merged into the project. THANK YOU MUCH EFC!
from signature_pad.
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.
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.
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.
@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.
Ah, yes, ofcourse. Thanks :).
from signature_pad.
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.
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.
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.
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.
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.
Can you give a few more instructions on setting this up? I presume
- AngularJS added above head
- Create a new Angular file with your sample code
- Add <ng-controller="app"> and your code to HTML
That's what I've done so far- and but it does not work.
- The buttons is not clickable
- 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.
Just adjusted the canvas size and signature pad size in the CSS. Worked for me. :)
from signature_pad.
@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.
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.
+1 @agilgur5
from signature_pad.
#49 (comment) This is exactly what I'm looking for. Thanks for sharing!
from signature_pad.
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.
Thanks for the snippet @jaredatch! Very helpful!
from signature_pad.
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.
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.
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.
@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.
@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 validString
prop/method will do)
Maybe I am not getting something...is it supposed to remove white space around the signature?
from signature_pad.
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.
not working on ipad. is there any solution or am i miss something?
from signature_pad.
@shaangidwani my example demo works fine on my iPad. Uses trim-canvas
under the hood.
from signature_pad.
Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The source width is 0.
I am getting this error using above code @jaredatch
from signature_pad.
@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.
@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.
@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.
@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.
@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.
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.
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.
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.
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.
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.
Hi Jared @jaredatch,
Could you please tell us what is the license for the above function? :)
Regards,
Andrei
from signature_pad.
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.
@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.
@efc Thanks for your suggestion, I will try and let you know.
from signature_pad.
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)
- missing circles with fast signing speed HOT 17
- Signature pad cursor issue on mac chrome. HOT 7
- Only Dots on Canvas HOT 4
- Point groups with only 2 points are not drawn HOT 4
- Buttons unresponsive immediately after fast strokes in Chrome Android HOT 3
- Pen highlight for drawing on images
- Cannot find name 'GlobalCompositeOperation'. HOT 2
- TypeError: Cannot convert null value to object HOT 4
- Cannot find name 'GlobalCompositeOperation'.
- Console error "Unable to preventDefault inside passive event listener invocation." HOT 3
- Loading fromDataUrl and fromData together? HOT 1
- issues in latest Google Chrome 117 HOT 6
- Problem when I use fromdata to duplicate the signature (Scale / Format?) HOT 1
- displaced drawing HOT 1
- Can we have an indicator to know if the signature pad is pristine or dirty? HOT 6
- If I just don't wanna draw in the canvas, just wanna click. HOT 1
- High DPI devices calling toSVG has the width/height cut off HOT 2
- The Troke drawing doesn't look good in the html canvas HOT 3
- Include `dist` (Compiled Resources) in Releases. HOT 6
- Include the willReadFrequently in getContext HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from signature_pad.