I have a Vue Signature Pad by neighborhood999 in my Larvel/Vue3 project. I want to crop the signature because the user might just use one side of the available space and not in the center. I have searched for it around and found this github issue in szimek/signature_pad offered by multiple users with different approaches.
However my main hurdle is all the functions written by the users in that post required the canvas element of the signature Pad which is not exposed by Vue Signature Pad by neighborhood999. And I dont know anyway on how to access that canvas element in vue3. Both code implementations I found returns the similar error canvas.getContext is not a function whether I use signaturePad.value or signaturePad. Here is what I have so far:
PS:: The signature data is in the records which is an array of signature records. the actual data is contained within records.[0].signature_data and i have next and prev buttons to navigate through them which i have omitted for cleaner look and I feel is not quite necessary for the problem at hand:
<template>
<div class="container w-[650px] h-[350px] flex flex-col border-2 border-primary-500 p-2 rounded-lg">
<div class="container overflow-auto">
<VueSignaturePad width="620px" height="300px" :options="options" :scaleToDevicePixelRatio="true"
ref="signaturePad" />
</div>
</div>
...
<div class="flex flex-row space-x-3 md:justify-start justify-around py-3">
...
<button type="button" class="btn-primary md:w-72 md:flex-none flex-1" @click="saveToFile">Save to file</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
import { VueSignaturePad } from 'vue-signature-pad';
import useSignatureOps from '@/Composables/useSignatureOps';
import axios from 'axios';
// const devStore = useDeveloperStore();
const { convertLegacySignature, StringToArray } = useSignatureOps;
const props = defineProps({
records: Array,
})
const options = ref({
minWidth: 0.5,
maxWidth: 2.5,
penColor: 'black',
backgroundColor: 'white',
});
const saveSignatureForm = useForm({
url: null,
signature_id: null,
})
const signaturePad = ref(null);
const currentIndex = ref(0);
const currentSignature = ref(props.records[currentIndex.value]);
....
const saveToFile = () => {
const { isEmpty, data } = signaturePad.value.saveSignature();
// without cropping the data object returned is perfect with the signature as in the signature pad
if (!isEmpty) {
// i want to crop the signature here and download the png to the user...
// this below line is throwing an exception canvas.getContext is not a function
const cropped = getCroppedCanvasImage(signaturePad.value);
console.log(cropped);
// this below line is another implementation i found also throwing an exception canvas.getContext is not a function
const cropped = getCroppedCanvasImage(signaturePad.value);
console.log(cropped);
} else {
console.warn('Warning: Signature is empty.');
}
}
function getCroppedCanvasImage(canvas) { // here how do I pass the canvas object??
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();
}
const cropSignatureCanvas = (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();
}
</script>
You need to pass the canvas hidden inside the
refvalue and then inside the actual signaturePad Object , Here's how i did itHere
signaturePadRefis equivalent tosignaturePadin your code You can modify your code this way