eoe 移动开发者论坛

 找回密码
 加入eoe

QQ登录

只需一步,快速开始

查看: 69453|回复: 6
收起左侧

通过编程减轻 Android* 应用的图像处理负载

[复制链接]

签到天数: 16 天

连续签到: 1 天

[LV.4]偶尔看看III

52

主题

99

帖子

1063

e币
发表于 2016-1-28 16:59:51 | 显示全部楼层 |阅读模式

没有eoe的账号,级别还太低,出门如何吹牛逼?

您需要 登录 才可以下载或查看,没有帐号?加入eoe

x
                                                                                      通过编程减轻 Android* 应用的图像处理负载

1.      简介

本文将逐步介绍可使用 OpenCL™ 和 RenderScript 编程语言卸载图像处理的 Android 应用示例。 这些编程语言经过设计,可利用高度并行化的图形硬件(着色器)计算大量数据集和高度重复的任务。 尽管大家也可以使用其他语言卸载 Android 应用中的图像处理,但本文仅介绍面向开发应用基础设施的 OpenCL 和 RenderScript 示例代码,以及图像处理算法代码。 我们还将介绍 OpenCL API 包装程序类,它可帮助编程并执行面向图像处理算法的 OpenCL。 OpenCL API 包装程序类源代码无需任何许可证,可免费使用。

大家应该对 OpenCL、RenderScript 和 Android 编程概念非常熟悉,因此本文仅介绍有关卸载图像处理或媒体生成计算的指令。 大家还需要安装、支持并配置一台 Android 设备,以运行 OpenCL(请参考面向 OpenCL 的英特尔® SDK 以了解 Android设备安装)。

注: 尽管可以使用其他语言和技术卸载图像处理或媒体生成,但此处仅为了突出代码差异。 后续文章将着重介绍在 GPU 执行 OpenCL 和 RenderScript 时的性能差异。

1.1    应用 UI 设计

在示例应用中,UI 上包含三个单选按钮,以便用户在 RenderScript、OpenCL 或本地代码之间快速切换应用执行。 另一组菜单设置可支持用户选择在 CPU 或 GPU 上运行 OpenCL。 菜单还为用户提供待运行的已实施效应列表,以供用户选择他们希望运行的效应。 设备选择仅适用于 OpenCL(不适用 RenderScript 或本地代码)。 英特尔® x86 平台包含基于 CPU 和 GPU 的 OpenCL 运行时支持。

以下是主 UI 截屏,显示了 OpenCL 处理的等离子效应。 示例应用 UI 显示了运行 OpenCL、RenderScript 或本机代码时的性能结果。

The plasma effect being processed by OpenCL

性能指标包括帧速率 (fps)、帧渲染,和效应计算用时, 以下截屏均已标出。

The performance metrics highlighted

请注意,截屏中显示的性能数据均为样本指标;实际性能指标可能因设备不同而有所差异。

1.1    API 和 SDK

除 ADT 外(同样包含 Android SDK 的 Android 开发工具),用于为示例应用编程,且基于 Android的主要 API 还包括面向 Android应用的 RenderScript 和 Intel® SDK for OpenCL。

英特尔 OpenCL™ SDK 基于并遵循面向交叉平台编程的开放、免版费标准 OpenCL™ 规范。 如需了解更多详情,请访问 Khronos 网站,参阅 OpenCL™ 标准。

首次推出的 RenderScript 为 2.2 ADT (API Level 8),是支持在 Android 上运行计算密集型任务的框架。 RenderScript 主要面向数据并行计算,尽管串行计算工作负载可也从中获益。 请访问 Android 开发人员站点,了解更多信息。

Google 开源资源库提供的最新版 ADT 包括相应的软件包,这些软件包需要导入以使用 RenderScript、JNI (Java* Native Interface) 和运行时 API。 如欲了解 OpenCL 设置、配置和运行时信息,请参阅本篇面向 Android 操作系统的 OpenCL 开发文章。 如欲了解其他编程详情,请查看 RenderScript 或 OpenCL。

1.3    基础设施代码

基础设施代码包含 “主” 活动和帮助函数。 本部分将着重介绍用于设置 UI、选择运行哪种效应和语言技术,且针对 OpenCL 选择使用哪台计算设备的帮助函数和代码。

尽管已实施部分帮助函数来集成用户选择命令,但此处仅着重介绍以下两种函数:

backgroundThread() 帮助函数可启动线程,以定期调用步骤流程函数,从而处理图像效应。 该函数使用的代码和功能可重复用于 RenderScript 入门文章中发布的其他示例应用,大家可以访问此处 (PDF),了解更多详情。

processStep() 函数由 backgroundThread() 调用,可处理并运行图像效应。 该函数依赖单选按钮回调函数来确定使用哪种语言。 processStep() 函数可调用相应的方法来使用 OpenCL、RenderScript 或明确的本地 C/C++ 代码处理图像效应。 由于该代码在后台线程上运行,因此用户只需通过简单地点击或触摸单选按钮,就可选择语言,即使正在处理效应也可如此。 应用可动态切换,以执行相应的步进渲染函数,从而达到既定的图像效应。

// The processStep() method runs in a separate (background) thread.
private void processStep() {
    try { switch (this.type.getCheckedRadioButtonId()) {
          case R.id.type_renderN:
              oclFlag = 0; // OpenCL is OFF
              stepRenderNative();
              break;
          case R.id.type_renderOCL:
              oclFlag = 1; // OpenCL is ON
              stepRenderOpenCL();
              break;
          case R.id.type_renderRS:
              oclFlag = 0; // OpenCL is OFF
              stepRenderScript();
              break;
          default:
              return;
          }
   } catch (RuntimeException ex) {
          // Handle exception as appropriate and log error
          Log.wtf("Android Image Processing", "render failed", ex);
     }
}
1.4     本地函数的 Java 定义

