Hoe gebruik ik Android Media-effecten met OpenGL ES

Met het Media Effects-framework van Android kunnen ontwikkelaars eenvoudig tal van indrukwekkende visuele effecten toepassen op foto's en video's. Aangezien het framework de GPU gebruikt om al zijn bewerkingen voor beeldverwerking uit te voeren, kan het alleen OpenGL-texturen als invoer accepteren. In deze zelfstudie leer je hoe je OpenGL ES 2.0 gebruikt om een ​​aantrekbare bron om te zetten in een structuur en vervolgens het kader te gebruiken om verschillende effecten toe te passen.

voorwaarden

Als u deze zelfstudie wilt volgen, moet u beschikken over:

  • een IDE die de ontwikkeling van Android-apps ondersteunt. Als u er geen hebt, downloadt u de nieuwste versie van Android Studio via de website Android Developer.
  • een apparaat met Android 4.0+ en een GPU die OpenGL ES 2.0 ondersteunt.
  • een basiskennis van OpenGL.

1. De OpenGL ES-omgeving instellen

Stap 1: Maak een GLSurfaceView

Als u OpenGL-afbeeldingen in uw app wilt weergeven, moet u a. Gebruiken GLSurfaceView voorwerp. Zoals elk ander Uitzicht, je kunt het aan een toevoegen Activiteit of Fragment door het te definiëren in een lay-out XML-bestand of door er een exemplaar van te maken in code.

In deze tutorial zul je een GLSurfaceView object als de enige Uitzicht in uw Activiteit. Daarom is het eenvoudiger om het in code te maken. Eenmaal gemaakt, geeft u deze door aan de setContentView methode zodat deze het hele scherm vult. Jouw Activiteit's onCreate methode zou er als volgt uit moeten zien:

protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); GLSurfaceView-weergave = nieuwe GLSurfaceView (this); setContentView (view); 

Omdat het kader Media Effects alleen OpenGL ES 2.0 of hoger ondersteunt, geeft u de waarde door 2 naar de setEGLContextClientVersion methode.

view.setEGLContextClientVersion (2);

Om ervoor te zorgen dat de GLSurfaceView maakt de inhoud alleen wanneer nodig, geef de waarde door RENDERMODE_WHEN_DIRTY naar de setRenderMode methode.

view.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);

Stap 2: Maak een renderer

EEN GLSurfaceView.Renderer is verantwoordelijk voor het tekenen van de inhoud van de GLSurfaceView.

Maak een nieuwe klasse die de. Implementeert GLSurfaceView.Renderer interface. Ik ga deze klas bellen EffectsRenderer. Na het toevoegen van een constructor en het overschrijven van alle methoden van de interface, zou de klasse er als volgt uit moeten zien:

public class EffectsRenderer implementeert GLSurfaceView.Renderer public EffectsRenderer (context context) super ();  @Override public void onSurfaceCreated (GL10 gl, EGLConfig config)  @Override public void onSurfaceChanged (GL10 gl, int width, int height)  @Override public void onDrawFrame (GL10 gl) 

Ga terug naar jouw Activiteit en bel de setRenderer methode zodat de GLSurfaceView maakt gebruik van de aangepaste renderer.

view.setRenderer (nieuwe EffectsRenderer (this));

Stap 3: bewerk het manifest

Als u van plan bent uw app te publiceren op Google Play, voegt u het volgende toe aan AndroidManifest.xml:

Dit zorgt ervoor dat uw app alleen kan worden geïnstalleerd op apparaten die OpenGL ES 2.0 ondersteunen. De OpenGL-omgeving is nu klaar.

2. Een OpenGL-vliegtuig maken

Stap 1: Vertices definiëren

De GLSurfaceView kan een foto niet rechtstreeks weergeven. De foto moet eerst in een structuur worden omgezet en eerst op een OpenGL-vorm worden toegepast. In deze zelfstudie maken we een 2D-vlak met vier hoekpunten. Laten we het omwille van de eenvoud een vierkant maken. Maak een nieuwe klas, Plein, om het vierkant te vertegenwoordigen.

openbare klas Square 

Het standaard OpenGL-coördinatensysteem heeft zijn oorsprong in het midden. Dientengevolge, de coördinaten van de vier hoeken van ons vierkant, waarvan de zijkanten zijn twee eenheden lang, zal zijn:

  • linkerbenedenhoek bij (-1, -1)
  • rechter benedenhoek bij (1, -1)
  • rechterbovenhoek bij (1, 1)
  • linkerbovenhoek bij (-1, 1)

