Cómo utilizar los efectos de medios de Android con OpenGL ES

El marco de efectos de medios de Android permite a los desarrolladores aplicar fácilmente muchos efectos visuales impresionantes a fotos y videos. Como el marco utiliza la GPU para realizar todas sus operaciones de procesamiento de imágenes, solo puede aceptar texturas OpenGL como entrada. En este tutorial, aprenderá cómo usar OpenGL ES 2.0 para convertir un recurso dibujable en una textura y luego usar el marco para aplicarle varios efectos..

Prerrequisitos

Para seguir este tutorial, necesitas tener:

  • Un IDE que soporta el desarrollo de aplicaciones para Android. Si no tiene una, obtenga la última versión de Android Studio en el sitio web de Android Developer.
  • un dispositivo que ejecuta Android 4.0+ y tiene una GPU que admite OpenGL ES 2.0.
  • una comprensión básica de OpenGL.

1. Configuración del entorno OpenGL ES

Paso 1: Crea una GLSurfaceView

Para mostrar los gráficos OpenGL en su aplicación, tiene que usar un GLSurfaceView objeto. Como cualquier otro Ver, puedes agregarlo a un Actividad o Fragmento definiéndolo en un archivo XML de diseño o creando una instancia del mismo en código.

En este tutorial, vas a tener un GLSurfaceView objeto como el único Ver en tus Actividad. Por lo tanto, crearlo en código es más simple. Una vez creado, pásalo al setContentView Método para que llene toda la pantalla. Tu Actividades onCreate El método debería verse así:

void protegido onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); GLSurfaceView view = new GLSurfaceView (esto); setContentView (ver); 

Debido a que el marco de Media Effects solo es compatible con OpenGL ES 2.0 o superior, pase el valor 2 al setEGLContextClientVersion método.

view.setEGLContextClientVersion (2);

Para asegurarse de que el GLSurfaceView renderiza su contenido solo cuando es necesario, pasa el valor RENDERMODE_WHEN_DIRTY al setRenderMode método.

view.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);

Paso 2: crear un renderizador

UNA GLSurfaceView.Renderer Es responsable de dibujar los contenidos de la GLSurfaceView.

Crear una nueva clase que implemente el GLSurfaceView.Renderer interfaz. Voy a llamar a esta clase EffectsRenderer. Después de agregar un constructor y anular todos los métodos de la interfaz, la clase debería tener este aspecto:

La clase pública EffectsRenderer implementa GLSurfaceView.Renderer public EffectsRenderer (contexto contextual) 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) 

Vuelve a tu Actividad y llamar al setRenderer método para que el GLSurfaceView utiliza el renderizador personalizado.

view.setRenderer (nuevo EffectsRenderer (this));

Paso 3: Editar el manifiesto

Si planea publicar su aplicación en Google Play, agregue lo siguiente a AndroidManifest.xml:

Esto asegura que su aplicación solo pueda instalarse en dispositivos que sean compatibles con OpenGL ES 2.0. El entorno OpenGL ya está listo.

2. Creando un plano OpenGL

Paso 1: Definir los vértices

los GLSurfaceView no se puede mostrar una foto directamente. La foto debe convertirse en una textura y aplicarse primero a una forma OpenGL. En este tutorial, crearemos un plano 2D que tiene cuatro vértices. Para simplificar, hagámoslo un cuadrado. Crear una nueva clase, Cuadrado, para representar el cuadrado.

clase publica cuadrada 

El sistema de coordenadas predeterminado de OpenGL tiene su origen en su centro. Como resultado, las coordenadas de las cuatro esquinas de nuestra plaza, cuyos lados son dos unidades largo, será:

  • esquina inferior izquierda en (-1, -1)
  • esquina inferior derecha en (1, -1)
  • esquina superior derecha en (1, 1)
  • esquina superior izquierda en (-1, 1)

Todos los objetos que dibujamos utilizando OpenGL deben estar formados por triángulos. Para dibujar el cuadrado, necesitamos dos triángulos con un borde común. Esto significa que las coordenadas de los triángulos serán:

triángulo 1: (-1, -1), (1, -1) y (-1, 1)
triángulo 2: (1, -1), (-1, 1) y (1, 1)

Crear un flotador matriz para representar estos vértices.

vértices flotantes privados [] = -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f,;

