Comment utiliser les effets multimédia Android avec OpenGL ES

La structure d’effets multimédias d’Android permet aux développeurs d’appliquer facilement de nombreux effets visuels impressionnants aux photos et aux vidéos. Comme la structure utilise le GPU pour effectuer toutes ses opérations de traitement d'image, elle ne peut accepter que les textures OpenGL en entrée. Dans ce didacticiel, vous allez apprendre à utiliser OpenGL ES 2.0 pour convertir une ressource dessinée en une texture, puis à utiliser le framework pour lui appliquer divers effets..

Conditions préalables

Pour suivre ce tutoriel, vous devez avoir:

  • un IDE qui prend en charge le développement d'applications Android. Si vous n'en avez pas, obtenez la dernière version d'Android Studio sur le site Web de développeur d'Android..
  • un appareil fonctionnant sous Android 4.0+ et doté d'un processeur graphique prenant en charge OpenGL ES 2.0.
  • une compréhension de base d'OpenGL.

1. Configuration de l'environnement OpenGL ES

Étape 1: Créer un GLSurfaceView

Pour afficher les graphiques OpenGL dans votre application, vous devez utiliser un GLSurfaceView objet. Comme n'importe quel autre Vue, vous pouvez l'ajouter à un Activité ou Fragment en le définissant dans un fichier XML de mise en page ou en en créant une instance dans le code.

Dans ce tutoriel, vous allez avoir un GLSurfaceView objet comme seul Vue dans ton Activité. Par conséquent, sa création dans le code est plus simple. Une fois créé, passez-le au setContentView méthode pour qu'il remplisse la totalité de l'écran. Votre Activitéde onCreate méthode devrait ressembler à ceci:

void protégé onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); GLSurfaceView view = new GLSurfaceView (this); setContentView (vue); 

Comme la structure Media Effects ne prend en charge que OpenGL ES 2.0 ou une version ultérieure, transmettez la valeur 2 au setEGLContextClientVersion méthode.

view.setEGLContextClientVersion (2);

Pour vous assurer que le GLSurfaceView rend son contenu uniquement lorsque cela est nécessaire, transmettez la valeur RENDERMODE_WHEN_DIRTY au setRenderMode méthode.

view.setRenderMode (GLSurfaceView.RENDERMODE_WHEN_DIRTY);

Étape 2: Créer un moteur de rendu

UNE GLSurfaceView.Renderer est responsable de dessiner le contenu de la GLSurfaceView.

Créer une nouvelle classe qui implémente le GLSurfaceView.Renderer interface. Je vais appeler cette classe EffetsRenderer. Après avoir ajouté un constructeur et redéfini toutes les méthodes de l'interface, la classe devrait ressembler à ceci:

Classe publique EffectsRenderer implémente GLSurfaceView.Renderer public EffectsRenderer (contexte de contexte) super ();  @Override public void onSurfaceCreated (GL10 gl, configuration EGLConfig)  @Override public void onSurfaceChanged (GL10 gl, int width, int height)  @Override public void onDrawFrame (GL10 gl) 

Retournez à votre Activité et appelez le setRenderer méthode pour que le GLSurfaceView utilise le moteur de rendu personnalisé.

view.setRenderer (new EffectsRenderer (this));

Étape 3: Modifier le manifeste

Si vous envisagez de publier votre application sur Google Play, ajoutez ce qui suit à AndroidManifest.xml:

Cela garantit que votre application ne peut être installée que sur des périphériques prenant en charge OpenGL ES 2.0. L'environnement OpenGL est maintenant prêt.

2. Créer un plan OpenGL

Étape 1: Définir les sommets

le GLSurfaceView ne peut pas afficher une photo directement. La photo doit être convertie en une texture et d'abord appliquée à une forme OpenGL. Dans ce tutoriel, nous allons créer un plan 2D à quatre sommets. Par souci de simplicité, faisons-en un carré. Créer une nouvelle classe, Carré, représenter le carré.

classe publique Square 

Le système de coordonnées OpenGL par défaut a son origine au centre. En conséquence, les coordonnées des quatre coins de notre carré, dont les côtés sont deux unités long, sera:

  • coin inférieur gauche à (-1, -1)
  • coin inférieur droit en (1, -1)
  • coin supérieur droit en (1, 1)
  • coin supérieur gauche à (-1, 1)

