Изменение html-холста черного фона на белый фон при создании jpg-изображения из png-изображения

У меня есть canvas, который загружается с изображением png. Я получаю его строку jpg base64 методом .toDataURL() следующим образом:

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

Но прозрачные части изображения png отображаются черным в новом jpg изображении.

Любые решения для изменения этого цвета на белый? Спасибо заранее.

Ответ 1

Это чернение происходит потому, что преобразование "image/jpeg" включает в себя настройку альфа всех холстов на полностью непрозрачные (альфа = 255). Проблема в том, что прозрачные холст-пиксели окрашены fully-black-but-transparent. Поэтому, когда вы поворачиваете эти черные пиксели непрозрачными, результатом является почерневший 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);

Ответ 2

Этот ответ немного длиннее, но я считаю его более правильным, поскольку он обрабатывает эти вещи без непосредственной модификации необработанных данных холста. Я считаю, что это довольно грязное и теоретически неудовлетворительное решение. Для этого есть встроенные функции, и они должны использоваться. Вот решение, которое я нашел/похитил:

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 имеет "холст" id, вы должны иметь возможность скопировать/вставить его и заставить его работать.

Это сообщение в блоге, в котором я его модифицировал:

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

Большим преимуществом моей функции над этим является то, что она выводится в jpeg вместо png, что, скорее всего, хорошо работает в chrome, у которого ограничение на dataurl составляет 2 МБ, и оно фактически захватывает контекст, который был вопиющим упущение в исходной функции.

Ответ 3

Ответ на метку верен, но если на изображении применено какое-то сглаживание, экспортированное изображение будет не таким хорошим, как должно быть (в основном, текст). Я хотел бы улучшить его решение:

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

Ответ 4

Если вы хотите переместить на белый только полные прозрачные пиксели, просто проверьте (данные [i + 3] == 0) вместо (данные [i + 3] < 255).

Ответ 5

После того, как я потратил много времени на этот и этот пост, и эти решения вроде как сработали, я просто не мог заставить холст выглядеть правильно. Как бы то ни было, я нашел это решение в другом месте и хотел опубликовать его здесь, если оно поможет кому-то еще потратить часы, пытаясь сделать черный фон белым и выглядеть как оригинал.

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

Ответ 6

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

Ответ 7

Вот моя функция, которая изменяет размер фотографии и решает проблему черного прозрачного фона:

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 различных ответов Qaru, связанных с изменением размера изображений на стороне клиента, и последний вопрос заключался в обработке прозрачных файлов PNG.

Мой ответ был бы невозможен без ответа от Laereom.