RenderScript – równoległe operacje na bitmapach bez użycia NDK

RenderScript – równoległe operacje na bitmapach bez użycia NDK

Musisz wykonać operacje na grafikach, a Java jest do tego zbyt wolna? Chcesz uniknąć zabawy z kompilacją kodu z użyciem NDK? Jest na to rozwiązanie! RenderScript pomoże Ci w tym zadaniu. Dzięki jego użyciu możesz zrównoleglić operacje, wykonując je zarówno na CPU, jak i GPU. Wszystko to jest dostępne w bardzo przystępnej formie, nie musisz nawet zastanawiać się, jak rozdzielić pracę.

Czym jest RenderScript?

RenderScript to framework przystosowany do wykonywania zrównoleglonych operacji. Jest on tak zaprojektowany, aby jego użycie nie wymagało od nas wiele wysiłku. Sam zajmuje się rozdzielaniem pracy, a nam pozostaje tylko skupienie się na zaprojektowaniu algorytmu.

Podstawowe własności

Pliki skryptów zazwyczaj trzyma się w folderze scr/rs/, format pliku to .rs. Sam skrypt piszemy w języku dziedziczącym z C-99. Każdy skrypt musi zawierać w sobie deklarację wersji oraz nazwy paczki, której klasy będą z niego korzystać. Na początku pliku podajemy te informacje w taki sposób:

#pragma version(1)
#pragma rs java_package_name(com.example.app)

W tej chwili wersja skryptu jest tylko jedna więc zawsze podajemy: 1.

Kolejnym elementem skryptu jest funkcja init(), która wywoływana jest zawsze tylko raz przy inicjowaniu skryptu. Nie jest ona wymagana więc można ją pominąć.

Implementacja

Najważniejszym elementem każdego skryptu jest funkcja wywołania. Wygląda ona w ten sposób:

uchar4 __attribute__((kernel)) greyscale(uchar4 in, uint32_t x, uint32_t y) {
  uchar4 out = in;
  int average = (in.r + in.g + in.b)/3;

  out.r = average;
  out.g = average;
  out.b = average;
  return out;
}

Pierwsze, co rzuca nam się w oczy to __attribute__((kernel)) w deklaracji funkcji. Zapis ten mówi nam, że funkcja ma być traktowana jako funkcja jądra RenderScriptu. W tym przypadku przyjmuje ona 3 argumenty. Pierwszy z nich jest typu uchar4 i przechowuje informację o kolorach danego piksela. Kolejne dwa to jego pozycja. Ponieważ RenderScript wykonuje zadania równolegle, nasza funkcja wywoływana jest równolegle dla każdego piksela z osobna. Tak stworzony RenderScript jest gotowy do użycia z poziomu klas Javy.

Użycie

Pierwszą rzeczą jaką musimy zrobić jest zadeklarowanie jego użycia w pliku build.gradle.

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.exmaple.app"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        renderscriptTargetApi 18
        renderscriptSupportModeEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Klasy RenderScriptu znajdują się w:

import android.support.v8.renderscript.*;

Jeśli korzystamy z Androida w wersji 4.2+ nie musimy korzystać z biblioteki Support, jest to jednak zalecana opcja, ponieważ w niej znajduje się zawsze najnowsza wersja RenderScriptu.

Aby mieć dostęp do naszego skryptu, musimy go zaimportować. Robimy to przez dodanie do nazwy skryptu przedrostka ScriptC.

import com.example.app.ScriptC_invert;

Wykonanie operacji na bitmapie z użyciem naszego RenderScriptu wygląda w ten sposób:

public static Bitmap invertBitmap(Bitmap bitmap, Context context) {
        RenderScript rs = RenderScript.create(context);

        Allocation allocation = Allocation.createFromBitmap(rs, bitmap);

        Type t = allocation.getType();

        Allocation invertedAllocation = Allocation.createTyped(rs, t);

        ScriptC_invert invertScript = new ScriptC_invert(rs);

        invertScript.forEach_invert(allocation, invertedAllocation);

        invertedAllocation.copyTo(bitmap);

        allocation.destroy();
        invertedAllocation.destroy();
        invertScript.destroy();
        t.destroy();
        rs.destroy();
        return bitmap;
    }

Na samym początku tworzymy obiekt RenderScriptu, następnie tworzymy obiekt alokacji z naszej bitmapy. Kolejnym wymaganym elementem jest obiekt alokacji wynikowej. Jej typ musi zgadzać się z tym, co podaliśmy w funkcji, więc po prostu pobieramy go z alokacji wejściowej.

Alokacja sama w sobie jest obiektem przechowującym elementy, te zaś mogę mieć różne typy, dzięki temu możliwe jest wykonywanie operacji nie tylko na obrazach, ale również wielu innych rzeczach, jak np. macierze czy wektory.

Gdy nasze alokacje są już gotowe, pozostaje nam tylko stworzyć obiekt naszego skryptu i wywołać przygotowaną funkcję. Nasza funkcją została zaopatrzona w przedrostek forEach mówiący nam o tym, że zostanie wykonana na każdym elemencie alokacji. Po pomyślnym wykonaniu operacji, należy przenieść dane z alokacji do naszej bitmapy (lub nowo utworzonej), a następnie zniszczyć wszystkie utworzone obiekty, aby zwolnić pamięć.

RenderScript jest bardzo przydatnym narzędziem, które oszczędzi nam zabawy z NDK, a jednocześnie pozwoli osiągnąć wysoką wydajność. Dzięki jego uniwersalności, może zostać wykorzystany w wielu elementach naszych aplikacji. Zachęcam do zagłębienia się w temat.

Grzesiek

Grzesiek Lipski

Profesjonalnym rozwojem aplikacji na Androida zajmuje się od 9 lat. Lubi nowoczesne rozwiązania i zawsze zwraca uwagę na dobrą jakość kodu. Grzesiek chętnie testuje nowe możliwości, dlatego tworzy aplikacje również we Flutterze, a po godzinach próbuje swoich sił przy tworzeniu gier w Unity3D. Kiedy akurat nie programuje, gra w squasha albo robi fotografie dronem.

Dowiedz się więcej

Wycena projektu

Opowiedz nam o swoim projekcie i napisz, jak możemy Ci pomóc.

Dlaczego warto rozwijać z nami projekty?

Logo Mobile Trends Awards

Mobile Trends Awards 2021

Wygrana w kategorii
ŻYCIE CODZIENNE

Nagroda Legalnych Bukmacherów

Nagroda Legalnych Bukmacherów 2019

Najlepsza aplikacja mobilna

Mobile Trends Awards logo

Mobile Trends Awards 2020

Nominacja w kategorii
SPORT I REKREACJA

23

opinie klientów

Clutch logo