Tous les objets que nous dessinons avec OpenGL doivent être composés de triangles. Pour dessiner le carré, nous avons besoin de deux triangles avec un bord commun. Cela signifie que les coordonnées des triangles seront:

triangle 1: (-1, -1), (1, -1) et (-1, 1)
triangle 2: (1, -1), (-1, 1) et (1, 1)

Créer un flotte tableau pour représenter ces sommets.

sommets de float privés [] = -1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f,;

Pour mapper la texture sur le carré, vous devez spécifier les coordonnées des sommets de la texture. Les textures suivent un système de coordonnées dans lequel la valeur de la coordonnée y augmente à mesure que vous montez. Créez un autre tableau pour représenter les sommets de la texture.

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

Étape 2: Créer des objets tampons

Les tableaux de coordonnées doivent être convertis en tampons d'octets avant qu'OpenGL puisse les utiliser. Commençons par déclarer ces tampons.

sommets privés FloatBufferBuffer; texture privée FloatBufferBuffer;

Ecrivez le code pour initialiser ces tampons dans une nouvelle méthode appelée initialiserBuffeurs. Utilisez le ByteBuffer.allocateDirect méthode pour créer le tampon. Parce qu'un flotte les usages 4 octets, vous devez multiplier la taille des tableaux par la valeur 4.

Ensuite, utilisez ByteBuffer.nativeOrder pour déterminer l’ordre des octets de la plate-forme native sous-jacente et définir l’ordre des tampons sur cette valeur. Utilisez le commeFloatBuffer méthode pour convertir le ByteBuffer par exemple dans un FloatBuffer. Après le FloatBuffer est créé, utilisez le mettre méthode pour charger le tableau dans le tampon. Enfin, utilisez le position méthode pour s'assurer que le tampon est lu depuis le début.

Le contenu de la initialiserBuffeurs méthode devrait ressembler à ceci:

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

Étape 3: Créer des shaders

Il est temps d'écrire vos propres shaders. Les shaders ne sont que de simples programmes C exécutés par le GPU pour traiter chaque sommet. Pour ce tutoriel, vous devez créer deux shaders, un vertex shader et un fragment shader..

Le code C pour le vertex shader est:

attribut vec4 aPosition; attribut vec2 aTexPosition; variant vec2 vTexPosition; void main () gl_Position = aPosition; vTexPosition = aTexPosition; ;

Le code C pour le fragment shader est:

float mediump de précision; uniforme sampler2D uTexture; variant vec2 vTexPosition; void main () gl_FragColor = texture2D (uTexture, vTexPosition); ;

Si vous connaissez déjà OpenGL, ce code devrait vous être familier car il est commun à toutes les plateformes. Sinon, pour comprendre ces programmes, vous devez vous reporter à la documentation OpenGL. Voici une brève explication pour vous aider à démarrer:

  • Le vertex shader est chargé de dessiner les différents sommets. une position est une variable qui sera liée à la FloatBuffer qui contient les coordonnées des sommets. De même, aTexPosition est une variable qui sera liée à la FloatBuffer qui contient les coordonnées de la texture. gl_Position est une variable OpenGL intégrée et représente la position de chaque sommet. le vTexPosition est un variant variable, dont la valeur est simplement transmise au fragment shader.
  • Dans ce tutoriel, fragment shader est chargé de colorer le carré. Il prend les couleurs de la texture en utilisant le texture2D méthode et les assigne au fragment à l'aide d'une variable intégrée nommée gl_FragColor.

Le code de shader doit être représenté comme Chaîne objets dans la classe.

private final String vertexShaderCode = "attribut vec4 aPosition;" + "attribut vec2 aTexPosition;" + "variant vec2 vTexPosition;" + "void main () " + "gl_Position = aPosition;" + "vTexPosition = aTexPosition;" + ""; private final String fragmentShaderCode = "précision mediump float;" + "uniform sampler2D uTexture;" + "variant vec2 vTexPosition;" + "void main () " + "gl_FragColor = texture2D (uTexture, vTexPosition);" + "";

Étape 4: Créer un programme

Créez une nouvelle méthode appelée initializeProgram créer un programme OpenGL après avoir compilé et lié les shaders.

