«

»

Авг
09

Андроид NDK. Меняем яркость изображения.

hi/lo contrast imageВ данной статье рассмотрим пример обработки изображения. Так как алгоритмы обработки изображений обычно требует высоких вычислительных затрат, реализовывать их мы будем с использованием NDK. Это позволит получить многократный выигрыш в производительности, по сравнению с реализацией того же алгоритма на JAVA. Скачать весь код приложения можно с github.

Чтобы не загромождать пример, алгоритм обработки изображения выберем из простых. Будем менять яркость изображения. Изменение яркости довольно простая операция, что поможет нам больше сосредоточится на изучении возможностей NDK, а не на реализации собственно алгоритма. Надеюсь, что NDK у вас уже стоит и helloworld вы уже запустили. И так приступим.

Планируем

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

Так как программировать на Си с NDK не так приятно, как на Java, то постараемся побольше кода, скорость выполнения которого не критична, написать на Java. На Си будем писать по-минимому и реализовывать на нем, только действительно необходимые вещи.

Пишем Java часть

Создадим новый класс, в котором организуем наш код обработки изображения. Назовем его ImageProcessor. Функцию для обработки изображения назовем AdjustImageBrightness. Ее задачей будет проверка входных параметров на корректность и, если проверка прошла успешно, вызов NDK функции преобразования изображения. Посмотрим код:

Код довольно простой. В функцию AdjustImageBrightness передаются 3 параметра: исходное изображение, изображение, куда будет сохранен результат преобразования, и параметр яркости. Далее идет проверка правильности параметров изображений. Они должны быть одинакового размера и содержать пиксели в формате ARGB_8888 (этот формат наиболее удобен). Значение яркости тоже проверяется на корректность.

Функция AdjustImageBrightnessNDK – это функция преобразования изображения. Ее мы напишем на Си. Чтобы Java поняла, что это особенная функция, при ее описании необходимо  использовать ключевое слово native. Добавим в разрабатываемый класс описание прототипа нашей функции, а так же код, который загрузит нашу библиотеку с реализацией функции. Библиотека будет называться libImageProcessor.so, поэтому чтобы ее загрузить необходимо в функцию loadLibrary передать часть ее названия без “lib” и “.so”. Итого добавляем следующий код:

Java часть готова, приступаем в разработке библиотеки.

Пишем native код

Код разместим в двух файлах: ImageProcessor.h и ImageProcessor.c. Заголовочный файл (который с расширением .h) должен содержать прототип функции, которую мы собираемся вызывать из Java. Его следует генерировать специальной утилитой javah. Как это делать можно посмотреть в сети. После запуска javah получаем файл со следующим содержимым:

Главное в этом файле это прототип функции Java_com_pandacoder…AdjustImageBrightnessNDK. Он должен соотвествовать определенному формату, чтобы JVM могла найти нашу функцию. По сравнению с Java функцией AdjustImageBrightnessNDK у Си функции изменилось не только название, но и набор параметров. JNIEnv * – указатель, через который мы можем получить доступ к различным JNI функциям, которые позволят нам взаимодействовать с JVM. jclass – объект для взаимодействия с классом, к которому принадлежит AdjustImageBrightnessNDK, то есть с ImageProcessor. Далее идут два jobject – это собственно наши изображения, jint – значение яркости.

Создаем файл ImageProcessor.c и реализуем тело функции. В начале подключаем заголовочные файлы: сгененированный javah ImageProcessor.h и android/bitmap.h – для работы с Bitmap‘ами. Далее прототипы наших функций, к которым мы вернемся чуть позже. И самое интересное – тело функции Java_com_pandacoder…AdjustImageBrightnessNDK.

В общем ничего интересного. Задачей функции является подготовка данных для вызова ProcessImagePixelsBrightness – непосредственного расчета значений цветов результирующего изображения. Для этого мы узнаем параметры оригинального изображения (AndroidBitmap_getInfo), чтобы знать его размеры, и получаем доступ к значениям пикселей исходного и результирующего изображения. Про обработку ошибок и функцию ThrowErrorJavaException я напишу чуть позже.

Создаем функцию ProcessImagePixelsBrightness, занимающуюся расчетом значений пикселей результирующего изображения.

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

Native код. Обрабатываем ошибки. Генерируем исключения.

Обработка ошибок всегда непростое дело, а в NDK с эти все совсем сложно. Самый распространенный способ – старые добрые коды ошибок, стандарт для языка Си. Приходится постоянно проверять не вернула ли нам функция NULL или код ошибки (зависит от функции). С версии NDK-r5 появилась поддержка исключений языка С++, поэтому внутри native кода для организации обработки ошибок можно пользоваться С++ исключениями. Но что делать, когда хочется сгенерировать исключение, прервать дальнейшее выполнение кода и обработать его уже в Java коде? Простого способа сделать это нет.

Для работы с Java исключениями у нас есть несколько функций в структуре JNIEnv, таких как ThrowNew – генерирует исключение, ExceptionOccurred – определяет было ли сгенерировано исключение, ExceptionDescribe – выдает описание исключения, ExceptionClear – очищает исключение и др. Особенность Java исключений сгенерированных в native коде в том, что они обрабатываются только когда native код завершает выполнятся и управление передается обратно в Java код. Причем такое исключение (ожидающее обработки) может быть только одно. То есть ThrowNew не останавливает выполнение native кода в точке, где произошло исключение. Он просто сохраняет его. Остановить выполнение и вернуть управление в Java должен сам разработчик (именно поэтому после функции ThrowErrorJavaException стоит return, которые отдает управление Java, где будет уже обработано исключение). Многие функции из JNIEnv тоже генерируют исключения. Если кто-то сгенерировал Java исключение, следует либо очистить его (функция ExceptionClear), либо вернуть управление Java коду, где оно будет обработано. Продолжать выполнение native кода с ожидающим исключением неследует. Так же запрещено вызывать функции из JNIEnv, за исключением функций предназначеных для работы с исключением (ExceptionClearExceptionOccurred  и др.).

В данном примере, для удобства обработки ошибок, была создана специальная функция ThrowErrorJavaException, которой передается сообщение описывающее ошибку. Данная функция получает класс исключения ImageProcessorException и, если такой класс существует, генерирует исключение с помощью функции ThrowNew. Не забываем о том, что после выполнения функции ThrowErrorJavaException следует завершить выполнение native кода и отдать управление Java, для обработки исключения. А вот и код.

Результат работы

Ну и пару картинок под конец с результатом проделанной работы.

результат работы

Полный код приложения можно взять тут.

Удачных разработок.

1 комментарий

  1. admin Сообщает:

    пора приступать к реализации рентгеновского фильтра xDDD

Добавить комментарий

Мы сохраним Ваш e-mail в тайне.