Alle objecten die we met OpenGL tekenen, moeten uit driehoeken bestaan. Om het vierkant te tekenen, hebben we twee driehoeken met een gemeenschappelijke rand nodig. Dit betekent dat de coördinaten van de driehoeken zijn:

driehoek 1: (-1, -1), (1, -1) en (-1, 1)
driehoek 2: (1, -1), (-1, 1) en (1, 1)

Maak een vlotter array om deze hoekpunten te vertegenwoordigen.

private float-hoekpunten [] = -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f,;

Als u de textuur op het vierkant wilt plaatsen, moet u de coördinaten van de hoekpunten van de textuur opgeven. Texturen volgen een coördinatensysteem waarin de waarde van de y-coördinaat toeneemt naarmate u hoger gaat. Maak een andere array om de hoekpunten van de textuur te vertegenwoordigen.

privé float textureVertices [] = 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f;

Stap 2: Bufferobjecten maken

De arrays van coördinaten moeten worden geconverteerd naar bytebuffers voordat OpenGL deze kan gebruiken. Laten we deze buffers eerst verklaren.

private FloatBuffer verticesBuffer; privé FloatBuffer-textuurBuffer;

Schrijf de code om deze buffers te initialiseren in een nieuwe methode genaamd initializeBuffers. Gebruik de ByteBuffer.allocateDirect methode om de buffer te maken. Omdat een vlotter toepassingen 4 bytes, moet u de grootte van de arrays vermenigvuldigen met de waarde 4.

Gebruik vervolgens ByteBuffer.nativeOrder om de bytevolgorde van het onderliggende native platform te bepalen en de volgorde van de buffers op die waarde in te stellen. Gebruik de asFloatBuffer methode om het te converteren ByteBuffer bijvoorbeeld in een FloatBuffer. Na de FloatBuffer is gemaakt, gebruik de leggen methode om de array in de buffer te laden. Gebruik tenslotte de positie methode om ervoor te zorgen dat de buffer vanaf het begin wordt gelezen.

De inhoud van de initializeBuffers methode zou er als volgt uit moeten zien:

private void initializeBuffers () ByteBuffer buff = ByteBuffer.allocateDirect (vertices.length * 4); buff.order (ByteOrder.nativeOrder ()); verticesBuffer = buff.asFloatBuffer (); verticesBuffer.put (vertices); verticesBuffer.position (0); buff = ByteBuffer.allocateDirect (textureVertices.length * 4); buff.order (ByteOrder.nativeOrder ()); textureBuffer = buff.asFloatBuffer (); textureBuffer.put (textureVertices); textureBuffer.position (0); 

Stap 3: maak Shaders

Het is tijd om je eigen shaders te schrijven. Shaders zijn niets anders dan eenvoudige C-programma's die door de GPU worden uitgevoerd om elk individueel hoekpunt te verwerken. Voor deze zelfstudie moet je twee shaders maken, een hoekpuntshader en een fragmentshader.

De C-code voor de hoekpuntshader is:

attribuut vec4 aPosition; kenmerk vec2 aTexPosition; variërende vec2 vTexPosition; void main () gl_Position = aPosition; vTexPosition = aTexPosition; ;

De C-code voor de fragmentshader is:

precisie mediump float; uniforme sampler2D uTextuur; variërende vec2 vTexPosition; void main () gl_FragColor = texture2D (uTexture, vTexPosition); ;

Als u OpenGL al kent, moet deze code u bekend voorkomen, omdat deze op alle platforms gebruikelijk is. Als u dit niet doet, moet u om deze programma's te begrijpen naar de OpenGL-documentatie verwijzen. Hier is een korte uitleg om aan de slag te gaan:

  • De vertex-arcering is verantwoordelijk voor het tekenen van de afzonderlijke hoekpunten. een positie is een variabele die zal worden gebonden aan de FloatBuffer die de coördinaten van de hoekpunten bevat. evenzo, aTexPosition is een variabele die gebonden zal zijn aan de FloatBuffer die de coördinaten van de textuur bevat. gl_Position is een ingebouwde OpenGL-variabele en vertegenwoordigt de positie van elke vertex. De vTexPosition is een wisselende variabele, waarvan de waarde eenvoudigweg wordt doorgegeven aan de fragmentshader.
  • In deze zelfstudie is de fragmentshader verantwoordelijk voor het inkleuren van het vierkant. Het pakt kleuren op uit de textuur met behulp van de texture2D methode en wijst ze toe aan het fragment met behulp van een ingebouwde variabele met de naam gl_FragColor.

