从PNG图像创建JPG图像时将HTML画布的黑色背景更改为白色背景

16

我有一个canvas,上面加载了一个png图片。我通过.toDataURL()方法获取它的jpg格式的base64字符串,代码如下:

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

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

有没有解决方法可以将其改为白色?提前致谢。

8个回答

19

这种变黑的情况是因为将'image/jpeg'转换时会将所有画布像素的alpha设置为完全不透明(alpha = 255)。问题在于,透明的画布像素被着成了完全黑色但透明。所以,当你将这些黑色像素变为不透明时,结果就是一个变黑的JPEG。

解决方法是手动将所有非不透明的画布像素更改为您想要的白色,而不是黑色。

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

以下是具体操作:

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

谢谢。在黑色填充图像传递到代码后面之后,我能在ASP.NET代码后面做这个吗? - Payam Sh
不用谢!在将图像传递给代码后端之前,您应该在客户端对其进行处理。这是因为HTML5 Canvas仅适用于JavaScript,而JS在代码后端中不是本地可用的(至少无法以使用HTML5 Canvas所需的方式使用)。 - markE
我不敢相信这是唯一解决这个问题的方法。一定有办法可以更改默认颜色。我需要进行研究。 - Laereom

14

在花费了很多时间研究这个问题,尤其是这篇文章和相关解决方案都有点用,但我就是无法让画布看起来正确。不过,我在其他地方找到了一个解决方法,并想在这里发布,以防止其他人浪费几个小时试图将黑色背景变为白色并看起来像原始的。

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

1
谢谢,这个评论让我看到我所需要的几乎全部都是 canvas.fillStyle = '#FFF'; - agm1984
经过数小时的努力,我得到了这个答案。这应该是最佳答案... - Jishan mondal
很好。我在這段程式碼上花了幾個小時。正如Jishan所說,這應該排在首位。謝謝。 - Val

13

这个答案比较长,但我认为它更加“正确”,因为它处理这些事情时没有直接修改原始画布数据。我认为那是一种相当混乱和理论上不令人满意的解决方案。有内置函数可以实现这一点,应该使用它们。以下是我找到/盗用的解决方案:

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

基本上,您需要创建一个白色背景图像,并在画布下面加入它,然后打印它。这个函数主要是从某个人的博客抄袭过来的,但它需要进行一些修改-例如实际获取上下文-并直接从我的(可工作的)代码中复制,因此只要你的画布元素具有id 'canvas',你就应该能够复制/粘贴它并使其工作。

我修改它的博客文章如下:

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

我的函数比这个函数的优势在于它输出为jpeg而不是png,更有可能在chrome中工作良好,因为它的dataurl限制为2MB,并且它实际上获取了上下文,这是原始函数中一个明显的遗漏。


2
马克的答案是正确的,但当图片应用了一些抗锯齿处理时,导出的图像将不如应有的好(主要是文本)。我想完善他的解决方案:
// 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);

2
如果您想要将像素移动到仅具有白色透明度,请检查(data[i+3]==0)而不是(data[i+3]<255)。

1
为什么不将其保存为PNG格式?
canvas.toDataURL("image/png");

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

我建议您添加一个解释,说明这如何解决问题。 - Obromios

0
这是我的函数,用于调整照片大小并处理黑色透明背景问题:
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个与客户端调整图像大小相关的StackOverflow答案组合在一起,最终的难点是处理透明PNG文件。

如果没有Laereom在这里的回答,我的答案将不可能实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接