其实有关 android 下实现图片模糊的文章有很多,大多都是使用 renderscript 内置的 ScriptIntrinsicBlur 来实现的,这篇文章中的例子也不例外,但如果仅仅是调用一下 api 的话就没必要去写了。所以接下来会介绍均值模糊以及高斯模糊的原理、什么是 renderscript 以及如何编写 renderscript。最终的例子是将图片高斯模糊处理后再调用自己编写的 rs 对其增加一层蒙版效果(这里会提到计算机是如何处理透明度以及颜色叠加的)。
具体实现
上面这张图片是用于图像算法测试的国际标准图像,使用这张图片主要有两个原因:
图像包含了各种细节、平滑区域、阴影和纹理,这些对测试各种图像处理算法很有用。
图像里是一个很迷人的女子。而图像处理领域里的人大多为男性,可以吸引更多的人。
然而这张图片其实出自 1972 年的 《花花公子》,所以上面给出的图片并不完整,下面我们写一个小 demo 来展示一下完整的图片。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = (ImageView) findViewById(R.id.image);
drag = (ImageView) findViewById(R.id.drag);
SeekBar progressBar = (SeekBar) findViewById(R.id.seek);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rina);
imageView.setImageBitmap(bitmap);
imageView.post(new Runnable() {
@Override
public void run() {
float scale = bitmap.getWidth() * 1f / imageView.getWidth();
mosaic = Bitmap.createBitmap(bitmap, (int) (drag.getX() * scale), (int) (drag.getY() * scale),
(int) (drag.getWidth() * scale), (int) (drag.getHeight() * scale));
drag.setImageBitmap(BlurHelper.mosaic(mosaic, currentRadius));
}
});
progressBar.setProgress(currentRadius * 5);
progressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (progress % 5 == 0) {
currentRadius = progress / 5;
drag.setImageBitmap(BlurHelper.mosaic(mosaic, currentRadius));
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
drag.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
xDown = event.getX();
yDown = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float targetX = event.getX() - xDown + v.getTranslationX();
float targetY = event.getY() - yDown + v.getTranslationY();
targetX = Math.min(Math.max(targetX, 0), imageView.getWidth() - drag.getWidth());
targetY = Math.min(Math.max(targetY, 0), imageView.getHeight() - drag.getHeight());
v.setTranslationX(targetX);
v.setTranslationY(targetY);
float scale = bitmap.getWidth() * 1f / imageView.getWidth();
b = Bitmap.createBitmap(bitmap, (int) (drag.getX() * scale), (int) (drag.getY() * scale),
(int) (drag.getWidth() * scale), (int) (drag.getHeight() * scale));
drag.setImageBitmap(BlurHelper.mosaic(mosaic, currentRadius));
break;
}
return true;
}
});
}
public static Bitmap mosaic(Bitmap bitmap, int radius) {
if (radius == 0) return bitmap;
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
final int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int x = j % radius;
int y = i % radius;
pixels[i * width + j] = pixels[(i - y) * width + j - x];
}
}
outBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return outBitmap;
}
因为原图有点少儿不宜,所以我这边手动给打了个码,在原图上盖了一层马赛克后的图片,每次拖动后都会重新计算。其实马赛克算法也是一种模糊算法,首先图片其实是由很多像素点组成的一个二维数组(或者矩阵)。上面的马赛克算法只是遍历了图片的每一个像素,然后在这个过程中对于给定的半径将所有的像素都设置成第一个像素的值。
我们由此抛砖引玉引出均值模糊(box blur),和马赛克算法差不多,他是每一个像素都取周围像素的平均值。算法也比较简单,如下:
public static Bitmap boxBlur(Bitmap bitmap, int radius) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final int[] pixels = new int[width * height];
final int[] outPixels = new int[width * height];
final Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
//遍历bitmap每一个像素
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
//取半径为radius的矩形区域,并处理边界情况
final int left = i - radius < 0 ? 0 : i - radius;
final int top = j - radius < 0 ? 0 : j - radius;
final int right = i + radius > width ? width : i + radius;
final int bottom = j + radius > height ? height : j + radius;
//矩形区域总像素
final int count = (right - left) * (bottom - top);
//分别求出矩形区域内rgb的总值
int r = 0, g = 0, b = 0;
for (int m = left; m < right; m++) {
for (int n = top; n < bottom; n++) {
final int pixel = pixels[n * width + m];
r += Color.red(pixel);
g += Color.green(pixel);
b += Color.blue(pixel);
}
}
//设置新的像素为矩形区域内像素的均值
outPixels[j * width + i] = Color.rgb(r / count, g / count, b / count);
}
}
outBitmap.setPixels(outPixels, 0, width, 0, 0, width, height);
bitmap.recycle();
return outBitmap;
}
上面这么写是为了看起来更清楚,他的时间复杂度为 O(n^2 * m^2)效率是极低的。anyway 进行均值模糊之后的效果如下图(图像大小 300 * 260,模糊半径 5 ):
可以看到模糊的效果并不是很平滑,仔细看可以看到一个个小格子。显然取平均的方式并不是特别好,对于图像而言我们可以认为越靠近中心点与其关系越密切,而离中心点越远的像素相关程度也就越低,采用加权平均的方式似乎更合理一些。如果你是理科生的话,不知道你是否还记得高中数学书上提到过的正态分布(高斯分布)。下图是正态分布的函数曲线:
距离中心点越近,值就越大,完全符合我们的需求,可以作为计算平均时的权值,使用正态分布曲线来进行模糊计算的方式就叫做高斯模糊(gaussian blur)。u = 0 时的二维高斯曲线的函数如下:
其中 sigma 决定了数据的离散程度,sigma 越大曲线越扁,反之依然。
下面我们用代码简单实现一下,首先生成一个高斯模糊的概率矩阵:
private static float[][] makeGaussianBlurKernel(int radius, float sigma) {
//根据公式先计算一下2 * sigma ^ 2 便于之后计算
final float sigmaSquare2 = sigma * sigma * 2;
//半径是指中心点距离边界的距离,所以如果半径为1,则需要一个3 * 3的矩阵
final int size = radius * 2 + 1;
final float[][] matrix = new float[size][size];
float sum = 0;
int row = 0;
for (int i = -radius; i <= radius; i++) {
int column = 0;
for (int j = -radius; j <= radius; j++) {
//根据公式计算出值
matrix[row][column] = (float) (1 / (Math.PI * sigmaSquare2)
* Math.exp(-(i * i + j * j) / sigmaSquare2));
sum += matrix[row][column];
column++;
}
row++;
//算出均值,使总概率为1
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
matrix[i][j] /= sum;
}
}
return matrix;
}
接下来就是根据上面算出的矩阵对图像进行加权平均了,在这里处理边界情况的时候偷了个懒,假设越界后的像素对于边界是镜面的。
public static Bitmap gaussianBlur(Bitmap bitmap, int radius) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final int[] pixels = new int[width * height];
final int[] outPixels = new int[width * height];
final Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
final float[][] blurMatrix = makeGaussianBlurKernel(radius, radius / 2.5f);
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
final int left = i - radius;
final int top = j - radius;
final int right = i + radius;
final int bottom = j + radius;
int row = 0;
float r = 0, g = 0, b = 0;
for (int n = top; n <= bottom; n++) {
int column = 0;
int y = n;
if (y >= height) y = height - 1 - (y - height);
if (y < 0) y = -y;
for (int m = left; m <= right; m++) {
int x = m;
if (x >= width) x = width - 1 - (x - width);
if (x < 0) x = -x;
final int pixel = pixels[y * width + x];
r = r + blurMatrix[row][column] * Color.red(pixel);
g = g + blurMatrix[row][column] * Color.green(pixel);
b = b + blurMatrix[row][column] * Color.blue(pixel);
column++;
}
row++;
}
outPixels[j * width + i] = Color.rgb((int) r, (int) g, (int) b);
}
}
outBitmap.setPixels(outPixels, 0, width, 0, 0, width, height);
bitmap.recycle();
return outBitmap;
}
效果如图(图像大小300 * 260,模糊半径 5,sigma = 2.5 ):
可以看到,模糊效果相较之前过度更加平滑,不过上面这四个 for 循环真的有点蛋疼,不算生成模糊矩阵的时间,处理这张 300 * 260 的图片总共花了 387ms,我们看看能不能进行一下优化。
首先我们可以把二维的高斯模糊拆成横向与纵向两个一维高斯模糊的组合,这样时间复杂度就下降到了 O(n ^ 2 * 2 * m)。u = 0 时,一维高斯曲线的函数如下:
根据公式生成一维矩阵:
private static float[] makeGaussianBlur1DKernel(int radius, float sigma) {
final float sigmaSquare2 = sigma * sigma * 2;
final int size = radius * 2 + 1;
final float[] matrix = new float[size];
float sum = 0;
int index = 0;
for (int i = -radius; i <= radius; i++) {
matrix[index] = (float) (1 / (Math.sqrt(2 * Math.PI) * sigma) * Math.exp(-(i * i) / sigmaSquare2));
sum += matrix[index];
index++;
for (int i = 0; i < size; i++) {
matrix[i] /= sum;
}
return matrix;
}
调整下之前的算法:
public static Bitmap gaussianBlur2(Bitmap bitmap, int radius) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final float[] blurMatrix = makeGaussianBlur1DKernel(radius, radius / 2f);
final Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
final int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
//横向高斯模糊
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
final int left = j - radius;
final int right = j + radius;
int index = 0;
float r = 0, g = 0, b = 0;
for (int m = left; m <= right; m++) {
int x = m;
if (x >= width) x = width - 1 - (x - width);
if (x < 0) x = -x;
final int pixel = pixels[i * width + x];
r = r + blurMatrix[index] * Color.red(pixel);
g = g + blurMatrix[index] * Color.green(pixel);
b = b + blurMatrix[index] * Color.blue(pixel);
index++;
}
pixels[i * width + j] = Color.rgb((int) r, (int) g, (int) b);
}
}
//纵向高斯模糊
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
final int top = j - radius;
final int bottom = j + radius;
int index = 0;
float r = 0, g = 0, b = 0;
for (int m = top; m <= bottom; m++) {
int y = m;
if (y >= height) y = height - 1 - (y - height);
if (y < 0) y = -y;
final int pixel = pixels[y * width + i];
r = r + blurMatrix[index] * Color.red(pixel);
g = g + blurMatrix[index] * Color.green(pixel);
b = b + blurMatrix[index] * Color.blue(pixel);
index++;
}
pixels[j * width + i] = Color.rgb((int) r, (int) g, (int) b);
}
}
outBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
bitmap.recycle();
return outBitmap;
}
运行一下,处理同一张图片的时间缩短到了 98ms。然而时间复杂度还可以降的更低,有一种作法是通过多次(通常是三次)均值模糊来模拟高斯模糊的效果。因为均值模糊的权值都为 1,所以可以记录一下移动过程中半径内像素的和,在 for 循环移动的过程中完成对所有像素点的计算,这样时间复杂度就降到了 O(n*n)。
public static Bitmap boxBlur(Bitmap bitmap, int radius) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
final Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), bitmap.getConfig());
final int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
//纵向模糊
for (int i = 0; i < width; i++) {
int r = 0, g = 0, b = 0;
int count = 0;
//处理边界情况先将模糊半径内像素导入并求和
for (int k = 0; k < radius; k++) {
final int pixel = pixels[k * width + i];
r += Color.red(pixel);
g += Color.green(pixel);
b += Color.blue(pixel);
count++;
}
int headIndex = 0;
//纵向逐个像素移动
for (int j = 0; j < height; j++) {
//预测尾部位置
int last = j + radius;
//超过宽度则从和中减去队列头部像素
if (last >= height) {
final int headPixel = pixels[headIndex * width + i];
r -= Color.red(headPixel);
g -= Color.green(headPixel);
b -= Color.blue(headPixel);
count--;
headIndex++;
} else if (count <= 2 * radius + 1) {
//队列长度不足2 * radius + 1时向队列尾部添加新的像素
final int pixel = pixels[j * width + i];
r += Color.red(pixel);
g += Color.green(pixel);
b += Color.blue(pixel);
count++;
} else {
//队列长度益处后加入新像素,移除头部像素
final int headPixel = pixels[headIndex * width + i];
final int pixel = pixels[j * width + i];
r = r - Color.red(headPixel) + Color.red(pixel);
g = g - Color.green(headPixel) + Color.green(pixel);
b = b - Color.blue(headPixel) + Color.blue(pixel);
headIndex++;
}
pixels[j * width + i] = Color.rgb(r / count, g / count, b / count);
}
}
//横向模糊
for (int i = 0; i < height; i++) {
int r = 0, g = 0, b = 0;
int count = 0;
for (int k = 0; k < radius; k++) {
final int pixel = pixels[i * width + k];
r += Color.red(pixel);
g += Color.green(pixel);
b += Color.blue(pixel);
count++;
}
int headIndex = 0;
for (int j = 0; j < width; j++) {
int last = j + radius;
if (last >= width) {
final int headPixel = pixels[i * width + headIndex];
r -= Color.red(headPixel);
g -= Color.green(headPixel);
b -= Color.blue(headPixel);
count--;
headIndex++;
} else if (count <= 2 * radius + 1) {
final int pixel = pixels[i * width + j];
r += Color.red(pixel);
g += Color.green(pixel);
b += Color.blue(pixel);
count++;
} else {
final int headPixel = pixels[i * width + headIndex];
final int pixel = pixels[i * width + j];
r = r - Color.red(headPixel) + Color.red(pixel);
g = g - Color.green(headPixel) + Color.green(pixel);
b = b - Color.blue(headPixel) + Color.blue(pixel);
headIndex++;
}
pixels[i * width + j] = Color.rgb(r / count, g / count, b / count);
}
}
outBitmap.setPixels(pixels, 0, width, 0, 0, width, height);
bitmap.recycle();
return outBitmap;
}
同一张图片,上面的代码花费的时间为 31ms,如果采用不同半径进行三次运算来模拟高斯模糊的话,花费的时间大约会在 100ms 左右,看来起跟上面第二种算法差别不大。我们把模糊半径设置为 20,上面的算法花费的时间为 26ms,而第二种算法的时间为 249ms。
准确的说这个算法的时间复杂度应该是 O(n * (n + m)),看起来已经快逼近极限了,那还有没有办法更快呢?聪明的你肯定会说,使用 JNI 把运算过程丢给执行效率更高的 C 语言,这几个 for 循环也完全可以并行计算。你说的很对,而且这一切都不用你自己去实现,android 为此已经提供了一套解决方案。下面就郑重有请今天的主角 RenderScript。
RenderScript 是 android 上的高性能计算密集型框架,在 native 层进行数据并行运算,并且可以充分利用多核CPU 以及 GPU 的运算能力。语言本身是基于 C99 标准的,会先用 LLVM 编译称字节码,然后会在设备运行时编译成相应的机器码,所以他是平台无关的。
android 提供了一些内置的 api,我们可以直接在 java 层 调用,如下:
名称 说明
ScriptIntrinsic3DLUT 把 RGB 转换成 RGBA
ScriptIntrinsicBLAS Basic Linear Algebra Subprograms 提供了一些基本的向量和矩阵运算
ScriptIntrinsicBlend 混合两张图片,类似 imageView 的 tint
ScriptIntrinsicBlur 对图片进行高斯模糊运算
ScriptIntrinsicColorMatrix 将图片乘上一个色彩矩阵,可以实现诸如灰度化等色彩偏移效果
ScriptIntrinsicConvolve3x3 3 * 3 卷积运算 (事实上,均值模糊和高斯模糊都是对图像矩阵进行了卷积)
ScriptIntrinsicConvolve5x5 5 * 5 卷积运算
ScriptIntrinsicHistogram 直方图过滤器
ScriptIntrinsicLUT Lookup table
ScriptIntrinsicResize 图像缩放
ScriptIntrinsicYuvToRGB YUV 转 RGB
下面我就用 ScriptIntrinsicBlur 对图像进行高斯模糊,代码非常的简单:
public static Bitmap blur(Context context, Bitmap image, float radius) {
RenderScript rs = RenderScript.create(context);
Bitmap outputBitmap = Bitmap.createBitmap(image.getHeight(), image.getWidth(), Bitmap.Config.ARGB_8888);
Allocation in = Allocation.createFromBitmap(rs, image);
Allocation out = Allocation.createFromBitmap(rs, outputBitmap);
ScriptIntrinsicBlur intrinsicBlur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
intrinsicBlur.setRadius(radius);
intrinsicBlur.setInput(in);
intrinsicBlur.forEach(out);
out.copyTo(outputBitmap);
image.recycle();
rs.destroy();
return outputBitmap;
}
同一张图片,模糊半径设为 20,rs 处理的时间仅为 4ms。
可以看到在上面的代码中把 Bitmap 分配给了 Allocation。Allocation 用于和 RenderScript 共享内存。此外,还有 Element 和 Type。
名称 说明
Element 描述了一个内存单元,可以表示一个 rs 中的基本单位如 float,或者是一个由一系列基本单位组成的结构体
Type 描述了一段内存结构,是一个抽象的概念,并不实际分配内存。由一个 Element 和一个或多个维度组成(通常是一数组的 Element)
Allocation 某一个 Type 所描述的类型实际所分配的内存
对 intrinsicBlur 设置完相应的参数后,调用 intrinsicBlur.forEach(out) 就能并行的将数据输出到 out 中了。
至此,关于图像模糊的部分就结束了,但 rs 的部分并没有结束,除了使用系统内置的 api 外,我们是可以编写自己的rs脚本的,并且在编译期间会自动生成相应的 java 类。
在 ps 中先填充满不透明的白色,然后再在上面新建一个图层并用 #999 填充满,然后把这个图层的透明度调至 40%。
0x99 = 153
0xff = 255
214 = 255 * 0.6 + 153 * 0.4
看来 c = c1 * (1 - a)+ c2 * a,最终 a = 255
那如果一张透明度为 0.6(为了直观下面全部换成小数展示)的图片,与一张透明度为 0.5 的图片进行叠加最后的透明度会是多少呢?
我们可以把这两张图片想象成现实生活的两块玻璃。当光穿过透明度为 0.6 的玻璃后,只剩下 40% 的强度,这 40% 的光再穿过透明度 0.5 的玻璃后剩下 20% 强度的光,所以我们可以认为这两块玻璃叠加之后,透明度变为了 0.8。所以透明度的计算公式为:
a = 1 - (1 - a1 ) (1 - a2) = a1 + a2 - a1a2
接下来就是计算叠加后的颜色了,我们可以假设叠加后的颜色为 x,然后将 x 与一张不透明的图片叠加,得到的结果应该等于不透明图片先叠加第一张图片,再叠加第二张图片。于是我们就得到一个方程,解得 x:
x = (c1a1 * (1-a2) + c2a2 ) / (a1 + a2 - a1a2)
有了理论基础,现在我们就可以动手去实现了,首先在项目的 main 文件夹下新建一个名为 rs 的文件夹,然后在这个文件夹下新建我们的 tint.rs 文件。在编译的过程中会从这个文件夹下寻找 rs 文件,生成后的字节码文件可在 app/build/generated/res/rs/ 目录下找到。要编译自己写的rs,我们还需在 gradle 中加入以下代码:
defaultConfig {
...
renderscriptTargetApi 18
renderscriptSupportModeEnabled true
}
下面就可以编写我们的 tint.rs 了
#pragma version(1)
#pragma rs java_package_name(com.test.helper)
uchar4 maskColor = {0, 0, 0, 0};
static uchar mixRGB (uchar src, uchar mask, float inAlpha, float maskAlpha, float outAlpha) {
return (uchar) (((src * (1 - maskAlpha) + mask * maskAlpha) / (inAlpha + maskAlpha - maskAlpha * inAlpha)) * outAlpha);
}
uchar4 __attribute__((kernel)) mask(uchar4 in) {
uchar4 out = in;
float inAlpha = (float)in.a / 255;
float maskAlpha = (float)maskColor.a / 255;
float outAlpha = 1 - (1 - maskAlpha) * (1 - inAlpha);
out.r = mixRGB(in.r, maskColor.r, inAlpha, maskAlpha, outAlpha);
out.g = mixRGB(in.g, maskColor.g, inAlpha, maskAlpha, outAlpha);
out.b = mixRGB(in.b, maskColor.b, inAlpha, maskAlpha, outAlpha);
out.a = (uchar) (outAlpha * 255);
return out;
}
第一行注释指定了 rs 的版本,目前只有 1。第二行的话指定了对应生成的 java 文件的包名。第三行定义了蒙版的颜色,可以看到是 uchar4 类型的。这其实是一个由4个uchar类型组成的结构体,而 uchar 的话之前说过,rs 是跨平台的,所以他抽象了一层数据类型来保持类型的统一:
8bits 16bits 32bits 64bits
integer: char, int8_t short, int16_t int32_t long, long long , int64_t
Unsigned integer: uchar, uint8_t ushort, uinit16_t uint, uint32_t ulong, uinit64_t
Floating point:
half float double
在 rs 里面声明的变量会在自动生成的java文件中自动生成 get、set 方法,生成的代码如下:
private Element __U8_4;
private FieldPacker __rs_fp_U8_4;
private final static int mExportVarIdx_maskColor = 0;
private Short4 mExportVar_maskColor;
public synchronized void set_maskColor(Short4 v) {
mExportVar_maskColor = v;
FieldPacker fp = new FieldPacker(4);
fp.addU8(v);
int []__dimArr = new int[1];
__dimArr[0] = 1;
setVar(mExportVarIdx_maskColor, fp, __U8_4, __dimArr);
}
public Short4 get_maskColor() {
return mExportVar_maskColor;
}
public Script.FieldID getFieldID_maskColor() {
return createFieldID(mExportVarIdx_maskColor, null);
}
//private final static int mExportForEachIdx_root = 0;
private final static int mExportForEachIdx_mask = 1;
public Script.KernelID getKernelID_mask() {
return createKernelID(mExportForEachIdx_mask, 35, null, null);
}
他具体做了些什么我们其实并不用关心,只要知道会生成 get 和 set 方法就可以了。
之后我们定义了一个 mixRGB 方法根据公式来混合像素点,将其声明为 static 方法,这样在编译的时候不会写进 java,而作为 rs 内部的方法使用。
接下来的 mask 方法是关键,__attribute__((kernel)) 可以简写为 RS_KERNEL,因为 rs 为了方便事先做了如下定义:
#define RS_KERNEL __attribute__((kernel))
这个声明告诉 rs,这个方法作为 mapping kernel 使用,他会在并行运算的时候被调用。类似函数式编程中的 map,对数据集依次执行 function,而这里被 RS_KERNEL 声明的方法就是这个 function。此外还有一个 reduction kernel,将数据集通过此方法合并成一个单一的值,类似于函数式编程中的 reduce 或者说 fold。
...
最后只需要这样调用一下就 ok 了
Picasso.with(this)
.load(R.drawable.b)
.transform(new BlurTransformation(this)
.setMaskColor("#661e1433")
.shouldScale(true)
.setMaxSize(32)
.setRadius(20))
.into(imageView);