De arceringscode moet worden weergegeven als Draad objecten in de klas.

private final String vertexShaderCode = "attribute vec4 aPosition;" + "attribute vec2 aTexPosition;" + "variërende vec2 vTexPosition;" + "void main () " + "gl_Position = aPosition;" + "vTexPosition = aTexPosition;" + ""; private final String fragmentShaderCode = "precision mediump float;" + "uniforme sampler2D uTexture;" + "variërende vec2 vTexPosition;" + "void main () " + "gl_FragColor = texture2D (uTexture, vTexPosition);" + "";

Stap 4: Maak een programma

Maak een nieuwe methode genaamd initializeProgram om een ​​OpenGL-programma te maken na het compileren en koppelen van de shaders.

Gebruik glCreateShader om een ​​arceringobject te maken en er een verwijzing naar terug te sturen in de vorm van een int. Als u een hoekpuntshader wilt maken, geeft u de waarde door GL_VERTEX_SHADER ernaar toe. Op dezelfde manier geeft u de waarde door om een ​​fragmentshader te maken GL_FRAGMENT_SHADER ernaar toe. Volgend gebruik glShaderSource om de juiste shadercode aan de arcering te koppelen. Gebruik glCompileShader om de shadercode te compileren.

Na het compileren van beide shaders, maakt u een nieuw programma met behulp van glCreateProgram. Net als  glCreateShader, ook dit geeft een int als een verwijzing naar het programma. telefoontje glAttachShader om de shaders aan het programma te hechten. Tot slot, bel glLinkProgram om het programma te linken.

Uw methode en de bijbehorende variabelen moeten er als volgt uitzien:

private int vertexShader; privé int fragmentShader; privé int-programma; private void initializeProgram () vertexShader = GLES20.glCreateShader (GLES20.GL_VERTEX_SHADER); GLES20.glShaderSource (vertexShader, vertexShaderCode); GLES20.glCompileShader (vertexShader); fragmentShader = GLES20.glCreateShader (GLES20.GL_FRAGMENT_SHADER); GLES20.glShaderSource (fragmentShader, fragmentShaderCode); GLES20.glCompileShader (fragmentShader); programma = GLES20.glCreateProgram (); GLES20.glAttachShader (programma, vertexShader); GLES20.glAttachShader (programma, fragmentShader); GLES20.glLinkProgram (programma);  

Je hebt misschien gemerkt dat de OpenGL-methoden (de methoden voorafgegaan door gl) behoren tot de klas GLES20. Dit komt omdat we OpenGL ES 2.0 gebruiken. Als u een hogere versie wilt gebruiken, moet u de klassen gebruiken GLES30 of GLES31.

Stap 5: Teken het vierkant

Maak een nieuwe methode genaamd trek om het vierkant daadwerkelijk te tekenen met de hoekpunten en shaders die we eerder hebben gedefinieerd.

Dit is wat u moet doen in deze methode:

  1. Gebruik glBindFramebuffer een benoemd framebufferobject maken (vaak FBO genoemd).
  2. Gebruik glUseProgram om het programma te gaan gebruiken dat we zojuist hebben gekoppeld.
  3. Geef de waarde door GL_BLEND naar glDisable om het mengen van kleuren tijdens het renderen uit te schakelen.
  4. Gebruik glGetAttribLocation om grip te krijgen op de variabelen een positie en aTexPosition genoemd in de vertex shader-code.
  5. Gebruik glGetUniformLocation om grip te krijgen op de constante uTexture genoemd in de fragment-arceringscode.
  6. Gebruik de glVertexAttribPointer om het te associëren een positie en aTexPosition handgrepen met de verticesBuffer en de textureBuffer respectievelijk.
  7. Gebruik glBindTexture om de textuur te binden (doorgegeven als een argument aan de trek methode) naar de fragmentshader.
  8. Wis de inhoud van de GLSurfaceView gebruik makend van glClear.
  9. Gebruik tenslotte de glDrawArrays methode om de twee driehoeken (en dus het vierkant) daadwerkelijk te tekenen.

