Как найти координаты пустых областей на картинке через JS и подставить туда другие изображения?

142
18 ноября 2018, 00:20

Возникла следующая проблема:

У меня есть множество различных картинок подобного рода:

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

Для этого нужно как-то получить координаты этих самых прозрачных областей на картинке.

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

Answer 1

Начать хочу с того, что катастрофически не согласен с мнением участника @qwabra, что часть вопроса о вставке изображений

выходит за рамки вопроса

Человек описал свою проблему - значит, поможем ее решить полностью)

Итак. С чего бы подступиться к Вашей проблеме?

Для начала определимся с тем, что же Вам нужно. А вам нужно найти такие прямоугольные области на изображении, в которые Вы бы могли вписать другие изображения.
Почему прямоугольные? Я вроде как ничего в этом мире еще не проспал и растровые изображения по сей день

представляются как прямоугольный двумерный массив чисел


Отлично. Мы определились с тем, что необходимо искать на изображении прямоугольную область для вставки. Но как же быть? Вот у Вас на картинке есть круги и прочие отличные от прямоугольников фигуры, куда надо вписать изображение...
Собственно, любую фигуру можно заключить в прямоугольник, для этого нам нужно лишь:

  1. Взять наименьший X из множества точек, принадлежащих фигуре (далее - множество)
  2. Взять наименьший Y из множества
  3. Взять наибольший X из множества
  4. Взять наибольший Y из множества
  5. Скомпоновать полученные значения в (Xmin, Ymin) и (Xmax, Ymax), что явится верхней левой и правой нижней точками искомого прямоугольника

Хорошо. А как же нам получить эти самые X и Y? Надо как-то обследовать изображение, выделив пустые регионы.

Небольшое отступление:
Вы написали про "белые области" в заголовке своего вопроса. Однако, исходя из данной Вами картинки, Вы явно имели в виду области, где альфа-канал пикселей равен 0 (то есть области прозрачны). Дальнейший алгоритм базируется на этом небольшом исправлении.

Если же Вам потребуются все таки белые области, то алгоритм можно будет, конечно, доработать, но совершенно непонятно, что же делать с белыми областями по бокам изображений...

Возвращаемся к нашим рассуждениям)

Как обнаружить пустые регионы? Я предлагаю Вам такой алгоритм:

  1. Проходимся по каждому пикселю изображения
  2. Если пиксель удовлетворяет заданному условию (в нашем случае: альфа-канал == 0), а также не содержится в массиве уже найденных регионов, начинаем рекурсивно обходить его соседей, "цепляясь" за те пиксели, которые удовлетворяют нашему селектору, при этом соседствуя с такими же. Читаем с них данные
  3. После завершения рекурсивного обхода, у нас на руках будут данные об искомых X и Y (min/max), а также о числе пикселей в регионе
  4. Если число пикселей больше некоторого порога (нас не интересуют области в 2-3 пикселя, правда? Что там рисовать? ¯\_(ツ)_/¯), то добавляем данные о пройденном регионе в массив

Обходить изображение мы будем от верхнего левого угла к правому нижнему.
В качестве оптимизации рекурсивного обхода могу предложить проводить его следующим образом:
Так как у Вас предполагаются простые фигуры для областей, куда могут быть вставлены изображения (безо всяких рюшечек посреди них), мы можем исследовать области построчно. Поясню:
Так как мы обходим изображение с самого верха, то и за искомую область мы "зацепимся" в высшей ее точке. Далее мы можем запустить функцию проверки по той же строчке влево и вправо, проверяя соседние пиксели на соответствие условию. Когда проверка заканчивается, мы спускаемся на пиксель ниже и проверяем следующую строку

Данный метод будет отрабатывать достаточно быстро, однако, подчеркну, он пригоден лишь для простых фигур! Если появятся витиеватости, то придется переписать рекурсивный обход

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

(29, 24)   - (159, 143)
(183, 26)  - (304, 147)
(369, 31)  - (625, 187)
(81, 156)  - (260, 297)
(369, 274) - (625, 431)
(181, 311) - (302, 431)
(29, 312)  - (165, 433)

Или, если визуализировать:

Что же теперь?
А теперь дело за малым!

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

Мы можем отрисовать нужные нам изображения в заранее рассчитанных областях, а потом просто как бы положить сверху "дырявую" картинку, так что наши будут видны из-под нее

