Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Делаем свои собственные фильтры изображений

Оригинал: Making Your Own Image Filters
Автор: Cate Huston
Дата публикации: Wed 13 April 2016
Перевод: Н.Ромоданов
Дата перевода: май 2016 г.

Основы фреймворка Processing

Размер и цвет

Мы не хотим, чтобы наше приложение было крошечным серым окошком, поэтому методы setup() и draw() будут теми двумя основными методами, с переопределения которых мы начнем работу над приложением. Метод setup() вызывается один раз, когда приложение запускается, и в нем устанавливается размер окна приложения. Метод draw() вызывается для каждой анимации, или после того, как с помощью метода redraw() могут быть выполнены определенные действия. Как указываеся в документации по фреймворку Processing, метод draw() не должен вызываться явно.

Фреймворк Processing отлично подходит для создания красивых анимированных прототипов, но в данном случае нам не нужна анимация [1], нам нужна реакция на нажатие клавиш. Для отключения анимации (что скажется на производительности) мы при настройке приложения обратимся к методу noLoop(). Это означает, что метод draw() будет вызван сразу после метода setup(), а также всякий раз, когда мы вызываем метод redraw().

private static final int WIDTH = 360;
private static final int HEIGHT = 240;

public void setup() {
  noLoop();

  // Настойка внешнего вида окна
  size(WIDTH, HEIGHT);
  background(0);
}

public void draw() {
  background(0);
}

На самом деле мы еще ничего не сделали, но попробуйте еще раз запустить приложение, поменяв значение констант WIDTH и HEIGHT. Размеры окна будут другими.

Метод background(0) задает черный фон окна. Попробуйте изменить значение, передаваемое в метод background(), и посмотрите, что произойдет – это значение значение альфа канала, и поэтому, если вы передаете только одно значение, то всегда будете получать оттенки серого. В качестве альтернативного варианта вы можете вызвать background(int r, int g, int b).

Объект PImage

Объект PImage является объектом фреймворка Processing, который представляет собой изображение. Мы собираемся использовать его многократно, так что стоит прочитать о нем в документации. Объект состоит из трех полей (таблица 1), а также имеет несколько методов, которыми мы будем пользоваться (таблица 2).

Таблица 1. Поля объекта PImage

pixels[]

Массив, в котором хранятся значения цвета всех пикселей изображения

width

Ширина изображения в пикселях

height

Высота изображения в пикселях

Таблица 2. Методы объекта PImage

loadPixels

Загружает данные о пикселях изображения в массив pixels[]

updatePixels

Обновляет изображение по данным из массива pixels[]

resize

Изменяет размер изображения для новых значений ширины и высоты

get

Читает цвет любого пикселя или прямоугольной области изображения, состоящей из пикселей

set

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

save

Сохраняет изображение в файле формата TIFF, TARGA, PNG или JPEG

Выбор файлов

Фреймворк Processing выполняет большую часть процедуры выбора файлов; нам просто нужно вызвать метод selectInput() и реализовать функцию обратного вызова (которая должна быть типа public).

Для тех, кто знаком с языком Java, такое решение может показаться странным; более подходящим могло бы быть использование слушателя (listener) или лямбда-выражения. Но фреймворк Processing был разработан как инструмент для дизайнера и от большей части подобных вещей абстрагировались с тем, чтобы они никого не пугали. Такой выбор был сделан дизайнерами: приоритет был отдан простоте и удобству использования, а не производительности и гибкости. Если вы пользуетесь редактором Processing с урезанными возможностями, а библиотекой Processing в Eclipse, то вам не нужно даже указывать имена классов.

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

// Вызывается при нажатии клавиши
private void chooseFile() {
  // Выбор файла
  selectInput("Select a file to process:", "fileSelected");
}

public void fileSelected(File file) {
  if (file == null) {
    println("User hit cancel.");
  } else {
    // Сохранение изобрадения
    redraw(); // Перерисовка изображения на экране дисплея
  }
}

Реакция на нажатие клавиш

