Русский
Русский
English
Статистика
Реклама

Pixel

Создание изображений в runtime (favicon, watermark, нарезка картинок) golang

27.11.2020 08:18:10 | Автор: admin

В Go есть возможность создавать файлы изображений.

С помощью этого мы можем создавать картинки на лету (в runtime).

Где же это может пригодится?

Вот небольшой список того, что мы можем создать используя данный функционал:

  1. Favicon

  2. Pixel-трекер

  3. Placeholder

  4. Наложение текста (watermark) на изображение

  5. Нарезка изображений

1. Favicon

Часто Go приложения рассматриваются как серверная часть для отдачи контента для внутренних и/или внешних сервисов и может отдавать контент для Вашего сайта.

Желательно, чтобы у Вас был внешний сервис (слой) для отдачи ответа - некий getaway, чтобы отдавать только нужный payload и, в том числе, без веб-специфичных запросов.

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

Но, что если ваше Go приложение также сможет отдавать favicon и манифесты самостоятельно. И не просто брать статичные файлы favicon и манифеста, а генерить/создавать эти массивы байт в runtime. Это позволит нашему приложению не зависеть от этих статичных файлов.

Вы, конечно, можете отдавать статичный файл:

func faviconHandler(w http.ResponseWriter, r *http.Request) {http.ServeFile(w, r, "relative/path/to/favicon.ico")}...func main() {  http.HandleFunc("/favicon.ico", faviconHandler)}

Но мы же хотим создавать favicon динамически.

Для динамического создания favicon есть, как минимум, два похожих способа:

  • Первый способ

import (   "bytes"   "image"   "image/gif"   "net/http"   ...)var buf bytes.Buffergif.Encode(&buf, image.Rect(0, 0, 16, 16), nil)w.Header().Set("Content-Type", "image/jpeg")w.Header().Set("Content-Length", strconv.Itoa(len(buf.Bytes())))w.Write(buf.Bytes())

Второй способ:

import (   "bytes"   "image"   "image/color"   "image/draw"   "image/jpeg"   ...)buf := new(bytes.Buffer)m := image.NewRGBA(image.Rect(0, 0, 16, 16))clr := color.RGBA{B: 0, A: 0}draw.Draw(m, m.Bounds(), image.NewUniform(clr), image.Pointer{}, draw.Src)jpeg.Encode(buffer, img, nil)w.Header().Set("Content-Type", "image/jpeg")w.Header().Set("Content-Length", strconv.Itoa(len(buffer.Bytes())))w.Write(buf.Bytes())

2. Pixel-трекер

Pixel-трекер широко используется при арбитраже трафика.

var buf bytes.Buffergif.Encode(&buf, image.Rect(0, 0, 1, 1, nil))

Динамическая генерация такого трекера позволит Вам в одном приложении отдавать и pixel, и собирать данные.

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

Во-первых, определимся с требованиями:

  • на вход к нам приходим запрос вида: /ШИРИНА/ВСОТА/ЦВЕТ/ТЕКСТ/ЦВЕТ_ТЕКСТА/РАЗМЕР_ШРИФТА

    Например: /600/200/622E68/Placeholder (заглушка)/FFFFFF

  • текст должен располагаться по середине картинки

  • текст приходит в запросе от клиента или собирается по формату: "ШИРИНА х ВСОТА"

  • текст должен поддерживать возможность латиницы и кириллицы

  • предусмотреть параметры по умолчанию

  • на выходе мы отдаём байты картинки

Разбор параметров начнём с пакета по работе с цветами картинки и текста (package colors).

Цвет будет приходить как строка в HEX формате. Другие форматы цвета, как входного параметра - не предвидятся.

Пакет для работы с цветами:

package colorsimport ("image/color""strconv")type rgb struct {red   uint8green uint8blue  uint8}func ToRGBA(h string) (color.RGBA, error) {rgb, err := hex2RGB(h)if err != nil {return color.RGBA{}, err}return color.RGBA{R: rgb.red, G: rgb.green, B: rgb.blue, A: 255}, nil}func hex2RGB(hex string) (rgb, error) {values, err := strconv.ParseUint(hex, 16, 32)if err != nil {return rgb{}, err}return rgb{red:   uint8(values >> 16),green: uint8((values >> 8) & 0xFF),blue:  uint8(values & 0xFF),}, nil}


