Så här använder du Android Media Effects med OpenGL ES

Android: s Media Effects-ram tillåter utvecklare att enkelt applicera massor av imponerande visuella effekter på foton och videoklipp. Eftersom ramen använder GPU för att utföra alla sina bildbehandlingsoperationer, kan den bara acceptera OpenGL-texturer som ingång. I denna handledning kommer du att lära dig hur du använder OpenGL ES 2.0 för att konvertera en dragbar resurs till en konsistens och använd sedan ramverket för att tillämpa olika effekter på det.

förutsättningar

För att följa denna handledning måste du ha:

  • en IDE som stöder Android Application Development. Om du inte har en, hämta den senaste versionen av Android Studio från Android Developer-webbplatsen.
  • en enhet som kör Android 4.0+ och har en GPU som stöder OpenGL ES 2.0.
  • en grundläggande förståelse för OpenGL.

1. Ställa in OpenGL ES Miljö

Steg 1: Skapa en GLSurfaceView

För att visa OpenGL-grafik i din app måste du använda en GLSurfaceView objekt. Som alla andra Se, du kan lägga till den i en Aktivitet eller Fragment genom att definiera den i en layout XML-fil eller genom att skapa en förekomst av den i kod.

I denna handledning kommer du att ha en GLSurfaceView objekt som det enda Se i din Aktivitet. Därför är det enklare att skapa det i kod. När du har skapat, skicka den till setContentView metod så att den fyller hela skärmen. Din Aktivitet's onCreate Metoden ska se ut så här:

skyddad tomgång onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); GLSurfaceView view = ny GLSurfaceView (detta); setContentView (vy); 

Eftersom ramen för Media Effects endast stöder OpenGL ES 2.0 eller högre, överför värdet 2 till setEGLContextClientVersion metod.

view.setEGLContextClientVersion (2);

För att se till att GLSurfaceView gör innehållet endast vid behov, överför värdet RENDERMODE_WHEN_DIRTY till setRenderMode metod.

view.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);

Steg 2: Skapa en Renderer

en GLSurfaceView.Renderer ansvarar för att dra innehållet i GLSurfaceView.

Skapa en ny klass som implementerar GLSurfaceView.Renderer gränssnitt. Jag ska ringa den här klassen EffectsRenderer. Efter att ha lagt till en konstruktör och överväger alla metoder i gränssnittet, bör klassen se så ut:

public class EffectsRenderer implementerar GLSurfaceView.Renderer public EffectsRenderer (Context context) super ();  @Override public void onSurfaceCreated (GL10 gl, EGLConfig config)  @Override public void onSurfaceChanged (GL10 gl, int bredd, int höjd)  ​​@Override public void onDrawFrame (GL10 gl) 

Gå tillbaka till din Aktivitet och ringa till setRenderer metod så att GLSurfaceView använder den anpassade renderaren.

view.setRenderer (new EffectsRenderer (this));

Steg 3: Redigera manifestet

Om du planerar att publicera din app på Google Play, lägg till följande till AndroidManifest.xml:

Detta gör att din app bara kan installeras på enheter som stöder OpenGL ES 2.0. OpenGL-miljön är nu klar.

2. Skapa ett OpenGL-plan

Steg 1: Definiera vertices

De GLSurfaceView kan inte visa ett foto direkt. Bilden måste konverteras till en textur och appliceras först på en OpenGL-form. I denna handledning skapar vi ett 2D-plan som har fyra hörn. För enkelhetens skull, låt oss göra det en torg. Skapa en ny klass, Fyrkant, att representera torget.

offentlig klass Square 

Standard OpenGL-koordinatsystemet har sitt ursprung i centrum. Som ett resultat, koordinaterna för de fyra hörnen på vårt torg, vars sidor är två enheter länge kommer att vara:

  • nedre vänstra hörnet vid (-1, -1)
  • nederst till höger vid (1, -1)
  • högra högra hörnet vid (1, 1)
  • övre vänstra hörnet vid (-1, 1)

Alla objekt som vi använder med OpenGL ska bestå av trianglar. För att rita torget behöver vi två trianglar med en gemensam kant. Detta innebär att trianglarnas koordinater kommer att vara:

triangel 1: (-1, -1), (1, -1) och (-1, 1)
triangel 2: (1, -1), (-1, 1) och (1, 1)

Skapa en flyta array för att representera dessa hörn.

privata float-vertices [] = -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f,;

För att kartlägga texturen på torget måste du ange koordinaterna för textornas toppunkter. Texturer följer ett koordinatsystem där värdet på y-koordinaten ökar när du går högre. Skapa en annan matris för att representera texturerna.

private float textureVertices [] = 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f;

Steg 2: Skapa buffertobjekt

Sammanställningsraderna måste konverteras till bytebuffertar innan OpenGL kan använda dem. Låt oss deklarera dessa buffertar först.

privat FloatBuffer verticesBuffer; privat FloatBuffer textureBuffer;

Skriv koden för att initiera dessa buffertar i en ny metod som heter initializeBuffers. Använd ByteBuffer.allocateDirect metod för att skapa bufferten. Eftersom a flyta användningar 4 byte måste du multiplicera storleken på arrayerna med värdet 4.

Använd sedan ByteBuffer.nativeOrder för att bestämma byteordningen för den underliggande inbyggda plattformen och ställa in buffertarnas ordning till det värdet. Använd asFloatBuffer Metod för att konvertera ByteBuffer instans i a FloatBuffer. Efter FloatBuffer är skapad, använd sätta metod för att ladda arrayen i bufferten. Slutligen, använd placera metod för att se till att bufferten läses från början.

Innehållet i initializeBuffers Metoden ska se ut så här:

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

Steg 3: Skapa Shaders

Det är dags att skriva egna shaders. Shaders är inget annat än enkla C-program som drivs av GPU för att bearbeta varje enskilt vertex. För denna handledning måste du skapa två shaders, en vertex shader och en fragment shader.

C-koden för vertex shader är:

attribut vec4 aPosition; attribut vec2 aTexPosition; varierande vec2 vTexPosition; void main () gl_Position = aPosition; vTexPosition = aTexPosition; ;

C-koden för fragmentskärningen är:

precision mediump float; enhetlig sampler2D uTexture; varierande vec2 vTexPosition; void main () gl_FragColor = texture2D (uTexture, vTexPosition); ;

Om du redan känner till OpenGL ska den här koden vara bekant för dig eftersom den är vanlig på alla plattformar. Om du inte gör det för att förstå dessa program måste du referera till OpenGL-dokumentationen. Här är en kort förklaring för att komma igång:

  • Vertex shader ansvarar för att dra de enskilda vertikalerna. en position är en variabel som kommer att vara bunden till FloatBuffer som innehåller koordinaterna för punkterna. Liknande, aTexPosition är en variabel som kommer att vara bunden till FloatBuffer som innehåller strukturens koordinater. gl_Position är en inbyggd OpenGL-variabel och representerar positionen för varje vertex. De vTexPosition är en varierande variabel, vars värde helt enkelt vidarebefordras till fragmentskäraren.
  • I denna handledning är fragmentskäraren ansvarig för att färglägga torget. Det plockar upp färger från texturen med hjälp av texture2D metod och tilldelar dem till fragmentet med hjälp av en inbyggd variabel som heter gl_FragColor.

Shader-koden måste representeras som Sträng objekt i klassen.

privat sista sträng vertexShaderCode = "attribut vec4 aPosition;" + "attribut vec2 aTexPosition;" + "varierande vec2 vTexPosition;" + "void main () " + "gl_Position = aPosition;" + "vTexPosition = aTexPosition;" + ""; privat sista sträng fragmentShaderCode = "precision mediump float;" + "enhetlig sampler2D uTexture;" + "varierande vec2 vTexPosition;" + "void main () " + "gl_FragColor = texture2D (uTexture, vTexPosition);" + "";

Steg 4: Skapa ett program

Skapa en ny metod som heter initializeProgram att skapa ett OpenGL-program efter sammansättning och länkning av shadersna.

Använda sig av glCreateShader att skapa ett shaderobjekt och returnera en referens till det i form av en int. För att skapa en vertex shader, överför värdet GL_VERTEX_SHADER till det. På samma sätt, för att skapa en fragmentskärare, överför värdet GL_FRAGMENT_SHADER till det. Nästa användning glShaderSource att associera den lämpliga skakningskoden med skuggaren. Använda sig av glCompileShader för att kompilera shader-koden.