De code voor de trek methode zou er als volgt uit moeten zien:

openbare ongeldige trekking (int texture) GLES20.glBindFramebuffer (GLES20.GL_FRAMEBUFFER, 0); GLES20.glUseProgram (programma); GLES20.glDisable (GLES20.GL_BLEND); int positionHandle = GLES20.glGetAttribLocation (programma, "aPosition"); int textureHandle = GLES20.glGetUniformLocation (programma, "uTexture"); int texturePositionHandle = GLES20.glGetAttribLocation (programma, "aTexPosition"); GLES20.glVertexAttribPointer (texturePositionHandle, 2, GLES20.GL_FLOAT, false, 0, textureBuffer); GLES20.glEnableVertexAttribArray (texturePositionHandle); GLES20.glActiveTexture (GLES20.GL_TEXTURE0); GLES20.glBindTexture (GLES20.GL_TEXTURE_2D, textuur); GLES20.glUniform1i (textureHandle, 0); GLES20.glVertexAttribPointer (positionHandle, 2, GLES20.GL_FLOAT, false, 0, verticesBuffer); GLES20.glEnableVertexAttribArray (positionHandle); GLES20.glClear (GLES20.GL_COLOR_BUFFER_BIT); GLES20.glDrawArrays (GLES20.GL_TRIANGLE_STRIP, 0, 4);  

Voeg een constructor toe aan de klasse om de buffers en het programma te initialiseren op het moment dat het object wordt gemaakt.

public Square () initializeBuffers (); initializeProgram (); 

3. Weergave van het OpenGL-vlak en de textuur

Momenteel doet onze renderer niets. We moeten dat veranderen zodat het het vliegtuig kan maken dat we in de vorige stappen hebben gemaakt.

Maar laten we eerst een maken Bitmap. Voeg een foto toe aan uw project res / betekenbare map. Het bestand dat ik gebruik wordt gebeld forest.jpg. Gebruik de BitmapFactory om de foto in een te converteren Bitmap voorwerp. Bewaar ook de afmetingen van de Bitmap object in afzonderlijke variabelen.

Wijzig de constructor van de EffectsRenderer klasse, zodat deze de volgende inhoud heeft:

privé bitmapfoto; private int photoWidth, photoHeight; public EffectsRenderer (context context) super (); photo = BitmapFactory.decodeResource (context.getResources (), R.drawable.forest); photoWidth = photo.getWidth (); photoHeight = photo.getHeight (); 

Maak een nieuwe methode genaamd generateSquare om de bitmap in een structuur om te zetten en a te initialiseren Plein voorwerp. U zult ook een array van gehele getallen nodig hebben om verwijzingen naar de OpenGL-texturen te behouden. Gebruik glGenTextures om de array te initialiseren en glBindTexture om de textuur bij index te activeren 0.

Gebruik vervolgens glTexParameteri om verschillende eigenschappen in te stellen die bepalen hoe de textuur wordt weergegeven:

  • set GL_TEXTURE_MIN_FILTER (de verkleinerfunctie) en de GL_TEXTURE_MAG_FILTER (de vergrotingsfunctie) naar GL_LINEAR om ervoor te zorgen dat de textuur er glad uitziet, zelfs wanneer deze uitgerekt of gekrompen is.
  • set GL_TEXTURE_WRAP_S en GL_TEXTURE_WRAP_T naar GL_CLAMP_TO_EDGE zodat de textuur nooit wordt herhaald.

Gebruik tenslotte de texImage2D methode om de kaart in kaart te brengen Bitmap naar de textuur. De implementatie van de generateSquare methode zou er als volgt uit moeten zien:

private int textures [] = nieuwe int [2]; privé Vierkant plein; private void generateSquare () GLES20.glGenTextures (2, textures, 0); GLES20.glBindTexture (GLES20.GL_TEXTURE_2D, texturen [0]); GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameteri (GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLUtils.texImage2D (GLES20.GL_TEXTURE_2D, 0, foto, 0); square = new Square (); 

Wanneer de afmetingen van de GLSurfaceView verander de onSurfaceChanged methode van de renderer wordt genoemd. Hier moet je bellen glViewport om de nieuwe dimensies van de viewport op te geven. Bel ook glClearColor om de te schilderen GLSurfaceView zwart. Bel vervolgens generateSquare om de texturen en het vlak opnieuw te initialiseren.

@Override public void onSurfaceChanged (GL10 gl, int width, int height) GLES20.glViewport (0,0, width, height); GLES20.glClearColor (0,0,0,1); generateSquare (); 

Tot slot belt u de Plein voorwerpen trek methode binnen de onDrawFrame methode van de renderer.

@Override public void onDrawFrame (GL10 gl) square.draw (textures [0]); 

U kunt nu uw app uitvoeren en zien dat de door u gekozen foto wordt weergegeven als een OpenGL-structuur in een vlak.

4. Het gebruik van het Media Effects Framework

De complexe code die we tot nu toe schreven, was slechts een vereiste om het kader voor media-effecten te gebruiken. Het is nu tijd om het framework zelf te gaan gebruiken. Voeg de volgende velden toe aan uw renderer klasse.

privé EffectContext effectContext; privé effect effect;

Initialiseer de effectContext veld met behulp van de EffectContext.createWithCurrentGlContext. Het is verantwoordelijk voor het beheer van de informatie over de visuele effecten in een OpenGL-context. Om de prestaties te optimaliseren, moet deze maar één keer worden aangeroepen. Voeg de volgende code toe aan het begin van uw onDrawFrame methode.

if (effectContext == null) effectContext = EffectContext.createWithCurrentGlContext (); 

Het creëren van een effect is heel eenvoudig. Gebruik de effectContext om een ​​te maken EffectFactory en gebruik de EffectFactory om een ​​te maken Effect voorwerp. Eens een Effect object is beschikbaar, u kunt bellen van toepassing zijn en geef een verwijzing naar de originele textuur door, in ons geval is dat zo texturen [0], samen met een verwijzing naar een leeg textuurobject, in ons geval is dat het geval textures [1]. Na de van toepassing zijn methode wordt genoemd, textures [1] bevat het resultaat van de Effect.

Bijvoorbeeld om het te maken en toe te passen grijstinten effect, hier is de code die je moet schrijven:

private void grayScaleEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_GRAYSCALE); effect.apply (texturen [0], photoWidth, photoHeight, textures [1]); 

Noem deze methode in onDrawFrame en ga voorbij textures [1] naar de Plein voorwerpen trek methode. Jouw onDrawFrame methode moet de volgende code hebben:

@Override public void onDrawFrame (GL10 gl) if (effectContext == null) effectContext = EffectContext.createWithCurrentGlContext ();  if (effect! = null) effect.release ();  grayScaleEffect (); square.draw (textures [1]); 

De vrijlating methode wordt gebruikt om alle bronnen vrij te maken die in het bezit zijn van een Effect. Wanneer u de app uitvoert, ziet u het volgende resultaat:

U kunt dezelfde code gebruiken om andere effecten toe te passen. Hier is bijvoorbeeld de code om de documentaire effect:

private void documentaryEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_DOCUMENTARY); effect.apply (texturen [0], photoWidth, photoHeight, textures [1]); 

Het resultaat ziet er als volgt uit:

Sommige effecten nemen parameters. Het helderheidsaanpassingseffect heeft bijvoorbeeld een helderheid parameter die een vlotter waarde. Je kunt gebruiken setParameter om de waarde van elke parameter te wijzigen. De volgende code laat zien hoe je het gebruikt:

private void brightnessEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_BRIGHTNESS); effect.setParameter ("brightness", 2f); effect.apply (texturen [0], photoWidth, photoHeight, textures [1]); 

Door het effect wordt uw app het volgende resultaat weergegeven:

Conclusie

In deze zelfstudie hebt u geleerd hoe u met het Media Effects Framework verschillende effecten op uw foto's kunt toepassen. Terwijl je dit deed, heb je ook geleerd hoe je een vliegtuig tekent met OpenGL ES 2.0 en verschillende texturen erop toepast.

Het raamwerk kan worden toegepast op zowel foto's als video's. In het geval van video's, moet je het effect gewoon toepassen op de individuele frames van de video in de onDrawFrame methode.

Je hebt al drie effecten in deze zelfstudie gezien en het raamwerk heeft nog tientallen extra effecten om mee te experimenteren. Raadpleeg de website van de Android-ontwikkelaar voor meer informatie over deze sites.