Функции для изображений (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(
"condition-1", "image1",
"condition-2", "image2",
"condition-3", "image3"
)
-
condition-1
- python-выражение, если истинно (True
), то возвращается изображениеimage1
, -
condition-2
- следующее условие, всё аналогично. Не проверяется, если предыдущее было верно.
Кол-во условий не ограничено.
Последним условием рекомендуется делать "True"
, чтобы оно было истинно в любом случае.
image
до размеров width
xheight
:
im.scale("image", width, height)
Здесь и далее под image
подразумевается путь к изображению или результат работы другой функции.
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
- убрать совсем.
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лужит для получения изображения по маске, работает это следующим образом:
- Проходимся по каждому пикселю, назовём текущий пиксель
image
-pixel_image
, а соответствующий ему пиксель вmask
-pixel_mask
, - Берём из
pixel_mask
каналchannel
(r
,g
,b
илиa
) и сравниваем его значение (0-255
) сvalue
, - Сравнение идёт с помощью функции
cmp_func_name
(<
,>
,==
,!=
,<=
,>=
), - Если условие выполняется, то
rgb
-каналы результирующего пикселя будут равныrgb
-каналамimage
, аalpha
-канал будет приравнен к каналуalpha_channel
из пикселяpixel_image
(приalpha_image == 1
) илиpixel_mask
(приalpha_image == 2
), - Иначе результирующий пиксель будет пустым.
Через 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 (для него есть отдельная глава).
Горизонтальное:
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
- высота.
Эта функция реализована с помощью ранее указанных функций:
- Берётся изображение чёрного пикселя (
1x1
) изimages/black.jpg
; - При необходимости с помощью
im.matrix_color
перекрашивается в нужный цвет; - При необходимости с помощью
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
- стоит ли использовать кэш (см. ниже).
Реализация:
- С помощью 3 прямоугольников рисуется "почти результат", но без маленьких прямоугольников по углам;
- На углах рисуются окружности таким образом, чтобы сделать их закруглёнными.
Итого эта операция выходит в 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
результат может быть с кратно бОльшими шириной и/или высотой относительноwidth
xheight
.
Последний параметр нужен для ситуаций, когда противоположные границы
занимают слишком много места (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
же это именно функции. А их принято называть именно так.