Efter att ha sammanställt båda shaders, skapa ett nytt program med glCreateProgram. Precis som  glCreateShader, detta returnerar också en int som en hänvisning till programmet. Ring upp glAttachShader att fästa shadersna till programmet. Slutligen ring glLinkProgram att länka programmet.

Din metod och de därtill hörande variablerna ska se ut så här:

privat int vertexShader; privat int fragmentShader; privata intprogram 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); program = GLES20.glCreateProgram (); GLES20.glAttachShader (program, vertexShader); GLES20.glAttachShader (program, fragmentShader); GLES20.glLinkProgram (program);  

Du kanske har märkt att OpenGL-metoderna (metoderna prefixed med gl) tillhör klassen GLES20. Detta beror på att vi använder OpenGL ES 2.0. Om du vill använda en högre version måste du använda klasserna GLES30 eller GLES31.

Steg 5: Rita torget

Skapa en ny metod som heter dra att faktiskt rita torget med hjälp av de vertices och shaders vi definierade tidigare.

Här är vad du behöver göra på den här metoden:

  1. Använda sig av glBindFramebuffer att skapa ett namngivet rambuffertobjekt (kallas ofta FBO).
  2. Använda sig av glUseProgram att börja använda det program som vi bara kopplade till.
  3. Passera värdet GL_BLEND till glDisable för att inaktivera blandning av färger under återgivning.
  4. Använda sig av glGetAttribLocation för att få ett handtag till variablerna en position och aTexPosition nämnd i vertex-skuggarkoden.
  5. Använda sig av glGetUniformLocation för att få ett handtag till konstanten uTexture nämnt i fragment-skuggarkoden.
  6. Använd glVertexAttribPointer att associera en position och aTexPosition handtag med verticesBuffer och den textureBuffer respektive.
  7. Använda sig av glBindTexture att binda texturen (skickad som ett argument till dra metod) till fragmentskäraren.
  8. Rensa innehållet i GLSurfaceView använder sig av glClear.
  9. Slutligen, använd glDrawArrays metod att faktiskt rita de två trianglarna (och därmed torget).

Koden för dra Metoden ska se ut så här:

offentlig tomrumsdragning (int textur) GLES20.glBindFramebuffer (GLES20.GL_FRAMEBUFFER, 0); GLES20.glUseProgram (program); GLES20.glDisable (GLES20.GL_BLEND); int positionHandle = GLES20.glGetAttribLocation (program, "aPosition"); int textureHandle = GLES20.glGetUniformLocation (program, "uTexture"); int texturePositionHandle = GLES20.glGetAttribLocation (program, "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, textur); 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);  

Lägg till en konstruktör i klassen för att initiera buffertarna och programmet när objekt skapades.

Public Square () initializeBuffers (); initializeProgram (); 

3. Rendering av OpenGL-planen och texturen

För närvarande gör vår renare ingenting. Vi behöver ändra det så att det kan göra planet vi skapade i de tidigare stegen.

Men först, låt oss skapa en Bitmap. Lägg till något foto i ditt projekt res / drawable mapp. Filen jag använder kallas forest.jpg. Använd BitmapFactory att konvertera fotot till en Bitmap objekt. Spara även dimensionerna av Bitmap objekt i separata variabler.

Ändra konstruktören av EffectsRenderer klass så att den har följande innehåll:

privat Bitmap-bild; privat int photoWidth, photoHeight; public EffectsRenderer (kontext sammanhang) super (); photo = BitmapFactory.decodeResource (context.getResources (), R.drawable.forest); photoWidth = photo.getWidth (); photoHeight = photo.getHeight (); 

Skapa en ny metod som heter generateSquare att konvertera bitmappen till en textur och initiera en Fyrkant objekt. Du behöver också en rad heltal för att hålla referenser till OpenGL-texturerna. Använda sig av glGenTextures att initiera arrayen och glBindTexture för att aktivera texturen i indexet 0.

Använd sedan glTexParameteri att ställa in olika egenskaper som bestämmer hur texturen görs:

  • Uppsättning GL_TEXTURE_MIN_FILTER (minifieringsfunktionen) och GL_TEXTURE_MAG_FILTER (förstoringsfunktionen) till GL_LINEAR För att se till att texturen ser jätte ut, även om den sträcker sig eller krympas.
  • Uppsättning GL_TEXTURE_WRAP_S och GL_TEXTURE_WRAP_T till GL_CLAMP_TO_EDGE så att texturen aldrig upprepas.

Slutligen, använd texImage2D Metod för att kartlägga Bitmap till texturen. Genomförandet av generateSquare Metoden ska se ut så här:

privata int texturer [] = new int [2]; privat torget; privat void generateSquare () GLES20.glGenTextures (2, texturer, 0); GLES20.glBindTexture (GLES20.GL_TEXTURE_2D, texturer [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); kvadrat = nytt kvadrat (); 

När dimensionerna av GLSurfaceView ändra onSurfaceChanged metod för Renderer kallas. Här är du där du måste ringa glViewPort för att ange visningsportens nya dimensioner. Ring också glClearColor att måla GLSurfaceView svart. Nästa, ring generateSquare att omstrukturera texturerna och planet.

@Override public void onSurfaceChanged (GL10 gl, int bredd, int höjd) GLES20.glViewport (0,0, bredd, höjd); GLES20.glClearColor (0,0,0,1); generateSquare (); 

Slutligen ringa till Fyrkant objekt dra metod inuti onDrawFrame metod för Renderer.

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

Du kan nu köra din app och se det foto du valt att bli gjord som en OpenGL-struktur på ett plan.

4. Använda Media Effects Framework

Den komplexa koden vi skrev fram till nu var bara en förutsättning för att använda ramen för Media Effects. Det är dags att börja använda ramverket själv. Lägg till följande fält i din Renderer klass.

privat EffectContext effectContext; privat effekt effekt

Initiera effectContext fält med hjälp av EffectContext.createWithCurrentGlContext. Det ansvarar för att hantera informationen om de visuella effekterna i ett OpenGL-kontext. För att optimera prestanda ska detta endast kallas en gång. Lägg till följande kod i början av din onDrawFrame metod.

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

Att skapa en effekt är väldigt enkel. Använd effectContext att skapa en EffectFactory och använd EffectFactory att skapa en Effekt objekt. En gång en Effekt Objektet är tillgängligt, du kan ringa tillämpa och skicka en hänvisning till den ursprungliga texturen till den, i vårt fall är det texturer [0], tillsammans med en hänvisning till ett tomt texturobjekt, i vårt fall är det texturer [1]. Efter tillämpa Metoden heter, texturer [1] kommer att innehålla resultatet av Effekt.

Till exempel, för att skapa och tillämpa gråskale effekt, här är koden du måste skriva:

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

Ring denna metod i onDrawFrame och passera texturer [1] till Fyrkant objekt dra metod. Din onDrawFrame Metoden ska ha följande kod:

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

De släpp Metoden används för att frigöra alla resurser som innehas av en Effekt. När du kör appen ska du se följande resultat:

Du kan använda samma kod för att tillämpa andra effekter. Till exempel, här är koden för att tillämpa dokumentär effekt:

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

Resultatet ser ut så här:

Vissa effekter har parametrar. Exempelvis har ljusstyrningsjusteringseffekten a ljusstyrka parameter som tar a flyta värde. Du kan använda setParameter för att ändra värdet av någon parameter. Följande kod visar hur du använder den:

privat tomt ljusstyrkaEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_BRIGHTNESS); effect.setParameter ("ljusstyrka", 2f); effect.apply (texturer [0], photoWidth, photoHeight, texturer [1]); 

Effekten kommer att få din app att göra följande resultat:

Slutsats

I den här handledningen har du lärt dig hur du använder Media Effects Framework för att tillämpa olika effekter på dina foton. Medan du gjorde det lärde du dig också att rita ett plan med OpenGL ES 2.0 och tillämpa olika texturer på den.

Ramverket kan tillämpas på både bilder och videor. Vid videor behöver du bara tillämpa effekten på de enskilda ramarna i videon i onDrawFrame metod.

Du har redan sett tre effekter i denna handledning och ramverket har dussintals mer för att du ska experimentera med. Om du vill veta mer om dem, hänvisar du till Android Developer-webbplatsen.