Фух. Наговорил я много, но также могу подкрепить свои слова кодом с комментариями)
Приведу его сразу в виде сниппета, чтобы Вы могли узреть результат (разверните на всю страницу):

// Наш обработчик изображений 
function ImageExplorer(img, selector) { 
     
    // Var 
    ImageExplorer.minRegion = 5000; 
    let it = this; 
    let image; 
    let canvas; 
    let context; 
    let emptyRegions = []; 
 
    // Initialization 
    function init() { 
 
        image = getHTMLImageElement(img); 
         
        // Создаем канвас для работы, получаем его контекст 
        canvas = document.createElement("canvas"); 
        context = canvas.getContext("2d"); 
 
        // Проверяем переданный селектор, определяющий, подходит ли нам пиксель 
        if (selector == undefined) 
            // Альфа-канал равен 0 => пиксель прозрачен 
            selector = color => color[0] == 0; 
    } 
 
    // Funcs 
 
    // Ищем пустые регионы 
    it.findRegions = () => new Promise(resolve => { 
        // Ожидаем загрузки изображения 
        new Promise(res => { 
           let intervalID = setInterval(() => { 
               if (image.complete) 
                { 
                    clearInterval(intervalID); 
                    clearCanvas(); 
                    // Разрешаем текущее обещание 
                    res(); 
                } 
           }, 300); 
        }).then(() => { 
            emptyRegions = []; 
            // Отрисовываем картинку 
            context.drawImage(image, 0, 0); 
            // Получаем данные о ее пикселях 
            let imgData = context.getImageData(0, 0, image.width, image.height).data; 
            // Функция, которая по значениям x и y вернет цвет указанного пикселя ([a, r, g, b]) 
            let getPixel = (dX, dY) => { 
                let index = 4 * (dX + dY * image.width); 
                return [imgData[index], imgData[index + 1], imgData[index + 2], imgData[index + 3]]; 
            }; 
 
            // Проходимся по каждому пикселю изображения 
            for (let y = 0; y < image.height; y++) 
                for (let x = 0; x < image.width; x++) { 
                    // Проверяем, существует ли пустой регион, которому принадлежит текущий пиксель 
                    let existing = emptyRegions.find(reg => reg.contains(x, y)); 
 
                    // Если нет, проверяем дальше 
                    if (!existing) { 
                        // Если пиксель подходит нам, начинаем обследование региона 
                        if (selector(getPixel(x, y))) { 
                            // Устанавливаем координаты для крайней левой верхней и крайней правой нижней точек 
                            // Ниже они будут меняться 
                            let left = x; 
                            let up = y; 
                            let right = x; 
                            let down = y; 
                            // Число пикселей в исследованном регионе 
                            let pixels = 1; 
 
                            // Самый сок 
                            // Самовызывающаяся рекурсивная функция, ссылающаяся на текущий контекст 
                            // Один из немногих поводов любить JS хД 
                            // 
                            // Логика такая: мы вызываем функцию на текущих координатах, а далее она 
                            // "расползается" по всему региону, где соседствующие пиксели 
                            // удовлетворяют селектору 
                            (function(dX, dY, type) { 
                                // Если мы не вышли за пределы изображения, а также пиксель подходит под селектор 
                                if (dX > -1 && dX < image.width && dY < image.height && selector(getPixel(dX, dY))) {          
                                    // Заменяем значения, если требуется 
                                    if (dX < left) 
                                        left = dX; 
                                    if (dX > right) 
                                        right = dX; 
                                    if (dY < up) 
                                        up = dY; 
                                    if (dY > down) 
                                        down = dY; 
                                    // Увеличиваем число найденых пикселей в регионе 
                                    ++pixels; 
 
                                    // type несет следующий смысл: 
                                    // 0: текущая функция запускает "исследование" по оси Ox, а потом переходит на пиксель ниже 
                                    // 1: текущая функция продолжает "исследование" по оси Ox (в сторону увеличения) 
                                    // 2: текущая функция продолжает "исследование" по оси Ox (в сторону убывания) 
                                    switch (type) { 
                                        case 0: 
                                            arguments.callee(dX + 1, dY, 1); 
                                            arguments.callee(dX - 1, dY, 2); 
                                            arguments.callee(dX, dY + 1, 0);   
                                            break; 
                                        case 1: 
                                            arguments.callee(dX + 1, dY, 1); 
                                            break; 
                                        case 2: 
                                            arguments.callee(dX - 1, dY, 2); 
                                        break;                                
                                        default: 
                                            break; 
                                    } 
                                } 
                            })(x, y, 0); 
                            // Если пикселей в регионе >= минимального кол-ва пикселей, то добавим этот регион 
                            if (pixels >= ImageExplorer.minRegion) 
                            { 
                                emptyRegions.push(new EmptyRegion([left, up], [right, down])); 
                                //x = right + 1; // Небольшая оптимизация. Дает не слишком большой выигрыш, так что выключена для большей точности 
                            } 
                        } 
                    } 
                    //else 
                        //x = existing.right + 1; // Небольшая оптимизация. Дает не слишком большой выигрыш, так что выключена для большей точности 
                } 
 
            // Разрешаем текущее обещание, отдаем найденные регионы 
            resolve(emptyRegions); 
        }); 
    }); 
 
    // Создадим новое изображение, передав только изображения, которые нужно 
    // "подложить" под основную картинку (в указанном порядке) 
    it.createImage = function() { 
        clearCanvas(); 
        // Преобразуем переданные аргументы в массив изображений 
        arguments[Symbol.isConcatSpreadable] = true; // https://ru.stackoverflow.com/a/860866/248572 
        let images = [].concat(arguments).map(getHTMLImageElement); 
 
        // Отрисуем каждое изображение, отмасштабировав его по сопутствующему региону 
        for (let i in images) 
            context.drawImage(images[i], emptyRegions[i].left, emptyRegions[i].up, emptyRegions[i].getWidth(), emptyRegions[i].getHeight()); 
 
        // Положим основное изображение поверх 
        context.drawImage(image, 0, 0); 
 
        // Вернем результирующее изображение 
        let resultImage = new Image(); 
        resultImage.src = canvas.toDataURL(); 
 
        return resultImage; 
    }; 
 
    // Создадим новое изображение, явно задав регионы 
    // Изображения будут отсортированы по наибольшему размерному соответствию переданным регионам 
    it.createImageFromRegions = function(regions, ...images) { 
        clearCanvas(); 
        let regs = regions.slice(); 
        // Преобразовываем входные аргументы в [изображение, наиболее подходящий регион] 
        let imgReg = images.map(x => { 
            // Получаем изображение 
            let result = getHTMLImageElement(x); 
            // Получаем соотношение его сторон 
            let ratio = result.width / result.height; 
            // Сортируем регионы по этому соотношению 
            regions = regs.sort((a, b) => a == b ? 0 : ((Math.abs(ratio - a.getRatio()) < Math.abs(ratio - b.getRatio())) ? -1 : 1)); 
            // Нулевой регион будет наиболее подходящим 
            let bestRegion = regs[0]; 
            // Удаляем его из массива 
            regs.splice(0, 1); 
            return [result, bestRegion]; 
        }); 
 
        // Отрисуем каждое изображение, отмасштабировав его по сопутствующему региону 
        for (let i in imgReg) 
            context.drawImage(imgReg[i][0], imgReg[i][1].left, imgReg[i][1].up, imgReg[i][1].getWidth(), imgReg[i][1].getHeight()); 
 
        // Положим основное изображение поверх 
        context.drawImage(image, 0, 0); 
 
        // Вернем результирующее изображение 
        let resultImage = new Image(); 
        resultImage.src = canvas.toDataURL(); 
 
        return resultImage; 
    }; 
 
    // Загружаем изображение 
    function getHTMLImageElement(data)  { 
        let result; 
        if ((typeof data) == "string") { 
            result = new Image();        
            // Передана ссылка/Base64-строка     
            result.src = data; 
        }  
        else 
            if (data instanceof HTMLImageElement) 
                // Передан элемент <img> 
                result = data; 
            else 
                if (data[0] instanceof HTMLImageElement) 
                    // Передан элемент <img>, бережно укутанный селектором jQuery 
                    result = data[0]; 
                else 
                    throw "Необходимо передать изображение!" 
        return result; 
    } 
 
    // Растягиваем канвас до размеров изображения и чистим его 
    function clearCanvas() { 
        canvas.width = image.width; 
        canvas.height = image.height; 
        context.clearRect(0, 0, canvas.width, canvas.height); 
    } 
 
    init(); 
} 
 