Para asignar la textura en el cuadrado, debe especificar las coordenadas de los vértices de la textura. Las texturas siguen un sistema de coordenadas en el que el valor de la coordenada y aumenta a medida que aumenta. Crea otra matriz para representar los vértices de la textura..

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

Paso 2: Crear objetos de búfer

Las matrices de coordenadas deben convertirse en búferes de bytes antes de que OpenGL pueda usarlos. Vamos a declarar estos buffers primero.

FloatBuffer privado verticesBuffer; FloatBuffer de textura privadaBuffer;

Escriba el código para inicializar estos buffers en un nuevo método llamado inicializarBuffers. Utilizar el ByteBuffer.allocateDirect Método para crear el búfer. Porque un flotador usos 4 bytes, debe multiplicar el tamaño de los arreglos con el valor 4.

A continuación, utilice ByteBuffer.nativeOrder para determinar el orden de bytes de la plataforma nativa subyacente y establecer el orden de los buffers en ese valor. Utilizar el comoFloatBuffer método para convertir el ByteBuffer instancia en un FloatBuffer. Después de la FloatBuffer es creado, usa el poner Método para cargar la matriz en el búfer. Finalmente, use el posición Método para asegurarse de que el búfer se lee desde el principio.

Los contenidos de la inicializarBuffers El método debería verse así:

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

Paso 3: Crear Shaders

Es hora de escribir tus propios shaders. Los Shaders no son más que simples programas en C que se ejecutan por la GPU para procesar cada vértice individual. Para este tutorial, tienes que crear dos sombreadores, un sombreado de vértice y un sombreador de fragmentos..

El código C para el sombreador de vértices es:

atributo vec4 aPosition; atributo vec2 aTexPosition; variable vec2 vTexPosition; void main () gl_Position = aPosition; vTexPosition = aTexPosition; ;

El código C para el sombreador de fragmentos es:

flotador mediump de precisión; muestreador uniforme2D uTextura; variable vec2 vTexPosition; void main () gl_FragColor = texture2D (uTexture, vTexPosition); ;

Si ya conoce OpenGL, este código le debe ser familiar porque es común en todas las plataformas. Si no lo hace, para comprender estos programas debe consultar la documentación de OpenGL. Aquí hay una breve explicación para comenzar:

  • El sombreador de vértices es responsable de dibujar los vértices individuales. una posición Es una variable que estará ligada a la FloatBuffer que contiene las coordenadas de los vértices. similar, aTexPosition Es una variable que estará ligada a la FloatBuffer que contiene las coordenadas de la textura. gl_Posición es una variable integrada de OpenGL y representa la posición de cada vértice. los vTexPosition es un variar variable, cuyo valor simplemente se pasa al fragmento shader.
  • En este tutorial, el fragmento de sombreado es responsable de colorear el cuadrado. Recoge los colores de la textura usando el textura2D Método y los asigna al fragmento utilizando una variable incorporada llamada gl_FragColor.

El código de sombreado debe representarse como Cuerda objetos en la clase.

Private final String vertexShaderCode = "attribute vec4 aPosition;" + "atributo vec2 aTexPosition;" + "variable vec2 vTexPosition"; + "void main () " + "gl_Position = aPosition;" + "vTexPosition = aTexPosition;" + ""; Private final String fragmentShaderCode = "precision mediump float;" + "muestreador uniforme2D uTextura;" + "variable vec2 vTexPosition"; + "void main () " + "gl_FragColor = texture2D (uTexture, vTexPosition);" + "";

Paso 4: Crear un programa

Crea un nuevo método llamado inicializar programa para crear un programa OpenGL después de compilar y vincular los shaders.

Utilizar glCreateShader para crear un objeto de sombreado y devolverle una referencia en forma de En t. Para crear un sombreador de vértice, pase el valor GL_VERTEX_SHADER lo. Del mismo modo, para crear un sombreador de fragmentos, pase el valor GL_FRAGMENT_SHADER lo. Siguiente uso glShaderSource para asociar el código de sombreado apropiado con el sombreador. Utilizar glCompileShader para compilar el código de sombreado.

Después de compilar ambos shaders, crea un nuevo programa usando glCreateProgram. Al igual que  glCreateShader, esto también devuelve un En t Como referencia al programa. Llamada glAttachShader para adjuntar los shaders al programa. Por fin llamar glLinkProgram para vincular el programa.

