Статьи

Мастер-манипулирование изображениями для мобильных устройств: тиснение изображений с помощью Android Renderscript API

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

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

Понимание алгоритма тиснения

Книги, написанные шрифтом Брайля, документы, запечатанные горячим воском, чтобы гарантировать их подлинность, и монеты иллюстрируют тиснение , акт поднятия содержимого с рельефа с поверхности. Тиснение помогает слепому читать через систему рельефных точек шрифтом Брайля и обеспечивает аутентификацию для штампованных, запечатанных документов или монетных монет. (В прошлом эти печати гарантировали вес и стоимость драгоценных металлов, используемых в монетах.) Наряду с этим реальным использованием, тиснение также придает изображениям трехмерный точеный вид. На рисунке 1 представлен пример.

Рисунок 1: Рельефное изображение также выглядит несколько металлическим.

Думайте об изображении как о гористой местности. Каждый пиксель представляет высоту: более яркие пиксели представляют более высокие возвышения. Когда воображаемый источник света освещает эту местность, загораются «подъемы», которые обращены к источнику света, а «наклоны», которые направлены от источника света, затенены. Алгоритм тиснения захватывает эту информацию.

Алгоритм сканирует изображение в направлении движения светового луча. Например, если источник света расположен слева от изображения, его световые лучи перемещаются слева направо, поэтому сканирование продолжается слева направо. Во время сканирования сравниваются смежные пиксели (в направлении сканирования). Разница в интенсивности представлена ​​определенным уровнем серого (от черного, полностью затененного до белого, полностью освещенного) в целевом изображении.

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

FOR row = 0 TO height-1 FOR column = 0 TO width-1 SET current TO src.rgb[row] SET upperLeft TO 0 IF row > 0 AND column > 0 SET upperLeft TO src.rgb[row-1][column-1] SET redIntensityDiff TO red (current)-red (upperLeft) SET greenIntensityDiff TO green (current)-green (upperLeft) SET blueIntensityDiff TO blue (current)-blue (upperLeft) SET diff TO redIntensityDiff IF ABS(greenIntensitydiff) > ABS(diff) SET diff TO greenIntensityDiff IF ABS(blueIntensityDiff) > ABS(diff) SET diff TO blueIntensityDiff SET grayLevel TO MAX(MIN(128+diff, 255), 0) SET dst.rgb[row] TO grayLevel NEXT column NEXT row 
FOR row = 0 TO height-1 FOR column = 0 TO width-1 SET current TO src.rgb[row] SET upperLeft TO 0 IF row > 0 AND column > 0 SET upperLeft TO src.rgb[row-1][column-1] SET redIntensityDiff TO red (current)-red (upperLeft) SET greenIntensityDiff TO green (current)-green (upperLeft) SET blueIntensityDiff TO blue (current)-blue (upperLeft) SET diff TO redIntensityDiff IF ABS(greenIntensitydiff) > ABS(diff) SET diff TO greenIntensityDiff IF ABS(blueIntensityDiff) > ABS(diff) SET diff TO blueIntensityDiff SET grayLevel TO MAX(MIN(128+diff, 255), 0) SET dst.rgb[row] TO grayLevel NEXT column NEXT row
FOR row = 0 TO height-1 FOR column = 0 TO width-1 SET current TO src.rgb[row] SET upperLeft TO 0 IF row > 0 AND column > 0 SET upperLeft TO src.rgb[row-1][column-1] SET redIntensityDiff TO red (current)-red (upperLeft) SET greenIntensityDiff TO green (current)-green (upperLeft) SET blueIntensityDiff TO blue (current)-blue (upperLeft) SET diff TO redIntensityDiff IF ABS(greenIntensitydiff) > ABS(diff) SET diff TO greenIntensityDiff IF ABS(blueIntensityDiff) > ABS(diff) SET diff TO blueIntensityDiff SET grayLevel TO MAX(MIN(128+diff, 255), 0) SET dst.rgb[row] TO grayLevel NEXT column NEXT row

Листинг 1: Этот алгоритм выполняет рельеф изображения слева направо и сверху вниз.

