Android
RenderScript
Zoeken…
Invoering
RenderScript is een scripttaal waarmee u grafische weergave met hoge prestaties en onbewerkte rekencode kunt schrijven. Het biedt een manier om kritieke prestatiecode te schrijven die het systeem later compileert naar native code voor de processor waarop het kan draaien. Dit kan de CPU, een multi-core CPU of zelfs de GPU zijn. Waar het uiteindelijk op draait, hangt af van vele factoren die niet direct beschikbaar zijn voor de ontwikkelaar, maar hangt ook af van welke architectuur de interne platformcompiler ondersteunt.
Ermee beginnen
RenderScript is een raamwerk om parallelle berekeningen met hoge prestaties op Android mogelijk te maken. Scripts die u schrijft, worden parallel uitgevoerd op alle beschikbare processors (bijv. CPU, GPU enz.), Zodat u zich kunt concentreren op de taak die u wilt bereiken in plaats van hoe deze is gepland en uitgevoerd.
Scripts zijn geschreven in een op C99 gebaseerde taal (C99 is een oude versie van de standaard voor programmeertaal C). Voor elk script wordt een Java-klasse gemaakt waarmee u eenvoudig kunt communiceren met RenderScript in uw Java-code.
Uw project opzetten
Er zijn twee verschillende manieren om toegang te krijgen tot RenderScript in uw app, met de Android Framework-bibliotheken of de ondersteuningsbibliotheek. Zelfs als u apparaten vóór API-niveau 11 niet wilt targeten, moet u altijd de implementatie van de ondersteuningsbibliotheek gebruiken omdat deze de compatibiliteit van apparaten op veel verschillende apparaten garandeert. Om de implementatie van de ondersteuningsbibliotheek te gebruiken, moet u minimaal build tools versie 18.1.0
!
Laten we nu het build.gradle-bestand van uw toepassing instellen:
android {
compileSdkVersion 24
buildToolsVersion '24.0.1'
defaultConfig {
minSdkVersion 8
targetSdkVersion 24
renderscriptTargetApi 18
renderscriptSupportModeEnabled true
}
}
-
renderscriptTargetApi
: Dit moet worden ingesteld op het vroegste versie-API-niveau dat alle benodigde RenderScript-functionaliteit biedt. -
renderscriptSupportModeEnabled
: Dit maakt het gebruik van de RenderScript-implementatie van de ondersteuningsbibliotheek mogelijk.
Hoe RenderScript werkt
Een typische RenderScript bestaat uit twee dingen: kernels en functies. Een functie is precies hoe het klinkt - het accepteert een invoer, doet iets met die invoer en retourneert een uitvoer. Een kernel is waar de echte kracht van RenderScript vandaan komt.
Een kernel is een functie die wordt uitgevoerd tegen elk element binnen een Allocation
. Een Allocation
kan worden gebruikt om gegevens zoals een Bitmap
of een byte
array door te geven aan een RenderScript
en ze worden ook gebruikt om een resultaat van een kernel te krijgen. Kernels kunnen de ene Allocation
als invoer en de andere als uitvoer gebruiken of ze kunnen de gegevens in slechts één Allocation
.
U kunt uw eigen kernels schrijven, maar er zijn ook veel vooraf gedefinieerde kernels die u kunt gebruiken om algemene bewerkingen uit te voeren, zoals een Gaussiaans beeldvervaging.
Zoals reeds vermeld voor elk RenderScript-bestand wordt een klasse gegenereerd om ermee te communiceren. Deze klassen beginnen altijd met het voorvoegsel ScriptC_
gevolgd door de naam van het RenderScript-bestand. Als uw RenderScript-bestand example
wordt genoemd, wordt de gegenereerde Java-klasse ScriptC_example
. Alle vooraf gedefinieerde Scripts beginnen gewoon met het voorvoegsel Script
- bijvoorbeeld het Gaussian Image Blur Script wordt ScriptIntrinsicBlur
.
Uw eerste RenderScript schrijven
Het volgende voorbeeld is gebaseerd op een voorbeeld op GitHub. Het voert basisbeeldmanipulatie uit door de verzadiging van een beeld te wijzigen. Je kunt de broncode hier vinden en bekijken als je er zelf mee wilt spelen. Hier is een korte gif van hoe het resultaat eruit zou moeten zien:
RenderScript-ketelplaat
RenderScript-bestanden bevinden zich in de map src/main/rs
in uw project. Elk bestand heeft de bestandsextensie .rs
en moet bovenaan twee #pragma
instructies bevatten:
#pragma version(1)
#pragma rs java_package_name(your.package.name)
#pragma version(1)
: dit kan worden gebruikt om de versie van RenderScript in te stellen die u gebruikt. Momenteel is er alleen versie 1.#pragma rs java_package_name(your.package.name)
: dit kan worden gebruikt om de pakketnaam in te stellen van de Java-klasse die wordt gegenereerd voor interactie met deze specifieke RenderScript.
Er is nog een #pragma
u gewoonlijk in elk van uw RenderScript-bestanden moet instellen en deze wordt gebruikt om de zwevende puntprecisie in te stellen. U kunt de zwevende puntprecisie instellen op drie verschillende niveaus:
-
#pragma rs_fp_full
: dit is de strengste instelling met de hoogste precisie en het is ook de standaardwaarde als u niets opgeeft. U moet dit gebruiken als u een hoge drijvende komma precisie nodig hebt. -
#pragma rs_fp_relaxed
: dit zorgt voor niet zo hoge precisie met een drijvend punt, maar op sommige architecturen maakt het een aantal optimalisaties mogelijk die ervoor kunnen zorgen dat uw scripts sneller worden uitgevoerd. -
#pragma rs_fp_imprecise
: dit zorgt voor nog minder precisie en moet worden gebruikt als precisie met drijvende komma niet echt belangrijk is voor uw script.
De meeste scripts kunnen gewoon #pragma rs_fp_relaxed
tenzij je echt een hoge drijvende komma-precisie nodig hebt.
Globale variabelen
Nu kunt u net als in C-code globale variabelen of constanten definiëren:
const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
float saturationLevel = 0.0f;
De variabele gMonoMult
is van het type float3
. Dit betekent dat het een vector is die bestaat uit 3 floatnummers. De andere float
naam saturationValue
is niet constant, daarom kunt u deze tijdens runtime instellen op een waarde die u leuk vindt. U kunt dergelijke variabelen gebruiken in uw kernels of functies en daarom zijn ze een andere manier om invoer te geven aan of te ontvangen van uw RenderScripts. Voor elke niet-constante variabele worden een getter- en settermethode gegenereerd op de bijbehorende Java-klasse.
kernels
Maar laten we nu beginnen met het implementeren van de kernel. Voor de doeleinden van dit voorbeeld ga ik de wiskunde die in de kernel wordt gebruikt om de verzadiging van de afbeelding te wijzigen niet verklaren, maar in plaats daarvan zal ik me concentreren op hoe een kernel te implementeren en hoe deze te gebruiken. Aan het einde van dit hoofdstuk zal ik snel uitleggen wat de code in deze kernel eigenlijk doet.
Pitten in het algemeen
Laten we eerst de broncode bekijken:
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);
}
Zoals je kunt zien, lijkt het op een normale C-functie met één uitzondering: het __attribute__((kernel))
tussen het __attribute__((kernel))
en de naam van de methode. Dit vertelt RenderScript dat deze methode een kernel is. Een ander ding dat je misschien opvalt is dat deze methode een parameter uchar4
accepteert en een andere waarde voor uchar4
retourneert. uchar4
is - net als de variabele float3
die we eerder in het hoofdstuk hebben besproken - een vector. Het bevat 4 uchar
waarden die gewoon bytewaarden zijn in het bereik van 0 tot 255.
U kunt op verschillende manieren toegang krijgen tot deze individuele waarden, bijvoorbeeld in.r
retourneert de byte die overeenkomt met het rode kanaal van een pixel. We gebruiken een uchar4
omdat elke pixel bestaat uit 4 waarden - r
voor rood, g
voor groen, b
voor blauw en a
voor alfa - en u kunt ze openen met deze steno. Met RenderScript kunt u ook een willekeurig aantal waarden uit een vector nemen en er een andere vector mee maken. in.rgb
zou bijvoorbeeld een uchar3
waarde retourneren die alleen de rode, groene en blauwe delen van de pixel bevat zonder de alfawaarde.
Tijdens runtime roept RenderScript deze kernelmethode aan voor elke pixel van een afbeelding. Daarom zijn de retourwaarde en parameter slechts één uchar4
waarde. RenderScript zal veel van deze aanroepen parallel op alle beschikbare processors uitvoeren. Daarom is RenderScript zo krachtig. Dit betekent ook dat u zich geen zorgen hoeft te maken over draadsnijden of draadveiligheid, u kunt gewoon implementeren wat u voor elke pixel wilt doen en RenderScript zorgt voor de rest.
Wanneer u een kernel in Java oproept, levert u twee Allocation
, een die de invoergegevens bevat en een andere die de uitvoer ontvangt. Uw kernelmethode wordt voor elke waarde in de Allocation
aangeroepen en schrijft het resultaat naar de Allocation
.
RenderScript Runtime API-methoden
In de kernel hierboven worden een paar methoden gebruikt die uit de doos worden geleverd. RenderScript biedt veel van dergelijke methoden en ze zijn van vitaal belang voor bijna alles wat u met RenderScript gaat doen. Onder hen zijn methoden om wiskundige bewerkingen uit te voeren, zoals sin()
en hulpmethoden zoals mix()
die twee waarden volgens andere waarden mengt. Maar er zijn ook methoden voor complexere bewerkingen bij het omgaan met vectoren, quaternions en matrices.
De officiële RenderScript Runtime API Reference is de beste bron die er is als u meer wilt weten over een bepaalde methode of op zoek bent naar een specifieke methode die een algemene bewerking uitvoert, zoals het berekenen van het puntproduct van een matrix. U kunt deze documentatie hier vinden .
Kernel-implementatie
Laten we nu eens kijken naar de details van wat deze kernel doet. Hier is de eerste regel in de kernel:
float4 f4 = rsUnpackColor8888(in);
De eerste regel roept de ingebouwde methode rsUnpackColor8888()
die de uchar4
waarde uchar4
in een float4
waarde. Elk kleurkanaal wordt ook getransformeerd naar het bereik 0.0f - 1.0f
waarbij 0.0f
overeenkomt met een bytewaarde van 0
en 1.0f
tot 255
. Het belangrijkste doel hiervan is om alle wiskunde in deze kernel een stuk eenvoudiger te maken.
float3 dotVector = dot(f4.rgb, gMonoMult);
Deze volgende regel gebruikt de ingebouwde methode dot()
om het puntproduct van twee vectoren te berekenen. gMonoMult
is een constante waarde die we een paar hoofdstukken hierboven hebben gedefinieerd. Omdat beide vectoren dezelfde lengte moeten hebben om het puntproduct te berekenen en ook omdat we alleen de kleurkanalen willen beïnvloeden en niet het alfakanaal van een pixel, gebruiken we de .rgb
om een nieuwe float3
vector te krijgen die alleen de rode, groene en blauwe kleurkanalen. Degenen onder ons die zich nog van school herinneren hoe het dot-product werkt, zullen snel merken dat het dot-product slechts één waarde moet retourneren en geen vector. In de bovenstaande code wijzen we het resultaat toe aan een float3
vector. Dit is opnieuw een functie van RenderScript. Wanneer u een eendimensionaal nummer aan een vector toewijst, worden alle elementen in de vector op deze waarde ingesteld. Het volgende fragment zal bijvoorbeeld 2.0f
toewijzen aan elk van de drie waarden in de float3
vector:
float3 example = 2.0f;
Het resultaat van het bovenstaande puntproduct wordt dus toegewezen aan elk element in de float3
vector hierboven.
Nu komt het gedeelte waarin we de globale variabele saturationLevel
gebruiken om de verzadiging van de afbeelding te wijzigen:
float3 newColor = mix(dotVector, f4.rgb, saturationLevel);
Dit maakt gebruik van de ingebouwde methode mix()
om de originele kleur te combineren met de puntproductvector die we hierboven hebben gemaakt. Hoe ze met elkaar worden gemengd, wordt bepaald door de variabele global saturationLevel
. Dus een saturationLevel
van 0.0f
zorgt ervoor dat de resulterende kleur geen deel uitmaakt van de oorspronkelijke kleurwaarden en alleen uit waarden in de dotVector
wat resulteert in een zwart-wit of grijs beeld. Een waarde van 1.0f
zorgt ervoor dat de resulterende kleur volledig bestaat uit de oorspronkelijke kleurwaarden en waarden boven 1.0f
vermenigvuldigen de oorspronkelijke kleuren om ze helderder en intenser te maken.
return rsPackColorTo8888(newColor);
Dit is het laatste deel in de kernel. rsPackColorTo8888()
transformeert de float3
vector terug naar een uchar4
waarde die vervolgens wordt geretourneerd. De resulterende bytewaarden worden geklemd tot een bereik tussen 0 en 255, dus 1.0f
hoger dan 1.0f
resulteren in een bytewaarde van 255 en waarden lager dan 0.0
zullen resulteren in een bytewaarde van 0
.
En dat is de hele kernelimplementatie. Nu is er nog maar één deel over: Hoe een kernel op Java te bellen.
RenderScript aanroepen in Java
Basics
Zoals hierboven al werd vermeld voor elk RenderScript-bestand, wordt een Java-klasse gegenereerd waarmee u kunt communiceren met uw scripts. Deze bestanden hebben het voorvoegsel ScriptC_
gevolgd door de naam van het RenderScript-bestand. Om een instantie van deze klassen te maken, hebt u eerst een instantie van de RenderScript
klasse nodig:
final RenderScript renderScript = RenderScript.create(context);
De statische methode create()
kan worden gebruikt om een RenderScript
instantie van een Context
. U kunt vervolgens de Java-klasse instantiëren die voor uw script is gegenereerd. Als je het RenderScript-bestand saturation.rs
wordt de klasse ScriptC_saturation
:
final ScriptC_saturation script = new ScriptC_saturation(renderScript);
In deze klasse kunt u nu het verzadigingsniveau instellen en de kernel oproepen. De setter die is gegenereerd voor de variabele saturationLevel
heeft het voorvoegsel set_
gevolgd door de naam van de variabele:
script.set_saturationLevel(1.0f);
Er is ook een getter voorafgegaan door get_
waarmee je het momenteel ingestelde verzadigingsniveau kunt krijgen:
float saturationLevel = script.get_saturationLevel();
Kernels die u in uw RenderScript definieert, worden voorafgegaan door forEach_
gevolgd door de naam van de Kernel-methode. De kernel die we hebben geschreven verwacht een Allocation
en een Allocation
als zijn parameters:
script.forEach_saturation(inputAllocation, outputAllocation);
De ingang Allocation
moet het ingangsbeeld bevatten, en na de forEach_saturation
methode is voltooid zal de uitgang toewijzing de gewijzigde beeldgegevens bevatten.
Als u eenmaal een Allocation
instantie hebt, kunt u gegevens van en naar die Allocations
kopiëren met behulp van de methoden copyFrom()
en copyTo()
. U kunt bijvoorbeeld een nieuwe afbeelding naar uw invoer `Allocatie kopiëren door te bellen naar:
inputAllocation.copyFrom(inputBitmap);
Op dezelfde manier kunt u de resultaatafbeelding ophalen door copyTo()
aan te roepen in de output Allocation
:
outputAllocation.copyTo(outputBitmap);
Toewijzingsinstanties maken
Er zijn veel manieren om een Allocation
te maken. Als u eenmaal een Allocation
instantie hebt, kunt u nieuwe gegevens van en naar die Allocations
kopiëren met copyTo()
en copyFrom()
zoals hierboven uitgelegd, maar om ze in eerste instantie te maken, moet u weten met wat voor soort gegevens u precies werkt. Laten we beginnen met de invoer Allocation
:
We kunnen de statische methode gebruikt createFromBitmap()
om snel onze inbreng creëren Allocation
van een Bitmap
:
final Allocation inputAllocation = Allocation.createFromBitmap(renderScript, image);
In dit voorbeeld van het ingevoerde beeld verandert nooit zodat we nooit nodig om de ingang te wijzigen Allocation
weer. We kunnen het elke keer dat het saturationLevel
verandert opnieuw gebruiken om een nieuwe output- Bitmap
.
Het creëren van de output Allocation
is een beetje ingewikkelder. Eerst moeten we een Type
. Een Type
wordt gebruikt om een Allocation
te vertellen met wat voor soort gegevens het te maken heeft. Meestal gebruikt men de Type.Builder
klasse om snel een geschikt Type
. Laten we eerst de code bekijken:
final Type outputType = new Type.Builder(renderScript, Element.RGBA_8888(renderScript))
.setX(inputBitmap.getWidth())
.setY(inputBitmap.getHeight())
.create();
We werken met een normale 32 bit (oftewel 4 byte) per pixel Bitmap
met 4 kleurkanalen. Daarom kiezen we Element.RGBA_8888
om het Type
te maken. Vervolgens gebruiken we de methoden setX()
en setY()
om de breedte en hoogte van de uitvoerafbeelding in te stellen op dezelfde grootte als de setY()
. De methode create()
vervolgens het Type
met de parameters die we hebben opgegeven.
Zodra we de juiste Type
kunnen we de uitvoer te maken Allocation
met de statische methode createTyped()
:
final Allocation outputAllocation = Allocation.createTyped(renderScript, outputType);
Nu zijn we bijna klaar. We hebben ook een output- Bitmap
nodig waarin we de gegevens van de Allocation
kunnen kopiëren. Om dit te doen gebruiken we de statische methode createBitmap()
om een nieuwe lege Bitmap
met dezelfde grootte en configuratie als de Bitmap
.
final Bitmap outputBitmap = Bitmap.createBitmap(
inputBitmap.getWidth(),
inputBitmap.getHeight(),
inputBitmap.getConfig()
);
En daarmee hebben we alle puzzelstukjes om onze RenderScript uit te voeren.
Volledig voorbeeld
Laten we dit allemaal samenvatten in één voorbeeld:
// 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);
Conclusie
Met deze introductie zou u helemaal klaar moeten zijn om uw eigen RenderScript-kernels te schrijven voor eenvoudige beeldmanipulatie. Er zijn echter een paar dingen waar u rekening mee moet houden:
- RenderScript werkt alleen in toepassingsprojecten : momenteel kunnen RenderScript-bestanden geen deel uitmaken van een bibliotheekproject.
- Pas op voor geheugen : RenderScript is erg snel, maar het kan ook geheugenintensief zijn. Er mag nooit meer dan één exemplaar van
RenderScript
tegelijk zijn. Je moet ook zoveel mogelijk hergebruiken. Normaal gesproken hoeft u slechts eenmaal eenAllocation
instantie te maken en deze in de toekomst opnieuw te gebruiken. Hetzelfde geldt voor outputBitmaps
of uw scriptinstanties. Hergebruik zoveel mogelijk. - Doe je werk op de achtergrond : Wederom is RenderScript erg snel, maar op geen enkele manier direct. Elke kernel, vooral complexe, moet worden uitgevoerd vanuit de UI-thread in een
AsyncTask
of iets dergelijks. Voor het grootste deel hoeft u zich echter geen zorgen te maken over geheugenlekken. Alle RenderScript-gerelateerde klassen gebruiken alleen de applicatieContext
en veroorzaken daarom geen geheugenlekken. Maar u moet zich nog steeds zorgen maken over de gebruikelijke dingen zoals lekkendeView
,Activity
of eenContext
die u zelf gebruikt! - Gebruik ingebouwde dingen : er zijn veel vooraf gedefinieerde scripts die taken uitvoeren zoals beeldvervaging, overvloeien, converteren, vergroten of verkleinen. En er zijn nog veel meer ingebouwde methoden die u helpen uw kernels te implementeren. De kans is groot dat als u iets wilt doen, er een script of methode is die al doet wat u probeert te doen. Vind het wiel niet opnieuw uit.
Als je snel aan de slag wilt gaan en met echte code wilt spelen, raad ik je aan een kijkje te nemen in het voorbeeld GitHub-project dat het exacte voorbeeld implementeert waarover in deze tutorial is gesproken. Je kunt het project hier vinden . Veel plezier met RenderScript!
Een afbeelding vervagen
Dit voorbeeld laat zien hoe u de Renderscript API kunt gebruiken om een afbeelding te vervagen (met Bitmap). In dit voorbeeld wordt ScriptInstrinsicBlur gebruikt van de 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;
}
}
Elk script heeft een kernel die de gegevens verwerkt en wordt meestal aangeroepen via de methode 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
}
}
Hiermee is het voorbeeld hier afgesloten. Het wordt geadviseerd om de verwerking in een achtergrondthread te doen.
Een weergave vervagen
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;
}
}
Gebruik:
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);