Szybkie rozmycie bitmapy dla zestawu SDK systemu Android


185

Obecnie w opracowywanej przeze mnie aplikacji na Androida zapętlam piksele obrazu, aby go rozmyć. To zajmuje około 30 sekund na obrazie 640x480.

Przeglądając aplikacje w Android Market, natknąłem się na taką, która zawiera funkcję rozmycia, a ich rozmycie jest bardzo szybkie (około 5 sekund), więc muszą używać innej metody rozmycia.

Czy ktoś zna szybszy sposób niż zapętlanie pikseli?


2
Niestety obrazy zawsze będą inne, więc nie będę w stanie wcześniej stworzyć zamazanej wersji. Poza tym z czasem nie poznam intensywności rozmycia.
Greg

Czy możesz opublikować swój kod, być może jest to algorytm / kod, który jest nieefektywny, 30 sekund na przejście przez obraz 640x480 jest powolny, myślałem, że 5 sekund jest powolne, ale znowu zależy od procesora.
vickirk

Odpowiedzi:


78

To jest zdjęcie w ciemności, ale możesz spróbować zmniejszyć obraz, a następnie ponownie go powiększyć. Można to zrobić za pomocą Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter). Upewnij się i ustaw parametr filtru na true. Będzie działał w kodzie natywnym, więc może być szybszy.


5
Po kilku testach i rozmyciu, że robię, ten fakt działa dla mnie wystarczająco dobrze i jest szybki. Dzięki!
Greg

4
Jeśli to działa dobrze. Szkoda, że ​​nigdy nie doszliśmy do sedna, dlaczego był nieefektywny.
vickirk

Możesz spróbować createScaledBitmap i pozostawić ten sam rozmiar obrazu.
Rozmywa

1
Oto dyskusja na temat znaczenia argumentu „filter”: stackoverflow.com/questions/2895065
user1364368

4
Nie jest to dokładnie sposób na uzyskanie z dwóch powodów: 1) potrzebuje pamięci pełnego obrazu, chociaż prawdopodobnie używasz tylko zmniejszonego obrazu 2) musisz załadować pełny obraz, który jest wolniejszy - użyj ładowania z inSampleSize i BitmapFactory.decodeResource (), który jest znacznie lepszym rozwiązaniem tego problemu.
Patrick Favre

303

Dla przyszłych Googlerów, oto algorytm, który przeniosłem z Quasimondo. Jest to swego rodzaju połączenie rozmycia ramki z rozmyciem gaussowskim, jest również bardzo ładne i dość szybkie.

Aktualizacja dla osób napotykających problem ArrayIndexOutOfBoundsException: @anthonycr w komentarzach zawiera następujące informacje:

Odkryłem, że przez zastąpienie Math.abs StrictMath.abs lub inną implementacją abs, awaria nie występuje.

