Android
RenderScript
Szukaj…
Wprowadzenie
RenderScript to język skryptowy, który pozwala pisać wysokowydajne renderowanie grafiki i surowy kod obliczeniowy. Zapewnia sposób pisania kodu krytycznego pod względem wydajności, który system kompiluje później do natywnego kodu dla procesora, na którym może działać. Może to być procesor, procesor wielordzeniowy, a nawet GPU. To, na czym ostatecznie działa, zależy od wielu czynników, które nie są łatwo dostępne dla programisty, ale zależy również od architektury obsługiwanej przez wewnętrzny kompilator platformy.
Pierwsze kroki
RenderScript jest strukturą umożliwiającą obliczenia równoległe o wysokiej wydajności w systemie Android. Skrypty, które piszesz, będą wykonywane równolegle na wszystkich dostępnych procesorach (np. CPU, GPU itp.), Umożliwiając skupienie się na zadaniu, które chcesz osiągnąć, zamiast na jego planowaniu i wykonywaniu.
Skrypty są pisane w języku opartym na C99 (C99 jest starą wersją standardu języka programowania C). Dla każdego skryptu tworzona jest klasa Java, która umożliwia łatwą interakcję z RenderScript w kodzie Java.
Konfiguracja twojego projektu
Istnieją dwa różne sposoby uzyskiwania dostępu do RenderScript w Twojej aplikacji, za pomocą bibliotek systemu Android Framework lub biblioteki pomocy technicznej. Nawet jeśli nie chcesz atakować urządzeń przed interfejsem API na poziomie 11, zawsze powinieneś używać implementacji biblioteki wsparcia, ponieważ zapewnia ona zgodność urządzeń na wielu różnych urządzeniach. Aby skorzystać z implementacji biblioteki wsparcia, musisz użyć co najmniej kompilacji narzędzi w wersji 18.1.0
!
Teraz skonfigurujmy plik build.gradle aplikacji:
android {
compileSdkVersion 24
buildToolsVersion '24.0.1'
defaultConfig {
minSdkVersion 8
targetSdkVersion 24
renderscriptTargetApi 18
renderscriptSupportModeEnabled true
}
}
-
renderscriptTargetApi
: Należy ustawić najwcześniejszą wersję interfejsu API, która zapewnia wszystkie wymagane funkcje RenderScript. -
renderscriptSupportModeEnabled
: Umożliwia korzystanie z implementacji biblioteki wsparcia RenderScript.
Jak działa RenderScript
Typowy RenderScript składa się z dwóch rzeczy: jądra i funkcji. Funkcja jest taka, jak się wydaje - akceptuje dane wejściowe, robi coś z tym wejściem i zwraca dane wyjściowe. Jądro jest źródłem prawdziwej mocy RenderScript.
Jądro to funkcja wykonywana na każdym elemencie wewnątrz Allocation
. Allocation
można użyć do przekazania danych, takich jak Bitmap
lub tablica byte
do RenderScript
a także są one używane do uzyskania wyniku z jądra. Jądra mogą przyjmować jedną Allocation
jako dane wejściowe, a drugą jako dane wyjściowe, lub mogą modyfikować dane w ramach tylko jednej Allocation
.
Możesz napisać swoje Jądra, ale istnieje również wiele predefiniowanych Jądrów, których możesz użyć do wykonywania typowych operacji, takich jak Rozmycie obrazu Gaussa.
Jak już wspomniano dla każdego pliku RenderScript, klasa jest generowana do interakcji z nim. Klasy te zawsze zaczynają się od przedrostka ScriptC_
po ScriptC_
następuje nazwa pliku RenderScript. Na przykład jeśli plik RenderScript nosi nazwę example
wygenerowana klasa Java będzie nazywała się ScriptC_example
. Wszystkie predefiniowane skrypty rozpoczynają się od przedrostka Script
- na przykład skrypt ScriptIntrinsicBlur
obrazu Gaussa nazywa się ScriptIntrinsicBlur
.
Pisanie pierwszego RenderScript
Poniższy przykład jest oparty na przykładzie na GitHub. Wykonuje podstawową manipulację obrazem, modyfikując nasycenie obrazu. Możesz znaleźć kod źródłowy tutaj i sprawdzić, czy chcesz się nim bawić. Oto krótki prezent tego, jak powinien wyglądać wynik:
RenderScript Boilerplate
Pliki RenderScript znajdują się w folderze src/main/rs
w projekcie. Każdy plik ma rozszerzenie .rs
i musi zawierać dwie instrukcje #pragma
u góry:
#pragma version(1)
#pragma rs java_package_name(your.package.name)
#pragma version(1)
: Można go użyć do ustawienia używanej wersji RenderScript. Obecnie dostępna jest tylko wersja 1.#pragma rs java_package_name(your.package.name)
: Można go użyć do ustawienia nazwy pakietu klasy Java generowanej do interakcji z tym konkretnym skryptem renderującym.
Jest inny #pragma
który zwykle należy ustawić w każdym pliku RenderScript i służy on do ustawienia precyzji zmiennoprzecinkowej. Możesz ustawić precyzję zmiennoprzecinkową na trzech różnych poziomach:
-
#pragma rs_fp_full
: Jest to najściślejsze ustawienie z najwyższą precyzją, a także wartość domyślna, jeśli niczego nie określasz. Powinieneś tego użyć, jeśli potrzebujesz wysokiej precyzji zmiennoprzecinkowej. -
#pragma rs_fp_relaxed
: Zapewnia to nie tak wysoką precyzję liczb zmiennoprzecinkowych, ale w niektórych architekturach umożliwia szereg optymalizacji, które mogą spowodować szybsze działanie skryptów. -
#pragma rs_fp_imprecise
: Zapewnia to jeszcze mniejszą precyzję i powinno być stosowane, jeśli precyzja zmiennoprzecinkowa nie ma tak naprawdę znaczenia dla skryptu.
Większość skryptów może po prostu użyć #pragma rs_fp_relaxed
chyba że naprawdę potrzebujesz wysokiej precyzji zmiennoprzecinkowej.
Zmienne globalne
Teraz, podobnie jak w kodzie C, możesz definiować zmienne globalne lub stałe:
const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
float saturationLevel = 0.0f;
Zmienna gMonoMult
jest typu float3
. Oznacza to, że jest to wektor składający się z 3 liczb zmiennoprzecinkowych. Inna zmienna float
o nazwie saturationValue
nie jest stała, dlatego możesz ustawić ją w środowisku wykonawczym na wartość, którą lubisz. Możesz używać takich zmiennych w swoich jądrach lub funkcjach i dlatego są one innym sposobem na wprowadzanie danych wejściowych lub otrzymywanie danych wyjściowych z twoich RenderScripts. Dla każdej niestałej zmiennej zostanie wygenerowana metoda pobierająca i ustawiająca w powiązanej klasie Java.
Jądra
Ale teraz zacznijmy wdrażanie jądra. Na potrzeby tego przykładu nie zamierzam wyjaśniać matematyki używanej w jądrze w celu modyfikacji nasycenia obrazu, ale skupię się na tym, jak zaimplementować jądro i jak go używać. Na końcu tego rozdziału wyjaśnię szybko, co właściwie robi kod w tym jądrze.
Jądra w ogóle
Najpierw spójrzmy na kod źródłowy:
uchar4 __attribute__((kernel)) saturation(uchar4 in) {
float4 f4 = rsUnpackColor8888(in);
float3 dotVector = dot(f4.rgb, gMonoMult);
float3 newColor = mix(dotVector, f4.rgb, saturationLevel);
return rsPackColorTo8888(newColor);
}
Jak widać, wygląda to jak normalna funkcja C z jednym wyjątkiem: __attribute__((kernel))
między typem zwracanym a nazwą metody. To mówi RenderScript, że ta metoda jest jądrem. Inną rzeczą, którą możesz zauważyć, jest to, że ta metoda akceptuje parametr uchar4
i zwraca inną wartość uchar4
. uchar4
jest - podobnie jak zmienna float3
, którą omówiliśmy w poprzednim rozdziale - wektorem. Zawiera 4 wartości uchar
, które są po prostu bajtowymi wartościami z zakresu od 0 do 255.
Możesz uzyskać dostęp do tych indywidualnych wartości na wiele różnych sposobów, na przykład in.r
zwróci bajt odpowiadający czerwonemu kanałowi piksela. Używamy uchar4
ponieważ każdy piksel składa się z 4 wartości - r
dla czerwonego, g
dla zielonego, b
dla niebieskiego i a
dla alfa - i można uzyskać do nich dostęp za pomocą tego skrótu. RenderScript pozwala także pobierać dowolną liczbę wartości z wektora i tworzyć z nimi kolejny wektor. Na przykład in.rgb
zwróci wartość uchar3
która zawiera tylko czerwoną, zieloną i niebieską część piksela bez wartości alfa.
W czasie wykonywania RenderScript wywoła tę metodę jądra dla każdego piksela obrazu, dlatego zwracana wartość i parametr są tylko jedną wartością uchar4
. RenderScript uruchomi wiele z tych wywołań równolegle na wszystkich dostępnych procesorach, dlatego RenderScript jest tak potężny. Oznacza to również, że nie musisz się martwić o wątki lub bezpieczeństwo wątków, możesz po prostu zaimplementować wszystko, co chcesz zrobić z każdym pikselem, a RenderScript zajmie się resztą.
Podczas wywoływania jądra w Javie podajesz dwie zmienne Allocation
, jedną zawierającą dane wejściowe, a drugą, która otrzyma dane wyjściowe. Twoja metoda jądra zostanie wywołana dla każdej wartości w Allocation
wejściowej i zapisze wynik w Allocation
wyjściowej.
Metody interfejsu API środowiska wykonawczego RenderScript
W jądrze powyżej użyto kilku metod, które są dostarczane po wyjęciu z pudełka. RenderScript udostępnia wiele takich metod i są one niezbędne do prawie wszystkiego, co zamierzasz zrobić z RenderScript. Wśród nich są metody wykonywania operacji matematycznych, takie jak sin()
oraz metody pomocnicze, takie jak mix()
która miesza dwie wartości według innych wartości. Istnieją jednak również metody bardziej skomplikowanych operacji w przypadku wektorów, czwartorzędów i macierzy.
Oficjalna dokumentacja interfejsu API środowiska wykonawczego RenderScript jest najlepszym źródłem, jeśli chcesz dowiedzieć się więcej o konkretnej metodzie lub szukasz konkretnej metody, która wykonuje typową operację, taką jak obliczanie iloczynu macierzy. Dokumentację można znaleźć tutaj .
Implementacja jądra
Teraz spójrzmy na szczegóły tego, co robi to jądro. Oto pierwsza linia w jądrze:
float4 f4 = rsUnpackColor8888(in);
Wzywa pierwsza linia wbudowany w metodzie rsUnpackColor8888()
, która przekształca uchar4
wartości do float4
wartości. Każdy kanał koloru jest również przekształcany do zakresu 0.0f - 1.0f
gdzie 0.0f
odpowiada wartości bajtu od 0
i 1.0f
do 255
. Głównym celem tego jest uproszczenie całej matematyki w tym jądrze.
float3 dotVector = dot(f4.rgb, gMonoMult);
W następnym wierszu zastosowano wbudowaną metodę dot()
do obliczenia iloczynu kropkowego dwóch wektorów. gMonoMult
to stała wartość, którą zdefiniowaliśmy kilka rozdziałów powyżej. Ponieważ oba wektory muszą mieć tę samą długość, aby obliczyć iloczyn iloczynu, a także dlatego, że chcemy tylko wpływać na kanały kolorów, a nie kanał alfa piksela, używamy .rgb
aby uzyskać nowy wektor float3
który zawiera po prostu kanały kolorów czerwonego, zielonego i niebieskiego. Ci z nas, którzy wciąż pamiętają ze szkoły, jak działa produkt kropkowy, szybko zauważą, że produkt kropkowy powinien zwrócić tylko jedną wartość, a nie wektor. Jednak w powyższym kodzie przypisujemy wynik do wektora float3
. Jest to ponownie funkcja RenderScript. Po przypisaniu do wektora liczby jednowymiarowej wszystkie elementy w wektorze zostaną ustawione na tę wartość. Na przykład poniższy fragment przypisze 2.0f
do każdej z trzech wartości w wektorze float3
:
float3 example = 2.0f;
Tak więc wynik iloczynu powyżej jest przypisany do każdego elementu w wektorze float3
powyżej.
Teraz pojawia się część, w której faktycznie używamy globalnej zmiennej saturationLevel
do modyfikacji nasycenia obrazu:
float3 newColor = mix(dotVector, f4.rgb, saturationLevel);
Wykorzystuje to wbudowaną metodę mix()
do mieszania oryginalnego koloru z wektorem kropkowym, który stworzyliśmy powyżej. Sposób ich mieszania zależy od globalnej zmiennej saturationLevel
. Zatem poziom saturationLevel
wynoszący 0.0f
spowoduje, że powstały kolor nie będzie miał części oryginalnych wartości kolorów i będzie składał się tylko z wartości w dotVector
co spowoduje powstanie czarno-białego lub wyszarzonego obrazu. Wartość 1.0f
spowoduje, że powstały kolor zostanie całkowicie złożony z oryginalnych wartości kolorów, a wartości powyżej 1.0f
pomnożą oryginalne kolory, dzięki czemu będą bardziej jasne i intensywne.
return rsPackColorTo8888(newColor);
To ostatnia część jądra. rsPackColorTo8888()
przekształca wektor float3
powrotem w wartość uchar4
która jest następnie zwracana. Wynikowe wartości bajtów są zaciśnięte w zakresie od 0 do 255, więc wartości zmiennoprzecinkowe wyższe niż 1.0f
spowodują, że wartość bajtu będzie wynosić 255, a wartości mniejsze niż 0.0
spowoduje powstanie wartości bajtu równej 0
.
I to jest cała implementacja jądra. Teraz pozostała tylko jedna część: Jak wywołać jądro w Javie.
Wywoływanie RenderScript w Javie
Podstawy
Jak już wspomniano powyżej, dla każdego pliku RenderScript generowana jest klasa Java, która umożliwia interakcję ze skryptami. Pliki te mają przedrostek ScriptC_
po ScriptC_
następuje nazwa pliku RenderScript. Aby utworzyć instancję tych klas, najpierw potrzebujesz instancji klasy RenderScript
:
final RenderScript renderScript = RenderScript.create(context);
Metoda statyczna create()
może zostać użyta do utworzenia instancji RenderScript
z Context
. Następnie można utworzyć instancję klasy Java, która została wygenerowana dla skryptu. Jeśli wywołałeś plik RenderScript saturation.rs
klasa będzie się nazywać ScriptC_saturation
:
final ScriptC_saturation script = new ScriptC_saturation(renderScript);
W tej klasie możesz teraz ustawić poziom nasycenia i wywołać jądro. Setter, który został wygenerowany dla zmiennej saturationLevel
, będzie miał przedrostek set_
a następnie nazwę zmiennej:
script.set_saturationLevel(1.0f);
Istnieje również getter z prefiksem get_
który pozwala uzyskać aktualnie ustawiony poziom nasycenia:
float saturationLevel = script.get_saturationLevel();
Jądra zdefiniowane w RenderScript są poprzedzone forEach_
a następnie nazwą metody Jądra. Jądro, które napisaliśmy, oczekuje Allocation
wejściowej i Allocation
wyjściowej jako parametrów:
script.forEach_saturation(inputAllocation, outputAllocation);
Allocation
wejściowa musi zawierać obraz wejściowy, a po zakończeniu metody forEach_saturation
alokacja wyjściowa będzie zawierać zmodyfikowane dane obrazu.
Po utworzeniu instancji Allocation
możesz kopiować dane zi do tych Allocations
, korzystając z metod copyFrom()
i copyTo()
. Na przykład możesz skopiować nowy obraz do swojego wejścia `Alokacja, wywołując:
inputAllocation.copyFrom(inputBitmap);
W ten sam sposób możesz odzyskać obraz copyTo()
wywołując copyTo()
na wyjściowym Allocation
:
outputAllocation.copyTo(outputBitmap);
Tworzenie instancji alokacji
Istnieje wiele sposobów utworzenia Allocation
. Po utworzeniu instancji Allocation
możesz kopiować nowe dane zi do tych Allocations
pomocą copyTo()
i copyFrom()
jak wyjaśniono powyżej, ale aby je utworzyć, musisz najpierw wiedzieć, z jakiego rodzaju danymi dokładnie współpracujesz. Zacznijmy od wejścia Allocation
:
Możemy użyć metody statycznej createFromBitmap()
, aby szybko stworzyć nasz wkład Allocation
z Bitmap
:
final Allocation inputAllocation = Allocation.createFromBitmap(renderScript, image);
W tym przykładzie obraz wejściowy nigdy się nie zmienia, więc nigdy więcej nie musimy modyfikować Allocation
wejściowej. Możemy go ponownie użyć za każdym razem, gdy zmienia się saturationLevel
aby utworzyć nową Bitmap
wyjściową.
Tworzenie wyjścia Allocation
jest nieco bardziej złożona. Najpierw musimy stworzyć tak zwany Type
. Type
służy do informowania Allocation
z jakim rodzajem danych ma do czynienia. Zwykle używa się klasy Type.Builder
aby szybko utworzyć odpowiedni Type
. Najpierw spójrzmy na kod:
final Type outputType = new Type.Builder(renderScript, Element.RGBA_8888(renderScript))
.setX(inputBitmap.getWidth())
.setY(inputBitmap.getHeight())
.create();
Pracujemy z normalną 32-bitową (lub inaczej 4-bajtową) Bitmap
na piksel z 4 kanałami kolorów. Dlatego wybieramy Element.RGBA_8888
aby utworzyć Type
. Następnie używamy metod setX()
i setY()
aby ustawić szerokość i wysokość obrazu wyjściowego na taki sam rozmiar jak obraz wejściowy. Metoda create()
następnie tworzy Type
z określonymi parametrami.
Kiedy już mamy prawidłowy Type
możemy stworzyć wyjściową Allocation
z metodą statyczną createTyped()
:
final Allocation outputAllocation = Allocation.createTyped(renderScript, outputType);
Teraz już prawie skończyliśmy. Potrzebujemy również wyjściowej Bitmap
w której możemy skopiować dane z wyjściowej Allocation
. Aby to zrobić, używamy statycznej metody createBitmap()
aby utworzyć nową pustą createBitmap()
Bitmap
o takim samym rozmiarze i konfiguracji co Bitmap
wejściowa.
final Bitmap outputBitmap = Bitmap.createBitmap(
inputBitmap.getWidth(),
inputBitmap.getHeight(),
inputBitmap.getConfig()
);
I dzięki temu mamy wszystkie elementy układanki do wykonania naszego RenderScript.
Pełny przykład
Połączmy to wszystko w jeden przykład:
// Create the RenderScript instance
final RenderScript renderScript = RenderScript.create(context);
// Create the input Allocation
final Allocation inputAllocation = Allocation.createFromBitmap(renderScript, inputBitmap);
// Create the output Type.
final Type outputType = new Type.Builder(renderScript, Element.RGBA_8888(renderScript))
.setX(inputBitmap.getWidth())
.setY(inputBitmap.getHeight())
.create();
// And use the Type to create am output Allocation
final Allocation outputAllocation = Allocation.createTyped(renderScript, outputType);
// Create an empty output Bitmap from the input Bitmap
final Bitmap outputBitmap = Bitmap.createBitmap(
inputBitmap.getWidth(),
inputBitmap.getHeight(),
inputBitmap.getConfig()
);
// Create an instance of our script
final ScriptC_saturation script = new ScriptC_saturation(renderScript);
// Set the saturation level
script.set_saturationLevel(2.0f);
// Execute the Kernel
script.forEach_saturation(inputAllocation, outputAllocation);
// Copy the result data to the output Bitmap
outputAllocation.copyTo(outputBitmap);
// Display the result Bitmap somewhere
someImageView.setImageBitmap(outputBitmap);
Wniosek
W tym wstępie powinieneś być gotowy do napisania własnych jąder RenderScript do prostej manipulacji obrazem. Jest jednak kilka rzeczy, o których należy pamiętać:
- RenderScript działa tylko w projektach aplikacji : obecnie pliki RenderScript nie mogą być częścią projektu bibliotecznego.
- Uważaj na pamięć : RenderScript jest bardzo szybki, ale może również wymagać dużej ilości pamięci. Nigdy nie powinno być więcej niż jedno wystąpienie
RenderScript
. Powinieneś także ponownie wykorzystać jak najwięcej. Zwykle wystarczy raz utworzyć instancjeAllocation
i będzie można ich ponownie użyć w przyszłości. To samo dotyczy wyjściowychBitmaps
lub instancji skryptu. Wykorzystaj jak najwięcej. - Wykonuj swoją pracę w tle : Ponownie RenderScript jest bardzo szybki, ale w żadnym wypadku nie jest natychmiastowy. Każde jądro, szczególnie złożone, powinno zostać wykonane poza wątkiem interfejsu użytkownika w
AsyncTask
lub w podobnym celu. Jednak w większości przypadków nie musisz martwić się o wycieki pamięci. Wszystkie klasy powiązane z RenderScript używają tylkoContext
aplikacji i dlatego nie powodują wycieków pamięci. Ale nadal musisz się martwić zwykłymi rzeczami, takimi jak wyciekView
,Activity
lub dowolna instancjaContext
której sam używasz! - Używaj wbudowanych elementów : Istnieje wiele predefiniowanych skryptów, które wykonują takie zadania, jak rozmycie obrazu, mieszanie, konwersja, zmiana rozmiaru. Istnieje wiele innych wbudowanych metod, które pomagają zaimplementować jądra. Są szanse, że jeśli chcesz coś zrobić, istnieje skrypt lub metoda, która już robi to, co próbujesz zrobić. Nie wymyślaj koła ponownie.
Jeśli chcesz szybko zacząć i bawić się prawdziwym kodem, polecam przyjrzeć się przykładowemu projektowi GitHub, który implementuje dokładny przykład omówiony w tym samouczku. Możesz znaleźć projekt tutaj . Baw się dobrze z RenderScript!
Rozmycie obrazu
W tym przykładzie pokazano, jak używać interfejsu API Renderscript do rozmycia obrazu (przy użyciu mapy bitowej). W tym przykładzie użyto ScriptInstrinsicBlur dostarczonego przez Android Renderscript API (API> = 17).
public class BlurProcessor {
private RenderScript rs;
private Allocation inAllocation;
private Allocation outAllocation;
private int width;
private int height;
private ScriptIntrinsicBlur blurScript;
public BlurProcessor(RenderScript rs) {
this.rs = rs;
}
public void initialize(int width, int height) {
blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
blurScript.setRadius(7f); // Set blur radius. 25 is max
if (outAllocation != null) {
outAllocation.destroy();
outAllocation = null;
}
// Bitmap must have ARGB_8888 config for this type
Type bitmapType = new Type.Builder(rs, Element.RGBA_8888(rs))
.setX(width)
.setY(height)
.setMipmaps(false) // We are using MipmapControl.MIPMAP_NONE
.create();
// Create output allocation
outAllocation = Allocation.createTyped(rs, bitmapType);
// Create input allocation with same type as output allocation
inAllocation = Allocation.createTyped(rs, bitmapType);
}
public void release() {
if (blurScript != null) {
blurScript.destroy();
blurScript = null;
}
if (inAllocation != null) {
inAllocation.destroy();
inAllocation = null;
}
if (outAllocation != null) {
outAllocation.destroy();
outAllocation = null;
}
}
public Bitmap process(Bitmap bitmap, boolean createNewBitmap) {
if (bitmap.getWidth() != width || bitmap.getHeight() != height) {
// Throw error if required
return null;
}
// Copy data from bitmap to input allocations
inAllocation.copyFrom(bitmap);
// Set input for blur script
blurScript.setInput(inAllocation);
// process and set data to the output allocation
blurScript.forEach(outAllocation);
if (createNewBitmap) {
Bitmap returnVal = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
outAllocation.copyTo(returnVal);
return returnVal;
}
outAllocation.copyTo(bitmap);
return bitmap;
}
}
Każdy skrypt ma jądro, które przetwarza dane i jest na ogół wywoływane za forEach
metody forEach
.
public class BlurActivity extends AppCompatActivity {
private BlurProcessor blurProcessor;
@Override
public void onCreate(Bundle savedInstanceState) {
// setup layout and other stuff
blurProcessor = new BlurProcessor(Renderscript.create(getApplicationContext()));
}
private void loadImage(String path) {
// Load image to bitmap
Bitmap bitmap = loadBitmapFromPath(path);
// Initialize processor for this bitmap
blurProcessor.release();
blurProcessor.initialize(bitmap.getWidth(), bitmap.getHeight());
// Blur image
Bitmap blurImage = blurProcessor.process(bitmap, true); // Use newBitamp as false if you don't want to create a new bitmap
}
}
To zakończyło przykład tutaj. Zaleca się przetwarzanie w wątku tła.
Rozmyj widok
BlurBitmapTask.java
public class BlurBitmapTask extends AsyncTask<Bitmap, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private final RenderScript renderScript;
private boolean shouldRecycleSource = false;
public BlurBitmapTask(@NonNull Context context, @NonNull ImageView imageView) {
// Use a WeakReference to ensure
// the ImageView can be garbage collected
imageViewReference = new WeakReference<>(imageView);
renderScript = RenderScript.create(context);
}
// Decode image in background.
@Override
protected Bitmap doInBackground(Bitmap... params) {
Bitmap bitmap = params[0];
return blurBitmap(bitmap);
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap == null || isCancelled()) {
return;
}
final ImageView imageView = imageViewReference.get();
if (imageView == null) {
return;
}
imageView.setImageBitmap(bitmap);
}
public Bitmap blurBitmap(Bitmap bitmap) {
// https://plus.google.com/+MarioViviani/posts/fhuzYkji9zz
//Let's create an empty bitmap with the same size of the bitmap we want to blur
Bitmap outBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
Bitmap.Config.ARGB_8888);
//Instantiate a new Renderscript
//Create an Intrinsic Blur Script using the Renderscript
ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
//Create the in/out Allocations with the Renderscript and the in/out bitmaps
Allocation allIn = Allocation.createFromBitmap(renderScript, bitmap);
Allocation allOut = Allocation.createFromBitmap(renderScript, outBitmap);
//Set the radius of the blur
blurScript.setRadius(25.f);
//Perform the Renderscript
blurScript.setInput(allIn);
blurScript.forEach(allOut);
//Copy the final bitmap created by the out Allocation to the outBitmap
allOut.copyTo(outBitmap);
// recycle the original bitmap
// nope, we are using the original bitmap as well :/
if (shouldRecycleSource) {
bitmap.recycle();
}
//After finishing everything, we destroy the Renderscript.
renderScript.destroy();
return outBitmap;
}
public boolean isShouldRecycleSource() {
return shouldRecycleSource;
}
public void setShouldRecycleSource(boolean shouldRecycleSource) {
this.shouldRecycleSource = shouldRecycleSource;
}
}
Stosowanie:
ImageView imageViewOverlayOnViewToBeBlurred
.setImageDrawable(ContextCompat.getDrawable(this, android.R.color.transparent));
View viewToBeBlurred.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_LOW);
viewToBeBlurred.setDrawingCacheEnabled(true);
BlurBitmapTask blurBitmapTask = new BlurBitmapTask(this, imageViewOverlayOnViewToBeBlurred);
blurBitmapTask.execute(Bitmap.createBitmap(viewToBeBlurred.getDrawingCache()));
viewToBeBlurred.setDrawingCacheEnabled(false);