Голанг, картинки и веселье

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

Наша задача звучит так:

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

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

Итак, начнем!

Как обычно, сначала мы создаем main.go с шаблоном для нашего билдера.

Давайте начнем

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

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

Добавим функцию getPartsPaths, а также поправим путь к иконкам и их размер в нашем сборщике.

Круто, мы можем собирать элементы нашей мозаики!

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

Напишем несколько функций:

  • loadImage — загрузит наше изображение с диска в память для быстрого доступа
  • calculateModalAverageColour — получит базовый цвет изображения.
  • getPartsMap — вернет нашу карту с изображениями

loadImage

Так как наши иконки в формате TGA, а встроенные библиотеки Golang не умеют работать с этим форматом, нам необходимо установить пакет github.com/ftrvxmtrx/tga.

зайди на github.com/ftrvxmtrx/tga

Также нам было бы хорошо быть уверенными, что наши образы будут иметь 1 и тот же размер, для этого воспользуемся пакетом github.com/nfnt/resize.

зайди на github.com/nfnt/resize

вычислитьModalAverageColour

Далее мы реализуем calculateModalAverageColour, который будет вычислять средний цвет изображения и возвращать значение цвета пикселя в модели RGB.

На самом деле мы просто вычислили среднее значение каждого параметра RGB для каждого пикселя, это не точная модель, но нам этого достаточно.

Далее объединяем написанное выше и реализуем функцию getPartsMap, которая будет возвращать готовую карту изображения.

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

Вернемся к нашей основной функции Build и добавим получение карты.

Вроде все готово, и можно начинать выкладывать наш образ цветами, но погоди, ковбой, еще рано ;)

Нашей карты будет недостаточно, так как она не покрывает ВСЮ цветовую палитру изображения. Нам нужно понять, какой цвет (какая иконка) нам больше подходит.

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

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

Обязательно заранее убедитесь, что размер карты › 0, иначе получите панику на нулевом указателе.

Отлично, у нас есть возможность получить максимально приближенные к нашим пикселям изображения, осталось только создать новое изображение, пройтись по исходному и выложить нашу мозаику, вперед!

Добавим заключительную часть, нашу функцию «Сборка», которая открывает исходное изображение img.jpg (можно использовать другие встроенные декодеры, например PNG) и создает res.png.

В этой функции мы намеренно уменьшили наше изображение до 300х300 пикселей, иначе оно будет ужасно большим, так как его размер в нашем случае увеличится в 60х60 раз!

Вот и все, полный листинг нашей программы выглядит так.

Вот и все!

Эту программу можно оптимизировать, но это может стать вашим домашним заданием!

В качестве оптимизации можно сделать:

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

Мы рассмотрели в этой статье:

  • Как работать с файлами в Голанге
  • Работал со встроенными типами в Golang: срезами, картами и созданием пользовательских типов.
  • Как работать с изображениями в Golang
  • НАУКА тронута! Конкретно алгоритм нахождения евклидова расстояния.