Cropping Signature Image in Vue SignaturePad

75 Views Asked by At

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>
1

There are 1 best solutions below

0
Muhammad abdullah Qamar On

You need to pass the canvas hidden inside the ref value and then inside the actual signaturePad Object , Here's how i did it

cropSignatureCanvas(signaturePadRef.value.signaturePad.canvas)

Here signaturePadRef is equivalent to signaturePad in your code You can modify your code this way

const saveToFile = () => {
  const { isEmpty, data } = signaturePad.value.saveSignature();
  if (!isEmpty) {
      const cropped = 
      cropSignatureCanvas(signaturePad.value.signaturePad.canvas); 
      console.log(cropped);
  } else {
      console.warn('Warning: Signature is empty.');
  }
}