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..
Pour suivre ce tutoriel, vous devez avoir:
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);
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));
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.
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:
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;
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);
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:
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.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);" + "";
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
.
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:
glBindFramebuffer
créer un objet nommé frame buffer (souvent appelé FBO).GlUseProgram
pour commencer à utiliser le programme que nous venons de lier.GL_BLEND
à glDisable
désactiver le mélange des couleurs lors du rendu.glGetAttribLocation
pour avoir une poignée sur les variables une position
et aTexPosition
mentionné dans le code vertex shader.glGetUniformLocation
pour avoir une poignée à la constante uTexture
mentionné dans le code fragment shader.glVertexAttribPointer
associer le une position
et aTexPosition
poignées avec le sommetsBuffer
et le textureBuffer
respectivement.glBindTexture
lier la texture (passée en argument à la dessiner
méthode) au fragment shader.GLSurfaceView
en utilisant glClear
.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 ();
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:
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.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.
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:
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..