Псевдокод тиснения в листинге 1 идентифицирует изображение для тиснения как src и идентифицирует изображение для сохранения результатов тиснения как dst . Предполагается, что эти изображения являются прямоугольными буферами пикселей RGB (прямоугольные буферы пикселей RGBA, где альфа-компонент игнорируется, также могут быть размещены).

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

После получения интенсивностей красного, зеленого и синего для каждого из текущих пикселей и их соответствующих верхних левых соседей псевдокод вычисляет разницу в каждой интенсивности. Интенсивности верхнего левого соседа вычитаются из интенсивностей текущего пикселя, потому что луч света движется в направлении от верхнего левого до нижнего правого.

Псевдокод идентифицирует наибольшую разницу (которая может быть отрицательной) между текущим пикселем и тремя различиями интенсивности его верхнего левого соседа. Это сделано для получения наилучшего «точеного» вида. Затем разница преобразуется в уровень серого между 0 и 255, и этот уровень серого сохраняется в целевом изображении в том же месте, что и текущий пиксель в исходном изображении.

Renderscript Primer

Renderscript объединяет язык на основе C99 с парой компиляторов низкоуровневой виртуальной машины (LLVM) и среду выполнения, которая обеспечивает доступ к графическим и вычислительным механизмам. Вы можете использовать Renderscript для написания части своего приложения в собственном коде, чтобы улучшить производительность рендеринга 3D-графики и / или обработки данных, сохраняя портативность своего приложения и избегая использования утомительного собственного интерфейса Java.


Примечание: Google представил Renderscript в Android 2.0, но не сделал общедоступной улучшенную версию до выпуска Android 3.0. Из-за последующих отзывов разработчиков, которые предпочитали работать напрямую с OpenGL, Google не одобрил часть графического движка Renderscript, начиная с Android 4.1.


Тиснение и другие операции по обработке изображений выполняются с помощью вычислительного механизма Renderscript. Хотя для выполнения этих двумерных задач можно использовать API-интерфейс 2D Canvas с ускорением на графическом процессоре Android (начиная с Android 3.0), Renderscript позволяет вам выйти за пределы виртуальной машины Dalvik, быстрее написать собственный код и запустить этот код через несколько потоков на доступных ядрах ЦП и ядра GPU и DSP в будущем).

При внедрении Renderscript в ваше приложение вам потребуется написать код Java и код Renderscript на основе C99, который называется скриптом . Код Java работает с типами, расположенными в пакете android.renderscript . Как минимум, вам понадобятся следующие типы доступа: Renderscript (для получения контекста) и Allocation (для выделения памяти от имени Renderscript):

  • Renderscript объявляет static RenderScript create(Context ctx) которому передается ссылка на ваше текущее действие и который возвращает ссылку на объект Renderscript . Этот объект служит контекстом, который должен быть передан другим API.
  • Allocation объявляет методы для переноса данных на стороне Java и привязки этих данных к среде выполнения Renderscript, чтобы скрипт мог получить доступ к данным. Также доступны методы для копирования результатов сценария обратно на сторону Java.

В процессе сборки Android интерфейсный компилятор LLVM компилирует скрипт и создает класс с ScriptC_ ScriptC_, суффикс которого берется из имени файла скрипта. Ваше приложение создает экземпляр этого класса и использует полученный экземпляр для инициализации и вызова сценария, а также для получения результатов сценария. Вам не нужно ждать результатов скрипта, потому что Renderscript позаботится об этом за вас.

Код C99 обычно хранится в одном файле, который организован следующим образом:

  • Пара директив #pragma должна быть указана и обычно указана в верхней части файла. Одна #pragma идентифицирует номер версии Renderscript (в настоящее время 1), которому соответствует файл. Другая #pragma идентифицирует пакет Java приложения, с которым связан этот файл.
  • Пара директив rs_allocation обычно следует и идентифицирует распределение ввода и вывода. Распределение входных данных относится к данным, которые должны быть обработаны (и которые упакованы одним экземпляром Allocation ), а распределение выходных данных определяет, где должны храниться результаты (и переносится другим экземпляром Allocation ).
  • Директива rs_script обычно следует и идентифицирует экземпляр с ScriptC_ ScriptC_ на стороне Java, чтобы Renderscript мог передавать результаты обратно в сценарий.
  • Могут быть объявлены дополнительные глобальные переменные, которые являются локальными для сценария или связаны с кодом Java. Например, вы можете объявить int width; и int height; и передайте этим переменным ширину и высоту изображения из вашего Java-кода (через вызовы методов с ScriptC_ set_ для объекта ScriptC_).
  • Обычно следует функция root() которая выполняется на каждом ядре ЦП для каждого элемента во входном распределении.
  • Обычно следует функция noargument init которая выполняет любую необходимую инициализацию и вызывает одну из перегруженных rsForEach() . Эта функция отвечает за определение количества ядер, создание потоков, работающих на этих ядрах, и многое другое. Каждый поток вызывает root() .

