5

We have a requirement to open an image attachment in a VisualForce page, the user should be able to draw on the image and those edits should be updated in the attachment on save. So to achieve this, we used HTML5 canvas tag and implemented the drawing functionality.

The following is the code used:

JavaScript

function canvasAdder() {
if ($("#sb-info").find(".undoButton").length == 0) {
    $("#sb-info").prepend("<button class='saveButton'>Save</button><button class='undoButton'>Undo</button>");
}

$("#sb-info").css("text-align", "center");
$(".undoButton").click(function() {
    var canvas = document.getElementById("imageFile");
    var ctx = canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    var existingImage = document.getElementById("sb-player");
    var sketch = document.querySelector('#sb-body-inner');
    var sketch_style = getComputedStyle(sketch);
    canvas.width = parseInt(sketch_style.getPropertyValue('width'));
    canvas.height = parseInt(sketch_style.getPropertyValue('height'));

    ctx.drawImage(existingImage, 0, 0, canvas.width, canvas.height);
});

$(".saveButton").click(function() {
    insertImg(currentAttachmentId);
});
$("#sb-body-inner").prepend("<canvas id='imageFile'></canvas>");
var existingImage = document.getElementById("sb-player");

var canvas = document.getElementById('imageFile');
var ctx = canvas.getContext("2d");

var sketch = document.querySelector('#sb-body-inner');
var sketch_style = getComputedStyle(sketch);
canvas.width = parseInt(sketch_style.getPropertyValue('width'));
canvas.height = parseInt(sketch_style.getPropertyValue('height'));

ctx.drawImage(existingImage, 0, 0, canvas.width, canvas.height);

// Creating a tmp canvas
var tmp_canvas = document.createElement('canvas');
var tmp_ctx = tmp_canvas.getContext('2d');
tmp_canvas.id = 'tmp_canvas';
tmp_canvas.width = canvas.width;
tmp_canvas.height = canvas.height;
tmp_canvas.style.top = -1 * (canvas.height + 3) + 'px';
sketch.appendChild(tmp_canvas);

var mouse = {
    x: 0,
    y: 0
};
var start_mouse = {
    x: 0,
    y: 0
};


/* Mouse Capturing Work */
tmp_canvas.addEventListener('mousemove', function(e) {
    mouse.x = typeof e.offsetX !== 'undefined' ? e.offsetX : e.layerX;
    mouse.y = typeof e.offsetY !== 'undefined' ? e.offsetY : e.layerY;
}, false);


/* Drawing on Paint App */
tmp_ctx.lineWidth = 5;
tmp_ctx.lineJoin = 'round';
tmp_ctx.lineCap = 'round';
tmp_ctx.strokeStyle = 'red';
tmp_ctx.fillStyle = 'red';

tmp_canvas.addEventListener('mousedown', function(e) {
    tmp_canvas.addEventListener('mousemove', onPaint, false);

    mouse.x = typeof e.offsetX !== 'undefined' ? e.offsetX : e.layerX;
    mouse.y = typeof e.offsetY !== 'undefined' ? e.offsetY : e.layerY;

    start_mouse.x = mouse.x;
    start_mouse.y = mouse.y;

    onPaint();
}, false);

tmp_canvas.addEventListener('mouseup', function() {
    tmp_canvas.removeEventListener('mousemove', onPaint, false);

    // Writing down to real canvas now
    ctx.drawImage(tmp_canvas, 0, 0);
    // Clearing tmp canvas
    tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);

}, false);

var onPaint = function() {

    // Tmp canvas is always cleared up before drawing.
    tmp_ctx.clearRect(0, 0, tmp_canvas.width, tmp_canvas.height);

    var x = Math.min(mouse.x, start_mouse.x);
    var y = Math.min(mouse.y, start_mouse.y);
    var width = Math.abs(mouse.x - start_mouse.x);
    var height = Math.abs(mouse.y - start_mouse.y);
    tmp_ctx.strokeRect(x, y, width, height);

};
}
function insertImg(attachmentId) {
    //var canvas = document.getElementsByTagName('canvas')[0];
    var canvas = document.getElementById('imageFile');
    var convertedImage = convertCanvasToImage(canvas);
    console.log(convertedImage);
    insertBlob(convertedImage, attachmentId);
}

function convertCanvasToImage(canvas) {
    var image = new Image();
    image.crossOrigin = "Anonymous"; // to avoid security Error: Tainted canvases may not be exported.
    image.setAttribute('crossOrigin', 'anonymous');
    image = canvas.toDataURL();
    return image;
}

Rendered HTML from VisualForce

<div id="sb-wrapper-inner" style="height: 258px;">
    <div id="sb-body">
        <div id="sb-body-inner">
            <canvas id="imageFile" width="701" height="526"></canvas>
            <img id="sb-player" src="https://**_our-domain1_**.cs17.visual.force.com/servlet/servlet.FileDownload?file=00Pg0000004gw3vEAA" height="258" width="344" style="position: absolute;">
            <canvas id="tmp_canvas" width="701" height="526" style="top: -529px;"></canvas>
        </div>
        <div id="sb-loading" style="display: none;">
            <div id="sb-loading-inner"><span>loading</span></div>
        </div>
    </div>
</div>

But on trying to save the edited image, we are hitting the following error:

Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

The error occurs at the function call canvas.toDataURL();

On searching how to solve this problem, we found that we had to set crossorigin attribute for the image as anonymous. But on trying that, we got the following error:

Redirect from 'https://our-domain1.cs17.my.salesforce.com/servlet/servlet.FileDownload?file=00Pg0000004ggDDEAY' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://our-domain1.cs17.visual.force.com' is therefore not allowed access

Is there any work around to save a tainted canvas file in salesforce?

Referred stackoverflow answers:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.