Change html canvas black background to white background when creating jpg image from png image
Asked Answered
A

8

16

I have a canvas which is loaded with a png image. I get its jpg base64 string by .toDataURL() method like this:

 $('#base64str').val(canvas.toDataURL("image/jpeg"));

But the transparent parts of the png image are shown black in the new jpg image.

Any solutions to change this color to white? Thanks in advance.

Allocate answered 22/8, 2015 at 19:42 Comment(0)
L
19

This blackening occurs because the 'image/jpeg' conversion involves setting the alpha of all canvas pixels to fully opaque (alpha=255). The problem is that transparent canvas pixels are colored fully-black-but-transparent. So when you turn these black pixels opaque, the result is a blackened jpeg.

The workaround is to manually change all non-opaque canvas pixels to your desired white color instead of black.

That way when they are made opaque they will appear as white instead of black pixels.

Here's how:

// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
    if(data[i+3]<255){
        data[i]=255;
        data[i+1]=255;
        data[i+2]=255;
        data[i+3]=255;
    }
}
ctx.putImageData(imgData,0,0);
Landrum answered 22/8, 2015 at 19:56 Comment(3)
Thank u. Can I do this in ASP.NET code behind after the black-padded image has been passed to code behind?Allocate
You're welcome! You should process the image on the client-side before passing it out to code-behind. That's because html5 canvas only works with javascript and JS is not natively available in code-behind (at least not available in the ways necessary to use html5 canvas).Landrum
I can't believe this is the only way to address this. There must be some way to change the default color. I am going to have to research this.Sapient
D
15

After spending a lot of time on this and this post specifically, and these solutions kinda worked expect I just couldn't get the canvas to look right. Anyway I found this solution elsewhere and wanted to post it here incase it helps someone else from spending hours trying to get the black background to white and look like the original.

public getURI(): string {
    let canvas = <HTMLCanvasElement>document.getElementById('chartcanvas');
    var newCanvas = <HTMLCanvasElement>canvas.cloneNode(true);
    var ctx = newCanvas.getContext('2d');
    ctx.fillStyle = "#FFF";
    ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);
    ctx.drawImage(canvas, 0, 0);
    return newCanvas.toDataURL("image/jpeg");
}
Doublestop answered 5/10, 2018 at 20:32 Comment(3)
Thanks, this comment allowed me to see that pretty much all I needed was canvas.fillStyle = '#FFF';.Fullmouthed
after spending hours i got this answer . this should be in top . . .Castiglione
Excellent. I did spend several hours on this code. As Jishan said this should be at the top. Thank youBedlamite
S
14

This answer is a bit longer, but I find it to be more 'correct' in that it handles these things without directly modifying raw canvas data. I find that to be a pretty messy and theoretically unsatisfying solution. There are built in functions to achieve that, and they ought to be used. Here is the solution I found/pilfered:

function canvasToImage(backgroundColor){

var context = document.getElementById('canvas').getContext('2d');

canvas = context.canvas;
//cache height and width        
var w = canvas.width;
var h = canvas.height;

var data;

//get the current ImageData for the canvas.
data = context.getImageData(0, 0, w, h);

//store the current globalCompositeOperation
var compositeOperation = context.globalCompositeOperation;

//set to draw behind current content
context.globalCompositeOperation = "destination-over";

//set background color
context.fillStyle = backgroundColor;

//draw background / rect on entire canvas
context.fillRect(0,0,w,h);

//get the image data from the canvas
var imageData = this.canvas.toDataURL("image/jpeg");

//clear the canvas
context.clearRect (0,0,w,h);

//restore it with original / cached ImageData
context.putImageData(data, 0,0);

//reset the globalCompositeOperation to what it was
context.globalCompositeOperation = compositeOperation;

//return the Base64 encoded data url string
return imageData;
}

Basically, you create a white background image and underlay it under the canvas and then print that. This function is mostly plagiarized from someone's blog, but it required a bit of modification -- such as actually getting the context -- and copied directly from my (working) code, so as long as your canvas element has the id 'canvas', you should be able to copy/paste it and have it work.

This is the blog post I modified it from:

http://www.mikechambers.com/blog/2011/01/31/setting-the-background-color-when-generating-images-from-canvas-todataurl/

The big advantage of my function over this is that it outputs to jpeg instead of png, which is more likely to work well in chrome, which has a dataurl limit of 2MB, and it actually grabs the context, which was a glaring omission in the original function.