// Пустой регион 
function EmptyRegion(LU, RD) { 
 
    // Var 
 
    // Добавочное значение 
    EmptyRegion.delta = 3; 
 
    let it = this; 
 
    it.left; 
    it.right; 
    it.up; 
    it.down; 
 
    // Initialization 
    function init(args) { 
        switch (args.length) { 
            case 2: 
                break; 
 
            case 4: 
                LU = [args[0], args[1]]; 
                RD = [args[2], args[3]]; 
 
                break; 
         
            default: 
                throw "Необходимо передать две точки!"; 
        } 
        it.left = LU[0] - EmptyRegion.delta; 
        it.up = LU[1] - EmptyRegion.delta; 
        it.right = RD[0] + EmptyRegion.delta; 
        it.down = RD[1] + EmptyRegion.delta; 
    } 
 
    // Funcs 
    it.getWidth = () => it.right - it.left; 
    it.getHeight = () => it.down - it.up; 
 
    // Получим соотношение сторон региона 
    it.getRatio = () => it.getWidth() / it.getHeight(); 
 
    // Содержит ли текущий регион указанную точку 
    it.contains = (x, y) => x >= it.left && x <= it.right && y >= it.up && y <= it.down; 
 
    init(arguments); 
}
<script> 
    function getImgByID(id) { 
        return document.getElementById(id); 
    }  
 
    function create() { 
        let explorer = new ImageExplorer(getImgByID("main")); 
        explorer.findRegions().then(regions => { 
            let img = explorer.createImageFromRegions(regions,  getImgByID("bill"),  
                                                                getImgByID("ford"),  
                                                                getImgByID("stan"),  
                                                                getImgByID("dipper"),  
                                                                getImgByID("mabel"),  
                                                                getImgByID("soos"),  
                                                                getImgByID("wendy")); 
            getImgByID("generated").src = img.src; 
        }); 
    } 
