Встречаются ситуации, когда картинка изображена на черном фоне и требуется сделать его прозрачным для сохранения в PNG. Например, брызги воды, которые невозможно сфотографировать на белом фоне. Дизайнеры для наложения таких слоёв используют в Photoshop метод наложения экран (screen), получая в итоге красивый макет. Но при резке макета на прозрачные PNG возникает неразрешимая проблема с фоном. Вырезать фон из полупрозрачных изображений обводкой и удалением не представляется возможным в Photoshop. Создать в Photoshop нужный α-канал оказалось сложнее, чем написать небольшой скрипт.

Предлагаю метод, реализованный на JavaScript. Реализация алгоритма возможна на любом языке, обладающим библиотекой попиксельной обработки изображений.

Маска прозрачности (α-канал) для каждого пикселя изображения будет формироваться из значений RGB пикселя.

Создадим обычный html файл, с заголовками (transparetbg.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Image glass</title>
</head>
<body>


</body>
</html>

Позовём красивую девушку, положив файл girl.jpg в корень диска

<body style="background:url(girl.jpg) right bottom no-repeat">

Добавим кнопки управления в body и canvas

<canvas id="_canvas"></canvas>

<div class="btn">
    Temperature <input type="range" min="0" max="255" step="1" value="255" id="_temp" onChange="decrunching();">
    <button onclick="decrunching('min')">Min</button>
    <button onclick="decrunching('max')">Max</button>
    <button onclick="decrunching('avg')">Avg</button>
    <button onclick="decrunching('r')">R</button>
    <button onclick="decrunching('g')">G</button>
    <button onclick="decrunching('b')">B</button>
</div>

Добавим в head стиль для кнопок

    <style>
        .btn input {
            float: left
        }
    </style>

Подготовим переменные. Для доступа к пикселям изображения используем canvas. При загрузке страницы загрузится картинка black_problem.jpg из корневой директории с которой мы будем работать. При загрузке методом onload её копия сохранится в переменной original.

<script>
    var canvas = document.getElementById("_canvas");
    var temp = document.getElementById("_temp");
    var context = canvas.getContext('2d');

    var original;
    var select_type;

    window.onload = function () {
        var i = new Image();
        i.onload = function () {
            canvas.width = i.width;
            canvas.height = i.height;
            context.drawImage(i, 0, 0, i.width, i.height);
            original = context.getImageData(0, 0, i.width, i.height);
        };
        i.src = '/black_problem.jpg';
    };
</script>

Напишем функцию обработки изображения

function decrunching(type) {

    // копируем исходное изображение
    context.putImageData(original, 0, 0);

    // создаём новое изображение на основе размеров исходного
    var newdata = context.createImageData(original);
    var newpx = newdata.data;
    
    //Сохраняем тип алгоритма или используем сохраненный 
    if (type != "") {
        select_type = type;
    } else {
        type = select_type;
    }

    // получаем доступ до пикселей изображения 
		var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
    
    // считаем количество пикселей изображения
    var pixels = imageData.data;
    var len = pixels.length;
    
    // организуем цикл для перебора всех пикселей изображения
    for (var i = 0; i < len; i += 4) {

        // копируем значение цвета каждого пикселя в переменную и новое изображение (без изменений)
        let r = newpx[i] = pixels[i];
        let g = newpx[i + 1] = pixels[i + 1];
        let b = newpx[i + 2] = pixels[i + 2];
        
        // Объявляем переменную для α-канала
        let rgba;

        switch (type) {
        
            // Рассчитываем прозрачность из значений красного цвета
            case 'r':
                rgba = r;
                break;

            // Рассчитываем прозрачность из значений зеленого цвета
            case 'g':
                rgba = g;
                break;
                
            // рассчитываем прозрачность из значений голубого цвета
            case 'b':
                rgba = b;
                break;

            // рассчитываем прозрачность из минимальных значений всех цветов
            case 'min':
                rgba = r;
                if (g > r) rgba = g;
                if (b > g) rgba = b;
                break;

            // рассчитываем прозрачность из максимальных значений всех цветов
            case 'max':
                rgba = b;
                if (g < b) rgba = g;
                if (b < r) rgba = r;
                break;

            // рассчитываем прозрачность из средних значений всех цветов
            default:
                rgba = ((r + g + b) / 3).toFixed(0);
                break;
        }

        // записываем значение прозрачности пикселя с учетом настройки "температуры прозрачности"
        newpx[i + 3] = (rgba * temp.value / 255).toFixed(0);
    }
    // Выводим на экран новую картинку
    context.putImageData(newdata, 0, 0);
}

Для формирования прозрачности каждого пикселя используются различные методы, но общая суть в формировании значения прозрачности в обратной зависимости от яркости пикселя. Белый пиксель будет полностью непрозрачным. Черный пиксель будет полностью прозрачным. В большинстве случаев подходит метод усреднения значений, т.е. default по коду.

Температура позволяет регулировать порог прозрачности изображения

Поиграемся

Чтобы Chrome разрешил нам работать с локальными изображениями запустим его с нужным ключиком (сделаем ярлык на рабочем столе) и откроем в нем наш html файл.

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security"

Нажмём кнопку Avg

Черный фон изображения стал прозрачным. Чудо!

Играя разными алгоритмами, видим незначительные изменения в изображении (для ценителей прекрасного).


/
/

Поиграем температурой

Сохраним изображение, кликнув правой кнопкой мышки

Полный листинг программы

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Image glass</title>
    <style>
        .btn input {
            float: left
        }
    </style>
</head>
<body style="background:url(girl.jpg) right bottom no-repeat">

<canvas id="_canvas"></canvas>

<div class="btn">
    Temperature <input type="range" min="0" max="255" step="1" value="255" id="_temp" onChange="decrunching();">
    <button onclick="decrunching('min')">Min</button>
    <button onclick="decrunching('max')">Max</button>
    <button onclick="decrunching('avg')">Avg</button>
    <button onclick="decrunching('r')">R</button>
    <button onclick="decrunching('g')">G</button>
    <button onclick="decrunching('b')">B</button>
</div>

<script>
    var canvas = document.getElementById("_canvas");
    var temp = document.getElementById("_temp");
    var context = canvas.getContext('2d');

    var original;
    var select_type;

    window.onload = function () {
        var i = new Image();
        i.onload = function () {
            canvas.width = i.width;
            canvas.height = i.height;
            context.drawImage(i, 0, 0, i.width, i.height);
            original = context.getImageData(0, 0, i.width, i.height);
        };
        i.src = '/black_problem.jpg';
    };


    function decrunching(type) {

        context.putImageData(original, 0, 0);

        var newdata = context.createImageData(original);
        var newpx = newdata.data;
        if (type != "") {
            select_type = type;
        } else {
            type = select_type;
        }

        var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
        var pixels = imageData.data;
        var len = pixels.length;
        for (var i = 0; i < len; i += 4) {

            let r = newpx[i] = pixels[i];
            let g = newpx[i + 1] = pixels[i + 1];
            let b = newpx[i + 2] = pixels[i + 2];
            let rgba;

            switch (type) {
                case 'r':
                    rgba = r;
                    break;

                case 'g':
                    rgba = g;
                    break;

                case 'b':
                    rgba = b;
                    break;

                case 'min':
                    rgba = r;
                    if (g > r) rgba = g;
                    if (b > g) rgba = b;
                    break;

                case 'max':
                    rgba = b;
                    if (g < b) rgba = g;
                    if (b < r) rgba = r;
                    break;

                default:
                    rgba = ((r + g + b) / 3).toFixed(0);
                    break;
            }

            newpx[i + 3] = (rgba * temp.value / 255).toFixed(0);
        }
        context.putImageData(newdata, 0, 0);
    }


</script>
</body>
</html>

Комментарии (4)


  1. COKPOWEHEU
    14.12.2021 15:48
    +3

    convert src.jpg -fuzz 10% -transparent black temp.png
    convert -composite -gravity center back.jpeg temp.png res.png


    1. MaoDarin Автор
      14.12.2021 19:38
      +1

      Вы имеете в виду утилиту от imagemagick.org.

      Да, указанным способом вы можете удалить черный цвет. Но фон удаляется до определенного порога, указываемого, например 10%. Все ,что выше порога не удаляется, в результате получаются четкие границы прозрачности и объекта. При наложении такого PNG в html верстке на фон будут артефакты. В предлагаемом алгоритме прозрачность уменьшается плавно, на всем диапазоне от 0 до 255.

      Почувствуйте разницу


  1. MAXH0
    14.12.2021 20:53

    Спасибо, прочитал!
    Код понравился. Девушки тоже...
    Особо понравилась простота кода... Это вряд ли открытие, но как демонстрация возможности, то почему бы и нет...


    1. MaoDarin Автор
      14.12.2021 21:46

      Спасибо. Я сам пользуюсь часто, вот ещё пример из проекта, где мне это понадобилось.