Sapient answered 25/5, 2017 at 7:9 Comment(0)
A
2

Marks answer is correct, but when a picture has some antialiasing applied, the exported image won't be as good as it should be (mainly text). I would like to enhance his solution:

// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
    if(data[i+3]<255){
        data[i] = 255 - data[i];
        data[i+1] = 255 - data[i+1];
        data[i+2] = 255 - data[i+2];
        data[i+3] = 255 - data[i+3];
    }
}
ctx.putImageData(imgData,0,0);
Aglimmer answered 19/1, 2016 at 9:6 Comment(0)
S
2

If you want to move to white only full transparent pixels just check for (data[i+3]==0) instead of (data[i+3]<255).

Stablish answered 11/1, 2017 at 12:9 Comment(0)
A
1

Why not to save it as PNG?

canvas.toDataURL("image/png");
Abiotic answered 8/1, 2021 at 12:1 Comment(0)
M
0
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
    if(data[i+3]<255){
        data[i] = 255 - data[i];
        data[i+1] = 255 - data[i+1];
        data[i+2] = 255 - data[i+2];
        data[i+3] = 255 - data[i+3];
    }
Mamiemamma answered 31/10, 2016 at 9:37 Comment(1)
I suggest you add an explanation of how this fixes the problemCineraria
F
0

Here is my function that resizes a photo and handles the black transparent background problem:

resizeImage({ file, maxSize, backgroundColor }) {
    const fr = new FileReader();
    const img = new Image();

    const dataURItoBlob = (dataURI) => {
        const bytes = (dataURI.split(',')[0].indexOf('base64') >= 0)
            ? window.atob(dataURI.split(',')[1])
            : window.unescape(dataURI.split(',')[1]);
        const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
        const max = bytes.length;
        const ia = new Uint8Array(max);
        for (let i = 0; i < max; i += 1) {
            ia[i] = bytes.charCodeAt(i);
        }
        return new Blob([ia], { type: mime });
    };

    const resize = () => {
        // create a canvas element to manipulate
        const canvas = document.createElement('canvas');
        canvas.setAttribute('id', 'canvas');
        const context = canvas.getContext('2d');

        // setup some resizing definitions
        let { width, height } = img;
        const isTooWide = ((width > height) && (width > maxSize));
        const isTooTall = (height > maxSize);

        // resize according to `maxSize`
        if (isTooWide) {
            height *= maxSize / width;
            width = maxSize;
        } else if (isTooTall) {
            width *= maxSize / height;
            height = maxSize;
        }

        // resize the canvas
        canvas.width = width;
        canvas.height = height;

        // place the image on the canvas
        context.drawImage(img, 0, 0, width, height);

        // get the current ImageData for the canvas
        const data = context.getImageData(0, 0, width, height);

        // store the current globalCompositeOperation
        const compositeOperation = context.globalCompositeOperation;

        // set to draw behind current content
        context.globalCompositeOperation = 'destination-over';

        // set background color
        context.fillStyle = backgroundColor;

        // draw background / rect on entire canvas
        context.fillRect(0, 0, width, height);

        // get the image data from the canvas
        const imageData = canvas.toDataURL('image/jpeg');

        // clear the canvas
        context.clearRect(0, 0, width, height);

        // restore it with original / cached ImageData
        context.putImageData(data, 0, 0);

        // reset the globalCompositeOperation to what it was
        context.globalCompositeOperation = compositeOperation;

        // return the base64-encoded data url string
        return dataURItoBlob(imageData);
    };

    return new Promise((resolve, reject) => {
        if (!file.type.match(/image.*/)) {
            reject(new Error('VImageInput# Problem resizing image: file must be an image.'));
        }

        fr.onload = (readerEvent) => {
            img.onload = () => resolve(resize());
            img.src = readerEvent.target.result;
        };

        fr.readAsDataURL(file);
    });
},

That is a Vue JS instance method that can be used like this:

// this would be the user-uploaded file from the input element
const image = file;

const settings = {
    file: image,
    maxSize: 192, // to make 192x192 image
    backgroundColor: '#FFF',
};

// this will output a base64 string you can dump into your database
const resizedImage = await this.resizeImage(settings);

My solution here is a combination of about 74 different StackOverflow answers related to resizing images client-side, and the final boss was to handle transparent PNG files.

My answer would not be possible without Laereom's answer here.

Fullmouthed answered 3/10, 2019 at 20:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.