Я использовал 10 000 фотографий, чтобы синтезировать наши прекрасные моменты.

внешний интерфейс Canvas
Я использовал 10 000 фотографий, чтобы синтезировать наши прекрасные моменты.

Это третий день моего участия в августовском испытании обновлений. Узнайте подробности мероприятия:  Испытание августовского обновления

Луна светит обратно в сердце озера, дикие журавли бегут к праздным облакам

предисловие

Вчера был День святого Валентина, всех очень порадовал~ Я тоже😚

Ну, без лишних слов, сегодня я представляю вам очень интересный проект.Разрезая целевое изображение, получается 10 000 квадратов, а соответствующие заполненные квадраты используются для достижения эффекта изображения тысячи изображений.Вы можете использовать его, чтобы сделать любой осмысленный большое изображение, которое вы хотите. (Например, я хочу использовать его, чтобы сделать супер-супер-супер-большое свадебное фото, используя его, чтобы сделать все фотографии меня и моего партнера от любви до свадьбы. родной городОзеро ПоянхуОн покрыт травой, и вы можете использовать дрон, чтобы обозревать его на большой высоте, тск, это очень интересно~ Я закопаю здесь точку, надеюсь, это будет реализовано через несколько лет 😊)

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

В заголовок

Сначала мы инициализируем холст размером 800*800.

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

//初始化画布
initCanvas() {
    this.canvas = new fabric.Canvas("canvas", {
        selectable: false,
        selection: false,
        hoverCursor: "pointer",
    });
    this.ctx = canvas.getContext("2d");
    this.addCanvasEvent();//给画布添加事件
},

Вы можете настроить размер холста в соответствии с конфигурацией вашего компьютера. В настоящее время нет никого, кто мог бы напрямую создавать тысячи изображений на стороне Интернета. Реализация этой функции на стороне Интернета действительно очень требовательна к производительности, потому что количество обрабатываемых данных очень велико, и объем вычислений также велик.
Следует отметить, что: холст 800*800 имеет 640000 пикселей, черезctx.getImageDataКаждый полученный пиксель — это 4 значения, что составляет 2 560 000 значений. Нам нужно обработать эти 2 560 000 значений позже, поэтому здесь я не буду делать их больше.

нарисовать целевое изображение с тканью

Следует отметить, что мы рисуем на холст через локальное изображение, и нам нужно передать полученный файл файла черезwindow.URL.createObjectURL(file)Преобразование файла в URL-адрес типа blob

Если вам нравится использовать компонент загрузки elementUI, просто напишите

//目标图片选择回调
slectFile(file, fileList) {
    let tempUrl = window.URL.createObjectURL(file.raw);
    this.drawImage(tempUrl);
},

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

export function inputFile() {
    return new Promise(function (resolve, reject) {
        if (document.getElementById("myInput")) {
            let inputFile = document.getElementById("myInput");
            inputFile.onchange = (e) => {
                let urlArr = [];
                for (let i = 0; i < e.target.files.length; i++) {
                    urlArr.push(URL.createObjectURL(e.target.files[i]));
                }
                resolve(urlArr);
            };
            inputFile.click();
        } else {
            let inputFile = document.createElement("input");
            inputFile.setAttribute("id", "myInput");
            inputFile.setAttribute("type", "file");
            inputFile.setAttribute("accept", "image/*");
            inputFile.setAttribute("name", "file");
            inputFile.setAttribute("multiple", "multiple");
            inputFile.setAttribute("style", "display: none");
            inputFile.onchange = (e) => {
                // console.log(e.target.files[0]);
                // console.log(e.target.files);
                // let tempUrl = URL.createObjectURL(e.target.files[0]);
                // console.log(tempUrl);
                let urlArr = [];
                for (let i = 0; i < e.target.files.length; i++) {
                    urlArr.push(URL.createObjectURL(e.target.files[i]));
                }
                resolve(urlArr);
            };
            document.body.appendChild(inputFile);
            inputFile.click();
        }
    });
}

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

