Slik bruker du Android Media Effects med OpenGL ES

Android-rammene for Media Effects gjør at utviklere enkelt kan bruke mange imponerende visuelle effekter på bilder og videoer. Siden rammen bruker GPU til å utføre alle bildebehandlingsoperasjoner, kan den bare akseptere OpenGL-teksturer som inngang. I denne opplæringen skal du lære hvordan du bruker OpenGL ES 2.0 til å konvertere en trekkbar ressurs til en tekstur og deretter bruke rammen til å bruke ulike effekter på det.

Forutsetninger

For å følge denne opplæringen må du ha:

  • en IDE som støtter Android Application Development. Hvis du ikke har en, får du den nyeste versjonen av Android Studio fra Android Developer-nettstedet.
  • en enhet som kjører Android 4.0 + og har en GPU som støtter OpenGL ES 2.0.
  • en grunnleggende forståelse av OpenGL.

1. Sette opp OpenGL ES Miljø

Trinn 1: Opprett en GLSurfaceView

Hvis du vil vise OpenGL-grafikk i appen din, må du bruke en GLSurfaceView gjenstand. Som alle andre Utsikt, du kan legge den til en Aktivitet eller Fragment ved å definere det i en layout XML-fil eller ved å lage en forekomst av det i kode.

I denne opplæringen skal du ha en GLSurfaceView objekt som den eneste Utsikt i din Aktivitet. Derfor er det enklere å lage det i kode. Når du er opprettet, send det til setContentView metode slik at den fyller hele skjermen. Din Aktivitet's onCreate metoden skal se slik ut:

beskyttet ugyldig onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); GLSurfaceView view = ny GLSurfaceView (dette); setContentView (syn); 

Fordi Media Effects-rammen bare støtter OpenGL ES 2.0 eller høyere, må du sende verdien 2 til setEGLContextClientVersion metode.

view.setEGLContextClientVersion (2);

For å være sikker på at GLSurfaceView gjør innholdet bare når det er nødvendig, passere verdien RENDERMODE_WHEN_DIRTY til setRenderMode metode.

view.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);

Trinn 2: Opprett en Renderer

EN GLSurfaceView.Renderer er ansvarlig for å tegne innholdet i GLSurfaceView.

Lag en ny klasse som implementerer GLSurfaceView.Renderer grensesnitt. Jeg skal ringe denne klassen EffectsRenderer. Etter å ha lagt til en konstruktør og overstyrer alle grensesnittets metoder, bør klassen se slik ut:

offentlig klasse EffectsRenderer implementerer GLSurfaceView.Renderer public EffectsRenderer (Kontekst kontekst) super ();  @Override public void onSurfaceCreated (GL10 gl, EGLConfig config)  @Override public void onSurfaceChanged (GL10 gl, int bredde, int høyde)  @Override public void onDrawFrame (GL10 gl) 

Gå tilbake til din Aktivitet og ring til setRenderer metode slik at GLSurfaceView bruker den tilpassede rendereren.

view.setRenderer (new EffectsRenderer (dette));

Trinn 3: Rediger manifestet

Hvis du planlegger å publisere appen din på Google Play, legger du til følgende i AndroidManifest.xml:

Dette sørger for at appen din kun kan installeres på enheter som støtter OpenGL ES 2.0. OpenGL-miljøet er nå klart.

2. Opprette et OpenGL-plan

Trinn 1: Definer vertikaler

De GLSurfaceView kan ikke vise et bilde direkte. Bildet må konverteres til en tekstur og brukes først til en OpenGL-form. I denne opplæringen skal vi lage et 2D-plan som har fire hjørner. For enkelhets skyld, la oss gjøre det til et torg. Lag en ny klasse, Torget, å representere torget.

offentlig klasse Square 

Standard OpenGL koordinatsystemet har sin opprinnelse i sentrum. Som et resultat, koordinatene til de fire hjørnene på torget vårt, hvis sider er to enheter lenge, vil være:

  • nederst til venstre på (-1, -1)
  • nederst til høyre på (1, -1)
  • øverste høyre hjørne ved (1, 1)
  • øverste venstre hjørne ved (-1, 1)