Su método y las variables asociadas deben tener este aspecto:

privado int vertexShader; privado int fragmentShader; programa int privado; 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); programa = GLES20.glCreateProgram (); GLES20.glAttachShader (programa, vertexShader); GLES20.glAttachShader (programa, fragmentShader); GLES20.glLinkProgram (programa);  

Es posible que haya notado que los métodos OpenGL (los métodos prefijados con gl) pertenecen a la clase GLES20. Esto es porque estamos utilizando OpenGL ES 2.0. Si desea utilizar una versión superior, deberá utilizar las clases Glos30 o GLES31.

Paso 5: Dibuja el cuadrado

Crea un nuevo método llamado dibujar para dibujar el cuadrado usando los vértices y sombreadores que definimos anteriormente.

Esto es lo que necesitas hacer en este método:

  1. Utilizar glBindFramebuffer para crear un objeto de búfer de cuadros con nombre (a menudo llamado FBO).
  2. Utilizar glUseProgram Para empezar a usar el programa que acabamos de enlazar..
  3. Pasar el valor GL_BLEND a glDisable para deshabilitar la mezcla de colores mientras se renderiza.
  4. Utilizar glGetAttribLocation para obtener un manejo de las variables una posición y aTexPosition mencionado en el código de sombreado de vértice.
  5. Utilizar glGetUniformLocation para obtener una manija a la constante uTextura mencionado en el código de sombreado de fragmentos.
  6. Utilizar el glVertexAttribPointer asociar el una posición y aTexPosition maneja con el verticesBuffer y el texturaBuffer respectivamente.
  7. Utilizar glBindTexture para unir la textura (pasada como un argumento a la dibujar Método) al fragmento shader.
  8. Borrar el contenido de la GLSurfaceView utilizando glClear.
  9. Finalmente, use el glDrawArrays Método para dibujar realmente los dos triángulos (y por lo tanto el cuadrado).

El código para el dibujar El método debería verse así:

sorteo público vacío (textura int) GLES20.glBindFramebuffer (GLES20.GL_FRAMEBUFFER, 0); GLES20.glUseProgram (programa); GLES20.glDisable (GLES20.GL_BLEND); int positionHandle = GLES20.glGetAttribLocation (program, "aPosition"); int textureHandle = GLES20.glGetUniformLocation (programa, "uTexture"); int texturePositionHandle = GLES20.glGetAttribLocation (programa, "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, textura); GLES20.glUniforme1i (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);  

Agregue un constructor a la clase para inicializar los buffers y el programa en el momento de la creación del objeto..

plaza pública () initializeBuffers (); initializeProgram (); 

3. Renderizando el plano y la textura de OpenGL

Actualmente, nuestro renderizador no hace nada. Necesitamos cambiar eso para que pueda renderizar el plano que creamos en los pasos anteriores.

Pero primero, vamos a crear una Mapa de bits. Agrega cualquier foto a tu proyecto res / drawable carpeta. El archivo que estoy usando se llama bosque.jpg. Utilizar el BitmapFactory para convertir la foto en una Mapa de bits objeto. Además, almacenar las dimensiones de la Mapa de bits objeto en variables separadas.

Cambiar el constructor de la EffectsRenderer clase para que tenga los siguientes contenidos:

Foto de mapa de bits privada; Privado int photoWidth, photoHeight; public EffectsRenderer (contexto de contexto) super (); photo = BitmapFactory.decodeResource (context.getResources (), R.drawable.forest); photoWidth = photo.getWidth (); photoHeight = photo.getHeight (); 

Crea un nuevo método llamado generarSquare para convertir el mapa de bits en una textura e inicializar una Cuadrado objeto. También necesitarás una matriz de enteros para contener referencias a las texturas de OpenGL. Utilizar glGenTextures para inicializar la matriz y glBindTexture para activar la textura en el índice 0.

A continuación, utilice glTexParameteri para establecer varias propiedades que decidan cómo se representa la textura:

  • Conjunto GL_TEXTURE_MIN_FILTER (la función minificadora) y la GL_TEXTURE_MAG_FILTER (la función de aumento) para GL_LINEAR para asegurarse de que la textura se vea suave, incluso cuando se estira o se encoge.
  • Conjunto GL_TEXTURE_WRAP_S y GL_TEXTURE_WRAP_T a GL_CLAMP_TO_EDGE Para que la textura nunca se repita..

Finalmente, use el texImage2D método para mapear el Mapa de bits a la textura. La implementación de la generarSquare El método debería verse así:

Texturas int privadas [] = int nuevo [2]; Plaza privada; private void generateSquare () GLES20.glGenTextures (2, texturas, 0); GLES20.glBindTexture (GLES20.GL_TEXTURE_2D, texturas [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 (); 

Siempre que las dimensiones de la GLSurfaceView cambiar el onSurfaceChanged método de la Renderizador se llama. Aquí es donde tienes que llamar glViewPort para especificar las nuevas dimensiones de la ventana gráfica. Además, llame glClearColor para pintar el GLSurfaceView negro. A continuación, llame generarSquare Reinicializar las texturas y el plano..

@Override public void onSurfaceChanged (GL10 gl, int width, int height) GLES20.glViewport (0,0, width, height); GLES20.glClearColor (0,0,0,1); generateSquare (); 

Finalmente, llame al Cuadrado objetos dibujar metodo dentro del onDrawFrame método de la Renderizador.

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

Ahora puede ejecutar su aplicación y ver que la foto que eligió se renderiza como una textura OpenGL en un plano.

4. Usando el Marco de Efectos de Medios

El código complejo que escribimos hasta ahora era solo un requisito previo para utilizar el marco de Media Effects. Ahora es el momento de comenzar a utilizar el marco en sí. Agregue los siguientes campos a su Renderizador clase.

Efecto de contexto privado Efecto de contexto; Efecto de efecto privado;

Inicializar el effectContext campo utilizando el EffectContext.createWithCurrentGlContext. Es responsable de administrar la información sobre los efectos visuales dentro de un contexto OpenGL. Para optimizar el rendimiento, esto debe llamarse solo una vez. Agregue el siguiente código al comienzo de su onDrawFrame método.

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

Crear un efecto es muy simple. Utilizar el effectContext para crear un EffectFactory y usar el EffectFactory para crear un Efecto objeto. Una vez que un Efecto El objeto está disponible, puedes llamar aplicar y pasarle una referencia a la textura original, en nuestro caso es texturas [0], junto con una referencia a un objeto de textura en blanco, en nuestro caso es texturas [1]. Después de la aplicar método se llama, texturas [1] contendrá el resultado de la Efecto.

Por ejemplo, para crear y aplicar el escala de grises Efecto, aquí tienes el código que debes escribir:

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

Llame a este método en onDrawFrame y pasar texturas [1] al Cuadrado objetos dibujar método. Tu onDrawFrame El método debe tener el siguiente código:

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

los lanzamiento Este método se utiliza para liberar todos los recursos en poder de un Efecto. Cuando ejecute la aplicación, debería ver el siguiente resultado:

Puedes usar el mismo código para aplicar otros efectos. Por ejemplo, aquí está el código para aplicar el documental efecto:

documentary void privadoEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_DOCUMENTARY); effect.apply (texturas [0], photoWidth, photoHeight, textures [1]); 

El resultado se ve así:

Algunos efectos toman parámetros. Por ejemplo, el efecto de ajuste de brillo tiene un brillo parámetro que toma un flotador valor. Puedes usar setParameter Para cambiar el valor de cualquier parámetro. El siguiente código le muestra cómo usarlo:

private void brightnessEffect () EffectFactory factory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_BRIGHTNESS); effect.setParameter ("brillo", 2f); effect.apply (texturas [0], photoWidth, photoHeight, textures [1]); 

El efecto hará que tu aplicación genere el siguiente resultado:

Conclusión

En este tutorial, ha aprendido cómo utilizar el Marco de efectos de medios para aplicar varios efectos a sus fotos. Mientras lo hacía, también aprendió a dibujar un plano con OpenGL ES 2.0 y a aplicarle varias texturas..

El marco puede aplicarse tanto a fotos como a videos. En el caso de los videos, simplemente tiene que aplicar el efecto a los cuadros individuales del video en el onDrawFrame método.

Ya has visto tres efectos en este tutorial y el marco tiene docenas más para experimentar. Para saber más sobre ellos, consulte el sitio web del desarrollador de Android.