Вы можете разместить эти элементы в любом месте файла. Однако rs_allocation , rs_script и любые другие глобальные переменные должны быть объявлены перед тем, как на них будут ссылаться.

Тиснение встречается с Renderscript

Теперь, когда у вас есть базовое понимание алгоритма тиснения и Renderscript, давайте объединим их. В этом разделе сначала описываются стороны приложения на Java и C99 (отдельное действие и сценарий тиснения), который демонстрирует тиснение с помощью Renderscript, а затем показывается, как создать это приложение.

Изучение Java стороны приложения для тиснения

В листинге 2 представлен EmbossImage действий EmbossImage :

 package ca.tutortutor.embossimage; import android.app.Activity; import android.os.Bundle; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.renderscript.Allocation; import android.renderscript.RenderScript; import android.view.View; import android.widget.ImageView; public class EmbossImage extends Activity { boolean original = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final ImageView iv = new ImageView(this); iv.setImageResource(R.drawable.leopard); setContentView(iv); iv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (original) drawEmbossed(iv, R.drawable.leopard); else iv.setImageResource(R.drawable.leopard); original = !original; } }); } private void drawEmbossed(ImageView iv, int imID) { Bitmap bmIn = BitmapFactory.decodeResource(getResources(), imID); Bitmap bmOut = Bitmap.createBitmap(bmIn.getWidth(), bmIn.getHeight(), bmIn.getConfig()); RenderScript rs = RenderScript.create(this); Allocation allocIn; allocIn = Allocation.createFromBitmap(rs, bmIn, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); Allocation allocOut = Allocation.createTyped(rs, allocIn.getType()); ScriptC_emboss script = new ScriptC_emboss(rs, getResources(), R.raw.emboss); script.set_in(allocIn); script.set_out(allocOut); script.set_script(script); script.invoke_filter(); allocOut.copyTo(bmOut); iv.setImageBitmap(bmOut); } } 

Листинг 2: Активность приложения тиснения связывается со скриптом тиснения через script .

Переопределяющий void onCreate(Bundle bundle) отвечает за настройку пользовательского интерфейса действия, который состоит из одного экземпляра android.widget.ImageView в котором зарегистрирован прослушиватель щелчков. Когда пользователь щелкает этот вид, отображается выбитая или оригинальная версия этого изображения, как определяется текущим значением original boolean переменной.

Связь со сценарием происходит в void drawEmbossed(ImageView iv, int imID) . Этот метод сначала получает объект android.graphics.Bitmap содержащий содержимое изображения для тиснения, и создает объект Bitmap содержащий пустое изображение с такими же размерами и конфигурацией, что и изображение другого объекта.

Затем контекст Renderscript получается путем выполнения RenderScript.create(this) . Полученный объект Renderscript передается в качестве первого аргумента каждому из двух методов фабрики Allocation вместе с конструктором ScriptC_emboss . Этот контекст аналогичен аргументу android.content.Context передаваемому конструкторам виджетов, таким как ImageView(Context context) .

Виртуальная машина Dalvik и окружающая среда управляют управлением памятью. Объекты и массивы всегда размещаются на стороне Java и впоследствии становятся доступными для сценария, помещая их в объекты Allocation . Этот класс предоставляет несколько фабричных методов для возврата экземпляров Allocation , включая следующую пару:

  • static Allocation createFromBitmap(RenderScript rs, Bitmap b, Allocation.MipmapControl mips, int usage) создает распределение, которое обертывает экземпляр Bitmap с определенным поведением и использованием mipmap . bmIn передается b идентифицируя входной экземпляр Bitmap (содержащий изображение) как экземпляр, который нужно обернуть. Allocation.MipmapControl.MIPMAP_NONE передается в mips , указывая, что mipmaps не используются. Allocation.USAGE_SCRIPT передается в usage , указывая, что выделение связано и доступно сценарию.
  • Allocation createTyped(RenderScript rs, Type type) создает выделение для использования сценарием с размером, заданным type и без созданных по умолчанию мип-карт. allocIn.getType() передается type , указывая, что расположение выделяемой памяти определяется ранее созданным входным распределением.