Непосредственно пакет сборки нашей картинки:

pacakge imgconst (        // Параметры по умолчаниюimgColorDefault = "E5E5E5"msgColorDefault = "AAAAAA"imgWDefault     = 300imgHDefault     = 300fontSizeDefault         = 0dpiDefault      float64 = 72fontfileDefault = "wqy-zenhei.ttf")// Do - входная точка.func Do(params []string) (*bytes.Buffer, error) {        // fetch img params: imgW, imgH, text, etc        // Соберём структуру Текстаlabel := Label{Text: msg, FontSize: fontSize, Color: msgColor}// Соберём структуру Картинки с нужными полями - высота, ширина, цвет и текстimg := Img{Width: imgW, Height: imgH, Color: imgColor, Label: label}// Сгенерим нашу картинку с текстомreturn img.generate()}// generate - соберёт картинку по нужным размерам, цветом и текстом.func (i Img) generate() (*bytes.Buffer, error) {// Если есть размеры и нет требований по Тексту - соберём Текст по умолчанию.if ((i.Width > 0 || i.Height > 0) && i.Text == "") || i.Text == "" {i.Text = fmt.Sprintf("%d x %d", i.Width, i.Height)}// Если нет требований по размеру шрифта - подберём его исходя из размеров картинки.if i.FontSize == 0 {i.FontSize = i.Width / 10if i.Height < i.Width {i.FontSize = i.Height / 5}}// Переведём цвет из строки в color.RGBA.clr, err := colors.ToRGBA(i.Color)if err != nil {return nil, err}// Создадим in-memory картинку с нужными размерами.m := image.NewRGBA(image.Rect(0, 0, i.Width, i.Height))// Отрисуем картинку:// - по размерам (Bounds)// - и с цветом (Uniform - обёртка над color.Color c Image функциями)// - исходя из точки (Point), как базовой картинки// - заполним цветом нашу Uniform (draw.Src)draw.Draw(m, m.Bounds(), image.NewUniform(clr), image.Point{}, draw.Src)// Добавим текст в картинку.if err = i.drawLabel(m); err != nil {return nil, err}var im image.Image = m// Выделим память под нашу данные (байты картинки).buffer := &bytes.Buffer{}// Закодируем картинку в нашу аллоцированную память.err = jpeg.Encode(buffer, im, nil)return buffer, err}// drawLabel - добавит текст на картинку.func (i *Img) drawLabel(m *image.RGBA) error {// Разберём цвет текста из строки в RGBA.clr, err := colors.ToRGBA(i.Label.Color)if err != nil {return err}// Получим шрифт (должен работать и с латиницей и с кириллицей).fontBytes, err := ioutil.ReadFile(fontfileDefault)if err != nil {return err}fnt, err := truetype.Parse(fontBytes)if err != nil {return err}// Подготовим Drawer для отрисовки текста на картинке.d := &font.Drawer{Dst: m,Src: image.NewUniform(clr),Face: truetype.NewFace(fnt, &truetype.Options{Size:    float64(i.FontSize),DPI:     dpiDefault,Hinting: font.HintingNone,}),}// Зададим базовую линию.d.Dot = fixed.Point26_6{X: (fixed.I(i.Width) - d.MeasureString(i.Text)) / 2,Y: fixed.I((i.Height+i.FontSize)/2 - 12),}// Непосредственно отрисовка текста в нашу RGBA картинку.d.DrawString(i.Text)return nil}

Пример, на запрос:

http://localhost:8080/600/200/004620/Заглушка/FFFFFF/50

получим:

Обратите внимание: мы можем создавать изображение с текстом на латинице и/или на кириллице.

А на такой запрос:

http://localhost:8080/480/200

4. Наложение текста (watermark) на изображение

Watermark может пригодится в таких кейсах:

  • для указания правообладателя на публикуемом изображении

  • на изображение можно накладывать текст рекламного характера

  • назначение изображения и его ограничения для использования

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

5. Нарезка изображений

Не редко на платформе есть необходимость хранить набор изображений и их нарезки, по размерам и форматам.

В в какой-то момент может прийти требование изменить набор нарезанных размеров.

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

Возможны два сценария реализации нарезки:

  • разовая (нарезка для всего набора по требованию)

  • динамическая (нарезка в runtime)

У разовой нарезки есть неоспоримое преимущество - мы разово (например, раз в сутки) нарезаем все картинки или часть из них.

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

Динамическая нарезка изображений, позволяет освободить место под все "нарезки" и не беспокоиться об их актуальности.

Но в данном случае мы требовательны к вычислительным ресурсам и слой "кеширования" нарезки можно "сдвинуть" ближе к клиенту и в любой момент обновить набор размеров.

То есть идеальным вариантом (для ресурсов) будет гибридная нарезка. Когда мы выделяем данные, которые нужно нарезать динамически или разово.

Также если у Вас часто меняются размеры и формат изображений - Вам скорее больше подходит динамическая нарезка (при наличии вычислительных мощностей). Настоятельно рекомендую делать просчёты под Ваши планируемые требования и мощности.

Примечание

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

Подробнее..

Анимация и экспорт. На примере игры Intravenous. Часть 1

09.02.2021 16:13:11 | Автор: admin

Сказ о том, как делать не стоит. Или, как я дважды сгорал на работе

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

Но, именно данный заказ натолкнул меня на мысль, что подобным опытом стоит поделиться. Дабы, не знакомые со сферой, знали, как работает внутренняя кухня, а коллеги, как делать не стоит и почему. К тому же, перспектива Top-Down специфическая и материалов по ней практически не существует. Когда я начинал работу, никакого опыта с top-down перспективой, кроме игровой, у меня не было, что подогревало интерес.

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


Promo-art для проектаPromo-art для проекта

Проект выполнен в жанре: Top Down Stealth - Action (уникальная смесь из серий Splinter Cell и Hotline Miami).

Движок: Love2D
Арт/Дизайн/Анимации выполнены в: Adobe Photoshop ( :) )
Художественное направление: Pixel art

