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ör att följa denna handledning måste du ha:
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);
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));
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.
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:
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;
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);
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:
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.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);" + "";
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
.
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:
glBindFramebuffer
att skapa ett namngivet rambuffertobjekt (kallas ofta FBO).glUseProgram
att börja använda det program som vi bara kopplade till.GL_BLEND
till glDisable
för att inaktivera blandning av färger under återgivning.glGetAttribLocation
för att få ett handtag till variablerna en position
och aTexPosition
nämnd i vertex-skuggarkoden.glGetUniformLocation
för att få ett handtag till konstanten uTexture
nämnt i fragment-skuggarkoden.glVertexAttribPointer
att associera en position
och aTexPosition
handtag med verticesBuffer
och den textureBuffer
respektive.glBindTexture
att binda texturen (skickad som ett argument till dra
metod) till fragmentskäraren.GLSurfaceView
använder sig av glClear
.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 ();
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:
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.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.
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:
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.