Alle objektene vi tegner ved hjelp av OpenGL, skal bestå av trekanter. For å tegne plassen trenger vi to trekanter med en felles kant. Dette betyr at koordinatene til trianglene vil være:

trekant 1: (-1, -1), (1, -1) og (-1, 1)
trekant 2: (1, -1), (-1, 1) og (1, 1)

Lage en flyte array for å representere disse punktene.

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

For å kartlegge tekstur på torget, må du angi koordinatene til tekstene i teksturen. Teksturer følger et koordinatsystem der verdien av y-koordinaten øker etter hvert som du går høyere. Opprett en annen matrise for å representere stikkene på tekstur.

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

Trinn 2: Opprett bufferobjekter

Arrays av koordinater må konverteres til bytebuffere før OpenGL kan bruke dem. La oss erklære disse buffere først.

private FloatBuffer verticesBuffer; privat FloatBuffer textureBuffer;

Skriv koden for å initialisere disse buffere i en ny metode som kalles initializeBuffers. Bruke ByteBuffer.allocateDirect metode for å lage buffer. Fordi a flyte bruker 4 byte, må du formere størrelsen på arrayene med verdien 4.

Deretter bruker du ByteBuffer.nativeOrder for å bestemme byteordren til den underliggende innfødte plattformen, og angi rekkefølgen til bufferne til den verdien. Bruke asFloatBuffer metode for å konvertere ByteBuffer forekommer i a FloatBuffer. Etter FloatBuffer er opprettet, bruk sette Metode for å laste opp arrayet i bufferen. Til slutt bruker du stilling metode for å sikre at bufferen er lest fra begynnelsen.

Innholdet i initializeBuffers metoden skal se slik ut:

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

Trinn 3: Opprett Shaders

Det er på tide å skrive dine egne shaders. Shaders er bare enkle C-programmer som drives av GPUen for å behandle hvert enkelt toppunkt. For denne opplæringen må du opprette to shaders, en vertex shader og en fragment shader.

C-koden for vertex shader er:

attributt vec4 aPosition; attributt vec2 aTexPosition; varierende vec2 vTexPosition; void main () gl_Position = aPosition; vTexPosition = aTexPosition; ;

C-koden for fragmentskader er:

presisjon mediump float; uniform sampler2D uTexture; varierende vec2 vTexPosition; void main () gl_FragColor = texture2D (uTexture, vTexPosition); ;

Hvis du allerede kjenner OpenGL, bør denne koden være kjent for deg, fordi den er vanlig på alle plattformer. Hvis du ikke gjør det, for å forstå disse programmene må du referere til OpenGL-dokumentasjonen. Her er en kort forklaring for å komme i gang:

  • Vertex shader er ansvarlig for å tegne de enkelte hjørner. en posisjon er en variabel som vil bli bundet til FloatBuffer som inneholder koordinatene til punktene. på samme måte, aTexPosition er en variabel som vil bli bundet til FloatBuffer som inneholder koordinatene til tekstur. gl_Position er en innebygd OpenGL-variabel og representerer posisjonen til hvert vertex. De vTexPosition er en varierende variabel, hvis verdi bare overføres til fragmentskader.
  • I denne opplæringen er fragment shader ansvarlig for å fargelegge torget. Den plukker opp farger fra tekstur ved hjelp av texture2D metode og tilordner dem til fragmentet ved hjelp av en innebygd variabel som heter gl_FragColor.

Skyggekoden må være representert som string objekter i klassen.

privat endelig streng vertexShaderCode = "attributt vec4 aPosition;" + "attributt vec2 aTexPosition;" + "varierende vec2 vTexPosition;" + "void main () " + "gl_Position = aPosition;" + "vTexPosition = aTexPosition;" + ""; privat endelig String fragmentShaderCode = "presisjon mediump float;" + "uniform sampler2D uTexture;" + "varierende vec2 vTexPosition;" + "void main () " + "gl_FragColor = texture2D (uTexture, vTexPosition);" + "";

Trinn 4: Opprett et program

Opprett en ny metode som heter initializeProgram å lage et OpenGL-program etter å ha kompilert og koblet shaders.