Проект находится в Steam, и ознакомится с ним можно по данной ссылке: Intravenous

Скриншот из ранних версийСкриншот из ранних версийСкриншот из демо-версииСкриншот из демо-версии

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

Я изготовил для проекта UI (редизайн/арт), эффекты, персонажей, анимации, тайлсеты, объекты, портреты, promo-art.
В общем, практически всё, что вы увидите в игре. Но в данной статье, речь пойдёт именно про анимации, т.к. они стали "камнем преткновения" всей разработки.


Немного о перспективе

Enter The Gungeon - хороший пример перспективы 3/4Enter The Gungeon - хороший пример перспективы 3/4

Существует распространённое заблуждение, что "top-down" - это любой угол поворота камеры, в том числе несколько видов изометрии и, так называемая, перспектива 3/4.

Скетчи персонажей для освоения top-down перспективыСкетчи персонажей для освоения top-down перспективы

Связано это с тем, что у ряда перспектив, не существует какого-то объединяющего понятия/термина отличного от "вида сверху" т.е. "Top-Down".
Отсюда и возникающие недопонимания при обсуждении того или иного проекта.

"Top-Down" (топ-даун) - это перспектива, камера в которой привязана исключительно над головой персонажа.
Примеры: GTA 1/2, Darkwood, Hotline Miami


Анимации

Скетчи персонажей в пикселяхСкетчи персонажей в пикселях

Техническое задание (оно же: Т/З) включало в себя создание довольно внушительного числа анимаций для нескольких персонажей (с рядом исключений). В следствие чего, мне было необходимо придумать способ, как упростить этот процесс на большинстве этапов разработки.

Первые потуги анимации в top-down перспективеПервые потуги анимации в top-down перспективе

Список анимаций для всех персонажей включал в себя:

  • 5 видов основного оружия (Дробовик, Обрез, MP5, UZI, AK103, M4);

  • 5 видов второстепенного (Glock19, HS2000, P89, SW457, VP9);

  • 5 видов уникальных приспособлений (Тазер, Переносной ЭМИ глушитель, Светошумовая граната, Осколочная граната, Пустые магазины);

  • Ближний бой на всех видах оружия, в том числе и рукопашный;

  • Выбивание двери;

  • Idle анимации;

  • Анимации смерти;

  • Подбор и взаимодействие с предметами;

