現在、私が開発している Android アプリケーションでは、画像のピクセルをループしてぼかしています。これには、640x480 の画像で約 30 秒かかります。
Android マーケットでアプリを閲覧しているときに、ぼかし機能を含むアプリを見つけましたが、そのぼかしは非常に高速 (5 秒など) であるため、別のぼかし方法を使用しているに違いありません。
ピクセルをループする以外のより速い方法を知っている人はいますか?
現在、私が開発している Android アプリケーションでは、画像のピクセルをループしてぼかしています。これには、640x480 の画像で約 30 秒かかります。
Android マーケットでアプリを閲覧しているときに、ぼかし機能を含むアプリを見つけましたが、そのぼかしは非常に高速 (5 秒など) であるため、別のぼかし方法を使用しているに違いありません。
ピクセルをループする以外のより速い方法を知っている人はいますか?
将来の Google 社員のために、私が Quasimondo から移植したアルゴリズムを次に示します。これは、ボックス ブラーとガウス ブラーを混ぜたようなもので、非常にきれいで高速です。
ArrayIndexOutOfBoundsException 問題に遭遇した人々のための更新:コメントの @anthonycr は、この情報を提供します:
Math.abs を StrictMath.abs またはその他の abs 実装に置き換えると、クラッシュが発生しないことがわかりました。
/**
* Stack Blur v1.0 from
* http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
* Java Author: Mario Klingemann <mario at quasimondo.com>
* http://incubator.quasimondo.com
*
* created Feburary 29, 2004
* Android port : Yahel Bouaziz <yahel at kayenko.com>
* http://www.kayenko.com
* ported april 5th, 2012
*
* This is a compromise between Gaussian Blur and Box blur
* It creates much better looking blurs than Box Blur, but is
* 7x faster than my Gaussian Blur implementation.
*
* I called it Stack Blur because this describes best how this
* filter works internally: it creates a kind of moving stack
* of colors whilst scanning through the image. Thereby it
* just has to add one new block of color to the right side
* of the stack and remove the leftmost color. The remaining
* colors on the topmost layer of the stack are either added on
* or reduced by one, depending on if they are on the right or
* on the left side of the stack.
*
* If you are using this algorithm in your code please add
* the following line:
* Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
*/
public Bitmap fastblur(Bitmap sentBitmap, float scale, int radius) {
int width = Math.round(sentBitmap.getWidth() * scale);
int height = Math.round(sentBitmap.getHeight() * scale);
sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false);
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
Log.e("pix", w + " " + h + " " + pix.length);
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] = ( 0xff000000 & pix[yi] ) | ( dv[rsum] << 16 ) | ( dv[gsum] << 8 ) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
Log.e("pix", w + " " + h + " " + pix.length);
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
Githubの Showcase/Benchmark アプリとソースを使用します。 また、私が現在取り組んでいる Blur フレームワークもチェックしてください: Dali。
多くの実験を行った結果、Android フレームワークを使用する際に Android での生活を楽にするための確実な推奨事項を安全に提供できるようになりました。
フルサイズのビットマップは絶対に使用しないでください。画像が大きくなればなるほど、より多くのぼかしが必要になり、またぼかしの半径を大きくする必要があり、通常、ぼかしの半径が大きくなるほど、アルゴリズムにかかる時間が長くなります。
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap blurTemplate = BitmapFactory.decodeResource(getResources(), R.drawable.myImage, options);
これにより、ビットマップがinSampleSize
8 で読み込まれるため、元の画像の 1/64 のみが読み込まれます。ニーズに合ったものをテストしinSampleSize
ますが、スケーリングによる品質の低下を避けるために、2^n (2,4,8,...) のままにしてください。詳細については、Google ドキュメントを参照してください
もう 1 つの非常に大きな利点は、ビットマップの読み込みが非常に高速になることです。初期のぼかしテストでは、ぼかしプロセス全体で最も長い時間は画像の読み込みであることがわかりました。したがって、ディスクから 1920x1080 の画像をロードするには、Nexus 5 で 500 ミリ秒が必要でしたが、ぼかしにはさらに 250 ミリ秒しかかかりませんでした。
Renderscript はScriptIntrinsicBlur
ガウスぼかしフィルターを提供します。それは優れたビジュアル品質を持ち、Android で実際に取得する最速のものです。Google は、 「通常、マルチスレッドの C 実装よりも 2 倍から 3 倍速く、Java 実装よりも 10 倍以上速い」と主張しています。Renderscript は非常に洗練されており (最速の処理デバイス (GPU、ISP など) を使用するなど)、2.2 まで互換性のある v8 サポート ライブラリもあります。. 少なくとも理論的には、私自身のテストと他の開発者からのレポートによると、Renderscript をやみくもに使用することはできないようです。これは、ハードウェア/ドライバーの断片化が一部のデバイスで問題を引き起こしているように思われるためです。 4.1 Nexus S で問題が発生する可能性があるため、注意して多くのデバイスでテストしてください。以下に簡単な例を示します。
//define this only once if blurring multiple times
RenderScript rs = RenderScript.create(context);
(...)
//this will blur the bitmapOriginal with a radius of 8 and save it in bitmapOriginal
final Allocation input = Allocation.createFromBitmap(rs, bitmapOriginal); //use this constructor for best performance, because it uses USAGE_SHARED mode which reuses memory
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(8f);
script.setInput(input);
script.forEach(output);
output.copyTo(bitmapOriginal);
Gradle で v8 サポートを使用する場合、「最新の改善が含まれているため」 Google が特に推奨しています。ビルド スクリプトに 2 行を追加android.support.v8.renderscript
し、現在のビルド ツールで使用するだけです ( android Gradle プラグイン v14+ の更新された構文) 。
android {
...
defaultConfig {
...
renderscriptTargetApi 19
renderscriptSupportModeEnabled true
}
}
Nexus 5 での簡単なベンチマーク - RenderScript を他の Java および Renderscript 実装と比較します。
さまざまな写真サイズでのぼかしあたりの平均実行時間
ぼかし可能なメガピクセル/秒
各値は 250 ラウンドの平均です。RS_GAUSS_FAST
であるScriptIntrinsicBlur
(そしてほぼ常に最速) であり、他のものRS_
は単純なカーネルを使用した畳み込み実装であることがほとんどです。アルゴリズムの詳細については、こちらを参照してください。かなりの部分が測定されるガベージ コレクションであるため、これは純粋にぼやけているわけではありません。これはここで見ることができます (ScriptIntrinsicBlur
約 500 ラウンドの 100x100 画像)
スパイクはGCです。
自分で確認できます。ベンチマーク アプリはプレイストアにあります: BlurBenchmark
ライブ ブラーなどに複数のブラーが必要で、メモリが許す場合は、ドローアブルからビットマップを複数回ロードせず、メンバー変数に「キャッシュ」したままにします。この場合、ガベージ コレクションを最小限に抑えるために、常に同じ変数を使用するようにしてください。
また、ビットマップ メモリを再利用してガベージ コレクション時間を節約する、ファイルまたはドローアブルからロードするときの新しいinBitmap
オプションも確認してください。
シンプルで素朴な方法は、 2 を使用しImageViews
、1 つをぼかし、アルファ フェードします。しかし、シャープからぼやけにスムーズにフェードする、より洗練された外観が必要な場合は、Roman Nurik の Muzei アプリのようにそれを行う方法についての投稿を確認してください。
基本的に彼は、さまざまなぼかし範囲でいくつかのフレームを事前にぼかし、それらを非常に滑らかに見えるアニメーションのキーフレームとして使用すると説明しています。
これは暗闇の中でのショットですが、画像を縮小してからもう一度拡大してみてください。これはで行うことができますBitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)
。フィルタパラメータをtrueに設定してください。ネイティブコードで実行されるため、高速になる可能性があります。
編集 (2014 年 4 月):これは、まだ多くのヒットがあるように思われる質問/回答ページです。私はいつもこの投稿に賛成票を集めていることを知っています. ただし、これを読んでいる場合は、ここに投稿された回答 (私の回答と受け入れられた回答の両方) が古くなっていることに気付く必要があります。効率的なぼかしを今日実装したい場合は、NDK や Java の代わりにRenderScript を使用する必要があります。RenderScript は Android 2.2 以降で ( Android サポート ライブラリを使用して) 実行されるため、使用しない理由はありません。
古い回答が続きますが、時代遅れなので注意してください。
future² の Google 社員のために、これは Yahel の Quasimondo のアルゴリズムのポートから移植したアルゴリズムですが、NDK を使用しています。もちろん、それはヤヘルの答えに基づいています。しかし、これはネイティブ C コードを実行しているため、高速です。はるかに高速。たとえば、40倍高速です。
NDK の使用は、Android ですべての画像操作を行う方法であることがわかりました...最初は実装するのがやや面倒ですが (JNI と NDK の使用に関する優れたチュートリアルを読むここ)、はるかに優れており、ほぼリアルタイムで多くのこと。
参考までに、Yahel の Java 関数を使用すると、ぼかし半径 10 で 480x532 ピクセルの画像をぼかすのに 10 秒かかりましたが、ネイティブ C バージョンを使用すると 250ms かかりました。そして、それはまださらに最適化できると確信しています...私はJavaコードのばかげた変換を行っただけです。おそらく短縮できる操作がいくつかあり、全体のリファクタリングにあまり時間を費やしたくありませんでした。
#include <jni.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <android/log.h>
#include <android/bitmap.h>
#define LOG_TAG "libbitmaputils"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
uint8_t alpha;
} rgba;
JNIEXPORT void JNICALL Java_com_insert_your_package_ClassName_functionToBlur(JNIEnv* env, jobject obj, jobject bitmapIn, jobject bitmapOut, jint radius) {
LOGI("Blurring bitmap...");
// Properties
AndroidBitmapInfo infoIn;
void* pixelsIn;
AndroidBitmapInfo infoOut;
void* pixelsOut;
int ret;
// Get image info
if ((ret = AndroidBitmap_getInfo(env, bitmapIn, &infoIn)) < 0 || (ret = AndroidBitmap_getInfo(env, bitmapOut, &infoOut)) < 0) {
LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
return;
}
// Check image
if (infoIn.format != ANDROID_BITMAP_FORMAT_RGBA_8888 || infoOut.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
LOGE("Bitmap format is not RGBA_8888!");
LOGE("==> %d %d", infoIn.format, infoOut.format);
return;
}
// Lock all images
if ((ret = AndroidBitmap_lockPixels(env, bitmapIn, &pixelsIn)) < 0 || (ret = AndroidBitmap_lockPixels(env, bitmapOut, &pixelsOut)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
}
int h = infoIn.height;
int w = infoIn.width;
LOGI("Image size is: %i %i", w, h);
rgba* input = (rgba*) pixelsIn;
rgba* output = (rgba*) pixelsOut;
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int whMax = max(w, h);
int div = radius + radius + 1;
int r[wh];
int g[wh];
int b[wh];
int rsum, gsum, bsum, x, y, i, yp, yi, yw;
rgba p;
int vmin[whMax];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int stack[div][3];
int stackpointer;
int stackstart;
int rbs;
int ir;
int ip;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = input[yi + min(wm, max(i, 0))];
ir = i + radius; // same as sir
stack[ir][0] = p.red;
stack[ir][1] = p.green;
stack[ir][2] = p.blue;
rbs = r1 - abs(i);
rsum += stack[ir][0] * rbs;
gsum += stack[ir][1] * rbs;
bsum += stack[ir][2] * rbs;
if (i > 0) {
rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];
} else {
routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
ir = stackstart % div; // same as sir
routsum -= stack[ir][0];
goutsum -= stack[ir][1];
boutsum -= stack[ir][2];
if (y == 0) {
vmin[x] = min(x + radius + 1, wm);
}
p = input[yw + vmin[x]];
stack[ir][0] = p.red;
stack[ir][1] = p.green;
stack[ir][2] = p.blue;
rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
ir = (stackpointer) % div; // same as sir
routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];
rinsum -= stack[ir][0];
ginsum -= stack[ir][1];
binsum -= stack[ir][2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = max(0, yp) + x;
ir = i + radius; // same as sir
stack[ir][0] = r[yi];
stack[ir][1] = g[yi];
stack[ir][2] = b[yi];
rbs = r1 - abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];
} else {
routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
output[yi].red = dv[rsum];
output[yi].green = dv[gsum];
output[yi].blue = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
ir = stackstart % div; // same as sir
routsum -= stack[ir][0];
goutsum -= stack[ir][1];
boutsum -= stack[ir][2];
if (x == 0) vmin[y] = min(y + r1, hm) * w;
ip = x + vmin[y];
stack[ir][0] = r[ip];
stack[ir][1] = g[ip];
stack[ir][2] = b[ip];
rinsum += stack[ir][0];
ginsum += stack[ir][1];
binsum += stack[ir][2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
ir = stackpointer; // same as sir
routsum += stack[ir][0];
goutsum += stack[ir][1];
boutsum += stack[ir][2];
rinsum -= stack[ir][0];
ginsum -= stack[ir][1];
binsum -= stack[ir][2];
yi += w;
}
}
// Unlocks everything
AndroidBitmap_unlockPixels(env, bitmapIn);
AndroidBitmap_unlockPixels(env, bitmapOut);
LOGI ("Bitmap blurred.");
}
int min(int a, int b) {
return a > b ? b : a;
}
int max(int a, int b) {
return a > b ? a : b;
}
次に、次のように使用します (上記のコードが示すように、com.insert.your.package.ClassName というクラスと functionToBlur というネイティブ関数を考えます):
// Create a copy
Bitmap bitmapOut = bitmapIn.copy(Bitmap.Config.ARGB_8888, true);
// Blur the copy
functionToBlur(bitmapIn, bitmapOut, __radius);
RGB_8888 ビットマップが必要です!
RGB_565 ビットマップを使用するには、パラメーターを渡す前に変換されたコピーを作成するか (yuck)、rgb565
代わりに新しい型を使用するように関数を変更しrgba
ます。
typedef struct {
uint16_t byte0;
} rgb565;
問題は、それを行うと.red
、ピクセルを読み取ることができなくなり、バイトを適切に読み取る必要があることです。以前にそれが必要だったとき、私はこれをしました:.green
.blue
r = (pixels[x].byte0 & 0xF800) >> 8;
g = (pixels[x].byte0 & 0x07E0) >> 3;
b = (pixels[x].byte0 & 0x001F) << 3;
しかし、おそらくそれを行うためのもっとばかげた方法があります。残念ながら、私は低レベルの C コーダーではありません。
RenderScript ライブラリのScriptIntrinsicBlurを使用して、すばやくぼかしを入れることができるようになりました。RenderScript API にアクセスする方法は次のとおりです。以下は、ビューとビットマップをぼかすために作成したクラスです。
public class BlurBuilder {
private static final float BITMAP_SCALE = 0.4f;
private static final float BLUR_RADIUS = 7.5f;
public static Bitmap blur(View v) {
return blur(v.getContext(), getScreenshot(v));
}
public static Bitmap blur(Context ctx, Bitmap image) {
int width = Math.round(image.getWidth() * BITMAP_SCALE);
int height = Math.round(image.getHeight() * BITMAP_SCALE);
Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
RenderScript rs = RenderScript.create(ctx);
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
theIntrinsic.setRadius(BLUR_RADIUS);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);
return outputBitmap;
}
private static Bitmap getScreenshot(View v) {
Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
v.draw(c);
return b;
}
}
これは私にとってはうまくいきました: How to Blur Images Efficiently with Android's RenderScript
public class BlurBuilder {
private static final float BITMAP_SCALE = 0.4f;
private static final float BLUR_RADIUS = 7.5f;
@SuppressLint("NewApi")
public static Bitmap blur(Context context, Bitmap image) {
int width = Math.round(image.getWidth() * BITMAP_SCALE);
int height = Math.round(image.getHeight() * BITMAP_SCALE);
Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height,
false);
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
RenderScript rs = RenderScript.create(context);
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs,
Element.U8_4(rs));
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
theIntrinsic.setRadius(BLUR_RADIUS);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);
return outputBitmap;
}
}
コードをありがとう@Yahel。誰かの時間を節約できるように、正しく機能させるのに時間がかかったので、アルファチャンネルのぼかしをサポートする同じ方法を投稿します。
/**
* Stack Blur v1.0 from
* http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
* Java Author: Mario Klingemann <mario at quasimondo.com>
* http://incubator.quasimondo.com
* <p/>
* created Feburary 29, 2004
* Android port : Yahel Bouaziz <yahel at kayenko.com>
* http://www.kayenko.com
* ported april 5th, 2012
* <p/>
* This is a compromise between Gaussian Blur and Box blur
* It creates much better looking blurs than Box Blur, but is
* 7x faster than my Gaussian Blur implementation.
* <p/>
* I called it Stack Blur because this describes best how this
* filter works internally: it creates a kind of moving stack
* of colors whilst scanning through the image. Thereby it
* just has to add one new block of color to the right side
* of the stack and remove the leftmost color. The remaining
* colors on the topmost layer of the stack are either added on
* or reduced by one, depending on if they are on the right or
* on the left side of the stack.
* <p/>
* If you are using this algorithm in your code please add
* the following line:
* Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
*/
public static Bitmap fastblur(Bitmap sentBitmap, float scale, int radius) {
int width = Math.round(sentBitmap.getWidth() * scale);
int height = Math.round(sentBitmap.getHeight() * scale);
sentBitmap = Bitmap.createScaledBitmap(sentBitmap, width, height, false);
Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
Log.e("pix", w + " " + h + " " + pix.length);
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int a[] = new int[wh];
int rsum, gsum, bsum, asum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][4];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum, aoutsum;
int rinsum, ginsum, binsum, ainsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = ainsum = routsum = goutsum = boutsum = aoutsum = rsum = gsum = bsum = asum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
sir[3] = 0xff & (p >> 24);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
asum += sir[3] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
ainsum += sir[3];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
aoutsum += sir[3];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
a[yi] = dv[asum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
asum -= aoutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
aoutsum -= sir[3];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
sir[3] = 0xff & (p >> 24);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
ainsum += sir[3];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
asum += ainsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
aoutsum += sir[3];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
ainsum -= sir[3];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = ainsum = routsum = goutsum = boutsum = aoutsum = rsum = gsum = bsum = asum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
sir[3] = a[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
asum += a[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
ainsum += sir[3];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
aoutsum += sir[3];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
pix[yi] = (dv[asum] << 24) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
asum -= aoutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
aoutsum -= sir[3];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
sir[3] = a[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
ainsum += sir[3];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
asum += ainsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
aoutsum += sir[3];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
ainsum -= sir[3];
yi += w;
}
}
Log.e("pix", w + " " + h + " " + pix.length);
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
ScriptIntrinsicBlur
これは、より強いガウスぼかしを得るためにの半径を大きくする必要があるすべての人向けです。
半径を 25 より大きくする代わりに、画像を縮小しても同じ結果が得られます。というクラスを書きましたGaussianBlur
。以下に、使用方法とクラス全体の実装を示します。
使用法:
GaussianBlur gaussian = new GaussianBlur(context);
gaussian.setMaxImageSize(60);
gaussian.setRadius(25); //max
Bitmap output = gaussian.render(<your bitmap>,true);
Drawable d = new BitmapDrawable(getResources(),output);
クラス:
public class GaussianBlur {
private final int DEFAULT_RADIUS = 25;
private final float DEFAULT_MAX_IMAGE_SIZE = 400;
private Context context;
private int radius;
private float maxImageSize;
public GaussianBlur(Context context) {
this.context = context;
setRadius(DEFAULT_RADIUS);
setMaxImageSize(DEFAULT_MAX_IMAGE_SIZE);
}
public Bitmap render(Bitmap bitmap, boolean scaleDown) {
RenderScript rs = RenderScript.create(context);
if (scaleDown) {
bitmap = scaleDown(bitmap);
}
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Allocation inAlloc = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE);
Allocation outAlloc = Allocation.createFromBitmap(rs, output);
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, inAlloc.getElement()); // Element.U8_4(rs));
script.setRadius(getRadius());
script.setInput(inAlloc);
script.forEach(outAlloc);
outAlloc.copyTo(output);
rs.destroy();
return output;
}
public Bitmap scaleDown(Bitmap input) {
float ratio = Math.min((float) getMaxImageSize() / input.getWidth(), (float) getMaxImageSize() / input.getHeight());
int width = Math.round((float) ratio * input.getWidth());
int height = Math.round((float) ratio * input.getHeight());
return Bitmap.createScaledBitmap(input, width, height, true);
}
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
public float getMaxImageSize() {
return maxImageSize;
}
public void setMaxImageSize(float maxImageSize) {
this.maxImageSize = maxImageSize;
}
}
NDK アプローチを選択する将来の Googler のために、信頼できる言及された stackblur アルゴリズムを見つけます。ここで SSE に依存しない C++ 実装を見つけました - http://www.antigrain.com/__code/include/agg_blur.h.html#stack_blur_rgba32には、次のような静的テーブルを使用したいくつかの最適化が含まれています。
static unsigned short const stackblur_mul[255] =
{
512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
289,287,285,282,280,278,275,273,271,269,267,265,263,261,259
};
static unsigned char const stackblur_shr[255] =
{
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24
};
マルチコア システム用にスタックブラー アルゴリズムを変更しました 。 4 倍の速度の利点。
x86 チップセットの Renderscript サポート ライブラリにまだ問題がある場合は、ライブラリの作成者によるこの投稿をご覧ください。彼が準備した修正はどうやら Build Tools v20.0.0 に反映されなかったようです。そのため、手動で修正するためのファイルとその方法の簡単な説明を提供しています。
Mario Viviani ブログから 17 Android バージョンからこのソリューションを使用できます。
https://plus.google.com/+MarioViviani/posts/fhuzYkji9zz
また
これは、RenderScript を使用したリアルタイム ブラー オーバーレイです。これは十分に高速なようです。
i/o 2019 では、次のソリューションが発表されました。
/**
* Blurs the given Bitmap image
* @param bitmap Image to blur
* @param applicationContext Application context
* @return Blurred bitmap image
*/
@WorkerThread
fun blurBitmap(bitmap: Bitmap, applicationContext: Context): Bitmap {
lateinit var rsContext: RenderScript
try {
// Create the output bitmap
val output = Bitmap.createBitmap(
bitmap.width, bitmap.height, bitmap.config)
// Blur the image
rsContext = RenderScript.create(applicationContext, RenderScript.ContextType.DEBUG)
val inAlloc = Allocation.createFromBitmap(rsContext, bitmap)
val outAlloc = Allocation.createTyped(rsContext, inAlloc.type)
val theIntrinsic = ScriptIntrinsicBlur.create(rsContext, Element.U8_4(rsContext))
theIntrinsic.apply {
setRadius(10f)
theIntrinsic.setInput(inAlloc)
theIntrinsic.forEach(outAlloc)
}
outAlloc.copyTo(output)
return output
} finally {
rsContext.finish()
}
}