/**
 * 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);
}

4
Dzięki Yahel. Rozwiązałeś mój problem. Dzięki jeszcze raz.
Yog Guru,

1
co powinienem podać jako promień?
krisDrOid

16
W przypadku promienia większego niż 1 czasami pojawia się wyjątek ArrayIndexOutOfBoundsException. Spróbuję zidentyfikować problem.
MikeL,

7
@MichaelLiberman Też napotkałem ten sam problem. Dowiedziałeś się jeszcze, dlaczego? „g [yi] = dv [gsum];” -> error: java.lang.ArrayIndexOutOfBoundsException: length = 112896; indeks = 114021
patrz

2
Wpadłem na znany ArrayIndexOutOfBoundsException, a po pewnej analizie uważam, że jest to spowodowane nieprawidłową optymalizacją przez maszynę wirtualną Dalvik. W forpętli bezpośrednio przed złym dereference, albo obliczanie rbszmiennej lub obliczenie gsum, rsumczy bsumzmienne nie są robione w prawo. Okazało się, że przy wymianie Math.absz StrictMath.abslub innej absrealizacji, awaria nie występuje. Ponieważ StrictMath.abssama się deleguje Math.abs, wydaje się, że musi to być zła optymalizacja.
anthonycr

255

Android Blur Guide 2016

z aplikacją Showcase / Benchmark i źródłem na Github . Sprawdź także środowisko Blur, nad którym obecnie pracuję: Dali .

Po wielu eksperymentach mogę teraz bezpiecznie dać ci solidne rekomendacje, które ułatwią Ci życie na Androidzie podczas korzystania z Android Framework.

Załaduj i użyj zmniejszonej mapy bitowej (dla bardzo rozmytych obrazów)

Nigdy nie używaj pełnego rozmiaru mapy bitowej. Im większy obraz, tym więcej musi być zamazanych, a także im większy musi być promień rozmycia, i zwykle im większy promień rozmycia, tym dłużej zajmuje algorytm.

final BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap blurTemplate = BitmapFactory.decodeResource(getResources(), R.drawable.myImage, options);

Spowoduje to załadowanie bitmapy z inSampleSize8, więc tylko 1/64 oryginalnego obrazu. Sprawdź, co inSampleSizeodpowiada Twoim potrzebom, ale zachowaj 2 ^ n (2,4,8, ...), aby uniknąć pogorszenia jakości z powodu skalowania. Zobacz dokument Google, aby uzyskać więcej

Kolejną naprawdę dużą zaletą jest to, że ładowanie bitmap będzie bardzo szybkie. Podczas moich wczesnych testów rozmycia doszedłem do wniosku, że najdłużej podczas całego procesu rozmycia było ładowanie obrazu. Tak więc, aby załadować obraz 1920x1080 z dysku, mój Nexus 5 potrzebował 500 ms, a rozmycie trwało tylko kolejne 250 ms.

Użyj Renderscript

Renderscript zapewnia ScriptIntrinsicBlurfiltr rozmycia Gaussa. Ma dobrą jakość wizualną i jest po prostu najszybszy, jaki masz realistycznie na Androida. Google twierdzi, że jest „zwykle 2-3 razy szybszy niż wielowątkowa implementacja C i często 10 razy szybszy niż implementacja Java” . Renderscript jest naprawdę wyrafinowany (przy użyciu najszybszego urządzenia przetwarzającego (GPU, ISP itp.) Itp.)). Dostępna jest również biblioteka obsługi v8, dzięki czemu jest kompatybilny z wersją 2.2. Cóż, przynajmniej teoretycznie, dzięki własnym testom i raportom innych deweloperów wydaje się, że nie można używać Renderscript na ślepo, ponieważ fragmentacja sprzętu / sterownika wydaje się powodować problemy z niektórymi urządzeniami, nawet z wyższym poziomem sdk lvl (np. Miałem problemy z 4.1 Nexusem S), więc bądź ostrożny i testuj na wielu urządzeniach. Oto prosty przykład, który pomoże Ci zacząć:

//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);

Korzystając z obsługi wersji 8 z Gradle, która jest szczególnie zalecana przez Google „, ponieważ zawierają najnowsze ulepszenia” , wystarczy dodać tylko 2 wiersze do skryptu kompilacji i używać android.support.v8.renderscriptz bieżącymi narzędziami kompilacji ( zaktualizowana składnia wtyczki Gradle dla Androida w wersji 14 + )

android {
    ...
    defaultConfig {
        ...
        renderscriptTargetApi 19
        renderscriptSupportModeEnabled true
    }
}

Prosty test porównawczy na Nexusie 5 - porównanie RenderScript z różnymi innymi implementacjami Java i Renderscript:

Średni czas działania na rozmycie dla różnych rozmiarów zdjęć Średni czas działania na rozmycie dla różnych rozmiarów zdjęć

Megapiksele na sekundę, które można zamazać Megapiksele na sekundę, które można zamazać

Każda wartość to średnia z 250 rund. RS_GAUSS_FASTjest ScriptIntrinsicBlur(i prawie zawsze najszybszy), inne, które zaczynają się od tego, RS_są w większości implementacjami złożonymi z prostymi jądrami. Szczegóły algorytmów można znaleźć tutaj . Nie jest to czysto rozmycie, ponieważ dobrą porcją jest mierzone zbieranie śmieci. Można to zobaczyć tutaj ( ScriptIntrinsicBlurna obrazie 100 x 100 z około 500 rundami)

wprowadź opis zdjęcia tutaj

Kolce są gc.

Możesz sam sprawdzić, czy aplikacja testowa znajduje się w sklepie internetowym: BlurBenchmark

Wykorzystuje bitmapę wszędzie tam, gdzie to możliwe (jeśli prio: wydajność> ślad pamięci)

Jeśli potrzebujesz wielu rozmycia dla rozmycia na żywo lub podobnego, a twoja pamięć pozwala, nie ładuj bitmapy z drawable wiele razy, ale przechowuj ją w pamięci podręcznej w zmiennej członka. W takim przypadku zawsze staraj się używać tych samych zmiennych, aby ograniczyć odśmiecanie do minimum.

Sprawdź także nową inBitmapopcję podczas ładowania z pliku lub pliku do rysowania, który ponownie wykorzysta pamięć bitmapy i zaoszczędzi czas na wyrzucanie elementów bezużytecznych.

Do mieszania od ostrego do rozmytego

Prostą i naiwną metodą jest użycie 2 ImageViews, jeden zamazany, a alfa znikną. Ale jeśli chcesz uzyskać bardziej wyrafinowany wygląd, który płynnie przechodzi od ostrego do rozmytego, zapoznaj się z postem Romana Nurika o tym, jak to zrobić w jego aplikacji Muzei .

Zasadniczo wyjaśnia, że ​​wstępnie rozmywa niektóre klatki z różnymi zakresami rozmycia i używa ich jako klatek kluczowych w animacji, która wygląda naprawdę gładko.

Schemat, na którym Nurik wyjaśnia swoje podejście


1
Przede wszystkim dzięki za ciężką pracę! Ale dostałem pytanie: „ponieważ używa trybu USAGE_SHARED, który ponownie wykorzystuje pamięć”. Gdzie znalazłeś stałą USAGE_SHARED? Nigdzie nie mogłem go znaleźć.
Some Noob Student

2
Znalazłem, USAGE_SHARED jest dostępny tylko w support.v8.renderscript
Some Noob Student

2
Szybkie renderowanie rozmycie gaussowskie kończy się niepowodzeniem z błędami alokacji pamięci C na urządzeniach końcowych. Testowane na ZTE Z992 (Android 4.1.1) i Kyocera Rise (Android 4.0.4) przy użyciu dostarczonej aplikacji Sklep Play. Miałem też raport o awarii Samsung Galaxy S3 mini. Ponieważ błędy występują w warstwie C, nie można ich uwięzić jako wyjątków w Javie, co oznacza, że ​​awaria aplikacji jest nieunikniona. Wygląda na to, że RenderScript może nie być gotowy do użytku produkcyjnego.
Theo

4
w przypadku nowszych wersji stopni użyj renderscriptSupportModeEnabled truelub nie zbuduje się! Szukałem na zawsze!
seb


53

EDYCJA (kwiecień 2014): To jest strona pytań / odpowiedzi, która wciąż wydaje się bardzo trafiona. Wiem, że zawsze otrzymuję pozytywne opinie na temat tego postu. Ale jeśli to czytasz, musisz zdać sobie sprawę, że odpowiedzi zamieszczone tutaj (zarówno moja, jak i zaakceptowana odpowiedź) są nieaktualne. Jeśli chcesz dziś wdrożyć wydajne rozmycie , powinieneś użyć RenderScript zamiast NDK lub Java. RenderScript działa na Androidzie 2.2+ (przy użyciu Biblioteki pomocy Android ), więc nie ma powodu, aby go nie używać.

Następuje stara odpowiedź, ale uważaj, ponieważ jest nieaktualna.


Dla przyszłych² Googlerów, oto algorytm, który przeniosłem z portu Yahela algorytmu Quasimondo, ale używając NDK. Oczywiście opiera się na odpowiedzi Yahel. Ale działa natywny kod C, więc jest szybszy. O wiele szybciej. Na przykład 40 razy szybciej.

Uważam, że za pomocą NDK jest jak wszystko manipulacja obrazu powinno być wykonane na Androida ... to nieco irytujące do realizacji w pierwszym (czytaj świetny tutorial na użyciu JNI i NDK tutaj ), ale o wiele lepiej, a przy czasie rzeczywistym Przez dużo rzeczy.

Dla porównania, używając funkcji Java Yahel, rozmycie mojego obrazu 480 x 532 pikseli zajęło 10 sekund z promieniem rozmycia wynoszącym 10. Ale natywna wersja C. zajęła 250 ms. I jestem prawie pewien, że można go dalej optymalizować ... Właśnie zrobiłem głupią konwersję kodu Java, prawdopodobnie są pewne manipulacje, które można skrócić, nie chciałem spędzać zbyt dużo czasu na refaktoryzacji całości.

#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;
}

Następnie użyj go w ten sposób (biorąc pod uwagę klasę o nazwie com.insert.your.package.ClassName i natywną funkcję o nazwie functionToBlur, jak podano powyżej):

// Create a copy
Bitmap bitmapOut = bitmapIn.copy(Bitmap.Config.ARGB_8888, true);
// Blur the copy
functionToBlur(bitmapIn, bitmapOut, __radius);

Oczekuje bitmapy RGB_8888!

Aby użyć mapy bitowej RGB_565, utwórz przekonwertowaną kopię przed przekazaniem parametru (fuj) lub zmień funkcję, aby użyć nowego rgb565typu zamiast rgba:

typedef struct {
    uint16_t byte0;
} rgb565;

Problem polega na tym, że jeśli zrobisz to, czego nie możesz odczytać .red, .greeni .bluepiksela, musisz poprawnie odczytać bajt, duh. Kiedy potrzebowałem tego wcześniej, zrobiłem to:

r = (pixels[x].byte0 & 0xF800) >> 8;
g = (pixels[x].byte0 & 0x07E0) >> 3;
b = (pixels[x].byte0 & 0x001F) << 3;

Ale prawdopodobnie jest to mniej głupi sposób. Obawiam się, że nie jestem programistą C na niskim poziomie.


Dzięki, bardzo mi pomogło :)
Dmitrij Zajcew

18
ALE wymaga dużo pamięci. Aby zmniejszyć zużycie pamięci typu zmiany z r[wh], g[wh]i b[wh]do uint8_t.
Dmitrij Zajcew

czy możesz mi pokazać, jak wygląda twój plik Android.mk w dniupastebin.com
CQM

1
Dla sprawnego przykład RenderScript z Gaussian Blur na Android SDK 17+ wygląd tutaj: stackoverflow.com/questions/14988990/android-fast-bitmap-blur
Martin Marconcini

2
RenderScript jest również dostępny jako część biblioteki wsparcia dla Androida 2.2+, więc nie ma powodu, aby nie używać go już wszędzie: android-developers.blogspot.com/2013/09/…
zeh

14

Ten kod jest dla mnie idealny

Bitmap tempbg = BitmapFactory.decodeResource(getResources(),R.drawable.b1); //Load a background.
Bitmap final_Bitmap = BlurImage(tempbg);


@SuppressLint("NewApi")
Bitmap BlurImage (Bitmap input)
{
    try
    {
    RenderScript  rsScript = RenderScript.create(getApplicationContext());
    Allocation alloc = Allocation.createFromBitmap(rsScript, input);

    ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rsScript,   Element.U8_4(rsScript));
    blur.setRadius(21);
    blur.setInput(alloc);

    Bitmap result = Bitmap.createBitmap(input.getWidth(), input.getHeight(), Bitmap.Config.ARGB_8888);
    Allocation outAlloc = Allocation.createFromBitmap(rsScript, result);

    blur.forEach(outAlloc);
    outAlloc.copyTo(result);

    rsScript.destroy();
    return result;
    }
    catch (Exception e) {
        // TODO: handle exception
        return input;
    }

}

Najprostszy sposób na rozmycie obrazu (y)
VAdaihiep

12

Możesz teraz używać ScriptIntrinsicBlur z biblioteki RenderScript, aby szybko rozmazać. Oto jak uzyskać dostęp do interfejsu API RenderScript. Oto klasa, którą stworzyłem, aby rozmazać widoki i mapy bitowe:

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;
    }
}

2
Kontekst Renderscript nie powinien być tworzony w metodzie rozmycia, ale zarządzany statycznie lub przekazywany tej metodzie. (jeśli przeszkadza ci występ)
Patrick Favre

1
Czy możesz podać przykład takiego zastosowania? Gdy próbuję go użyć, pojawia się następujący błąd: java.lang.IllegalArgumentException: szerokość i wysokość muszą wynosić> 0
Donal Rafferty

10

Działa to dla mnie dobrze: Jak skutecznie zamazywać obrazy za pomocą RenderScript na Androidzie

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;
    }
}

Byłoby to jeszcze lepsze / szybsze, jeśli utworzyłeś buforowany obiekt RenderScript. Tworzenie nowej instancji za każdym razem, gdy chcesz rozmazać obraz, powoduje jedynie niepotrzebne obciążenie (tworzenie / niszczenie obiektów Java).
Stephen Hines,


4

Dotyczy to wszystkich osób, które muszą zwiększyć promień, ScriptIntrinsicBluraby uzyskać mocniejsze rozmycie gaussowskie.

Zamiast ustawić promień większy niż 25, możesz przeskalować obraz i uzyskać ten sam wynik. Napisałem klasę o nazwie GaussianBlur. Poniżej możesz zobaczyć, jak używać i implementację całej klasy.

Stosowanie:

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);

Klasa:

 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;
}
    }

nie, jeśli przeskalujesz obraz, aby go skalować później, odzyskasz obraz blokowy zamiast obrazu blured :(
loki

4

Dzięki @Yahel za kod. Opublikowanie tej samej metody z obsługą zamazywania kanału alfa, ponieważ zajęło mi trochę czasu, aby działała poprawnie, dzięki czemu może zaoszczędzić czyjś czas:

/**
 * 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);
}

nadal z „indeksem poza zakresem” w urządzeniach> hdpi jako oryginalne źródło
Gabriel Ferreira

4

Użyłem tego wcześniej ...

public static Bitmap myblur(Bitmap image, Context context) {
            final float BITMAP_SCALE = 0.4f;
            final float BLUR_RADIUS = 7.5f;
            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;
        }

2

Dla przyszłych Googlersów, którzy wybiorą podejście NDK - znajduję wiarygodny wspomniany algorytm stackblur. Znalazłem tutaj implementację C ++, która nie polega na SSE - http://www.antigrain.com/__code/include/agg_blur.h.html#stack_blur_rgba32, która zawiera pewne optymalizacje przy użyciu statycznych tabel, takich jak:

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
}; 

Dokonałem modyfikacji algorytmu stackblur dla systemów wielordzeniowych - można go znaleźć tutaj http://vitiy.info/stackblur-algorithm-multi-threaded-blur-for-cpp/ Ponieważ coraz więcej urządzeń ma 4 rdzenie - optymalizacje dają 4x szybsza korzyść.


1

Porada Nicolas POMEPUY. Myślę, że ten link będzie pomocny: Efekt rozmycia w projektowaniu Androida

Przykładowy projekt na github

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static Bitmap fastblur16(Bitmap source, int radius, Context ctx) {    
    Bitmap bitmap = source.copy(source.getConfig(), true);    
    RenderScript rs = RenderScript.create(ctx);
    Allocation input = Allocation.createFromBitmap(rs, source, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
    Allocation output = Allocation.createTyped(rs, input.getType());
    ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
    script.setRadius(radius);
    script.setInput(input);
    script.forEach(output);
    output.copyTo(bitmap);
    return bitmap;
}

Chociaż ten link może odpowiedzieć na pytanie, lepiej dołączyć tutaj istotne części odpowiedzi i podać link w celach informacyjnych. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli zmieni się połączona strona.
Amal Murali

1
Amal Murali, masz rację. Teraz zmień mój post. Dobrze, że nie tylko głosujesz, ale także komentujesz.
Yura Shinkarev,

1

Próbowaliśmy zaimplementować rozmycie RenderScript jak wspomniano powyżej w różnych odpowiedziach. Ograniczono nas do używania wersji RenderScript v8, co spowodowało wiele problemów.

  • Samsung S3 ulegał awarii losowo za każdym razem, gdy próbowaliśmy użyć renderscript
  • Inne urządzenia (w różnych interfejsach API) losowo wykazywały różne problemy z kolorem

Chcę udostępnić naszą brudną wersję przeznaczoną tylko dla języka Java, która jest powolna i powinna być wykonywana w osobnym wątku oraz, jeśli to możliwe, przed użyciem, a zatem trwała.

private final Paint mPaint = new Paint();

public Bitmap blur(final String pathToBitmap) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    final Bitmap normalOne = BitmapFactory.decodeFile(pathToBitmap, options);
    final Bitmap resultBitmap = Bitmap.createBitmap(options.outWidth, options.outHeight, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(resultBitmap);
    mPaint.setAlpha(180);
    canvas.drawBitmap(normalOne, 0, 0, mPaint);
    int blurRadius = 12;
    for (int row = -blurRadius; row < blurRadius; row += 2) {
        for (int col = -blurRadius; col < blurRadius; col += 2) {
            if (col * col + row * row <= blurRadius * blurRadius) {
                mPaint.setAlpha((blurRadius * blurRadius) / ((col * col + row * row) + 1) * 2);
                canvas.drawBitmap(normalOne, row, col, mPaint);
            }
        }
    }
    normalOne.recycle();
    return resultBitmap;
}

To rozwiązanie jest dalekie od ideału, ale daje rozsądny efekt rozmycia, ponieważ rysuje bardzo przezroczystą wersję tego samego obrazu na ledwo przezroczystej „ostrej” wersji. Alfa zależy od odległości do początku.

Możesz dostosować niektóre „magiczne liczby” do swoich potrzeb. Chciałem tylko udostępnić to „rozwiązanie” wszystkim, którzy mają problemy z wersją RenderScript obsługującą wersję 8.


Mam również problem z RenderScript na niektórych starych urządzeniach armeabi, ale twoje rozwiązanie jest zbyt zajmujące pamięć.
AsafK,

0

Jeśli nadal masz problemy z biblioteką obsługi Renderscript na mikroukładach x86, zapoznaj się z tym postem twórcy biblioteki. Wygląda na to, że poprawka, którą przygotował, nie dotarła w jakiś sposób do Build Tools v20.0.0, więc udostępnia pliki do ręcznej naprawy i krótkie wyjaśnienie, jak to zrobić.

https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=71347




0

Przekonałem się, że zmniejszenie kontrastu, jasności i nasycenia trochę upiększa rozmazane obrazy, dlatego połączyłem różne metody od przepełnienia stosu i stworzyłem tę klasę rozmycia, która zajmuje się rozmyciem zdjęć, zmianą jasności, nasycenia, kontrastu i rozmiaru rozmytych obrazów. Może także konwertować obrazy z rysowalnej na bitmapę i odwrotnie.


0

Na I / O 2019 zaprezentowano następujące rozwiązanie:

/**
 * 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()
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.