Il framework di effetti multimediali di Android consente agli sviluppatori di applicare facilmente molti effetti visivi impressionanti a foto e video. Poiché il framework utilizza la GPU per eseguire tutte le operazioni di elaborazione delle immagini, può accettare solo trame OpenGL come input. In questo tutorial, imparerai come usare OpenGL ES 2.0 per convertire una risorsa estraibile in una texture e poi usare la struttura per applicare vari effetti ad essa.
Per seguire questo tutorial, devi avere:
GLSurfaceView
Per visualizzare la grafica OpenGL nella tua app, devi usare a GLSurfaceView
oggetto. Come qualsiasi altro vista
, puoi aggiungerlo a un Attività
o Frammento
definendolo in un file XML di layout o creando un'istanza di esso nel codice.
In questo tutorial, avrai a GLSurfaceView
oggetto come unico vista
nel tuo Attività
. Pertanto, la sua creazione nel codice è più semplice. Una volta creato, passalo al setContentView
metodo in modo che riempia l'intero schermo. Il tuo Attività
'S onCreate
il metodo dovrebbe assomigliare a questo:
protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); GLSurfaceView view = new GLSurfaceView (questo); setContentView (vista);
Poiché il framework degli effetti multimediali supporta solo OpenGL ES 2.0 o versioni successive, passa il valore 2
al setEGLContextClientVersion
metodo.
view.setEGLContextClientVersion (2);
Per assicurarsi che il GLSurfaceView
restituisce il suo contenuto solo quando necessario, passa il valore RENDERMODE_WHEN_DIRTY
al setRenderMode
metodo.
view.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);
UN GLSurfaceView.Renderer
è responsabile per il disegno dei contenuti del GLSurfaceView
.
Crea una nuova classe che implementa il GLSurfaceView.Renderer
interfaccia. Ho intenzione di chiamare questa classe EffectsRenderer
. Dopo aver aggiunto un costruttore e aver annullato tutti i metodi dell'interfaccia, la classe dovrebbe apparire così:
public class EffectsRenderer implementa 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)
Torna al tuo Attività
e chiama il setRenderer
metodo in modo che il GLSurfaceView
usa il renderizzatore personalizzato.
view.setRenderer (new EffectsRenderer (this));
Se hai intenzione di pubblicare la tua app su Google Play, aggiungi quanto segue a AndroidManifest.xml:
Questo assicura che la tua app possa essere installata solo su dispositivi che supportano OpenGL ES 2.0. L'ambiente OpenGL è ora pronto.
Il GLSurfaceView
non è possibile visualizzare una foto direttamente. La foto deve essere convertita in una texture e applicata prima a una forma OpenGL. In questo tutorial, creeremo un piano 2D con quattro vertici. Per semplicità, facciamo un quadrato. Crea una nuova classe, Piazza
, rappresentare il quadrato.
public class Square
Il sistema di coordinate OpenGL predefinito ha la sua origine al centro. Di conseguenza, le coordinate dei quattro angoli della nostra piazza, di cui sono i lati due unità lungo, sarà:
Tutti gli oggetti che disegniamo usando OpenGL dovrebbero essere fatti di triangoli. Per disegnare il quadrato, abbiamo bisogno di due triangoli con un bordo comune. Ciò significa che le coordinate dei triangoli saranno:
triangolo 1: (-1, -1), (1, -1) e (-1, 1)
triangle 2: (1, -1), (-1, 1) e (1, 1)
Creare un galleggiante
array per rappresentare questi vertici.
vertici float privati [] = -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f,;
Per mappare la trama sul quadrato, è necessario specificare le coordinate dei vertici della trama. Le trame seguono un sistema di coordinate in cui il valore della coordinata y aumenta man mano che si sale. Crea un altro array per rappresentare i vertici della trama.
private float textureVertices [] = 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f;
Le matrici di coordinate devono essere convertite in buffer di byte prima che OpenGL possa usarle. Dichiariamo prima questi buffer.
private FloatBuffer verticesBuffer; private FloatBuffer textureBuffer;
Scrivi il codice per inizializzare questi buffer in un nuovo metodo chiamato initializeBuffers
. Utilizzare il ByteBuffer.allocateDirect
metodo per creare il buffer. Perché a galleggiante
usi 4 byte, è necessario moltiplicare la dimensione degli array con il valore 4.
Quindi, usa ByteBuffer.nativeOrder
per determinare l'ordine dei byte della piattaforma nativa sottostante e impostare l'ordine dei buffer su quel valore. Utilizzare il asFloatBuffer
metodo per convertire il ByteBuffer
istanza in a FloatBuffer
. Dopo il FloatBuffer
viene creato, usa il mettere
metodo per caricare la matrice nel buffer. Infine, usa il posizione
metodo per assicurarsi che il buffer sia letto dall'inizio.
Il contenuto del initializeBuffers
il metodo dovrebbe assomigliare a questo:
private void initializeBuffers () ByteBuffer buff = ByteBuffer.allocateDirect (vertices.length * 4); buff.order (ByteOrder.nativeOrder ()); verticesBuffer = buff.asFloatBuffer (); verticesBuffer.put (vertici); verticesBuffer.position (0); buff = ByteBuffer.allocateDirect (textureVertices.length * 4); buff.order (ByteOrder.nativeOrder ()); textureBuffer = buff.asFloatBuffer (); textureBuffer.put (textureVertices); textureBuffer.position (0);
È tempo di scrivere i tuoi stessi shader. Gli shader non sono altro che semplici programmi C gestiti dalla GPU per elaborare ogni singolo vertice. Per questo tutorial, devi creare due ombreggiatori, uno shader di vertici e uno shader di frammenti.
Il codice C per vertex shader è:
attributo vec4 aPosition; attributo vec2 aTexPosition; variando vec2 vTexPosition; void main () gl_Position = aPosition; vTexPosition = aTexPosition; ;
Il codice C per lo shader di frammenti è:
galleggiante mediocre di precisione; uniforme sampler2D uTexture; variando vec2 vTexPosition; void main () gl_FragColor = texture2D (uTexture, vTexPosition); ;
Se conosci già OpenGL, questo codice dovrebbe esserti familiare perché è comune su tutte le piattaforme. In caso contrario, per comprendere questi programmi è necessario fare riferimento alla documentazione OpenGL. Ecco una breve spiegazione per iniziare:
una posizione
è una variabile che sarà associata al FloatBuffer
che contiene le coordinate dei vertici. allo stesso modo, aTexPosition
è una variabile che sarà associata al FloatBuffer
che contiene le coordinate della trama. GL_POSITION
è una variabile OpenGL incorporata e rappresenta la posizione di ciascun vertice. Il vTexPosition
è un variando
variabile, il cui valore viene semplicemente passato allo shader del frammento.Texture2D
metodo e li assegna al frammento utilizzando una variabile incorporata denominata gl_FragColor
.Il codice shader deve essere rappresentato come Stringa
oggetti nella classe.
private final String vertexShaderCode = "attributo vec4 aPosition;" + "attributo vec2 aTexPosition;" + "variando vec2 vTexPosition;" + "void main () " + "gl_Position = aPosition;" + "vTexPosition = aTexPosition;" + ""; private final String fragmentShaderCode = "precision mediump float;" + "uniforme sampler2D uTexture;" + "variando vec2 vTexPosition;" + "void main () " + "gl_FragColor = texture2D (uTexture, vTexPosition);" + "";
Crea un nuovo metodo chiamato initializeProgram
per creare un programma OpenGL dopo aver compilato e collegato gli shader.
Uso glCreateShader
per creare un oggetto shader e restituire un riferimento ad esso sotto forma di un int
. Per creare un vertex shader, passa il valore GL_VERTEX_SHADER
ad esso. Allo stesso modo, per creare uno shader di frammenti, passare il valore GL_FRAGMENT_SHADER
ad esso. Prossimo uso glShaderSource
associare il codice shader appropriato allo shader. Uso glCompileShader
compilare il codice dello shader.
Dopo aver compilato entrambi gli shader, crea un nuovo programma usando glCreateProgram
. Proprio come glCreateShader
, anche questo restituisce un int
come riferimento al programma. Chiamata glAttachShader
per allegare gli shader al programma. Infine, chiama glLinkProgram
collegare il programma.
Il tuo metodo e le variabili associate dovrebbero assomigliare a questo:
private int vertexShader; private int fragmentShader; programma privato int; 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);
Potresti aver notato che i metodi OpenGL (i metodi con il prefisso gl
) appartengono alla classe GLES20
. Questo perché stiamo usando OpenGL ES 2.0. Se desideri utilizzare una versione più alta, dovrai utilizzare le classi GLES30
o GLES31
.
Crea un nuovo metodo chiamato disegnare
disegnare effettivamente il quadrato usando i vertici e gli shader definiti in precedenza.
Ecco cosa devi fare in questo metodo:
glBindFramebuffer
creare un oggetto buffer frame con nome (spesso chiamato FBO).glUseProgram
per iniziare a utilizzare il programma che abbiamo appena collegato.GL_BLEND
a glDisable
per disabilitare la fusione dei colori durante il rendering.glGetAttribLocation
per ottenere un controllo sulle variabili una posizione
e aTexPosition
menzionato nel codice vertex shader.glGetUniformLocation
per ottenere una maniglia per la costante uTexture
menzionato nel codice shader del frammento.glVertexAttribPointer
associare il una posizione
e aTexPosition
maniglie con il verticesBuffer
e il textureBuffer
rispettivamente.glBindTexture
legare la trama (passata come argomento al file disegnare
metodo) allo shader del frammento.GLSurfaceView
utilizzando glClear
.glDrawArrays
metodo per disegnare effettivamente i due triangoli (e quindi il quadrato).Il codice per il disegnare
il metodo dovrebbe assomigliare a questo:
public void draw (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, texture); 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);
Aggiungi un costruttore alla classe per inizializzare i buffer e il programma al momento della creazione dell'oggetto.
public Square () initializeBuffers (); initializeProgram ();
Al momento, il nostro renderer non fa nulla. Abbiamo bisogno di cambiarlo in modo che possa rendere il piano che abbiamo creato nei passaggi precedenti.
Ma prima, creiamo un Bitmap
. Aggiungi una foto al tuo progetto res / drawable cartella. Viene chiamato il file che sto usando forest.jpg. Utilizzare il BitmapFactory
convertire la foto in a Bitmap
oggetto. Inoltre, memorizzare le dimensioni del Bitmap
oggetto in variabili separate.
Cambia il costruttore del EffectsRenderer
classe in modo che abbia il seguente contenuto:
foto bitmap privata; private int photoWidth, photoHeight; public EffectsRenderer (Context context) super (); photo = BitmapFactory.decodeResource (context.getResources (), R.drawable.forest); photoWidth = photo.getWidth (); photoHeight = photo.getHeight ();
Crea un nuovo metodo chiamato generateSquare
per convertire la bitmap in una texture e inizializzare a Piazza
oggetto. Avrai anche bisogno di una serie di numeri interi per conservare i riferimenti alle trame OpenGL. Uso glGenTextures
inizializzare la matrice e glBindTexture
attivare la texture all'indice 0
.
Quindi, usa glTexParameteri
per impostare varie proprietà che decidono come viene resa la texture:
GL_TEXTURE_MIN_FILTER
(la funzione minifying) e il GL_TEXTURE_MAG_FILTER
(la funzione di ingrandimento) a GL_LINEAR
per assicurarti che la texture sia liscia, anche quando è allungata o ridotta.GL_TEXTURE_WRAP_S
e GL_TEXTURE_WRAP_T
a GL_CLAMP_TO_EDGE
in modo che la trama non venga mai ripetuta.Infine, usa il texImage2D
metodo per mappare il Bitmap
alla trama L'implementazione del generateSquare
il metodo dovrebbe assomigliare a questo:
int texture private [] = new int [2]; piazza privata; private void generateSquare () GLES20.glGenTextures (2, texture, 0); GLES20.glBindTexture (GLES20.GL_TEXTURE_2D, textures [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 ();
Ogni volta che le dimensioni del GLSurfaceView
cambiare il onSurfaceChanged
metodo del Renderer
è chiamato. Ecco dove devi chiamare glViewport
per specificare le nuove dimensioni della vista. Inoltre, chiama glClearColor
dipingere il GLSurfaceView
nero. Quindi, chiama generateSquare
per reinizializzare le trame e il piano.
@Override public void onSurfaceChanged (GL10 gl, int width, int height) GLES20.glViewport (0,0, larghezza, altezza); GLES20.glClearColor (0,0,0,1); generateSquare ();
Infine, chiama il Piazza
oggetto di disegnare
metodo all'interno del onDrawFrame
metodo del Renderer
.
@Override public void onDrawFrame (GL10 gl) square.draw (textures [0]);
Ora puoi eseguire la tua app e vedere la foto che hai scelto di essere resa come una texture OpenGL su un piano.
Il codice complesso che abbiamo scritto fino ad ora era solo un prerequisito per utilizzare il framework degli effetti multimediali. È giunto il momento di iniziare a utilizzare il framework stesso. Aggiungi i seguenti campi al tuo Renderer
classe.
effetto EffectContext privatoContext; effetto effetto privato;
Inizializza il effectContext
campo usando il EffectContext.createWithCurrentGlContext
. È responsabile della gestione delle informazioni sugli effetti visivi all'interno di un contesto OpenGL. Per ottimizzare le prestazioni, questo dovrebbe essere chiamato una sola volta. Aggiungi il seguente codice all'inizio del tuo onDrawFrame
metodo.
if (effectContext == null) effectContext = EffectContext.createWithCurrentGlContext ();
Creare un effetto è molto semplice. Utilizzare il effectContext
creare un EffectFactory
e usa il EffectFactory
creare un Effetto
oggetto. Una volta un Effetto
l'oggetto è disponibile, puoi chiamare applicare
e passare un riferimento alla trama originale ad esso, nel nostro caso lo è texture [0]
, insieme a un riferimento a un oggetto texture vuoto, nel nostro caso lo è texture [1]
. Dopo il applicare
il metodo è chiamato, texture [1]
conterrà il risultato del Effetto
.
Ad esempio, per creare e applicare il in scala di grigi effetto, ecco il codice che devi scrivere:
private void grayScaleEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_GRAYSCALE); effect.apply (textures [0], photoWidth, photoHeight, texture [1]);
Chiama questo metodo onDrawFrame
e passare texture [1]
al Piazza
oggetto di disegnare
metodo. Il tuo onDrawFrame
il metodo dovrebbe avere il seguente codice:
@Override public void onDrawFrame (GL10 gl) if (effectContext == null) effectContext = EffectContext.createWithCurrentGlContext (); if (effect! = null) effect.release (); grayScaleEffect (); square.draw (texture [1]);
Il pubblicazione
il metodo è utilizzato per liberare tutte le risorse detenute da un Effetto
. Quando esegui l'app, dovresti vedere il seguente risultato:
Puoi usare lo stesso codice per applicare altri effetti. Ad esempio, ecco il codice per applicare il documentario effetto:
private void documentaryEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_DOCUMENTARY); effect.apply (textures [0], photoWidth, photoHeight, texture [1]);
Il risultato è simile a questo:
Alcuni effetti prendono parametri. Ad esempio, l'effetto di regolazione della luminosità ha a luminosità
parametro che accetta a galleggiante
valore. Puoi usare setParameter
per cambiare il valore di qualsiasi parametro. Il seguente codice mostra come usarlo:
private void brightnessEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_BRIGHTNESS); effect.setParameter ("brightness", 2f); effect.apply (textures [0], photoWidth, photoHeight, texture [1]);
L'effetto renderà la tua app rendere il seguente risultato:
In questo tutorial, hai appreso come utilizzare Media Effects Framework per applicare vari effetti alle tue foto. Mentre lo facevi, hai anche imparato a disegnare un piano usando OpenGL ES 2.0 e ad applicare varie trame.
Il framework può essere applicato sia a foto che a video. In caso di video, devi semplicemente applicare l'effetto ai singoli fotogrammi del video in onDrawFrame
metodo.
Hai già visto tre effetti in questo tutorial e il framework ha dozzine di cose in più per farti sperimentare. Per saperne di più su di loro, fare riferimento al sito Web dello sviluppatore Android.