После создания распределений ввода и вывода класс ScriptC_emboss (созданный LLVM в процессе сборки приложения) ScriptC_emboss(rs, getResources(), R.raw.emboss) путем вызова ScriptC_emboss(rs, getResources(), R.raw.emboss) . Наряду с ранее созданным контекстом Renderscript, следующие аргументы передаются в конструктор:

  • ссылка на объект android.content.res.Resources (для доступа к ресурсам приложения). Эта ссылка возвращается путем вызова метода Context для Resources getResources() .
  • R.raw.emboss (идентификатор ресурса скрипта тиснения). Во время сборки интерфейсный компилятор LLVM компилирует скрипт в файл (с расширением .bc ), содержащий переносимый битовый код, который хранится в APK в иерархии res/raw . Во время выполнения внутренний компилятор LLVM извлекает этот файл и компилирует его в специфичный для устройства код (если файл не был скомпилирован и кэширован на устройстве).

Внешний компилятор LLVM генерирует метод set_ -prefixed для каждой static переменной, которую он находит в скрипте. Поскольку сценарий тиснения содержит две директивы rs_allocation (именованные in и out ) и директиву rs_script (именованные script ), set_in() , set_out() и set_script() создаются и используются для передачи allocIn , allocOut и script в скрипт.

Сценарий выполняется путем вызова метода invoke_ -prefixed для экземпляра класса ScriptC_ -prefixed. Суффикс для этого метода взят из имени функции инициализации noargument (с возвращаемым типом void ), которая появляется в скрипте. В данном случае это имя является filter .

Поскольку Renderscript обрабатывает проблемы с allocOut.copyTo(bmOut); , allocOut.copyTo(bmOut); может быть выполнен сразу после вызова, чтобы скопировать результаты сценария в выходное растровое изображение, а полученное изображение впоследствии отобразится с помощью iv.setImageBitmap(bmOut); ,

Изучение стороны C99 приложения для тиснения

В листинге 3 представлен сценарий тиснения.

 #pragma version(1) #pragma rs java_package_name(ca.tutortutor.embossimage) rs_allocation out; rs_allocation in; rs_script script; void root(const uchar4* v_in, uchar4* v_out, const void* usrData, uint32_t x, uint32_t y) { float4 current = rsUnpackColor8888(*v_in); float4 upperLeft = { 0, 0, 0, 0 }; if (x > 0 && y > 0) upperLeft = rsUnpackColor8888(*(uchar*) rsGetElementAt(in, x-1, y-1)); float rDiff = current.r-upperLeft.r; float gDiff = current.g-upperLeft.g; float bDiff = current.b-upperLeft.b; float diff = rDiff; if (fabs(gDiff) > fabs(diff)) diff = gDiff; if (fabs(bDiff) > fabs(diff)) diff = bDiff; float grayLevel = fmax(fmin(0.5f+diff, 1.0f), 0); current.r = grayLevel; current.g = grayLevel; current.b = grayLevel; *v_out = rsPackColorTo8888(current.r, current.g, current.b, current.a); } void filter() { rsDebug("RS_VERSION = ", RS_VERSION); #if !defined(RS_VERSION) || (RS_VERSION < 14) rsForEach(script, in, out, 0); #else rsForEach(script, in, out); #endif } 

Листинг 3: Функция filter() является точкой входа скрипта тиснения.

Скрипт начинается с пары #pragma которые идентифицируют 1 как версию Renderscript, на которую нацелен скрипт, и ca.tutortutor.embossimage как пакет APK. rs_script следуют пара глобальных переменных rs_script глобальных rs_script , обеспечивающих доступ к их Java-аналогам (переданным в сценарий с помощью ранее упомянутых вызовов метода set_ -prefixed).