Обычно в языке Java для обеспечения реакции на нажатие клавиши требуется добавление слушателей listeners и реализации анонимных функций. Но когда мы выбираем файлы, фреймворк Processing многое делает за нас. Нам просто нужно реализовать метод keyPressed().

public void keyPressed() {
  print(“key pressed: ” + key);
}

Если вы снова запустите приложение, то каждый раз, когда вы нажимаете клавишу, приложение будет выводить на консоль сообщение. Позже, когда вы захотите выпольнять различные действия в зависимости от того, какая клавиша была нажата, то для того, чтобы это сделать, вы просто добавите переключатель по значению key. Эта переменная есть в суперклассе PApplet, и содержит значение, соответствующее последней нажатой клавиши.

Написание тестов

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

Мне нравится фреймворк Processing, но он предназначен для создания визуальных приложений, и в этой области, возможно, модульное тестирование не является большой проблемой. Понятно, что фреймворк был написан без учета возможности тестирования; на самом деле он написан таким образом, что его тестирование невозможно. Отчасти это происходит потому, что он скрывает сложность приложения, а некоторые аспекты этой скрытой сложности действительно полезны при написании тестов для модульного тестирования. Использование методов static и final усложняет использование моков (объектов mock, в которых реализовано взаимодействие и которые позволяют имитировать работу части вашей системы с тем, чтобы проверить, что другая часть вашей системы работает правильно), которые подключаются как подклассы.

Мы могли бы начать прект с нуля с намерением выполнить всю разработку по технологии с предваряющим написанием тестов - Test Driven Development (TDD) и достичь идеального тестового покрытия, но на самом деле мы, как правило, смотрим весь код, написанный многими и разными разработчиками, и пытаемся понять, что он, предположительно, делает, и как и почему это делается не так, как надо. Тогда, возможно, мы напишем неидеальные тесты, но написание тестов поможет нам, в общем случае, ориентироваться ситуации, документировать то, что происходит, и двигаться дальше.

Мы создаем "швы", по которым мы можем выделеть некоторую часть из общей аморфной массы спутанных кусков и проверить эти части по-отдельности. Для того, чтобы это сделать, мы иногда будем создавать классы-обвертки, которые будут имитировать работу с остальными частями приложения. В этих классах будут просто хранится набор методов, используемых для имитации остальных частей, или переадресовывать вызовы на другой объект, поведение которого не может быть проиммитированно (из-за методов final или static), и, хотя их они очень скучно писать, но они вляются ключом к созданию "швов" и делают код тестируемым.

Поскольку я работала на языке Java с фреймворком Processing, который представлен в виде библиотеки, то для тестирования воспользовалась методикой JUnit. Для мокинга использовала пакет Mockito. Вы можете скачать пакет Mockito и добавить архив JAR в ваш путь сборки buildpath точно также, как вы добавляли архив core.jar. Я создала два вспомогательных класса, которые позволяют использовать мокинг и протестировать приложение (в противном случае мы не можем протестировать тех частей, где используются методы PImage или PApplet).

IFAImage является тонкой оберткой вокруг PImage. PixelColorHelper является оберткой методов апплета, работающих с цветом пикселей. В эти обертках можно вызывать методы final и static, но вызывающие методы сами не будут методами final или static, и их можно будет использовать при мокинге. Они намеренно сделаны легковеснымии мы могли бы продвинуться дальше, но этого было достаточно для того, чтобы решить главную проблему – тестирования при использовании фреймворка Processing – наличие методов static и final. Цель, в конце концов, состояла в том, чтобы сделать приложение, а не фреймворк тестирования для фреймворка Processing!

Класс, который называется ImageState, образует «модель» этого приложения, удаляя из класса, расширяющего класс PApplet, столько логики, насколько это возможно с тем, чтобы обеспечить лучшую тестируемость. Это также способствует более лучшему дизайну и разделению задач: метод App контролирует взаимодействие с пользователем и реализует пользовательский интерфейс, а не детали обработки изображений.

Перейти к следующей части статьи.