</script> 
<h1>Главное изображение:</h1> 
<img id="main"> 
 
<br><br><br> 
 
<h1>Изображения для вставки:</h1> 
<img id="bill"> 
<img id="ford"> 
<img id="stan"> 
<img id="dipper"> 
<img id="mabel"> 
<img id="soos"> 
<img id="wendy"> 
 
<br><br><br> 
 
<h1>Скомпонованное изображение:</h1> 
<img id="generated"> 
<br> 
<button onclick="create()">Создать</button> 
<script src="https://kir-antipov.github.io/StackOverflow/q872682.js"></script>

Если Вы нажмете на кнопку в сниппете, то увидите, как Ваша картинка объединится с моими изображениями вот в это:

Думаю, именно этого результата Вы и хотели достичь!

Надеюсь, мой ответ смог Вам помочь и Вы продолжите двигаться к своим свершениями)
Если что-то было не очень понятно, либо же Вам требуется помощь в доработке описанного метода - не стесняйтесь и спрашивайте!

Answer 2
Как найти координаты белых или прозрачных областей на картинке с помощью JS?

В них должны вставляться другие картинки с автоизменением их размера

выходит за рамки вопроса

Для этого нужно как-то получить координаты этих белых областей на картинке

вот пример создания матрицы искомых областей arr[i][j] = ddq(i * mod, j * mod)

шаг поиска mod = 10

рабочий пример для запуска

var lib; 
(function (lib) { 
    lib.mod = 10; 
})(lib || (lib = {})); 
(function (lib) { 
    let addTobody = el => { document.body.appendChild(el); }; 
    let callF = f => { f(); }; 
    lib.load_elStack = []; 
    lib.load_fooStack = []; 
    window.addEventListener("load", () => { 
        lib.load_elStack.forEach(addTobody); 
        lib.load_fooStack.forEach(callF); 
    }); 
})(lib || (lib = {})); 
(function (lib) { 
    lib.img = document.createElement('img'); 
    lib.img.src = getImg(); 
})(lib || (lib = {})); 
(function (lib) { 
    lib.canvas = document.createElement('canvas'); 
    lib.load_elStack.push(lib.canvas); 
    lib.canvas.setAttribute('style', 'border: 1px solid grey;'); 
    lib.ctx = lib.canvas.getContext("2d"); 
    lib.load_fooStack.push(() => { 
        lib.canvas.width = lib.img.width; 
        lib.canvas.height = lib.img.height; 
        lib.ctx.drawImage(lib.img, 0, 0); 
    }); 
    function ddq(x, y, q = 1) { 
        let p = lib.ctx.getImageData(x, y, 10, 10); 
        let d = p.data; 
        if (q) { 
            d = d.filter((q, i) => (i % 4) === 0); 
            d = d.filter(q => q); 
        } 
        else { 
            d = d.filter((q, i) => (i % 4) !== 0); 
            d = d.filter(q => q !== 255); 
        } 
        if (d.length === 0) { 
            lib.ctx.fillRect(x, y, lib.mod, lib.mod); 
            return 0; 
        } 
        return 1; 
    } 
    lib.load_fooStack.push(() => { 
        let arr = Array.from({ length: lib.canvas.width / lib.mod }, () => Array.from({ length: lib.canvas.height / lib.mod })); 
        if (true) { 
            for (var i = 0, l = arr.length; i < l; i++) { 
                for (var j = 0, l2 = arr[i].length; j < l2; j++) { 
                    arr[i][j] = ddq(i * lib.mod, j * lib.mod); 
                } 
            } 
        } 
        else { 
            ddq(50, 50, 1); 
            ddq(2, 45, 0); 
        } 
    }); 
})(lib || (lib = {})); 
function getImg() { 
    return 'data:image/png;base64,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'; 
} 
//# sourceMappingURL=index.js.map

код на TypeScript с комментариями

namespace lib {
    /**
     * шаг - диаметр минимальной области поиска
     */
    export const mod = 10
}
namespace lib {
    let addTobody = el => { document.body.appendChild(el) }
    let callF = f => { f() }
    // --
    export let load_elStack = [] as HTMLElement[]
    export let load_fooStack = [] as Function[]
    // --
    window.addEventListener("load", () => {
        load_elStack.forEach(addTobody)
        load_fooStack.forEach(callF)
    })
}
namespace lib {
    export let img = document.createElement('img')
    img.src = getImg()
    // load_elStack.push(img)
}
namespace lib {
    export let canvas = document.createElement('canvas')
    load_elStack.push(canvas)
    canvas.setAttribute('style', 'border: 1px solid grey;')
    export let ctx = canvas.getContext("2d")
    load_fooStack.push(() => {
        canvas.width = img.width
        canvas.height = img.height
        ctx.drawImage(img, 0, 0)
    })
    function ddq(x, y, q = 1) {
        let p = ctx.getImageData(x, y, 10, 10)
        let d = p.data

        if (q) {
            // --> фильтер по нулефой прозрачности
            d = d.filter((q, i) => (i % 4) === 0)
            d = d.filter(q => q)
            // <--
        } else {
            // --> фильтер по белым
            d = d.filter((q, i) => (i % 4) !== 0)
            d = d.filter(q => q !== 255)
            // <--
        }
        // console.log(p)
        // console.log(d.length)
        // if (d.length !== 0) {
        if (d.length === 0) {
            ctx.fillRect(x, y, mod, mod)
            return 0
        }
        return 1
    }
    load_fooStack.push(() => {
        let arr = Array.from({ length: canvas.width / mod }, () => Array.from({ length: canvas.height / mod }))
        if (true) {
            for (var i = 0, l = arr.length; i < l; i++) {
                for (var j = 0, l2 = arr[i].length; j < l2; j++) {
                    arr[i][j] = ddq(i * mod, j * mod)
                }
            }
        } else {
            ddq(50, 50, 1) // - прозрачная
            ddq(2, 45, 0) // - белая область
        }
    })
}
READ ALSO
Uncaught SyntaxError: Unexpected token &lt; Что это за ошибка?

Uncaught SyntaxError: Unexpected token < Что это за ошибка?

Эта ошибка возникает, когда перемещаю код jquery (отправляет данные на сервер node js) в отделный файл

232
Вставка символа где-то в строку

Вставка символа где-то в строку

Допустим есть строка с временем, например 1234, по какой-то причине она не разделена, привычным нам, двоеточиемМы знаем, что двоеточие будет...

149
Как зашифровать сообщение в aes256 на чистом JavaScript?

Как зашифровать сообщение в aes256 на чистом JavaScript?

Охота зашифровать сообщение в AES256Допустим есть ключ и есть сообщение

149