Utilisation glCreateShader pour créer un objet shader et y renvoyer une référence sous la forme d'un int. Pour créer un vertex shader, transmettez la valeur GL_VERTEX_SHADER à cela. De même, pour créer un fragment shader, transmettez la valeur GL_FRAGMENT_SHADER à cela. Utilisation suivante glShaderSource associer le code de shader approprié au shader. Utilisation glCompileShader compiler le code shader.

Après avoir compilé les deux shaders, créez un nouveau programme en utilisant glCreateProgram. Juste comme  glCreateShader, cela aussi retourne un int comme référence au programme. Appel glAttachShader attacher les shaders au programme. Enfin, appelez glLinkProgram relier le programme.

Votre méthode et les variables associées doivent ressembler à ceci:

private int vertexShader; private int fragmentShader; programme int privé; 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); programme = GLES20.glCreateProgram (); GLES20.glAttachShader (programme, vertexShader); GLES20.glAttachShader (programme, fragmentShader); GLES20.glLinkProgram (programme);  

Vous avez peut-être remarqué que les méthodes OpenGL (les méthodes préfixées par gl) appartient à la classe GLES20. En effet, nous utilisons OpenGL ES 2.0. Si vous souhaitez utiliser une version supérieure, vous devrez utiliser les classes GLES30 ou GLES31.

Étape 5: Dessinez le carré

Créez une nouvelle méthode appelée dessiner pour réellement dessiner le carré en utilisant les sommets et les shaders définis précédemment.

Voici ce que vous devez faire avec cette méthode:

  1. Utilisation glBindFramebuffer créer un objet nommé frame buffer (souvent appelé FBO).
  2. Utilisation GlUseProgram pour commencer à utiliser le programme que nous venons de lier.
  3. Passer la valeur GL_BLEND à glDisable désactiver le mélange des couleurs lors du rendu.
  4. Utilisation glGetAttribLocation pour avoir une poignée sur les variables une position et aTexPosition mentionné dans le code vertex shader.
  5. Utilisation glGetUniformLocation pour avoir une poignée à la constante uTexture mentionné dans le code fragment shader.
  6. Utilisez le glVertexAttribPointer associer le une position et aTexPosition poignées avec le sommetsBuffer et le textureBuffer respectivement.
  7. Utilisation glBindTexture lier la texture (passée en argument à la dessiner méthode) au fragment shader.
  8. Effacer le contenu de la GLSurfaceView en utilisant glClear.
  9. Enfin, utilisez le glDrawArrays méthode pour dessiner les deux triangles (et donc le carré).

Le code pour le dessiner méthode devrait ressembler à ceci:

public void draw (int texture) GLES20.glBindFramebuffer (GLES20.GL_FRAMEBUFFER, 0); GLES20.glUseProgram (programme); GLES20.glDisable (GLES20.GL_BLEND); int positionHandle = GLES20.glGetAttribLocation (programme, "aPosition"); int textureHandle = GLES20.glGetUniformLocation (programme, "uTexture"); int texturePositionHandle = GLES20.glGetAttribLocation (programme, "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);  

Ajouter un constructeur à la classe pour initialiser les tampons et le programme au moment de la création de l'objet.

place publique () initializeBuffers (); initializeProgram (); 

3. Rendu du plan et de la texture OpenGL

Actuellement, notre moteur de rendu ne fait rien. Nous devons changer cela pour qu'il puisse rendre le plan que nous avons créé dans les étapes précédentes.

Mais d’abord, créons un Bitmap. Ajoutez une photo à votre projet res / drawable dossier. Le fichier que j'utilise s'appelle forest.jpg. Utilisez le BitmapFactory convertir la photo en Bitmap objet. Stockez également les dimensions du Bitmap objet dans des variables séparées.

Changer le constructeur du EffetsRenderer class afin qu'il ait le contenu suivant:

photo bitmap privée; privé int photoWidth, photoHeight; public EffectsRenderer (contexte de contexte) super (); photo = BitmapFactory.decodeResource (context.getResources (), R.drawable.forest); photoWidth = photo.getWidth (); photoHeight = photo.getHeight (); 

Créez une nouvelle méthode appelée générerSquare convertir le bitmap en une texture et initialiser un Carré objet. Vous aurez également besoin d'un tableau d'entiers pour contenir des références aux textures OpenGL. Utilisation glGenTextures pour initialiser le tableau et glBindTexture activer la texture à l'index 0.

Ensuite, utilisez glTexParameteri pour définir diverses propriétés qui déterminent le rendu de la texture:

  • Ensemble GL_TEXTURE_MIN_FILTER (fonction minifiante) et le GL_TEXTURE_MAG_FILTER (la fonction de grossissement) à GL_LINEAR pour que la texture soit lisse, même lorsqu'elle est étirée ou rétrécie.
  • Ensemble GL_TEXTURE_WRAP_S et GL_TEXTURE_WRAP_T à GL_CLAMP_TO_EDGE pour que la texture ne se répète jamais.

Enfin, utilisez le texImage2D méthode pour cartographier le Bitmap à la texture. La mise en œuvre de la générerSquare méthode devrait ressembler à ceci:

privé int textures [] = new int [2]; carré privé; private void generateSquare () GLES20.glGenTextures (2, textures, 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, photo, 0); square = new Square (); 

Chaque fois que les dimensions de la GLSurfaceView changer la onSurfaceChanged méthode du Renderer est appelé. Voici où vous devez appeler glViewPort pour spécifier les nouvelles dimensions de la fenêtre. Aussi, appelez glClearColor peindre le GLSurfaceView noir. Ensuite, appelez générerSquare réinitialiser les textures et le plan.

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

Enfin, appelez le Carré objets dessiner méthode à l'intérieur du onDrawFrame méthode du Renderer.

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

Vous pouvez maintenant exécuter votre application et voir la photo que vous avez choisie étant rendue sous forme de texture OpenGL dans un plan.

4. Utilisation du cadre des effets multimédias

Le code complexe que nous avons écrit jusqu'à présent était simplement une condition préalable à l'utilisation de la structure Media Effects. Il est maintenant temps de commencer à utiliser le cadre lui-même. Ajoutez les champs suivants à votre Renderer classe.

private EffectContext effectContext; effet d'effet privé;

Initialiser le effetContext champ en utilisant le EffectContext.createWithCurrentGlContext. Il est responsable de la gestion des informations sur les effets visuels dans un contexte OpenGL. Pour optimiser les performances, appelez-le une seule fois. Ajoutez le code suivant au début de votre onDrawFrame méthode.

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

Créer un effet est très simple. Utilisez le effetContext créer un EffectFactory et utiliser le EffectFactory créer un Effet objet. Une fois Effet l'objet est disponible, vous pouvez appeler appliquer et passez une référence à la texture originale, dans notre cas il est textures [0], avec une référence à un objet de texture vide, dans notre cas, il est textures [1]. Après le appliquer la méthode s'appelle, textures [1] contiendra le résultat de la Effet.

Par exemple, pour créer et appliquer le niveaux de gris effet, voici le code que vous devez écrire:

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

Appelez cette méthode dans onDrawFrame et passe textures [1] au Carré objets dessiner méthode. Votre onDrawFrame méthode devrait avoir le code suivant:

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

le Libération Cette méthode est utilisée pour libérer toutes les ressources détenues par un Effet. Lorsque vous exécutez l'application, vous devriez voir le résultat suivant:

Vous pouvez utiliser le même code pour appliquer d'autres effets. Par exemple, voici le code pour appliquer le documentaire effet:

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

Le résultat ressemble à ceci:

Certains effets prennent des paramètres. Par exemple, l’effet de réglage de la luminosité a une luminosité paramètre qui prend un flotte valeur. Vous pouvez utiliser setParameter pour changer la valeur de n'importe quel paramètre. Le code suivant vous montre comment l'utiliser:

private void brightEffect () Fabrique EffectFactory = effectContext.getFactory (); effect = factory.createEffect (EffectFactory.EFFECT_BRIGHTNESS); effect.setParameter ("luminosité", 2f); effect.apply (textures [0], photoWidth, photoHeight, textures [1]); 

L'effet rendra votre application rendre le résultat suivant:

Conclusion

Dans ce didacticiel, vous avez appris à utiliser Media Effects Framework pour appliquer divers effets à vos photos. Ce faisant, vous avez également appris à dessiner un plan avec OpenGL ES 2.0 et à lui appliquer diverses textures..

Le cadre peut être appliqué à la fois aux photos et aux vidéos. Dans le cas de vidéos, vous devez simplement appliquer l’effet aux images individuelles de la vidéo dans onDrawFrame méthode.

Vous avez déjà vu trois effets dans ce tutoriel et le cadre en a des dizaines d'autres à expérimenter. Pour en savoir plus, consultez le site Web du développeur Android..