Bruk glCreateShader å skape et shaderobjekt og returnere en referanse til det i form av en int. For å opprette en vertex shader, pass verdien GL_VERTEX_SHADER til det. På samme måte, for å opprette en fragment shader, passere verdien GL_FRAGMENT_SHADER til det. Neste bruk glShaderSource å knytte den tilhørende skyggekoden med skyggeren. Bruk glCompileShader å kompilere skyggekoden.

Etter å ha samlet begge shaders, opprett et nytt program ved hjelp av glCreateProgram. Akkurat som  glCreateShader, dette returnerer også en int som referanse til programmet. Anrop glAttachShader å feste shaders til programmet. Til slutt, ring glLinkProgram å koble programmet.

Metoden din og tilhørende variabler skal se slik ut:

privat int vertexShader; privat int fragmentShader; privat int program; privat tomrom initialiseringsprogram () 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 har kanskje lagt merke til at OpenGL-metodene (metodene prefikset med gl) tilhører klassen GLES20. Dette skyldes at vi bruker OpenGL ES 2.0. Hvis du ønsker å bruke en høyere versjon, må du bruke klassene GLES30 eller GLES31.

Trinn 5: Tegn plassen

Opprett en ny metode som heter tegne å faktisk tegne torget ved hjelp av vinkler og shaders vi definerte tidligere.

Her er hva du trenger å gjøre i denne metoden:

  1. Bruk glBindFramebuffer å opprette et navngitt rammebufferobjekt (ofte kalt FBO).
  2. Bruk glUseProgram å begynne å bruke programmet vi nettopp har koblet til.
  3. Pass verdien GL_BLEND til glDisable for å deaktivere blanding av farger under gjenoppretting.
  4. Bruk glGetAttribLocation å få et håndtak til variablene en posisjon og aTexPosition nevnt i vertex shader-koden.
  5. Bruk glGetUniformLocation å få et håndtak til konstanten uTexture nevnt i fragment shader-koden.
  6. Bruke glVertexAttribPointer å knytte en posisjon og aTexPosition håndterer med verticesBuffer og textureBuffer henholdsvis.
  7. Bruk glBindTexture å binde tekstur (passert som et argument til tegne metode) til fragment shader.
  8. Fjern innholdet i GLSurfaceView ved hjelp av glClear.
  9. Til slutt bruker du glDrawArrays metode for faktisk å tegne de to trekanter (og dermed kvadratet).

Koden for tegne metoden skal se slik ut:

offentlig tomromstegning (int tekstur) 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, tekstur); 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);  

Legg til en konstruktør i klassen for å initialisere buffere og programmet når objektet ble opprettet.

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

3. Rendering av OpenGL-plan og tekstur

For tiden gjør vår gjengivelse ingenting. Vi må endre det slik at det kan gjøre planet vi opprettet i de foregående trinnene.

Men først, la oss lage en bitmap. Legg til et bilde på prosjektet ditt res / teikne mappe. Filen jeg bruker kalles forest.jpg. Bruke BitmapFactory å konvertere bildet til en bitmap gjenstand. Lagre også dimensjonene til bitmap objekt i separate variabler.

Endre konstruktøren til EffectsRenderer klassen slik at den har følgende innhold:

privat Bitmap-bilde; privat int photoWidth, photoHeight; offentlig EffectsRenderer (kontekst kontekst) super (); photo = BitmapFactory.decodeResource (context.getResources (), R.drawable.forest); photoWidth = photo.getWidth (); photoHeight = photo.getHeight (); 

Opprett en ny metode som heter generateSquare å konvertere bitmapet til en tekstur og initialisere a Torget gjenstand. Du vil også trenge en rekke heltal for å holde referanser til OpenGL-teksturer. Bruk glGenTextures å initialisere arrayen og glBindTexture for å aktivere tekstur på indeksen 0.

