Более продвинутая работа с изображениями - **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

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

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

***

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

`image` до размеров `width`x`height`:  
`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` не учитываются).
```python
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]`:
```python
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`):  
`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](./ScreenLang), т. е. будет вызываться, возможно, по несколько десятков раз за кадр.  
Соответственно, она должна быть оптимизирована для этих целей: предполагается, что размеры и цвета будут
требоваться в небольшом кол-ве вариантов, и их следует кэшировать, т. е. вычислить 1 раз,
запомнить результат, а далее по возможности выдавать уже его без новых вычислений.  
Если же по каким-то причинам кэширование нужно отключить, то можно передать `False` в аргумент `use_cache`.

***

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

`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` же это именно функции. А их принято называть именно так.

***

[Далее ->](./Эффекты)
