Функции для изображений (im функции) - TrueCat17/Ren-Engine GitHub Wiki

Более продвинутая работа с изображениями - im-функции.
Они позволяют проделывать с изображениями всякие хитрые (и не очень) вещи. На вход они могут принимать путь до изображения и результат других функций (не только im, в отличие от Ren'Py). Посмотреть список всех существующих im-функций можно в файле Ren-Engine/rpy/im_functions.rpy.


Примеры

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

Увеличение картинки до размеров 945x1620:
image np scaled = im.scale("images/sprites/np/np_normal", 945, 1620)

Пример чуть сложнее - изменение оттенков (перекраска):
image np unusual = im.MatrixColor("images/sprites/np/np_normal.png", im.matrix.tint(0.6, 0.2, 0.9))

Объединение нескольких картинок в одну:
image np smile = im.Composite((630, 1080), (0, 0), "images/sprites/np/body.png", (0, 0), "images/sprites/np/smile.png")


ConditionSwitch

Возвращает разные изображения в зависимости от условий:

ConditionSwitch(
	"condition-1", "image1",
	"condition-2", "image2",
	"condition-3", "image3"
)
  • condition-1 - python-выражение, если истинно (True), то возвращается изображение image1,
  • condition-2 - следующее условие, всё аналогично. Не проверяется, если предыдущее было верно.

Кол-во условий не ограничено. Последним условием рекомендуется делать "True", чтобы оно было истинно в любом случае.


Масштабирование

image до размеров widthxheight:
im.scale("image", width, height)

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


Масштабирование в k раз

im.factor_scale("image", w, h = None)
Где

  • w - коэф. для масштабирования по ширине,
  • h - по высоте (если не указано, то берётся w).

Числа могут быть нецелыми и меньше 1.


Вырезка

Взять прямоугольную область в координатах (x, y) с размерами (width, height):
im.crop("image", x, y, width, height)

Может быть более удобной форма
im.crop("image", rect)
Где rect - кортёж или список из всё тех же свойств (x, y, width, height).


Наложение

Отрисовать картинки поверх друг друга:
im.composite((width, height), (x1, y1), "image1", (x2, y2), "image2")
Где

  • (width, height) - размеры создаваемого изображения,
  • (x1, y1) - координаты, в которых будет отрисовано 1-е изображение,
  • image1 - 1-е изображение,
  • (x2, y2) - координаты для 2-го изображения,
  • image2 - 2-е изображение,
  • кол-во изображений не ограничено.

Отражение

im.flip("image", horizontal = False, vertical = False)
Где

  • horizontal - нужно ли отразить по горизонтали (True - да, False - нет),
  • vertical - аналогично для вертикали.

Изменение оттенков

im.recolor("image", r, g, b, a = 255)
Где r, g, b, a - коэффициенты для изменения каналов (красного, зелёного, синего и прозрачности соответственно).
255 - оставить как есть, 128 - оставить половину, 51 - 20%, 0 - убрать совсем.


Изменение оттенков 2

im.color("image", color)
Аналог im.recolor, но принимает коэффициенты одним параметром, вроде "#F0F", "#80FF80FF" или (0, 255, 255, 128).
Работает через recolor.


Изменение прозрачности

im.alpha("image", k)
0 <= k <= 1
Работает тоже через recolor.


Поворот и масштабирование

im.rotozoom("image", angle, zoom = 1.0)
Где

  • angle - угол поворота (против часовой стрелки),
  • zoom - коэффициент масштабирования (может быть отрицательным, тогда изображение переворачивается).

Матрицы преобразований

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

Использование:
im.matrix_color("image", matrix)
Где matrix - матрица преобразований.

Матрица - что-то вроде массива, содержащего в себе 20 или 25 чисел (если 25, то последние 5 не учитываются).

matrix = [
	a, b, c, d, e,
	f, g, h, i, j,
	k, l, m, n, o,
	p, q, r, s, t
]

Функция преобразования проходит по каждому пикселю изображения.
Пиксель имеет 4 канала: R, G, B, A - красный, зелёный, синий и прозрачность соответственно. Для каждого пикселя [R, G, B, A] с помощью переданной матрицы будет получен новый пиксель [R2, G2, B2, A2]:

R2 = (a * R) + (b * G) + (c * B) + (d * A) + e
G2 = (f * R) + (g * G) + (h * B) + (i * A) + j
B2 = (k * R) + (l * G) + (m * B) + (n * A) + o
A2 = (p * R) + (q * G) + (r * B) + (s * A) + t

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

im.matrix.identity() - единичная матрица (нейтральная, ничего не делает).

im.matrix.tint(r, g, b, a = 1) - аналог recolor для матриц, ожидаются числа от 0 до 1.

im.matrix.saturation(level, desat=(0.2126, 0.7152, 0.0722)) - level определяет насыщенность (1 - без изменений)
desat - сколько оставить от каждого канала R, G, B, если level будет равен 0. Обычно используется значение по умолчанию, т. е. указывать его не требуется.

im.matrix.invert() - инвертирует каналы изображения.

im.matrix.brightness(level) - добавляет level яркости (0 - ничего не делать, может быть отрицательным).

im.matrix.contrast(level) - изменяет контраст (больше 0; 1 - ничего не делать).

im.matrix.opacity(level) - изменяет прозрачность (1 - оставить как есть).

im.matrix.colorize(black_color, white_color) - раскрашивание в 2 указанных цвета.

Матрицы можно перемножать друг на друга, это сэкономит игроку время и оперативку:
im.matrix_color(im.matrix_color("image", im.matrix.tint(0.5, 0.7, 0.8)), im.matrix.invert()) - неправильно!
im.matrix_color("image", im.matrix.tint(0.5, 0.7, 0.8) * im.matrix.invert()) - правильно!
Именно для удобства реализации этого перемножения матрицы и имеют 25 элементов, несмотря на то, что последние 5 не используются.

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


С помощью matrix_color так же реализованы ещё некоторые другие функции:

Оттенки серого

im.grayscale("image")

Сепия

im.sepia("image")


Со временем в Ren-Engine появляются новые im-функции, и далее перечисляются именно они. Напоминаю, что полный список im-функций всегда можно найти в файле Ren-Engine/rpy/im_functions.rpy.


Маска

im.mask(image, mask, value, channel = 'r', cmp_func_name = '<=', alpha_channel = 'a', alpha_image = 1)
Cлужит для получения изображения по маске, работает это следующим образом:

  1. Проходимся по каждому пикселю, назовём текущий пиксель image - pixel_image, а соответствующий ему пиксель в mask - pixel_mask,
  2. Берём из pixel_mask канал channel (r, g, b или a) и сравниваем его значение (0-255) с value,
  3. Сравнение идёт с помощью функции cmp_func_name (<, >, ==, !=, <=, >=),
  4. Если условие выполняется, то rgb-каналы результирующего пикселя будут равны rgb-каналам image, а alpha-канал будет приравнен к каналу alpha_channel из пикселя pixel_image (при alpha_image == 1) или pixel_mask (при alpha_image == 2),
  5. Иначе результирующий пиксель будет пустым.

Через im.mask реализована im.alpha_mask (параметры: image, mask, 0, 'r', '>', 'r', 2):
im.alpha_mask(image, mask)
Честно говоря, довольно непросто придумать, зачем она такая нужна, но она была в Ren'Py, и её было довольно просто реализовать с помощью im.mask.

Размеры изображений должны быть равны.

Посмотреть эту функцию в действии можно в моде "Маски" в демке.


Масштабирование через рендерер

im.renderer_scale(image, width, height)
Аналог im.scale, но масштабируется видеокартой (или программно, если рендерер именно такой), используется для того, чтобы получить именно то изображение, которое получится при отрисовке image с указанием размера (width, height) в ScreenLang (для него есть отдельная глава).


Размытие (blur)

Горизонтальное:
im.blur_h("image", dist = 5)
Здесь dist - "радиус" размытия.

Вертикальное:
im.blur_v("image", dist = 5)

Всё вместе (blur_h + blur_v):
im.blur("image", dist_h = 5, dist_v = 5)
Кстати, эту функцию уже сделали в Ren'Py.

Размытие движением (Motion Blur):
im.motion_blur("image", cx = 0.5, cy = 0.5, dist = 5)
Здесь cx, cy - координаты центра размытия (целые числа - пиксели, дробные - относительно соответствующей стороны).


Однотонные фигуры

Однотонный прямоугольник:
im.rect(color, width = 1, height = 1)
Где:

  • color - цвет (как обычно, в форматах '#F0F', 0xFF0000, (255, 255, 0, 128) и т. д.),
  • width - ширина,
  • height - высота.

Эта функция реализована с помощью ранее указанных функций:

  1. Берётся изображение чёрного пикселя (1x1) из images/black.jpg;
  2. При необходимости с помощью im.matrix_color перекрашивается в нужный цвет;
  3. При необходимости с помощью im.scale растягивается до нужных размеров.

Впрочем, на самом деле (для скорости) вместо загрузки изображений с путём вида images/black.* движок просто самостоятельно создаёт изображение 1x1 чёрного цвета.

Однотонный круг:
im.circle(color, width = 64, height = None)
Где:

  • color - цвет,
  • width - ширина,
  • height - высота (по умолчанию равна ширине, при изменении выйдет эллипс).

Реализация аналогична im.rect, но в качестве базового изображения выступает images/black_circle.png (под указанную ранее "оптимизацию", очевидно, не попадает), размером 64x64 (поэтому такие размеры и приняты по умолчанию в аргументах).

Однотонный прямоугольник с закруглёнными углами:
im.round_rect(color, width = 512, height = 64, left = 16, right = None, top = None, bottom = None, use_cache = True)
Где:

  • width - ширина прямоугольника,
  • height - высота,
  • left - размеры "закруглений" слева,
  • right - справа (по умолчанию берётся значение left),
  • top - сверху (по умолчанию берётся значение left, если оно не равно 0, иначе - right),
  • bottom - снизу (по умолчанию = top),
  • use_cache - стоит ли использовать кэш (см. ниже).

Реализация:

  1. С помощью 3 прямоугольников рисуется "почти результат", но без маленьких прямоугольников по углам;
  2. На углах рисуются окружности таким образом, чтобы сделать их закруглёнными.

Итого эта операция выходит в 7 раз более трудоёмкой + из этих кусков требуется собрать единый результат.
Конечно, как и другие im-функции, эта тоже работает лишь со строками, а не самими изображениями.
Однако, как видно по параметрам по умолчанию, предполагается, что эта функция будет использоваться для создания кнопок в ScreenLang, т. е. будет вызываться, возможно, по несколько десятков раз за кадр.
Соответственно, она должна быть оптимизирована для этих целей: предполагается, что размеры и цвета будут требоваться в небольшом кол-ве вариантов, и их следует кэшировать, т. е. вычислить 1 раз, запомнить результат, а далее по возможности выдавать уже его без новых вычислений.
Если же по каким-то причинам кэширование нужно отключить, то можно передать False в аргумент use_cache.


Масштабирование без границ (с сохранением их размера)

im.scale_without_borders(image, width, height, left = None, top = None, right = None, bottom = None, need_scale = False)
Где

  • image - изображение для масштабирования,
  • width - ширина результата,
  • height - высота,
  • left - размер левой границы изображения (по умолчанию None - треть от минимальной стороны image),
  • top - размер верхней границы (при None берётся из left),
  • right - правой (при None берётся из left),
  • bottom - нижней (при None берётся из top),
  • need_scale - при False результат может быть с кратно бОльшими шириной и/или высотой относительно widthxheight.

Последний параметр нужен для ситуаций, когда противоположные границы занимают слишком много места (left + right > width * 0.8 для ширины).
Такое поведение может показаться странным, но на практике именно оно обычно является желательным, т. к. результат этой функции идёт на отрисовку в ScreenLang, так что дополнительное масштабирование будет лишним.

В качестве примера необходимости такого поведения можно привести следующее:

  • Есть картинка кнопки чекбокса шириной в 50 пикселей,
  • Она должна занимать 0.03 от ширины окна игры,
  • Размер границ - 15 пикселей.

Вариант 1:

  • Ширина окна - 1920,
  • Т. е. масштабировать нужно до 58 пикселей, из которых 30 будут занимать границы,
  • Всё хорошо.

Вариант 2:

  • Ширина окна - 1200,
  • Т. е. масштабировать нужно до 36 пикселей, из которых 30 будут занимать границы,
  • Это - уже нехорошо, а ведь 1200 - ещё хорошее разрешение окна, можно же и до 640 уменьшить (тогда отдать 30 пикселей на границы будет вообще невозможно, т. к. их всего будет 19),
  • Поэтому ширина результата будет в этом случае 36 * 2 = 72, из которых всё те же 30 будут занимать границы,
  • При рендере движок автоматически уменьшит масштаб с минимальной потерей качества (т. к. коэффициент масштабирования - целое число; впрочем, излишне тонкие линии на границе могут пострадать даже так), ведь в ScreenLang'е будут указаны реальные размеры.

Дополнительно:

  • Параметр left может быть списком или кортежом из 4 элементов, тогда считается, что размеры для всех границ указаны именно в нём;
  • Параметры left, right, top и bottom могут иметь тип float для указания размеров относительно сторон image;
  • Эта функция использует кэширование, так что относительно дорогие вычисления будут лишь при первом вызове, далее (при тех же аргументах) результат будет отдаваться почти мгновенно.

Сохранение изображения

im.save(image, path, width = None, height = None)
Где

  • image - изображение для сохранения,
  • path - путь для файла,
  • width, height - размеры, к которым масштабируется изображение.
    Если width и/или height являются None, то используется размер соответствующей стороны image.

По умолчанию изображение сохраняется в формате PNG, но path может иметь расширение jpg/jpeg для сохранения в формате JPEG.
Также в конце можно указать качество от 1 до 100 (по умолчанию 97) после двойного двоеточия:
im.save(my_image, '../var/my_image.jpg::90')


Кому-то может показаться странным, что имена функций здесь, вроде rotozoom или matrix_color, начинаются с маленькой буквы. Ведь в Ren'Py-то это Rotozoom и MatrixColor. А как же обратная совместимость? На самом деле, у них есть синонимы для этого, поэтому всё будет работать, как и ожидается.

Зачем же это всё нужно было делать?
В Ren'Py это классы внутри модуля im. Классы принято называть с большой буквы, поэтому там так и сделано.
В Ren-Engine же это именно функции. А их принято называть именно так.


Далее ->

⚠️ **GitHub.com Fallback** ⚠️