//绘制目标图片
drawImage(url) {
    fabric.Image.fromURL(url, (img) => {
        //设置缩放比例,长图的缩放比为this.canvas.width / img.width,宽图的缩放比为this.canvas.height / img.height
        let scale =
            img.height > img.width
                ? this.canvas.width / img.width
                : this.canvas.height / img.height;
        img.set({
            left: this.canvas.height / 2, //距离左边的距离
            originX: "center", //图片在原点的对齐方式
            top: 0,
            scaleX: scale, //横向缩放
            scaleY: scale, //纵向缩放
            selectable: false, //可交互
        });
        //图片添加到画布的回调函数
        img.on("added", (e) => {
            //这里有个问题,added后获取的是之前的画布像素数据,其他手动触发的事件,不会有这种问题
            //故用一个异步解决
            setTimeout(() => {
                this.getCanvasData();
            }, 500);
        });
        this.canvas.add(img); //将图片添加到画布
        this.drawLine(); //绘制网格线条
    });
},

После того, как нарисуете картинку, кстати, нарисуйте на холсте сетку 100*100.

//栅格线
drawLine() {
    const blockPixel = 8;
    for (let i = 0; i <= this.canvas.width / blockPixel; i++) {
        this.canvas.add(
            new fabric.Line([i * blockPixel, 0, i * blockPixel, this.canvas.height], {
                left: i * blockPixel,
                stroke: "gray",
                selectable: false, //是否可被选中
            })
        );
        this.canvas.add(
            new fabric.Line([0, i * blockPixel, this.canvas.height, i * blockPixel], {
                top: i * blockPixel,
                stroke: "gray",
                selectable: false, //是否可被选中
            })
        );
    }
},

После рисования вы можете увидеть эффект добавления линий сетки на картинку, которая по-прежнему очень красива~😘

Сохранение цветовых блоков изображения в массиве

Написание этого в начале привело к краху браузера


Я плачу 😥, слишком много вложенных циклов таким образом (и кардинальность 800*800*4==2560000--> Я должен написать это хорошо, иначе я извиняюсь, что pixelList манипулировался мной 2 560 000 раз) Приходится оптимизировать метод написания, так как браузер жареный, тупой метод не сработает, его можно только изменить~

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

//获取画布像素数据
getCanvasData() {
    for (let Y = 0; Y < this.canvas.height / 8; Y++) {
        for (let X = 0; X < this.canvas.width / 8; X++) {
            //每8*8像素的一块区域一组
            let tempColorData = this.ctx.getImageData(X * 8, Y * 8, 8, 8).data;
            //将获取到数据每4个一组,每组都是一个像素
            this.blockList[Y * 100 + X] = { position: [X, Y], color: [] };
            for (let i = 0; i < tempColorData.length; i += 4) {
                this.blockList[Y * 100 + X].color.push([
                    tempColorData[i],
                    tempColorData[i + 1],
                    tempColorData[i + 2],
                    tempColorData[i + 3],
                ]);
            }
        }
    }
    console.log(mostBlockColor(this.blockList));
    this.mostBlockColor(this.blockList);//获取每个小块的主色调
    this.loading = false;
},

😅 После изменения способа записи, здесь мы делим каждый блок 8*8 пикселей на группу, чтобы получить 10000 элементов, каждый элемент имеет 4 значения, которые представляют собой значение RGBA, мы будем использовать его позже Соответствующие 10000 изображений заполняют соответствующие пиксельные блоки

После получения всех значений пикселей на холсте нам нужно найти основной цвет каждого квадратика
Позже нам понадобятся эти маленькие квадратики основных цветов, сфотографировав их цвет и ресурсы, чтобы определить конкретную коробку, которая заполнена картинкой 😊
Я очень взволнован, чтобы попасть сюда, я чувствую, что почти наполовину сделал, но это не так, я почешу голову позже 😭

 //获取每个格子的主色调
mostBlockColor(blockList) {
    for (let i = 0; i < blockList.length; i++) {
        let colorList = [];
        let rgbaStr = "";
        for (let k = 0; k < blockList[k].color.length; k++) {
            rgbaStr = blockList[i].color[k];
            if (rgbaStr in colorList) {
                ++colorList[rgbaStr];
            } else {
                colorList[rgbaStr] = 1;
            }
        }
        let arr = [];
        for (let prop in colorList) {
            arr.push({
                // 如果只获取rgb,则为`rgb(${prop})`
                color: prop.split(","),
                // color: `rgba(${prop})`,
                count: colorList[prop],
            });
        }
        // 数组排序
        arr.sort((a, b) => {
            return b.count - a.count;
        });
        arr[0].position = blockList[i].position;
        this.blockMainColors.push(arr[0]);
    }
    console.log(this.blockMainColors);
},

Мозг не прост в использовании, используется бумага для заметок

Получить основной цвет каждого изображения ресурса

export function getMostColor(imgUrl) {
    return new Promise((resolve, reject) => {
        try {
            const canvas = document.createElement("canvas");
            //设置canvas的宽高都为20,越小越快,但是越小越不精确
            canvas.width = 20;
            canvas.height = 20;
            const img = new Image(); // 创建img元素
            img.src = imgUrl; // 设置图片源地址
            img.onload = () => {
                const ctx = canvas.getContext("2d");
                const scaleH = canvas.height / img.height;
                img.height = canvas.height;
                img.width = img.width * scaleH;
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                console.log(img.width, img.height);
                // 获取像素数据
                let pixelData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
                let colorList = [];
                let color = [];
                let colorKey = "";
                let colorArr = [];
                // 分组循环
                for (let i = 0; i < pixelData.length; i += 4) {
                    color[0] = pixelData[i];
                    color[1] = pixelData[i + 1];
                    color[2] = pixelData[i + 2];
                    color[3] = pixelData[i + 3];
                    colorKey = color.join(",");
                    if (colorKey in colorList) {
                        ++colorList[colorKey];
                    } else {
                        colorList[colorKey] = 1;
                    }
                }
                for (let prop in colorList) {
                    colorArr.push({
                        color: prop.split(","),
                        count: colorList[prop],
                    });
                }
                // 对所有颜色数组排序,取第一个为主色调
                colorArr.sort((a, b) => {
                    return b.count - a.count;
                });
                colorArr[0].url = imgUrl;
                console.log(
                    `%c rgba(${colorArr[0].color.join(",")})`,
                    `background: rgba(${colorArr[0].color.join(",")})`
                );
                resolve(colorArr[0]);
            };
        } catch (e) {
            reject(e);
        }
    });
}

После того, как мы случайным образом выберем несколько файлов, распечатайте их основные цвета, чтобы увидеть эффект

цветовое пространство

Чтобы запросить разницу в цвете, нам сначала нужно вместе понять определение цвета.Существует много способов выражения цвета, и их стандарты разные, такие как CMYK, RGB, HSB, LAB и т. д.
Здесь у нас RGBA, цветовая модель RGB с дополнительной альфа-информацией.

RGBA — это цветовое пространство, которое представляет красный (красный), зеленый (зеленый), синий (синий) и альфа-канал. Хотя его иногда называют цветовым пространством, на самом деле это просто дополнительная информация к модели RGB. Используемые цвета — RGB, которые могут принадлежать любому цветовому пространству RGB, но Кэтмелл и Смит в 1971–1972 годах предложили это обязательное альфа-значение, которое сделало возможным альфа-рендеринг и альфа-композитинг. Автор назвал альфа из-за греческой буквы, используемой в классическом уравнении линейной интерполяции αA + (1-α)B.

Дополнительную информацию о других цветах см.
здесь:zhuanlan.zhihu.com/p/24281841
илиздесьEncyclopedia.Baidu.com/item/%E9%A2…

Как найти разницу в цвете

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

Во-первых, давайте разберемся с основной концепцией евклидова расстояния.

Евклидова метрика (также называемая евклидовым расстоянием) — это обычно используемое определение расстояния, относящееся к истинному расстоянию между двумя точками в m-мерном пространстве или к естественной длине вектора (т. . Евклидово расстояние в 2D и 3D пространстве — это фактическое расстояние между двумя точками.

Преобразуйте формулу в код:

//计算颜色差异
colorDiff(color1, color2) {
    let distance = 0;//初始化距离
    for (let i = 0; i < color1.length; i++) {
        distance += (color1[i] - color2[i]) ** 2;//对两组颜色r,g,b[a]的差的平方求和
    }
    return Math.sqrt(distance);//开平方后得到两个颜色在色彩空间的绝对距离
},

Есть много способов рассчитать разницу в цвете, вы можете увидетьwikiwand:Woohoo.wikiplay.com/ru/color_th…
Или вы можете использовать для сравнения библиотеку обработки цвета, например ColorRNA.js, мы не будем здесь слишком много описывать.

Визуализируйте изображение после вычисления разницы

Здесь нам нужно сравнить основной цвет каждого блока пикселей с основным цветом всех изображений ресурса, и взять тот, у которого разница наименьшая, и отрендерить его в соответствующий квадрат

//生成图片
generateImg() {
    this.loading = true;
    let diffColorList = [];
    //遍历所有方块
    for (let i = 0; i < this.blockMainColors.length; i++) {
        diffColorList[i] = { diffs: [] };
        //遍历所有图片
        for (let j = 0; j < this.imgList.length; j++) {
            diffColorList[i].diffs.push({
                url: this.imgList[j].url,
                diff: this.colorDiff(this.blockMainColors[i].color, this.imgList[j].color),
                color: this.imgList[j].color,
            });
        }
        //对比较过的图片进行排序,差异最小的放最前面
        diffColorList[i].diffs.sort((a, b) => {
            return a.diff - b.diff;
        });
        //取第0个图片信息
        diffColorList[i].url = diffColorList[i].diffs[0].url;
        diffColorList[i].position = this.blockMainColors[i].position;
        diffColorList[i].Acolor = this.blockMainColors[i].color;
        diffColorList[i].Bcolor = diffColorList[i].diffs[0].color;
    }
    this.loading = false;
    console.log(diffColorList);
    //便利每一个方块,对其渲染
    diffColorList.forEach((item) => {
        fabric.Image.fromURL(item.url, (img) => {
            let scale = img.height > img.width ? 8 / img.width : 8 / img.height;
            // img.scale(8 / img.height);
            img.set({
                left: item.position[0] * 8,
                top: item.position[1] * 8,
                originX: "center",
                scaleX: scale,
                scaleY: scale,
            });
            this.canvas.add(img);
        });
    });
},

Хороший парень!!!Что за чертовщина???Это была ночь, да ладно?

Я плачу, уже пять часов, а я еще не спала~

Не сдавайся, не сдавайся, держись до конца - это победа

Тщательный анализ каждого шага, шаг за шагом, чтобы найти проблему Отталкиваясь от данных о пикселях целевого изображения в начале, я посмотрел на правильность данных о пикселях, но не нашел проблемы, и данные были в порядке.Первоначальное суждение было, что проблема с расчет основного цвета блока пикселей, поэтому я подумал, будет ли это основной цвет?Вместо того, чтобы брать цвет, который чаще всего появляется на изображении или блоке пикселей в качестве основного цвета, как насчет того, чтобы взять среднее значение все их цвета в качестве основного цвета?
Думая об этом, я так взволнован!
Чуть не разбудил спящего Гувази, начал заново расчесывать

Здесь я изменил каждый маленький квадрат 8*8, чтобы найти основной цвет по среднему значению.

//获取每个格子的主色调
mostBlockColor(blockList) {
    for (let i = 0; i < blockList.length; i++) {
        let r = 0,
            g = 0,
            b = 0,
            a = 0;
        for (let j = 0; j < blockList[i].color[j].length; j++) {
            r += blockList[i].color[j][0];
            g += blockList[i].color[j][1];
            b += blockList[i].color[j][2];
            a += blockList[i].color[j][3];
        }
        // 求取平均值
        r /= blockList[i].color[0].length;
        g /= blockList[i].color[0].length;
        b /= blockList[i].color[0].length;
        a /= blockList[i].color[0].length;
        // 将最终的值取整
        r = Math.round(r);
        g = Math.round(g);
        b = Math.round(b);
        a = Math.round(a);
        this.blockMainColors.push({
            position: blockList[i].position,
            color: [r, g, b, a],
        });
    }
    console.log(this.blockMainColors);
}

Затем для каждой картинки также меняется найти основной цвет по среднему

export function getAverageColor(imgUrl) {
    return new Promise((resolve, reject) => {
        try {
            const canvas = document.createElement("canvas");
            //设置canvas的宽高都为20,越小越快,但是越小越不精确
            canvas.width = 20;
            canvas.height = 20;
            const img = new Image(); // 创建img元素
            img.src = imgUrl; // 设置图片源地址
            img.onload = () => {
                console.log(img.width, img.height);
                let ctx = canvas.getContext("2d");
                const scaleH = canvas.height / img.height;
                img.height = canvas.height;
                img.width = img.width * scaleH;
                ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
                // 获取像素数据
                let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
                let r = 0,
                    g = 0,
                    b = 0,
                    a = 0;
                // 取所有像素的平均值
                for (let row = 0; row < canvas.height; row++) {
                    for (let col = 0; col < canvas.width; col++) {
                        r += data[(canvas.width * row + col) * 4];
                        g += data[(canvas.width * row + col) * 4 + 1];
                        b += data[(canvas.width * row + col) * 4 + 2];
                        a += data[(canvas.width * row + col) * 4 + 3];
                    }
                }
                // 求取平均值
                r /= canvas.width * canvas.height;
                g /= canvas.width * canvas.height;
                b /= canvas.width * canvas.height;
                a /= canvas.width * canvas.height;

                // 将最终的值取整
                r = Math.round(r);
                g = Math.round(g);
                b = Math.round(b);
                a = Math.round(a);
                console.log(
                    `%c ${"rgba(" + r + "," + g + "," + b + "," + a + ")"}
                                                                        `,
                    `background: ${"rgba(" + r + "," + g + "," + b + "," + a + ")"};`
                );
                resolve({ color: [r, g, b, a], url: imgUrl });
            };
        } catch (e) {
            reject(e);
        }
    });
}

Пора волноваться!!!!!!!!!!!!Аааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааааа

После одной операции, выберите целевое изображение, выберите изображение ресурса и нажмите кнопку «Создать изображение», я начал ждать победного звонка!

Я иду, это даже уродье, что происходит

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

Я думаю, что на это влияет, картинки не могут быть нарисованы одним цветом, что такое один и тот же цвет???

wo kao~ не может быть 100*100 линий, которые я нарисовал

Так что я вернулся,drawLineфункция, я ее закомментировал~

nice!

Каждый блок можно растягивать, вращать, перемещать в интерактивном режиме, и основные функции холста здесь выполнены.посыпать цветы🌹🏵🌸💐🌺🌻🌼🌷

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

 //导出图片
exportCanvas() {
    const dataURL = this.canvas.toDataURL({
        width: this.canvas.width,
        height: this.canvas.height,
        left: 0,
        top: 0,
        format: "png",
    });
    const link = document.createElement("a");
    link.download = "canvas.png";
    link.href = dataURL;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
},

Этот День святого Валентина действительно немного насыщает, сейчас 6:30 утра~ У меня очередная волна печени, спать и спать, жизнь важнее всего, и я должен выходить играть днем ​​😅😅

наконец

Возвышенное это:

На романтическом Танабате в воздухе витает запах любви. Счастливое приглашение для влюбленных, после заката, голова ивы на голове, шепча, красивые пейзажи хорошего дня, цветок полной луны хорош! Благословите влюбленных в мире, будьте счастливы и счастливы!

Я закинул этот проект в свойgithubначальство(GitHub.com/Ван Ронг Дина…~

Я Жундин, и я рад быть здесь с вами, чтобы стать сильнее! ВместеУдачного программирования!😉

Если вы также любите технологии, связанные с интерфейсом! Добро пожаловать в мой маленький секретный круг~ 🦄 Нажмите →здесь