从 png 图片创建 jpg 图片时,将 html canvas 黑色背景更改为白色背景

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

我有一个 canvas,其中加载了 png 图片。我通过这样的 .toDataURL() 方法得到它的 jpg base64 字符串:

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

但是 png 图像的透明部分在新 jpg 图像中显示为黑色。

有什么办法可以把这个颜色改成白色吗?提前致谢。

发生这种变黑是因为 'image/jpeg' 转换涉及将所有 canvas 像素的 alpha 设置为完全不透明 (alpha=255)。问题是透明 canvas 像素是彩色的 fully-black-but-transparent。因此,当您将这些黑色像素变为不透明时,结果是变黑的 jpeg。

解决方法是手动将所有非不透明 canvas 像素更改为所需的白色而不是黑色。

这样当它们变得不透明时,它们将显示为白色而不是黑色像素。

方法如下:

// 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);

标记答案是正确的,但是当图片应用了一些抗锯齿后,导出的图像将达不到应有的效果(主要是文本)。我想增强他的解决方案:

// 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);
// 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];
    }

如果你想移动到只有白色的全透明像素,只需检查 (data[i+3]==0) 而不是 (data[i+3]<255)。

这个答案有点长,但我发现它更 'correct' 因为它处理这些事情而不直接修改原始 canvas 数据。我发现这是一个非常混乱且理论上不能令人满意的解决方案。有内置函数可以实现这一点,应该使用它们。这是我 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;
}

基本上,您创建一个白色背景图像并将其置于 canvas 下方,然后打印它。这个函数主要是从某人的博客上抄袭的,但它需要一些修改——比如实际获取上下文——并直接从我的(工作)代码中复制,所以只要你的 canvas 元素有 id 'canvas',你应该能够 copy/paste 它并让它工作。

这是博客 post 我修改自:

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

我的函数相对于此的最大优势在于它输出为 jpeg 而不是 png,后者更可能在 chrome 中运行良好,后者的 dataurl 限制为 2MB,并且它实际上抓住了上下文,这是原始功能中的明显遗漏。

在这个和这个 post 上花了很多时间之后,这些解决方案有点奏效,但我只是无法让 canvas 看起来正确。无论如何,我在其他地方找到了这个解决方案,并想 post 在这里它帮助其他人花费数小时试图将黑色背景变成白色并看起来像原来的那样。

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");
}

这是我调整照片大小和处理黑色透明背景问题的函数:

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);
    });
},

这是一个Vue JS实例方法,可以这样使用:

// 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);

我这里的解决方案是大约 74 个与客户端调整图像大小相关的不同 Whosebug 答案的组合,最终的目标是处理透明的 PNG 文件。

如果没有 Laereom 的回答,我的回答是不可能的。

为什么不保存为 PNG 格式?

canvas.toDataURL("image/png");