Mit dem Media Effects-Framework von Android können Entwickler auf beeindruckende Weise visuelle Effekte auf Fotos und Videos anwenden. Da das Framework die GPU für alle Bildverarbeitungsvorgänge verwendet, kann es nur OpenGL-Texturen als Eingabe akzeptieren. In diesem Lernprogramm erfahren Sie, wie Sie mithilfe von OpenGL ES 2.0 eine Ressource mit Zeichenstruktur in eine Textur konvertieren und anschließend das Framework verwenden, um verschiedene Effekte darauf anzuwenden.
Um diesem Tutorial zu folgen, benötigen Sie:
GLSurfaceView
Um OpenGL-Grafiken in Ihrer App anzuzeigen, müssen Sie ein verwenden GLSurfaceView
Objekt. Wie jeder andere Aussicht
, Sie können es zu einem hinzufügen Aktivität
oder Fragment
Definieren Sie es in einer Layout-XML-Datei oder erstellen Sie eine Instanz davon im Code.
In diesem Tutorial haben Sie eine GLSurfaceView
Objekt als einziges Aussicht
in deiner Aktivität
. Daher ist die Erstellung im Code einfacher. Nach dem Erstellen übergeben Sie es an die setContentView
Methode, so dass es den gesamten Bildschirm ausfüllt. Ihre Aktivität
's onCreate
Methode sollte so aussehen:
protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); GLSurfaceView view = new GLSurfaceView (this); setContentView (Ansicht);
Da das Media Effects-Framework nur OpenGL ES 2.0 oder höher unterstützt, übergeben Sie den Wert 2
zum setEGLContextClientVersion
Methode.
view.setEGLContextClientVersion (2);
Um sicherzustellen, dass die GLSurfaceView
macht seinen Inhalt nur bei Bedarf wieder, übergeben Sie den Wert RENDERMODE_WHEN_DIRTY
zum setRenderMode
Methode.
view.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);
EIN GLSurfaceView.Renderer
ist verantwortlich für das Zeichnen des Inhalts der GLSurfaceView
.
Erstellen Sie eine neue Klasse, die das implementiert GLSurfaceView.Renderer
Schnittstelle. Ich werde diese Klasse anrufen EffectsRenderer
. Nachdem Sie einen Konstruktor hinzugefügt und alle Methoden der Schnittstelle überschrieben haben, sollte die Klasse folgendermaßen aussehen:
public class EffectsRenderer implementiert GLSurfaceView.Renderer public EffectsRenderer (Kontext-Kontext) super (); @Override public void onSurfaceCreated (GL10 gl, EGLConfig config) @Override public void onSurfaceChanged (GL10 gl, Breite, int Höhe) @Override public void onDrawFrame (GL10 gl)
Geh zurück zu deinem Aktivität
und rufen Sie an setRenderer
Methode, so dass die GLSurfaceView
verwendet den benutzerdefinierten Renderer.
view.setRenderer (neuer EffectsRenderer (this));
Wenn Sie Ihre App in Google Play veröffentlichen möchten, fügen Sie Folgendes zu hinzu AndroidManifest.xml:
Dadurch wird sichergestellt, dass Ihre App nur auf Geräten installiert werden kann, die OpenGL ES 2.0 unterstützen. Die OpenGL-Umgebung ist jetzt bereit.
Das GLSurfaceView
kann ein Foto nicht direkt anzeigen. Das Foto muss zuerst in eine Textur umgewandelt und auf eine OpenGL-Form angewendet werden. In diesem Lernprogramm erstellen wir eine 2D-Ebene mit vier Scheitelpunkten. Lassen Sie uns der Einfachheit halber ein Quadrat bilden. Erstellen Sie eine neue Klasse, Quadrat
, das Quadrat darstellen.
öffentliche Klasse Square
Das Standard-OpenGL-Koordinatensystem hat seinen Ursprung in der Mitte. Als Ergebnis sind die Koordinaten der vier Ecken unseres Quadrats, deren Seiten sind zwei Einheiten lang wird sein:
Alle Objekte, die wir mit OpenGL zeichnen, sollten aus Dreiecken bestehen. Um das Quadrat zu zeichnen, benötigen wir zwei Dreiecke mit einer gemeinsamen Kante. Dies bedeutet, dass die Koordinaten der Dreiecke sind:
Dreieck 1: (-1, -1), (1, -1) und (-1, 1)
Dreieck 2: (1, -1), (-1, 1) und (1, 1)
Ein ... kreieren schweben
Array, um diese Scheitelpunkte darzustellen.
Private Float-Knoten [] = -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f,;
Um die Textur auf das Quadrat abzubilden, müssen Sie die Koordinaten der Scheitelpunkte der Textur angeben. Texturen folgen einem Koordinatensystem, in dem der Wert der y-Koordinate steigt, je höher Sie stehen. Erstellen Sie ein anderes Array, um die Scheitelpunkte der Textur darzustellen.
private float textureVertices [] = 0f, 1f, 1f, 1f, 0f, 0f, 1f, 0f;
Die Koordinaten-Arrays müssen in Byte-Puffer umgewandelt werden, bevor OpenGL sie verwenden kann. Lassen Sie uns zuerst diese Puffer deklarieren.
private FloatBuffer verticesBuffer; privater FloatBuffer textureBuffer;
Schreiben Sie den Code, um diese Puffer in einer neuen aufgerufenen Methode zu initialisieren initializeBuffers
. Verwenden Sie die ByteBuffer.allocateDirect
Methode zum Erstellen des Puffers. Weil ein schweben
Verwendet 4 Bytes, müssen Sie die Größe der Arrays mit dem Wert multiplizieren 4.
Als nächstes verwenden ByteBuffer.nativeOrder
um die Byte-Reihenfolge der zugrunde liegenden systemeigenen Plattform zu bestimmen und die Reihenfolge der Puffer auf diesen Wert festzulegen. Verwenden Sie die asFloatBuffer
Methode zur Konvertierung der ByteBuffer
Beispiel in eine FloatBuffer
. Nach dem FloatBuffer
erstellt wird, verwenden Sie die stellen
Methode, um das Array in den Puffer zu laden. Verwenden Sie schließlich die Position
Methode, um sicherzustellen, dass der Puffer von Anfang an gelesen wird.
Der Inhalt der initializeBuffers
Methode sollte so aussehen:
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);
Es ist Zeit, eigene Shader zu schreiben. Shader sind nichts weiter als einfache C-Programme, die von der GPU ausgeführt werden, um jeden einzelnen Scheitelpunkt zu verarbeiten. Für dieses Lernprogramm müssen Sie zwei Shader, einen Vertex-Shader und einen Fragment-Shader erstellen.
Der C-Code für den Vertex-Shader lautet:
Attribut vec4 aPosition; Attribut vec2 aTexPosition; variierendes vec2 vTexPosition; void main () gl_Position = aPosition; vTexPosition = aTexPosition; ;
Der C-Code für den Fragment-Shader lautet:
Präzisions-Mediumschwimmer; einheitliche sampler2D uTexture; variierendes vec2 vTexPosition; void main () gl_FragColor = texture2D (uTexture, vTexPosition); ;
Wenn Sie OpenGL bereits kennen, sollte Ihnen dieser Code bekannt sein, da er auf allen Plattformen gleich ist. Wenn Sie diese Programme nicht verstehen, müssen Sie sich auf die OpenGL-Dokumentation beziehen. Hier ist eine kurze Erklärung, um den Einstieg zu erleichtern:
eine Position
ist eine Variable, die an das gebunden wird FloatBuffer
das enthält die Koordinaten der Scheitelpunkte. Ähnlich, aTexPosition
ist eine Variable, die an das gebunden wird FloatBuffer
das enthält die Koordinaten der Textur. gl_Position
ist eine integrierte OpenGL-Variable und repräsentiert die Position jedes Scheitelpunkts. Das vTexPosition
ist ein variierend
Variable, deren Wert einfach an den Fragment-Shader übergeben wird.texture2D
Methode und ordnet sie dem Fragment mithilfe einer eingebauten Variablen namens zu gl_FragColor
.Der Shader-Code muss als dargestellt werden String
Objekte in der Klasse.
private final String vertexShaderCode = "Attribut vec4 aPosition;" + "Attribut vec2 aTexPosition;" + "variierendes vec2 vTexPosition;" + "void main () " + "gl_Position = aPosition;" + "vTexPosition = aTexPosition;" + ""; private final String fragmentShaderCode = "precision mediump float;" + "einheitliche sampler2D uTexture;" + "variierendes vec2 vTexPosition;" + "void main () " + "gl_FragColor = texture2D (uTexture, vTexPosition);" + "";
Erstellen Sie eine neue Methode namens initializeProgram
Erstellen eines OpenGL-Programms nach dem Kompilieren und Verknüpfen der Shader.
Benutzen glCreateShader
ein Shader-Objekt erstellen und einen Verweis darauf in Form eines zurückgeben int
. Übergeben Sie den Wert, um einen Vertex-Shader zu erstellen GL_VERTEX_SHADER
dazu Um einen Fragment-Shader zu erstellen, übergeben Sie den Wert entsprechend GL_FRAGMENT_SHADER
dazu Nächste Verwendung glShaderSource
um den entsprechenden Shader-Code mit dem Shader zu verknüpfen. Benutzen glCompileShader
um den Shader-Code zu kompilieren.
Nachdem Sie beide Shader kompiliert haben, erstellen Sie ein neues Programm mit glCreateProgram
. So wie glCreateShader
, auch das gibt ein zurück int
als Referenz zum Programm. Anruf glAttachShader
um die Shader an das Programm anzuhängen. Schließlich rufen Sie an glLinkProgram
um das Programm zu verlinken.
Ihre Methode und die zugehörigen Variablen sollten folgendermaßen aussehen:
private int vertexShader; private int fragmentShader; privates int-Programm; 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 (Programm, vertexShader); GLES20.glAttachShader (Programm, fragmentShader); GLES20.glLinkProgram (Programm);
Möglicherweise haben Sie festgestellt, dass die OpenGL-Methoden (die Methoden mit dem Präfix) beginnen gl
) gehören zur Klasse GLES20
. Dies liegt daran, dass wir OpenGL ES 2.0 verwenden. Wenn Sie eine höhere Version verwenden möchten, müssen Sie die Klassen verwenden GLES30
oder GLES31
.
Erstellen Sie eine neue Methode namens zeichnen
Um das Quadrat tatsächlich mit den zuvor definierten Scheitelpunkten und Shadern zu zeichnen.
Folgendes müssen Sie bei dieser Methode tun:
glBindFramebuffer
Erstellen eines benannten Frame-Pufferobjekts (häufig als FBO bezeichnet).glUseProgram
um das Programm zu verwenden, das wir gerade verlinkt haben.GL_BLEND
zu glDisable
um das Mischen von Farben während des Renderns zu deaktivieren.glGetAttribLocation
um die Variablen in den Griff zu bekommen eine Position
und aTexPosition
im Vertex-Shader-Code erwähnt.glGetUniformLocation
die Konstante in den Griff bekommen uTextur
im Fragment-Shader-Code erwähnt.glVertexAttribPointer
das verbinden eine Position
und aTexPosition
behandelt mit dem verticesBuffer
und das textureBuffer
beziehungsweise.glBindTexture
um die Textur zu binden (wird als Argument an die übergeben zeichnen
method) an den Fragment-Shader.GLSurfaceView
mit glClear
.glDrawArrays
Methode, um tatsächlich die beiden Dreiecke (und damit das Quadrat) zu zeichnen.Der Code für die zeichnen
Methode sollte so aussehen:
public void draw (int texture) GLES20.glBindFramebuffer (GLES20.GL_FRAMEBUFFER, 0); GLES20.glUseProgram (Programm); GLES20.glDisable (GLES20.GL_BLEND); int positionHandle = GLES20.glGetAttribLocation (Programm, "aPosition"); int textureHandle = GLES20.glGetUniformLocation (Programm, "uTexture"); int texturePositionHandle = GLES20.glGetAttribLocation (Programm, "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);
Fügen Sie der Klasse einen Konstruktor hinzu, um die Puffer und das Programm zum Zeitpunkt der Objekterstellung zu initialisieren.
public Square () initializeBuffers (); initializeProgram ();
Derzeit macht unser Renderer nichts. Wir müssen das ändern, damit es die Ebene rendern kann, die wir in den vorherigen Schritten erstellt haben.
Aber lassen Sie uns zuerst eine Bitmap
. Fügen Sie Ihrem Projekt ein beliebiges Foto hinzu res / drawable Mappe. Die Datei, die ich verwende, wird aufgerufen wald.jpg. Verwenden Sie die BitmapFactory
um das Foto in eine zu konvertieren Bitmap
Objekt. Speichern Sie auch die Abmessungen der Bitmap
Objekt in separaten Variablen.
Ändern Sie den Konstruktor von EffectsRenderer
Klasse, so dass es den folgenden Inhalt hat:
privates Bitmap-Foto; private int photoWidth, photoHeight; public EffectsRenderer (Kontext-Kontext) super (); photo = BitmapFactory.decodeResource (context.getResources (), R.drawable.forest); photoWidth = photo.getWidth (); photoHeight = photo.getHeight ();
Erstellen Sie eine neue Methode namens generateSquare
um die Bitmap in eine Textur zu konvertieren und zu initialisieren a Quadrat
Objekt. Sie benötigen außerdem ein Array von Ganzzahlen, um Verweise auf die OpenGL-Texturen aufzunehmen. Benutzen glGenTextures
das Array zu initialisieren und glBindTexture
um die Textur beim Index zu aktivieren 0
.
Als nächstes verwenden glTexParameteri
um verschiedene Eigenschaften festzulegen, die entscheiden, wie die Textur gerendert wird:
GL_TEXTURE_MIN_FILTER
(die Minifizierungsfunktion) und die GL_TEXTURE_MAG_FILTER
(die Vergrößerungsfunktion) auf GL_LINEAR
um sicherzustellen, dass die textur glatt aussieht, auch wenn sie gedehnt oder geschrumpft ist.GL_TEXTURE_WRAP_S
und GL_TEXTURE_WRAP_T
zu GL_CLAMP_TO_EDGE
so dass die Textur nie wiederholt wird.Verwenden Sie schließlich die texImage2D
Methode zur Zuordnung der Bitmap
auf die Textur Die Umsetzung der generateSquare
Methode sollte so aussehen:
private int textures [] = neues int [2]; privater Platz; 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); Quadrat = neues Quadrat ();
Wann immer die Abmessungen des GLSurfaceView
ändere das onSurfaceChanged
Methode der Renderer
wird genannt. Hier müssen Sie anrufen glViewPort
um die neuen Dimensionen des Ansichtsfensters anzugeben. Rufen Sie auch an glClearColor
malen die GLSurfaceView
schwarz. Rufen Sie an generateSquare
um die Texturen und die Ebene neu zu initialisieren.
@Override public void onSurfaceChanged (GL10 gl, int width, int height) GLES20.glViewport (0,0, width, height); GLES20.glClearColor (0,0,0,1); generateSquare ();
Zum Schluss rufen Sie die Quadrat
Objekt ist zeichnen
Methode innerhalb der onDrawFrame
Methode der Renderer
.
@Override public void onDrawFrame (GL10 gl) square.draw (Texturen [0]);
Sie können jetzt Ihre App ausführen und das von Ihnen ausgewählte Foto als OpenGL-Textur auf einer Ebene anzeigen lassen.
Der komplexe Code, den wir bisher geschrieben haben, war nur eine Voraussetzung für die Verwendung des Media Effects-Frameworks. Es ist jetzt an der Zeit, das Framework selbst zu verwenden. Fügen Sie folgende Felder hinzu Renderer
Klasse.
private EffectContext effectContext; privater Effekteffekt;
Initialisieren Sie die effectContext
Feld mit der EffectContext.createWithCurrentGlContext
. Es ist für die Verwaltung der Informationen zu den visuellen Effekten in einem OpenGL-Kontext verantwortlich. Um die Leistung zu optimieren, sollte dies nur einmal aufgerufen werden. Fügen Sie den folgenden Code am Anfang Ihres ein onDrawFrame
Methode.
if (effectContext == null) effectContext = EffectContext.createWithCurrentGlContext ();
Einen Effekt zu erzeugen ist sehr einfach. Verwenden Sie die effectContext
ein erstellen EffectFactory
und benutze die EffectFactory
ein erstellen Bewirken
Objekt. Einmal ein Bewirken
Objekt ist verfügbar, Sie können anrufen sich bewerben
und geben Sie einen Verweis auf die ursprüngliche Textur an, in unserem Fall ist es das Texturen [0]
, zusammen mit einem Verweis auf ein leeres Texturobjekt, in unserem Fall ist es das Texturen [1]
. Nach dem sich bewerben
Methode wird aufgerufen, Texturen [1]
enthält das Ergebnis der Bewirken
.
Zum Beispiel, um das zu erstellen und anzuwenden Graustufen Effekt, hier ist der Code, den Sie schreiben müssen:
private void grayScaleEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_GRAYSCALE); effect.apply (Texturen [0], photoWidth, photoHeight, Texturen [1]);
Rufen Sie diese Methode in auf onDrawFrame
und weitergeben Texturen [1]
zum Quadrat
Objekt ist zeichnen
Methode. Ihre onDrawFrame
Methode sollte den folgenden Code haben:
@Override public void onDrawFrame (GL10 gl) if (effectContext == null) effectContext = EffectContext.createWithCurrentGlContext (); if (effect! = null) effect.release (); grayScaleEffect (); square.draw (Texturen [1]);
Das Veröffentlichung
Diese Methode wird verwendet, um alle Ressourcen eines Systems freizugeben Bewirken
. Wenn Sie die App ausführen, sollten Sie das folgende Ergebnis sehen:
Sie können den gleichen Code verwenden, um andere Effekte anzuwenden. Hier ist zum Beispiel der Code zum Anwenden von Dokumentarfilm bewirken:
private void documentaryEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_DOCUMENTARY); effect.apply (Texturen [0], photoWidth, photoHeight, Texturen [1]);
Das Ergebnis sieht so aus:
Einige Effekte benötigen Parameter. Zum Beispiel hat der Helligkeitseinstellungseffekt eine Helligkeit
Parameter, der a braucht schweben
Wert. Sie können verwenden setParameter
um den Wert eines Parameters zu ändern. Der folgende Code zeigt Ihnen, wie Sie es verwenden:
private void HelligkeitEffekt () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_BRIGHTNESS); effect.setParameter ("Helligkeit", 2f); effect.apply (Texturen [0], photoWidth, photoHeight, Texturen [1]);
Der Effekt bewirkt, dass Ihre App das folgende Ergebnis rendert:
In diesem Lernprogramm haben Sie gelernt, wie Sie mit Media Effects Framework verschiedene Effekte auf Ihre Fotos anwenden. Sie haben dabei auch gelernt, mit OpenGL ES 2.0 eine Ebene zu zeichnen und verschiedene Texturen darauf anzuwenden.
Das Framework kann sowohl auf Fotos als auch auf Videos angewendet werden. Bei Videos müssen Sie den Effekt einfach auf die einzelnen Frames des Videos in der Grafik anwenden onDrawFrame
Methode.
Sie haben bereits drei Effekte in diesem Lernprogramm gesehen, und das Framework hat Dutzende mehr, mit denen Sie experimentieren können. Weitere Informationen finden Sie auf der Android Developer-Website.