Впоследствии объявленная void root(const uchar4* v_in, uchar4* v_out, const void* usrData, uint32_t x, uint32_t y) вызывается для каждого пикселя (известного как элемент в этой функции) и для каждого доступного ядра. (Каждый элемент обрабатывается независимо от других.) Он объявляет следующие параметры:

  • v_in содержит адрес текущего элемента размещения ввода, который обрабатывается.
  • v_out содержит адрес эквивалентного элемента размещения вывода.
  • usrData содержит адрес дополнительных данных, которые можно передать этой функции. Никаких дополнительных данных для тиснения не требуется.
  • x и y идентифицируют нулевое местоположение элемента, переданного в v_in . Для одномерного размещения аргумент y не передается. Для растрового изображения аргументы передаются обоим параметрам.

Функция root() реализует алгоритм тиснения в листинге 1 для текущего элемента ввода, который представляет собой 32-разрядное значение, обозначающее красный, зеленый, синий и альфа-компоненты элемента. Этот элемент распаковывается в четырехэлементный вектор значений с плавающей точкой (каждое от 0,0 до 1,0) с помощью функции Renderscript float4 rsUnpackColor8888(uchar4 c) .

Затем вызывается функция Renderscript const void* rsGetElementAt(rs_allocation, uint32_t x, uint32_t y) для возврата элемента в местоположение, переданное в x и y . В частности, получается элемент к северо-западу от текущего элемента, который затем распаковывается в четырехэлементный вектор значений с плавающей точкой.

Renderscript предоставляет функции fabs fabs() , fmax() и fmin() , которые эквивалентны их аналогам алгоритма из листинга 1. Эти функции вызываются в последующей логике. В конечном итоге, функция Renderscript uchar4 rsPackColorTo8888(float r, float g, float b, float a) вызывается для упаковки рельефных компонентов в 32-битное значение, назначенное *v_out .


Примечание. Полный список функций Renderscript можно найти в онлайн-документации или файлах .rsh в вашей установке Android SDK.


Скрипт завершается функцией void filter() . Имя является произвольным и соответствует суффиксу, прикрепленному к ScriptC_ invoke_ -prefixed класса invoke_ -prefixed. Эта функция выполняет любую необходимую инициализацию и выполняет функцию root() на нескольких ядрах, вызывая одну из перегруженных rsForEach() .

Вместо инициализации эта функция вызывает одну из перегруженных rsDebug() , которая полезна для вывода информации в журнал устройства, доступ к которой осуществляется с помощью вызова adb logcat из командной строки или из Eclipse. В этом случае rsDebug() используется для вывода значения константы RS_VERSION .

filter() использует конструкцию #if#else#endif для выбора подходящего rsForEach() функции rsForEach() для компиляции в зависимости от того, компилируется ли скрипт в контексте Android SDK или Eclipse. RS_VERSION может содержать разные значения в каждом из этих контекстов компиляции, и это значение определяет, какие перегруженные функции rsForEach() разрешено вызывать, следующим образом:

 00134 #if !defined(RS_VERSION) || (RS_VERSION < 14) 00135 extern void __attribute__((overloadable)) 00136 rsForEach(rs_script script, rs_allocation input, 00137 rs_allocation output, const void * usrData, 00138 const rs_script_call_t *sc); 00142 extern void __attribute__((overloadable)) 00143 rsForEach(rs_script script, rs_allocation input, 00144 rs_allocation output, const void * usrData); 00145 #else 00146 00165 extern void __attribute__((overloadable)) 00166 rsForEach(rs_script script, rs_allocation input, rs_allocation output, 00167 const void * usrData, size_t usrDataLen, const rs_script_call_t *); 00171 extern void __attribute__((overloadable)) 00172 rsForEach(rs_script script, rs_allocation input, rs_allocation output, 00173 const void * usrData, size_t usrDataLen); 00177 extern void __attribute__((overloadable)) 00178 rsForEach(rs_script script, rs_allocation input, rs_allocation output); 00179 #endif 

Этот фрагмент кода является выдержкой из заголовочного файла Renderscript rs_core.rsh при просмотре в Интернете . Функция filter() вызывает соответствующую rsForEach() с минимальным количеством аргументов на основе текущего значения RS_VERSION , как показано в этом отрывке.

Сборка и запуск EmbossImage

Давайте EmbossImage и запустим EmbossImage . Для краткости в этом разделе показано только, как создать и установить APK этого приложения в среде командной строки. Хотя он также не показывает эквивалент Eclipse, нетрудно экстраполировать инструкции для среды Eclipse.

Это приложение было разработано в следующем контексте:

  • Windows 7 является платформой разработки. Измените обратную косую черту на прямую и удалите обозначение диска C: для среды Linux.
  • Каталог сборки проекта — C:prjdev . Замените вхождение C:prjdev на ваш предпочтительный эквивалент.
  • Android SDK Revision 20 был установлен, и был создан AVD на основе Android 4.1, связанный с целевым идентификатором 1. Выполните android list targets чтобы определить ваш эквивалентный идентификатор цели.

Выполните следующие шаги, чтобы создать и построить проект EmbossImage :

  1. Создайте проект EmbossImage , выполнив android create project -t 1 -p C:prjdevEmbossImage -a EmbossImage -k ca.tutortutor.embossimage .
  2. Замените скелетное содержимое EmbossImagesrccatutortutorembossimageEmbossImage.java на листинг 2.
  3. emboss.rs файл emboss.rs с содержимым листинга 3 в каталог src .
  4. Создайте drawable подкаталог EmbossImageres . Скопируйте файл leopard.jpg который включен в архив кода этой статьи, в этот каталог.
  5. С EmbossImage в качестве текущего каталога, выполните ant debug для сборки приложения.

Последний шаг может завершиться ошибкой со следующим предупреждающим сообщением (разбито на несколько строк для удобства чтения):

 WARNING: RenderScript include directory 'C:prjdevGrayScale${android.renderscript.include.path}' does not exist! [llvm-rs-cc.exe] :2:10: fatal: 'rs_core.rsh' file not found 

К счастью, проблема 34569 в базе данных проблем Google для Android предоставляет решение проблемы. Просто добавьте следующее свойство (разбито на несколько строк для удобства чтения) в файл build.xml который находится в подкаталоге toolsant вашего домашнего каталога Android SDK, чтобы решить эту проблему:

 <property name="android.renderscript.include.path" location="${android.platform.tools.dir}/renderscript/include: ${android.platform.tools.dir}/renderscript/clang-include"/> в <property name="android.renderscript.include.path" location="${android.platform.tools.dir}/renderscript/include: ${android.platform.tools.dir}/renderscript/clang-include"/> 

В частности, поместите этот элемент <property> после следующего элемента <path> :

 <!-- Renderscript include Path --> <path id="android.renderscript.include.path"> <pathelement location="${android.platform.tools.dir}/renderscript/include" /> <pathelement location="${android.platform.tools.dir}/renderscript/clang-include" /> </path> 

Rexecute ant debug и сборка должна быть успешной.

В случае успеха вы должны обнаружить файл EmbossImage-debug.apk в каталоге EmbossImagebin . Вы можете установить этот APK на свое эмулированное или реальное устройство, выполнив adb install binEmbossImage-debug.apk .

Если установка прошла успешно, перейдите на экран запуска приложений и найдите общий значок для EmbossImage APK. Нажмите на этот значок, и приложение должно запуститься. На рисунке 2 показано, как должен выглядеть начальный экран в эмуляторе Android с Android 4.1 AVD.

Рисунок 2: На занятии отображается леопард без тиснения при запуске.

Нажмите на изображение леопарда ( любезно предоставлено Vera Kratochvil на PublicDomainPictures.net ), и вы должны увидеть тисненый эквивалент, показанный на рисунке 3.

Рисунок 3: Щелчок по действию вызывает появление рельефного леопарда.

Вывод

Тиснение и другие операции обработки изображений довольно легко записать в Renderscript. В качестве упражнения улучшите сценарий тиснения, чтобы поддержать источник света, идущий не в северо-западном направлении (например, в северо-восточном направлении). Этот метод может быть неоценимым для использования цвета, контрастности и присущей «высоте» пикселей для создания интуитивно понятных интерфейсов и обеспечения возможности расширенного управления изображениями.