Наброски анимацийНаброски анимаций

Уникальные для персонажа игрока:

  • Перенос тел;

  • Оглушение или добивание персонажей;

  • Использование отмычки;

  • Лаз через 2 вида препятствий;

  • Движение ползком;

  • Бег;

Обрез. Умелый.Обрез. Умелый.

Помимо этого, существует 3 степени умения обращения с оружием (что увеличило список анимаций втрое!), которые мы условно назвали:

- Умелый; (персонаж игрока, профессиональные военные)
- Не умелый; (киллеры, наёмники)
- Абсолютно не умелый; (гангстеры, шпана)

Обрез. Неумелый.Обрез. Неумелый.

Все 3 степени отличаются геймплейно:

- точностью при стрельбе;
- скоростью перезарядки;
- скоростью реакции на события;

Что отражается визуально, через:
- наличие лишних телодвижений при перезарядке;
- положение персонажа (стойку);

Обрез. Абсолютно неумелый.Обрез. Абсолютно неумелый.

Список анимаций на каждого персонажа достигал значения 200+. Т.к. каждая анимация, даже в которой было изменён исключительно спрайт пистолета был уникальным.

Для команды из 2 человек (программист и художник), объём работы был колоссальный. Это означало, что нам необходим шаблон персонажа, дабы упростить производство.
В дальнейшем, несмотря на то, что мы понимали, что существует более изощрённый способ для решения проблемы, нами был выбран крайне монотонный и исключающий большинство проблем при переносе.

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


Шаблон

Анимация падения и подъёмаАнимация падения и подъёма

Шаблон персонажа включал в себя:

- Голову;
- Тело;
- Руки;
- Оружие;
- Дополнительные слои (ладони/детали);
-
Ноги/нижняя половина тела (отдельно);

Из которых покадрово собирался цикл анимации.


Экспорт

Существует 2 пути экспорта шаблонных анимаций.

Способ 1:

  • Все части шаблона - отделены (слоями);

  • Оружие легко заменяется (если позволяет анимация);

  • Одежда кладётся поверх слоёв в игре;

Pixel art со скелетной анимациейPixel art со скелетной анимацией

+ Упор делается на сборку составляющих внутри движка игры;
+ Гибкость, возможность осуществлять исправления, буквально, на лету;
- Требователен к инструментарию движка;

Не совсем корректный, но отличный пример: Garage: Bad Trip.
(На самом деле, она известна своей скелетной анимацией скрещенной с Pixel-art графикой, и даже существует статья на эту тему, но я её не нашёл) ("Пес-песа" - тебя помнят!)

Способ 2:

  • Все части шаблона склеены (монолитный слой);

  • Оружие заменяется исключительно в исходнике (PSD/GIF файле);

  • Одежда склеивается вместе с частями шаблона;

Spritesheet персонажа из Hotline MiamiSpritesheet персонажа из Hotline Miami

+ Упор делается на финализацию работ перед отправкой;
+ Лёгкий импорт в движок игры;
- Многократно возрастающий объём работы;
- РУТИНА;
- Не подходит проектам, в которых используется большой размер спрайтов;

Отличный пример: Hotline Miami

Как вы уже понимаете, нами был выбран 2 вариант. Почему?
На это повлиял целый ряд причин:

  • Отсутствие инструментария для анимации (игра разрабатывалась на Love2D);

  • Необходимость разгрузки программиста от лишней работы (переизбыток задач);

  • Тримминг текстур (упаковка кадров анимации в spritesheets);

  • Малый размер спрайтов;

А теперь поподробнее.

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

  • Разработка инструментария для анимаций не рассматривалась вовсе, т.к. эти силы разумно было бросить на встроенный level-editor (редактор уровней) и проработку ИИ (искусственного интеллекта) врагов;

Тримминг кадров анимации и упаковкаТримминг кадров анимации и упаковка

Продолжение в части 2.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru