Utilizing the Vue Signature Pad by neighborhood999 within my Larvel/Vue3 project has been a key feature. However, I am facing an issue crop the signature as users may not utilize the entire available space and might stay towards one side. Researching this problem led me to a GitHub discussion in szimek/signature_pad where multiple users proposed different approaches.
The main challenge I encountered is that all the solutions provided in the post require access to the canvas element of the Signature Pad, which is not exposed by Vue Signature Pad by neighborhood999. I am unsure how to access the canvas element in Vue3. Implementing the code snippets from the post resulted in a common error message:
error canvas.getContext is not a function
, regardless of whether using signaturePad.value or signaturePad. Here's what I have tried so far:
Note: The signature data resides in records, which is an array of signature records, with the actual data contained within records.[0].signature_data. Omitted for clarity are next and prev buttons used for navigation, which I believe are not crucial to the current issue 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>