示例应用实施 NativeLib 类,主要定义通过 JNI 调用本地功能以处理既定效应的函数。 例如,示例应用可实施三种效应:等离子效应、黑白效应和单色效应。 同样,这个类还可定义 renderPlasma(…)、renderSepia(…) 和 renderMonoChrome(…) 函数。 这些 Java 函数可通过 JNI 作为切入点运行本地或 OpenCL 功能。

JNI 函数可执行 C/C++ 代码,或设置和执行 OpenCL 程序,从而实施图像效应。 该类使用 Android 位图和 AssetManager 软件包。 BitMap 对象可用于传递和返回正在处理的图像或媒体的数据。 应用依赖 AssetManager 对象访问定义 OpenCL 内核的 OpenCL 文件(例如 sepia.cl)。

以下是实际 NativeLib Java 类定义。 其中的 //TODO 备注表示应用可以通过扩展实施其他图像效应。

package com.example.imageprocessingoffload;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
public class NativeLib
{
   // Implemented in libimageeffects.so
   public static native void renderPlasma(Bitmap bitmapIn, int renderocl, long time_ms, String    eName, int devtype, AssetManager mgr);
   public static native void renderMonoChrome(Bitmap bitmapIn, Bitmap bitmapOut, int renderocl, long time_ms, String eName, int simXtouch, int simYtouch, int radHi, int radLo, int devtype, AssetManager mgr);
   public static native void renderSepia(Bitmap bitmapIn, Bitmap bitmapOut, int renderocl, long time_ms, String eName, int simXtouch, int simYtouch, int radHi, int radLo, int devtype, AssetManager mgr);
   //TODO public static native <return type> render<Effectname>(…);
   //load actual native library
   static {
      System.loadLibrary("imageeffectsoffloading");
   }
}
请注意,Android AssetManager 和 BitMap 对象可视作本地代码的图像输入和图像结果。 本地代码可使用 AssetManager 对象访问定义 OpenCL 内核的 CL 文件。 BitMap 对象可用于制作像素数据,以供本地代码计算并生成图像结果。

UI 参数 deviceType 可用于规定 OpenCL 是在 CPU 上还是在 GPU 上执行。 Android 系统必须经过配置,且能够在两种设备上运行 OpenCL。 现代英特尔® 凌动™ 和英特尔® 酷睿™ 处理器可在 CPU 和集成图形处理器或 GPU 上运行 OpenCL。

eName 参数可用于指出待编译和运行的 OpenCL 内核。 尽管示例应用实施每种图像效应一个 JNI 函数,但这似乎没有必要。 不过,我们可以在单个 CL 文件和/或 JNI 函数中定义多种相关的图像效应。 这样,eName 可用来编译并加载相应的 CL 程序和/或内核。

renderocl 可用作标记,表明运行 OpenCL 或本地 C/C++ 代码。 该标记只有在用户选择 OpenCL 单选按钮的情况下设置,否则,它仍然保持未设置状态。

time_ms 参数用于传递时间戳(毫秒),以计算性能指标。 在等离子效应下,时间戳用于计算等离子效应的步进。

其他是特定于图像效应算法的参数,可从图像中心开始以放射状的形式渲染效应。 例如,simXtouch、simYtouch、radLo 和 radHi 参数,以及宽度和高度,可用于计算和展示单色效应和黑白效应的放射进展。

1.5    用于运行本地代码(C 或 OpenCL)的定义和资源

本部分将介绍针对示例应用中实施的各种效应的 JNI 本地函数定义。 如前所述,每种效应一个函数可以有效地简化解释,并展示使用 OpenCL 卸载图像效应处理时的功能元素。 我们还引用了 C 或串行代码,以及代码片段,希望未来版本的示例应用能够用于评估这些语言技术的性能。

JNI 函数和 Java 本地函数之间存在 1:1 的关系。 因此,我们必须准确地声明和定义 JNI 同类函数。 Java SDK 包含 javah 工具,可准确地生成 JNI 函数声明。 我们强烈建议使用该工具,以避免代码准确编译,却在运行时过程中产生错误的情况。

如下所示为示例应用中用于 “图像效应卸载” 的 JNI 函数。 JNI 函数签名由 javah 工具实用程序生成。

