Комиксатор

Судьба распорядилась так, что в последние пару месяцев я активно работал с OpenCV библиотекой.
Судьба распорядилась так, что в последние пару месяцев я активно работал с OpenCV библиотекой. Библиотека конечно шикарная, дает множество возможностей. Я правда, к сожалению, наверно не освоил и одного процента ее, но для моего проекта решить задачу, хоть и топорно, но удалось Пока я изучал возможности этого проекта, пришлось перечитать немало информации, и вот один из сайтов в интернете мне хорошо помог в понимании - http://robocraft.ru/page/opencv/. Среди статей там нашлась интересная идея как картинку преобразовать в вариант комикса. Захотел повторить реализацию, немного изменив условия - сделать это с помощью javascript у себя на сайте, при этом все вычисления будут происходить в браузере пользователя

Полученный результат можно сохранить (правой кнопкой, сохранить как...)
Исходный код с комментариями привожу ниже. Саму библиотеку opencv.js можно либо скомпилировать самому (инструкция для 4.1.2, например, здесь - https://docs.opencv.org/4.1.2/d4/da1/tutorial_js_setup.html), либо просто забрать отсюда ;)
Download file src.js
const OPENCV_URL = '/js/opencv.js';
var OPENCV_LOADED = false;
 
// OpenCV загружаем так чтобы проконтролировать процесс загрузки, ведь все вычисления можно производить только после того как он загружен.
// Размер opencv.js достаточно большой, поэтому надо сделать так чтобы он не мешал загрузке самой страницы - грузим его только если OpenCV понадобится
loadOpenCv = function(onloadCallback) {
        let script = document.createElement('script');
        script.setAttribute('async', '');
        script.setAttribute('type', 'text/javascript');
        script.addEventListener('load', () => {
            if (cv.getBuildInformation)
            {
                console.log(cv.getBuildInformation());
                onloadCallback();
            }
            else
            {
                // WASM
                cv['onRuntimeInitialized']=()=>{
                    console.log(cv.getBuildInformation());
                    onloadCallback();
                }
            }
            OPENCV_LOADED = true;
        });
        script.addEventListener('error', () => {
            console.log('Failed to load ' + OPENCV_URL);
        });
        script.src = OPENCV_URL;
        let node = document.getElementsByTagName('script')[0];
        node.parentNode.insertBefore(script, node);
};
 
// Это основная функция обработки изображения
mainCV = function()
{
    let src = cv.imread('imageCanvas');  // читаем загруженную картинку
 
    let gray = new cv.Mat();
    cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0); // переводим в градации серого
 
    let canny = new cv.Mat();
    cv.Canny(gray, canny, 10, 100, 3, false);  // находим границы Canny
 
    let rgbaPlanes = new cv.MatVector();
    cv.split(src, rgbaPlanes);  // разбиваем исходную картинку на каналы R G B
    let R = rgbaPlanes.get(0);  
    let G = rgbaPlanes.get(1);
    let B = rgbaPlanes.get(2);  // получаем адреса отдельных каналов R G B
 
    cv.subtract(R, canny, R);   // вычитаем из каждого канала границу которую получили в сером. Если вычислать границы каждого канала,
    cv.subtract(G, canny, G);   // и затем вычитать ее из этого же канала, то это приводит к такому инетересному явлению, что, 
    cv.subtract(B, canny, B);   // если эта граница появляется только на одном канале, то линии "комиксные" будут разноцветные. А лучше все же воспринимаются серые
 
    let dst = new cv.Mat();
    cv.merge(rgbaPlanes, dst);  // собираем картинку обратно, в новый Mat
    cv.imshow('imageComics', dst); // отображаем его как результат
 
    src.delete();               // подчищаем за собой
    dst.delete();
    rgbaPlanes.delete();
    R.delete(); G.delete(); B.delete(); 
}
 
function handleImage(e){
    var reader = new FileReader();
    reader.onload = function(event){
        var img = new Image();
        img.onload = function(){
            canvas.width = img.width;
            canvas.height = img.height;
            ctx.drawImage(img,0,0);
            if(OPENCV_LOADED) {
                mainCV();
            }
            else {
                loadOpenCv(mainCV);
            }
        }
        img.src = event.target.result;
    }
    reader.readAsDataURL(e.target.files[0]);     
}