Матричные преобразования для редактирования изображений в C#

Матричные преобразования для редактирования изображений в C#

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

Матричное умножение – довольно незамысловатая операция. Реализуем функцию умножения матриц в коде. Если вам не хватает знаний линейной алгебры, наглядное представление можно получить из нашей публикации. Для начала создадим C#-класс Matrices и его основной метод Multiply :

Мы начинаем с определения размеров обеих матриц с помощью метода Array.GetLength() . Записываем полученные результаты в переменные, чтобы не пришлось их заново вычислять. Далее убеждаемся, что полученные матрицы в принципе можно перемножить: число столбцов первой матрицы должно равняться количеству строк второй, иначе выбрасываем исключение.

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

Теперь можно использовать алгоритм умножения для того, чтобы создать матрицу трансформации изображения. Эту матрицу затем нужно последовательно применить к каждой точке изображения (X, Y) или к ее цвету (ARGB) . В результате мы получим новое – трансформированное – изображение.

Начнем с определения абстрактного интерфейса трансформации IImageTransformation с двумя членами: методом CreateTransformationMatrix() и булевым свойством IsColorTransformation .

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

Вращение

Преобразование вектора при помощи матрицы поворотаПреобразование вектора при помощи матрицы поворота

На основе приведенной выше матрицы поворота напишем класс, отвечающий за вращение:

Функции Sin() и Cos() принимают углы в радианах, поэтому мы добавили две дополнительные функции для конвертации градусов в радианы и обратно, чтобы упростить взаимодействие для конечного пользователя класса.

Объяснение матрицы вращения вокруг точки доступно изложено в статье 2D Rotation about a point.

Растягивание/Масштабирование

Вторая популярная трансформация – масштабирование по определенному коэффициенту. Она работает путем простого умножения нужных координат (X/Y) на коэффициент масштабирования по соответствующей оси ( xk , ytk ). Напишем класс для этой трансформации в 2D-пространстве:

Единичная матрица

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

Добавьте этот метод в класс Matrices .

Отражение

Третья трансформация, с которой мы будем работать – отражение. Она выполняется за счет изменения знака X и Y – соответственно вектор переворачивается по вертикальной или горизонтальной оси. Класс для применения этой матрицы выглядит так:

Преобразование цвета

Последняя трансформация, которую мы рассмотрим, – изменение цветовой плотности. Она предусматривает применение разных коэффициентов к компонентам цвета (красный, зеленый, синий и альфа-канал). Например, если требуется сделать изображение на 50% прозрачным, нужно умножить значение альфа-канала на 0.5 . Чтобы удалить полностью красный цвет, нужно умножить его на 0 , и так далее.

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

Мы начали с определения двух перегрузок функции Apply . Одна из них принимает первым параметром имя файла изображения, а другая – bitmap-объект. вторым аргументом передается список трансформаций, которые нужно применить.

Внутри Apply() преобразования разделяются на 2 группы:

  • манипулирующие положением точек ( X и Y );
  • манипулирующие цветом.

Для каждой группы объединяем трансформации в единую матрицу с помощью функции CreateTransformationMatrix() .

Затем сканируем изображение и применяем преобразования к точкам и цветам соответственно. Обратите внимание, мы добавили проверку на то, что преобразованные значения каналов находятся в допустимом диапазоне.

После применения трансформаций данные сохраняются в массиве для последующего использования.

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

Наконец, создаем bitmap-объект с новыми данными изображения.

Клиентская часть приложения очень простая. Вот так выглядит форма для ввода данных:

Скриншот формы для ввода данныхСкриншот формы для ввода данных

Взглянем на управляющий ей код:

Единственное, что следует отметить, – это хорошая практика вызова Dispose() на disposable-объектах для лучшей производительности.

В основном методе Multiply() вызов метода Array.GetLength() оказывает большое влияние на производительность. В этом можно убедиться, сравнив скорость выполнения кода с многократным вызовом GetLength и с кэшированием результатов единственного вызова – они отличаются почти в 2 раза!

Еще один способ повысить производительность Multiply() – использовать небезопасный код, получив прямой доступ к содержимому массива:

Небезопасный код не будет компилироваться, если вы не разрешите эту опцию в меню Проект -> Свойства -> Сборка .

Сравнение производительности разных сценариев выполнения функции MultiplyСравнение производительности разных сценариев выполнения функции Multiply

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