// Defines new JNI entry function signatures
#ifndef _Included_com_example_imageprocessingoffload_NativeLib
#define _Included_com_example_imageprocessingoffload_NativeLib
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     com_example_imageprocessingoffload_NativeLib
* Method:    renderPlasma
* Signature: (Landroid/graphics/Bitmap;IJLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_com_example_imageprocessingoffload_NativeLib_renderPlasma
  (JNIEnv *, jclass, jobject, jint, jlong, jstring, jint, jobject);

/*
* Class:     com_example_imageprocessingoffload_NativeLib
* Method:    renderMonoChrome
* Signature: (Landroid/graphics/Bitmap;Landroid/graphics/Bitmap;IJLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_com_example_imageprocessingoffload_NativeLib_renderMonoChrome
  (JNIEnv *, jclass, jobject, jobject, jint, jlong, jstring, jint, jint, jint, jint, jint, jobject);

/*
* Class:     com_example_imageprocessingoffload_NativeLib
* Method:    renderSepia
* Signature: (Landroid/graphics/Bitmap;Landroid/graphics/Bitmap;IJLjava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_com_example_imageprocessingoffload_NativeLib_renderSepia
  (JNIEnv *, jclass, jobject, jobject, jint, jlong, jstring, jint, jint, jint, jint, jint, jobject);
}
#endif
javah 工具可生成正确的 JNI 函数签名;但是,定义 Java 本地函数的类必须已经在 Android 应用项目中编译。 如果要生成标头文件,可使用 javah 命令,如下所示:

     {javahLocation} -o {outputFile} -classpath {classpath} {importName}

针对示例应用生成的函数签名如下所示:

      javah -o junk.h -classpath bin\classes com.example.imageprocessingoffloading.NativeLib

然后将 junk.h 形式的 JNI 函数签名添加至 imageeffects.cpp,这样就可以设置并运行 OpenCL 或 C 代码。 接下来,我们来分配资源,以运行 OpenCL 或本地代码,从而实现已实施的效应:等离子效应、单色效应和黑白效应。

     1.5.1    等离子效应

Java_com_example_imageprocessingoffload_NativeLib_renderPlasma(…) 函数是执行 OpenCL 或本地代码以达到等离子效应的入口代码。 函数 startPlasmaOpenCL(…)、runPlasmaOpenCL(…) 和 runPlasmaNative(…) 在 imageeffects.cpp 代码的外部,在独立的 plasmaEffect.cpp 源文件中定义。 如需引用,可在 OpenCL 包装程序类代码下载中查找 plasmaEffect.cpp 源文件。

renderPlasma(…) 入口函数使用 OpenCL 包装程序类查询可支持 OpenCL 的 Android 设备系统。 它调用包装程序类函数 ::initOpenCL(…) 对 OpenCL 环境进行初始化。 设备类型将 CPU 或 GPU 视作可创建 OpenCL 环境的设备。 Android 资产管理器使用 ceName 参数识别并加载 CL 文件,以便编译内核代码。

如果成功设置 OpenCL 环境,renderPlasma(…) 入口函数将调用 startPlasmaOpenCL() 函数来分配 OpenCL 资源并执行等离子 OpenCL 内核。 请注意,gOCL 是一个全局变量,包含 OpenCL 包装程序类的对象实例。 gOCL 变量对所有 JNI 入口函数都是可见的。 这样,所有已编程效应都可以对 OpenCL 环境进行初始化。

等离子效应不使用图像,屏幕上渲染的媒体由已编程算法生成。 bitmapIn 参数是 BitMap 对象,包含等离子效应生成的媒体。 startPlasma(…) 函数传递的像素参数可映射至位图纹理,并用于本地或 OpenCL 内核代码读写像素数据,以在屏幕上渲染纹理。 再次提醒,assetManager 对象可用于访问 CL 文件,该文件包含面向等离子效应的 OpenCL 内核。

JNIEXPORT void Java_com_example_imageprocessingoffload_NativeLib_renderPlasma(JNIEnv * env, jclass, jobject bitmapIn, jint renderocl, jlong time_ms, jstring ename, jint devtype, jobject assetManager) {

JNIEXPORT void Java_com_example_imageprocessingoffload_NativeLib_renderPlasma(JNIEnv * env, jclass, jobject bitmapIn, jint renderocl, jlong time_ms, jstring ename, jint devtype, jobject assetManager) {
… // code omitted to simplify       
   
    // code locks mem for BitMapIn and sets “pixels” pointer that is passed to OpenCL or Native functions.  
    ret = AndroidBitmap_lockPixels(env, bitmapIn, &pixels);

… // code omitted to simplify

  If OCL not initialized
     AAssetManager *amgr = AAssetManager_fromJava(env, assetManager);
     gOCL.initOpenCL(clDeviceType, ceName, amgr);
     startPlasmaOpenCL((cl_ushort *) pixels, infoIn.height, infoIn.width, (float) time_ms, ceName, cpinit);
else
     runPlasmaOpenCL(infoIn.width, infoIn.height, (float) time_ms, (cl_ushort *) pixels);
… // code omitted
}
startPlasmaOpenCL(…) 外部函数可生成并填充 Palette 和 Angles 缓冲区,该缓冲区中包含等离子效应所需的数据。 该函数依赖 OpenCL 命令队列、环境和内核运行等离子 OpenCL 内核,它们均定义为包装程序类的数据成员。

runPlasmaOpenCL(…) 函数可连续运行等离子 OpenCL 内核。 启动 OpenCL 内核后,该独立函数会得到充分利用,随后的内核执行只需一个新的时间戳值(作为输入)。 只需发送面向时间戳值的内核参数,就可进行下一次内核运行迭代,因此只需一个独立函数。

extern int startPlasmaOpenCL(cl_ushort* pixels, cl_int height, cl_int width, cl_float ts, const char* eName, int inittbl);
extern int runPlasmaOpenCL(int width, int height, cl_float ts, cl_ushort *pixels);
extern void runPlasmaNative( AndroidBitmapInfo*  info, void*  pixels, double  t, int inittbl );
runPlasmaNative(…) 函数包含用 C 代码编写而成的等离子算法逻辑。 inittbl 参数可用作布尔值,表示是否需要生成等离子效应所需的 Palette 和 Angles 数据。 plasmaEffect.cpp 源文件提供面向等离子效应的 OpenCL 内核代码。

#define FBITS                16
#define FONE                (1 << FBITS)
#define FFRAC(x)        ((x) & ((1 << FBITS)-1))
#define FIXED_FROM_FLOAT(x)  ((int)((x)*FONE))

/* Color palette used for rendering plasma */
#define  PBITS   8
#define  ABITS   9
#define  PSIZE   (1 << PBITS)
#define  ANGLE_2PI (1 << ABITS)
#define  ANGLE_MSK (ANGLE_2PI - 1)

#define  YT1_INCR  FIXED_FROM_FLOAT(1/100.0f)
#define  YT2_INCR  FIXED_FROM_FLOAT(1/163.0f)
#define  XT1_INCR  FIXED_FROM_FLOAT(1/173.0f)
#define  XT2_INCR  FIXED_FROM_FLOAT(1/242.0f)

#define  ANGLE_FROM_FIXED(x)        ((x) >> (FBITS - ABITS)) & ANGLE_MSK

ushort pfrom_fixed(int x, __global ushort *palette)
{
    if (x < 0) x = -x;
    if (x >= FONE) x = FONE-1;
    int  idx = FFRAC(x) >> (FBITS - PBITS);
    return palette[idx & (PSIZE-1)];
}

__kernel
void plasma(__global ushort *pixels, int height, int width, float t, __global ushort *palette, __global int *angleLut)
{
    int yt1 = FIXED_FROM_FLOAT(t/1230.0f);
    int yt2 = yt1;
    int xt10 = FIXED_FROM_FLOAT(t/3000.0f);
    int xt20 = xt10;

    int x = get_global_id(0);
    int y = get_global_id(1);
    int tid = x+y*width;

    yt1 += y*YT1_INCR;
    yt2 += y*YT2_INCR;

    int base = angleLut[ANGLE_FROM_FIXED(yt1)] + angleLut[ANGLE_FROM_FIXED(yt2)];
    int xt1 = xt10;
    int xt2 = xt20;

    xt1 += x*XT1_INCR;
    xt2 += x*XT2_INCR;

    int ii = base + angleLut[ANGLE_FROM_FIXED(xt1)] + angleLut[ANGLE_FROM_FIXED(xt2)];
    pixels[tid] = pfrom_fixed(ii/4, palette);
}
The RenderScript kernel code for the plasma effect:

#pragma version(1)
#pragma rs java_package_name(com.example.imageprocessingoffload)

rs_allocation *gPalette;
rs_allocation *gAngles;
rs_script gScript;
float ts;
int gx;
int gy;

static int32_t intFromFloat(float xfl) {
      return (int32_t)((xfl)*(1 << 16));
}
const float YT1_INCR = (1/100.0f);
const float YT2_INCR = (1/163.0f);
const float XT1_INCR = (1/173.0f);
const float XT2_INCR = (1/242.0f);

static uint16_t pfrom_fixed(int32_t dx) {
    unsigned short *palette = (unsigned short *)gPalette;
    uint16_t ret;
    if (dx < 0)  dx = -dx;
    if (dx >= (1 << 16))  dx = (1 << 16)-1;   

    int  idx = ((dx & ((1 << 16)-1)) >> 8);
    ret = palette[idx & ((1<<8)-1)];
    return ret;
}

uint16_t __attribute__((kernel)) root(uint16_t in, uint32_t x, uint32_t y) {
    unsigned int *angles = (unsigned int *)gAngles;
    uint32_t out = in;
    int yt1 = intFromFloat(ts/1230.0f);

    int yt2 = yt1;
    int xt10 = intFromFloat(ts/3000.0f);
    int xt20 = xt10;
   
    int y1 = y*intFromFloat(YT1_INCR);
    int y2 = y*intFromFloat(YT2_INCR);
    yt1 = yt1 + y1;
    yt2 = yt2 + y2;
   
    int a1 = (yt1 >> 7) & ((1<<9)-1);
    int a2 = (yt2 >> 7) & ((1<<9)-1);
    int base = angles[a1] + angles[a2];
   
    int xt1 = xt10;
    int xt2 = xt20;
    xt1 += x*intFromFloat(XT1_INCR);
    xt2 += x*intFromFloat(XT2_INCR);
       
    a1 = (xt1 >> (16-9)) & ((1<<9)-1);
    a2 = (xt2 >> (16-9)) & ((1<<9)-1);
    int ii = base + angles[a1] + angles[a2];
       
   out = pfrom_fixed(ii/4);
   return out;
}
void filter(rs_script gScript, rs_allocation alloc_in, rs_allocation alloc_out) {
    //rsDebug("Inputs TS, X, Y:", ts, gx, gy);
    rsForEach(gScript, alloc_in, alloc_out);
}
     1.5.2    单色效应

Java_com_example_imageprocessingoffload_NativeLib_renderMonochrome(…) 函数是执行 OpenCL 或本地代码以进行单色处理的入口代码。 函数 executeMonochromeOpenCL(…) 和 executeMonochromeNative(…) 在 imageeffects.cpp 代码的外部,在独立的源文件中定义。 由于存在等离子效应,该入口函数也使用 OpenCL 包装程序类查询可支持 OpenCL 的 Android 设备系统,并调用函数 ::initOpenCL(…) 对 OpenCL 环境进行初始化。

下列两行代码可简化外部(或为 NDK 编译器呈现)executeMonochromeOpenCL(…) 和 executeMonochromeNative(…) 函数的函数签名。 由于这些函数均在独立源文件中定义,因此这些代码行必不可少。

extern int executeMonochromeOpenCL(cl_uchar4 *srcImage, cl_uchar4 *dstImage, int radiHi, int radiLo, int xt, int yt, int nWidth, int nHeight);
extern int executeMonochromeNative(cl_uchar4 *srcImage, cl_uchar4 *dstImage, int radiHi, int radiLo, int xt, int yt, int nWidth, int nHeight);
与等离子效应不同,该效应使用输入和输出图像。 bitmapIn 和 bitmapOut 以 ARGB_8888 位图的形式分配,且都映射至 cl_uchar4 矢量的 CL 缓冲区。 请注意,pixelsIn 和 pixelsOut 需要进行转换,以便 OpenCL 将 BitMap 对象映射至 cl_uchar4 矢量的缓冲区。

JNIEXPORT void JNICALL Java_com_example_imageprocessingoffload_NativeLib_renderMonochrome(JNIEnv * env, jclass obj, jobject bitmapIn, jobject bitmapOut, jint renderocl, jlong time_ms, jstring ename, jint xto, jint yto, jint radHi, jint radLo, jint devtype, jobject assetManager)  {

  … // code omitted for simplification

   // code locks mem for BitMapIn and sets “pixelsIn” pointer that is passed to OpenCL or Native functions.  
   ret = AndroidBitmap_lockPixels(env, bitmapIn, &pixelsIn);

   // code locks mem for BitMapOut and sets “pixelsOut” pointer that is passed to OpenCL or Native functions.  
   ret = AndroidBitmap_lockPixels(env, bitmapOut, &pixelsOut);

… // code omitted for simplification
If OpenCL
   If OCL not initialized
     AAssetManager *amgr = AAssetManager_fromJava(env, assetManager);
     gOCL.initOpenCL(clDeviceType, ceName, amgr);
   else
     executeMonochromeOpenCL((cl_uchar4*) pixelsIn,(cl_uchar4*) pixelsOut, radiHi, radiLo, xt, yt, infoIn.width, infoIn.height);
    // end of OCL initialized
else
   executeMochromeNative((cl_uchar4*) pixelsIn,(cl_uchar4*) pixelsOut, radiHi, radiLo, xt, yt, infoIn.width, infoIn.height);
// End of OpenCL
… // code omitted
}
调用 executeMonochromeOpenCL(…) 时,该函数会以 cl_uchar4 缓冲区的形式转换并传递 pixelsIn 和 pixelsOut。 在适当的时候,该函数使用 OpenCL API 创建缓冲区和其他资源。 它可设置内核参数,并排列必要的命令来执行 OpenCL 内核。 pixelsIn 指向的图像输入缓冲区以 read_only 缓冲区的形式分配。 内核代码使用 pixelsIn 指示器获取传入的像素数据。 内核算法会使用像素数据将传入的图像转换成单色图像。 输出缓冲区是包含图像结果并由 pixelsOut 指向的 read_write 缓冲区。 如欲了解更多有关 OpenCL 的详细信息,请参考英特尔编程与优化指南。

executeMonochromeNative(…) 函数包含用 C 代码编程的单色算法。 该基础算法包含一个外层循环(面向 y 循环)和一个内层循环(面向 x 循环),可计算像素数据,其结果保存在 pixelsOut 指向的 dstImage 之中。 pixlesIn 指向的 srcImage 可用于取消对输入像素数据的引用,以便算法公式转化成单色像素。

面向单色效应的 OpenCL 内核代码:

constant uchar4 cWhite = {1.0f, 1.0f, 1.0f, 1.0f};
constant float3 channelWeights = {0.299f, 0.587f, 0.114f};
constant float saturationValue = 0.0f;

__kernel void mono (__global uchar4 *in, __global uchar4 *out, int4 intArgs, int width) {
    int x = get_global_id(0);
    int y = get_global_id(1);
   
    int xToApply = intArgs.x;
    int yToApply = intArgs.y;
    int radiusHi = intArgs.z;
    int radiusLo = intArgs.w;
    int tid = x + y * width;
    uchar4 c4 = in[tid];
    float4 f4 = convert_float4 (c4);
    int xRel = x - xToApply;
    int yRel = y - yToApply;
    int polar = xRel*xRel + yRel*yRel;
   
    if (polar > radiusHi || polar < radiusLo)   {
        if (polar < radiusLo)   {
            float4 outPixel = dot (f4.xyz, channelWeights);
            outPixel = mix ( outPixel, f4, saturationValue);
            outPixel.w = f4.w;
            out[tid] = convert_uchar4_sat_rte (outPixel);
        }
        else  {
            out[tid] = convert_uchar4_sat_rte (f4);
        }
    }
    else   {
         out[tid] = convert_uchar4_sat_rte (cWhite);
    }
}

The RenderScript kernel code for the monochrome effect:

#pragma version(1)
#pragma rs java_package_name(com.example.imageprocessingoffload)

int radiusHi;
int radiusLo;
int xToApply;
int yToApply;

const float4 gWhite = {1.f, 1.f, 1.f, 1.f};
const float3 channelWeights = {0.299f, 0.587f, 0.114f};
float saturationValue = 0.0f;

uchar4 __attribute__((kernel)) root(const uchar4 in, uint32_t x, uint32_t y)
{
    float4 f4 = rsUnpackColor8888(in);
    int xRel = x - xToApply;
    int yRel = y - yToApply;
    int polar = xRel*xRel + yRel*yRel;
    uchar4 out;
   
    if(polar > radiusHi || polar < radiusLo) {
        if(polar < radiusLo) {
            float3 outPixel = dot(f4.rgb, channelWeights);
            outPixel = mix( outPixel, f4.rgb, saturationValue);
            out = rsPackColorTo8888(outPixel);
        }
        else {
            out = rsPackColorTo8888(f4);
        }
    }
    else {
         out = rsPackColorTo8888(gWhite);
    }
    return out;
}
     1.5.3    黑白效应

面向黑白效应的代码与面向单色效应的代码非常类似。 唯一的不同点在于像素的算法计算。 它们使用不同的算法和常数来获得最终的像素数据。 以下是支持黑白效应运行 OpenCL 和本地 C 代码是函数声明。 大家可以看到,如果不是为了区别名称,该函数声明和定义大体相同。

extern int executeSepiaOpenCL(cl_uchar4 *srcImage, cl_uchar4 *dstImage, it int radiHi, int radiLo, int xt, int yt, int nWidth, int nHeight);

extern int executeSepiaNative(cl_uchar4 *srcImage, cl_uchar4 *dstImage, int radiHi, int radiLo, int xt, int yt, int nWidth, int nHeight);

JNIEXPORT jstring JNICALL Java_com_example_imageprocessingoffload_NativeLib_renderSepia(JNIEnv * env, jclass obj, jobject bitmapIn, jobject bitmapOut, jint renderocl, jlong time_ms, jstring ename, jint xto, jint yto, jint radHi, jint radLo, jint devtype, jobject assetManager) { … }

Java_com_example_imageprocessingoffload_NativeLib_renderSepia(…) 中的源代码片段与单色示例非常类似,因此已被省略。

调用 executeSepiaOpenCL(…) 时,该函数会以 cl_uchar4 缓冲区的形式转换并传递 pixelsIn 和 pixelsOut。 在适当的时候,该函数使用 OpenCL API 创建缓冲区和其他资源。 它可设置内核参数,并排列必要的命令来执行 OpenCL 内核。 pixelsIn 指向的图像输入缓冲区以 read_only 缓冲区的形式分配。 内核代码使用 pixelsIn 缓冲区指示器获取像素数据。 内核算法会使用像素数据将传入的图像转换成单色图像。 输出缓冲区是包含图像结果并由 pixelsOut 指向的 read_write 缓冲区。

executeSepiaNative(…) 函数包含用 C 代码编程的黑白算法。 该基础算法包含一个外层循环(面向 y 循环)和一个内层循环(面向 x 循环),可计算像素数据,其结果保存在 pixelsOut 指向的 dstImage 之中。 pixlesIn 指向的 srcImage 可用于取消对输入像素数据的引用,以便算法公式转化成单色像素。

面向黑白效应的 OpenCL 内核代码

constant uchar4 cWhite = {1, 1, 1, 1};
constant float3 sepiaRed = {0.393f, 0.769f, 0.189f};
constant float3 sepiaGreen = {0.349f, 0.686f, 0.168f};
constant float3 sepiaBlue = {0.272f, 0.534f, 0.131f};

__kernel void sepia(__global uchar4 *in, __global uchar4 *out, int4 intArgs, int2 wh)
{
    int x = get_global_id(0);
    int y = get_global_id(1);
    int width = wh.x;
    int height = wh.y;
   
    if(width <= x || height <= y) return;
   
    int xTouchApply = intArgs.x;
    int yTouchApply = intArgs.y;
    int radiusHi = intArgs.z;
    int radiusLo = intArgs.w;
    int tid = x + y * width;
       
    uchar4 c4 = in[tid];
    float4 f4 = convert_float4(c4);
    int xRel = x - xTouchApply;
    int yRel = y - yTouchApply;
    int polar = xRel*xRel + yRel*yRel;
   
    uchar4 pixOut;
      
    if(polar > radiusHi || polar < radiusLo)
    {
        if(polar < radiusLo)
        {
                float4 outPixel;
            float tmpR = dot(f4.xyz, sepiaRed);
            float tmpG = dot(f4.xyz, sepiaGreen);
            float tmpB = dot(f4.xyz, sepiaBlue);
            
            outPixel = (float4)(tmpR, tmpG, tmpB, f4.w);
            pixOut = convert_uchar4_sat_rte(outPixel);
        }
        else
        {
            pixOut= c4;
        }
    }
    else
    {
         pixOut = cWhite;
    }
    out[tid] = pixOut;
}

The RenderScript kernel code for the sepia effect:

#pragma version(1)
#pragma rs java_package_name(com.example.imageprocessingoffload)
#pragma rs_fp_relaxed

int radiusHi;
int radiusLo;
int xTouchApply;
int yTouchApply;

rs_script gScript;
const float4 gWhite = {1.f, 1.f, 1.f, 1.f};

const static float3 sepiaRed = {0.393f, 0.769f, 0.189f};
const static float3 sepiaGreen = {0.349f, 0.686, 0.168f};
const static float3 sepiaBlue = {0.272f, 0.534f, 0.131f};

uchar4 __attribute__((kernel)) sepia(uchar4 in, uint32_t x, uint32_t y)
{
    uchar4 result;
    float4 f4 = rsUnpackColor8888(in);
   
    int xRel = x - xTouchApply;
    int yRel = y - yTouchApply;
    int polar = xRel*xRel + yRel*yRel;
   
    if(polar > radiusHi || polar < radiusLo)
    {
            if(polar < radiusLo)
      {
                float3 out;
                              
                float tmpR = dot(f4.rgb, sepiaRed);
                float tmpG = dot(f4.rgb, sepiaGreen);
                float tmpB = dot(f4.rgb, sepiaBlue);
               
                out.r = tmpR;
                out.g = tmpG;
                out.b = tmpB;
                result = rsPackColorTo8888(out);
        }
        else
        {
            result = rsPackColorTo8888(f4);
        }
    }
    else  
    {
         result = rsPackColorTo8888(gWhite);
    }
    return result;
}
1.6    用于运行 RenderScript 的代码和资源

RenderScript 实施需具备哪些条件才能执行图像效应? 为简单起见,示例应用不定规则,也不做推荐,而使用在全局范围内定义的通用资源和变量。 Android 开发人员可使用不同的方法根据应用的复杂程度定义通用资源。

MainActivity.java 源文件中声明并定义了以下通用资源和全局变量。

private RenderScript rsContext;

rsContext 变量通用于所有脚本,可保存 RenderScript 环境。 该环境作为 RenderScript 框架的一部分进行设置。 如欲了解更多有关 inner-working 的信息,请参阅 RenderScript 框架。

private ScriptC_plasma plasmaScript;
private ScriptC_mono monoScript;

private ScriptC_sepia sepiaScript;plasmaScript、monoScript 和 sepiaScript 变量是类的示例,后者可包装对特定 RenderScript 内核的访问。 Eclipse* IDE 可通过 rs 文件自动生成 Java 类,例如,通过 plasma.rs 生成 ScriptC_plasma,通过 mono.rs 生成 ScriptC_mono,通过 sepia.rs 生成 ScriptC_sepia。 生成的特定 RenderScript 包装程序类可放在 gen 文件夹下的 Java 文件中。 例如,就 sepia.rs 文件来说,Java 类则位于 ScriptC_sepia.java 文件中。 如欲生成 Java 代码,rs 文件必须完整定义 RenderScript 内核代码,并确保语法正确以供编译。 对示例应用而言,所有 ScriptC_<*> 类都已导入至 MainActivity.java 代码。

private Allocation allocationIn;private Allocation allocationOut;
private Allocation allocationPalette;
private Allocation allocationAngles;
分配是供 RenderScript 内核运行的内存提取。 例如,allocationIn 和 allocationOut 包含输入和输出图像的纹理数据。 AllocationIn 是针对示例应用中脚本的输入,而 AllocationOut 是输出,该输出包含 RenderScript 内核或内核生成的图像数据。 Palette 和 Angles 分配用于向内核传递角度和查询表数据。 主活动代码中的数据在调用面向等离子效应的 RenderScript 之前生成。 等离子效应媒体的生成需要 Palette 和 Angles 数据。

胶合资源和已生成代码以运行 RenderScript 内核的代码在面向示例应用的 initRS(…) 帮助函数中定义。

protected void initRS() { … };
initRS()函数使用 RenderScript 对象的创建方法对 RenderScript 环境进行初始化。 如前所述,环境处理通用于所有渲染脚本,且保存在 rsContext 全局变量中。 RenderScript 环境是 RenderScript 对象进行实例化所必不可少的。 以下代码行可在示例应用 MainActivity 的范围内创建 RenderScript 环境,因此 “this” 通过 RenderScript.create(…) 方法调用传递。

rsContext = RenderScript.create(this);
RenderScript 环境创建后,分配执行内核代码所需的特定应用 RenderScript 对象。 以下源代码行显示了适时对 RenderScript 对象进行实例化的 initRS() 函数的逻辑。

if (effectName.equals("plasma")) {
plasmaScript = new ScriptC_plasma(rsContext);
} else if (effectName.equals("mono")) {
        monoScript = new ScriptC_mono(rsContext);
} else if (effectName.equals("sepia")) {
        sepiaScript = new ScriptC_sepia(rsContext);
} // add here to add additional effects to the application
stepRenderScript(…) 是一种帮助函数,可调用来运行 RenderScript,从而实现既定效应。 它使用 RenderScript 对象设置所需的参数,并调用 RenderScript 内核。 以下源代码是 stepRendeScript(…) 函数的一部分,展示了如何调用 RenderScript 内核以达到等离子和单色效应。

private void stepRenderScript(…) {

… // code omitted for simplification
if(effectName.equals("plasma")) {
        plasmaScript.bind_gPalette(allocationPalette);
        plasmaScript.bind_gAngles(allocationAngles);
        plasmaScript.set_gx(inX - stepCount);
        plasmaScript.set_gy(inY - stepCount);
        plasmaScript.set_ts(System.currentTimeMillis() - mStartTime);
        plasmaScript.set_gScript(plasmaScript);
        plasmaScript.invoke_filter(plasmaScript, allocationIn, allocationOut);
}
else if(effectName.equals("mono")) {
// Compute parameters "circle of effect" depending on number of elapsed steps.
        int radius = (stepApply == -1 ? -1 : 10*(stepCount - stepApply));
        int radiusHi = (radius + 2)*(radius + 2);
        int radiusLo = (radius - 2)*(radius - 2);
        // Setting parameters for the script.
        monoScript.set_radiusHi(radiusHi);
        monoScript.set_radiusLo(radiusLo);
        monoScript.set_xInput(xToApply);
        monoScript.set_yInput(yToApply);
        // Run the script.
        monoScript.forEach_root(allocationIn, allocationOut);
        if(stepCount > FX_COUNT)
        {
                stepCount = 0;
                stepApply = -1;
        }
}
else if(effectName.equals("sepia")) {
    … // code similar to mono effect
}
… // code omitted for simplification

};
gPalette、gAngles, gx、gy 和 gScript 都是等离子 RenderScript 内核定义的全局变量。 RenderScript 框架可生成函数以将所需的数据传递至内核运行时。 所有变量均在 plasma.rs 文件中声明。 变量定义为 rs_allocation generate bind_<var> function。 如欲达到等离子效应,将生成 bind_<gvars> 函数以将 Palette 和 Angles 数据绑定在 RenderScript 环境中。 就 gx、gy、ts 和 gScript 等标量参数而言,将生成一种 set_<var> 方法,发送面向该参数的特定数据。 标量参数用于发送等离子 RenderScript 内核所需的运行 x 值、y 值和时间戳。 invoke_filter(…) 函数以 RenderScript 定义为基础生成。 用户函数(比如等离子脚本中的 filter() 函数)的定义可用于为可配置和/或可重复使用的 RenderScript 内核代码编程。

如欲达到单色效应,可使用 radius 计算 radiusHi 和 radiusLo 参数。 这些参数,与 xInput 和 yInput 一起,都可用于计算和显示单色效应的放射进展。 请注意,对于单色脚本,无需调用用户函数,可直接调用 forEach_root()。 forEach_root(…) 是默认方法,由面向渲染脚本的框架生成。 请注意,radiusHi、radiusLo、xInput 和 yInput 均定义为内核代码的全局变量,且生成的 set_<var> 方法可向 RenderScript 内核传递所需的数据。

如需更多帮助,请参阅 RenderScript 源代码定义。



2.      OpenCL 包装程序类

包装程序类提供面向 OpenCL API 的函数,以编译和执行 OpenCL 内核。 它还可提供面向 API 的包装程序函数,以对 OpenCL 运行时进行初始化。 包装程序类旨在促进运行时环境的初始化和设置,以便于执行 OpenCL 内核。 以下是包装程序类中有关各方法的简介与用法。 请点击下载链接,获取 OpenCL 包装程序类的全部资源。

class openclWrapper {
private:
cl_device_id* mDeviceIds;        // Holds OpenCL device Ids (CPU, GPU, etc...)
        cl_kernel mKernel;                // Holds handle for kernel to run
        cl_command_queue mCmdQue;        // Holds command queue for CL device
        cl_context mContext;                // Holds OpenCL context
        cl_program mProgram;                // Holds OpenCL program handle

public:
        openclWrapper() {
                mDeviceIds = NULL;
                mKernel = NULL;
                mCmdQue = NULL;
                mContext = NULL;
                mProgram = NULL;
        };
        ~openclWrapper() { };
        cl_context getContext() { return mContext; };
        cl_kernel getKernel() { return mKernel; };
        cl_command_queue getCmdQue() { return mCmdQue; };

        int createContext(cl_device_type deviceType);
        bool LoadInlineSource(char* &sourceCode, const char* eName);
        bool LoadFileSource(char* &sourceCode, const char* eName, AAssetManager *mgr);
        int buildProgram(const char* eName, AAssetManager *mgr);
        int createCmdQueue();
        int createKernel(const char *kname);
        // overloaded function
        int initOpenCL(cl_device_type clDeviceType, const char* eName, AAssetManager *mgr=NULL);
};
::createContext(cl device) 函数 - 一种使用设备类型(CPU 或 GPU)验证 OpenCL 支持并从系统获取设备编号的帮助函数。 该函数使用设备编号创建 OpenCL 执行环境。 它作为 OpenCL 初始化步骤的一部分调用。 它返回 SUCCESS 并设置类环境处理(比如 mContext),或在平台或设备编号列举和/或环境创建失败的情况下返回 FAIL。
::createCmdQue() 函数 - 列举与 CL 环境相关的设备编号。 它依赖私有数据成员 mContext 创建命令队列。 它返回 SUCCESS 并设置命令队列处理(比如 mCmdQue),如果无法为 createContext(…) 函数之前列举的设备编号创建命令队列,它将返回 FAIL。
::buildProgram(effectName, AssetManager) 函数 - 一种重载函数,包含同样定义为效应名称的图像处理算法以及针对 Android JNI 资产管理器的指示器。 该资产管理器使用效应名称查找并读取包含内核源代码的 OpenCL 文件。 包装程序类还使用效应名称查找并加载 “inline” OpenCL 源代码。 如果声明在默认情况下将资产管理器指示器设置为 NULL,该函数将重载。 从本质上来说,只有在效应名称或效应和有效资产管理器指示器决定何时编译行内定义的 OpenCL 代码或从独立文件加载 OpenCL 代码的情况下才调用该函数。 这样有利于编程人员以内联字符串的形式或在独立 OpenCL 文件中定义和部署 OpenCL 程序。 资产管理器指示器的数值用于调用可通过字符串加载 OpenCL 程序的函数,或调用可使用资产管理器 API 将 OpenCL 源读取至缓冲区的函数。
buildProgram(…) 函数调用 OpenCL API clCreateProgramWithSource(…) 创建带有源的程序。 如果 OpenCL 源代码存在语法错误,带有源的程序创建会返回错误,且无法创建程序。 OpenCL 环境和源缓冲区以参数的形式传递。 如果 CL 程序编译成功,clCreateProgramWithSource(…) 将返回程序处理。
clBuildProgram(…) API 接受 clCreateProgramWithSource(…) 或 clCreateProgramWithBinary(…) API 创建的程序处理。 可调用 clBuidProgram(…) 来编译和链接将在 CL 设备上运行的程序可执行文件。 如果出现错误,可以使用 clGetProgramBuildInfo(…) 转储编译错误。 如欲查看示例,请参考包装程序类源代码。
::createKernel(…) 函数 - 获取效应名称,并使用程序对象创建内核。 如果内核创建成功,该函数将返回 SUCCESS。 有效内核处理保存在 mKernel 之中,随后将用于设置内核参数,并执行可实施图像处理算法的 OpenCL 内核。
::getContext()、 ::getCmdQue() 和 ::getKernel() 方法仅返回环境、命令队列和内核处理。 这些处理可用于 JNI 函数,以便它们排列运行 OpenCL 内核所需的命令。

签到天数: 209 天

连续签到: 1 天

[LV.7]常住居民III

1

主题

398

帖子

3165

e币
发表于 2016-2-2 11:04:16 | 显示全部楼层
代码有点乱啊,能整理一下就好了~

签到天数: 150 天

连续签到: 1 天

[LV.7]常住居民III

69

主题

1577

帖子

1万

e币
QQ认证社区认证会员
发表于 2016-2-24 11:12:10 | 显示全部楼层
但是现在 很多 设备都不支持 OpenCL啊  系统里 没有OpenCL 的 库   你还得找个 高通的 库跟头文件 放里   并且 4.4 的 和 5.0 的 还不一样

签到天数: 6 天

连续签到: 1 天

[LV.2]偶尔看看I

1

主题

33

帖子

8

e币
社区认证会员 QQ认证
发表于 2016-4-8 19:22:58 | 显示全部楼层
回复有E币 啦啦 哈哈哈
头像被屏蔽

该用户从未签到

0

主题

2

帖子

37

e币
发表于 2016-4-17 16:32:08 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽

签到天数: 2 天

连续签到: 1 天

[LV.1]初来乍到

21

主题

65

帖子

0

e币
发表于 2016-6-22 16:03:12 | 显示全部楼层
谢谢分享,很宝贵的经验啊啊啊啊

签到天数: 24 天

连续签到: 1 天

[LV.4]偶尔看看III

14

主题

701

帖子

1931

e币
发表于 2016-10-24 17:57:37 | 显示全部楼层
我能说我看不懂吗
*滑动验证:
您需要登录后才可以回帖 登录 | 加入eoe

本版积分规则

推荐阅读
赞助商们

QQ|联系我们|小黑屋|手机版|eoe 移动开发者论坛 ( 京ICP备11018032 京公网安11010802020210  

GMT+8, 2017-3-26 13:26 , Processed in 0.695341 second(s), 18 queries , Memcache On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表
关闭

扫一扫 关注eoe官方微信