Deretter bruker du glTexParameteri å angi forskjellige egenskaper som bestemmer hvordan teksten gjengis:

  • Sett GL_TEXTURE_MIN_FILTER (minifunksjonsfunksjonen) og GL_TEXTURE_MAG_FILTER (forstørrelsesfunksjonen) til GL_LINEAR for å sørge for at tekstur ser jevnt ut, selv når den strekkes eller krympes.
  • Sett GL_TEXTURE_WRAP_S og GL_TEXTURE_WRAP_T til GL_CLAMP_TO_EDGE slik at tekstur aldri blir gjentatt.

Til slutt bruker du texImage2D metode for å kartlegge bitmap til tekstur. Gjennomføringen av generateSquare metoden skal se slik ut:

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

Når dimensjonene av GLSurfaceView endre, onSurfaceChanged metode av renderer er kalt. Her er hvor du må ringe glViewPort å angi de nye dimensjonene til visningsporten. Også, ring glClearColor å male GLSurfaceView svart. Neste, ring generateSquare å reinitialisere teksturer og flyet.

@Override offentlig tomrum påSurfaceChanged (GL10 gl, int bredde, int høyde) GLES20.glViewport (0,0, bredde, høyde); GLES20.glClearColor (0,0,0,1); generateSquare (); 

Til slutt, ring til Torget objektets tegne metode inne i onDrawFrame metode av renderer.

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

Du kan nå kjøre appen din og se bildet du valgte å bli gjengitt som en OpenGL-tekstur på et fly.

4. Bruke Media Effects Framework

Den komplekse koden vi skrev til nå var bare en forutsetning for å bruke Media Effects-rammeverket. Nå er det dags å begynne å bruke rammen selv. Legg til følgende felt i din renderer klasse.

privat EffectContext effectContext; privat effekt effekt;

Initialiser effectContext feltet ved å bruke EffectContext.createWithCurrentGlContext. Det er ansvarlig for å administrere informasjonen om de visuelle effektene i en OpenGL-kontekst. For å optimalisere ytelsen, bør dette bare kalles én gang. Legg til følgende kode i begynnelsen av din onDrawFrame metode.

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

Å skape en effekt er veldig enkel. Bruke effectContext å opprette en EffectFactory og bruk EffectFactory å opprette en Effekt gjenstand. En gang en Effekt Objektet er tilgjengelig, du kan ringe søke om og send en referanse til den opprinnelige teksten til den, i vårt tilfelle er det teksturer [0], sammen med en referanse til et tomt teksturobjekt, i vårt tilfelle er det teksturer [1]. Etter søke om Metoden kalles, teksturer [1] vil inneholde resultatet av Effekt.

For eksempel, for å opprette og bruke gråtoner effekt, her er koden du må skrive:

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

Ring denne metoden i onDrawFrame og passere teksturer [1] til Torget objektets tegne metode. Din onDrawFrame metoden skal ha følgende kode:

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

De utgivelse Metoden brukes til å frigjøre alle ressurser som en Effekt. Når du kjører appen, bør du se følgende resultat:

Du kan bruke samme kode for å bruke andre effekter. For eksempel, her er koden for å bruke dokumentar effekt:

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

Resultatet ser slik ut:

Noen effekter tar parametere. For eksempel har lysstyrkejusteringseffekten a lysstyrke parameter som tar a flyte verdi. Du kan bruke setParameter for å endre verdien av en hvilken som helst parameter. Følgende kode viser deg hvordan du bruker den:

privat tomt lysstyrkeEffect () EffectFactory fabrikk = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_BRIGHTNESS); effect.setParameter ("lysstyrke", 2f); effect.apply (teksturer [0], photoWidth, photoHeight, teksturer [1]); 

Effekten vil gjøre at appen gir følgende resultat:

Konklusjon

I denne opplæringen har du lært hvordan du bruker Media Effects Framework til å bruke ulike effekter på bildene dine. Mens du gjør det, lærte du også å tegne et fly ved hjelp av OpenGL ES 2.0 og bruke ulike teksturer på den.

Rammen kan brukes på både bilder og videoer. I tilfelle av videoer, må du bare bruke effekten til de enkelte rammene av videoen i onDrawFrame metode.

Du har allerede sett tre effekter i denne opplæringen og rammen har dusinvis mer for deg å eksperimentere med. Hvis du vil vite mer om dem, kan du se på Android Developer's nettsted.