diff --git a/build/sdk.atree b/build/sdk.atree index b00aa6499..da744a5cd 100644 --- a/build/sdk.atree +++ b/build/sdk.atree @@ -167,6 +167,7 @@ development/samples/BluetoothHDP samples/${PLATFORM_NAME}/Bluetoot development/samples/ContactManager samples/${PLATFORM_NAME}/ContactManager development/samples/CrossCompatibility samples/${PLATFORM_NAME}/CrossCompatibility development/samples/CubeLiveWallpaper samples/${PLATFORM_NAME}/CubeLiveWallpaper +development/samples/HelloEffects samples/${PLATFORM_NAME}/HelloEffects development/samples/Home samples/${PLATFORM_NAME}/Home development/samples/HoneycombGallery samples/${PLATFORM_NAME}/HoneycombGallery development/samples/JetBoy samples/${PLATFORM_NAME}/JetBoy diff --git a/samples/HelloEffects/Android.mk b/samples/HelloEffects/Android.mk new file mode 100644 index 000000000..1d9a50a54 --- /dev/null +++ b/samples/HelloEffects/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := samples + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_SDK_VERSION := current + +LOCAL_PACKAGE_NAME := HelloEffects + +include $(BUILD_PACKAGE) diff --git a/samples/HelloEffects/AndroidManifest.xml b/samples/HelloEffects/AndroidManifest.xml new file mode 100644 index 000000000..d3c5f64e0 --- /dev/null +++ b/samples/HelloEffects/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/samples/HelloEffects/_index.html b/samples/HelloEffects/_index.html new file mode 100644 index 000000000..adf1fa849 --- /dev/null +++ b/samples/HelloEffects/_index.html @@ -0,0 +1,23 @@ + + +

This sample shows how to use the Media +Effects APIs that were introduced in Android 4.0. These APIs let you apply +effects to image frames represented as OpenGL ES 2.0 textures. Image frames can +be images loaded from disk, frames from the device's camera, or other video +streams.

+ + diff --git a/samples/HelloEffects/res/drawable-hdpi/ic_launcher.png b/samples/HelloEffects/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 000000000..76278e36b Binary files /dev/null and b/samples/HelloEffects/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/HelloEffects/res/drawable-ldpi/ic_launcher.png b/samples/HelloEffects/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 000000000..44ede026f Binary files /dev/null and b/samples/HelloEffects/res/drawable-ldpi/ic_launcher.png differ diff --git a/samples/HelloEffects/res/drawable-mdpi/ic_launcher.png b/samples/HelloEffects/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 000000000..162d976a7 Binary files /dev/null and b/samples/HelloEffects/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/HelloEffects/res/drawable-xhdpi/ic_launcher.png b/samples/HelloEffects/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 000000000..131d3a0c7 Binary files /dev/null and b/samples/HelloEffects/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/HelloEffects/res/drawable/puppy.jpg b/samples/HelloEffects/res/drawable/puppy.jpg new file mode 100644 index 000000000..ef79be200 Binary files /dev/null and b/samples/HelloEffects/res/drawable/puppy.jpg differ diff --git a/samples/HelloEffects/res/layout/main.xml b/samples/HelloEffects/res/layout/main.xml new file mode 100644 index 000000000..94c23e26e --- /dev/null +++ b/samples/HelloEffects/res/layout/main.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/samples/HelloEffects/res/menu/options_menu.xml b/samples/HelloEffects/res/menu/options_menu.xml new file mode 100644 index 000000000..531a8dca3 --- /dev/null +++ b/samples/HelloEffects/res/menu/options_menu.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/HelloEffects/res/values/strings.xml b/samples/HelloEffects/res/values/strings.xml new file mode 100644 index 000000000..66203d76c --- /dev/null +++ b/samples/HelloEffects/res/values/strings.xml @@ -0,0 +1,42 @@ + + + + HelloEffects + None + Autofix + Min/Max Color Intensity + Brightness + Contrast + Cross Process + Documentary + Duo Tone + Fill Light + Fish Eye + Flip Vertical + Flip Horizontal + Grain + Grayscale + Lomoish + Negative + Posterize + Rotate + Saturate + Sepia + Sharpen + Temperature + Tint + Vignette + diff --git a/samples/HelloEffects/src/com/example/android/mediafx/GLToolbox.java b/samples/HelloEffects/src/com/example/android/mediafx/GLToolbox.java new file mode 100644 index 000000000..0062fa189 --- /dev/null +++ b/samples/HelloEffects/src/com/example/android/mediafx/GLToolbox.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.mediafx; + +import android.opengl.GLES20; + +public class GLToolbox { + + public static int loadShader(int shaderType, String source) { + int shader = GLES20.glCreateShader(shaderType); + if (shader != 0) { + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + String info = GLES20.glGetShaderInfoLog(shader); + GLES20.glDeleteShader(shader); + shader = 0; + throw new RuntimeException("Could not compile shader " + + shaderType + ":" + info); + } + } + return shader; + } + + public static int createProgram(String vertexSource, + String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + + int program = GLES20.glCreateProgram(); + if (program != 0) { + GLES20.glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, + 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + String info = GLES20.glGetProgramInfoLog(program); + GLES20.glDeleteProgram(program); + program = 0; + throw new RuntimeException("Could not link program: " + info); + } + } + return program; + } + + public static void checkGlError(String op) { + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + throw new RuntimeException(op + ": glError " + error); + } + } + + public static void initTexParams() { + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, + GLES20.GL_TEXTURE_MIN_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); + } + +} diff --git a/samples/HelloEffects/src/com/example/android/mediafx/HelloEffects.java b/samples/HelloEffects/src/com/example/android/mediafx/HelloEffects.java new file mode 100644 index 000000000..cfecce052 --- /dev/null +++ b/samples/HelloEffects/src/com/example/android/mediafx/HelloEffects.java @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.mediafx; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import android.media.effect.Effect; +import android.media.effect.EffectContext; +import android.media.effect.EffectFactory; + +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.opengl.GLUtils; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +public class HelloEffects extends Activity implements GLSurfaceView.Renderer { + + private GLSurfaceView mEffectView; + private int[] mTextures = new int[2]; + private EffectContext mEffectContext; + private Effect mEffect; + private TextureRenderer mTexRenderer = new TextureRenderer(); + private int mImageWidth; + private int mImageHeight; + private boolean mInitialized = false; + int mCurrentEffect; + + public void setCurrentEffect(int effect) { + mCurrentEffect = effect; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + /** + * Initialize the renderer and tell it to only render when + * explicity requested with the RENDERMODE_WHEN_DIRTY option + */ + mEffectView = (GLSurfaceView) findViewById(R.id.effectsview); + mEffectView.setEGLContextClientVersion(2); + mEffectView.setRenderer(this); + mEffectView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + mCurrentEffect = R.id.none; + } + + private void loadTextures() { + // Generate textures + GLES20.glGenTextures(2, mTextures, 0); + + // Load input bitmap + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), + R.drawable.puppy); + mImageWidth = bitmap.getWidth(); + mImageHeight = bitmap.getHeight(); + mTexRenderer.updateTextureSize(mImageWidth, mImageHeight); + + // Upload to texture + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]); + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + + // Set texture parameters + GLToolbox.initTexParams(); + } + + private void initEffect() { + EffectFactory effectFactory = mEffectContext.getFactory(); + if (mEffect != null) { + mEffect.release(); + } + /** + * Initialize the correct effect based on the selected menu/action item + */ + switch (mCurrentEffect) { + + case R.id.none: + break; + + case R.id.autofix: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_AUTOFIX); + mEffect.setParameter("scale", 0.5f); + break; + + case R.id.bw: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_BLACKWHITE); + mEffect.setParameter("black", .1f); + mEffect.setParameter("white", .7f); + break; + + case R.id.brightness: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_BRIGHTNESS); + mEffect.setParameter("brightness", 2.0f); + break; + + case R.id.contrast: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_CONTRAST); + mEffect.setParameter("contrast", 1.4f); + break; + + case R.id.crossprocess: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_CROSSPROCESS); + break; + + case R.id.documentary: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_DOCUMENTARY); + break; + + case R.id.duotone: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_DUOTONE); + mEffect.setParameter("first_color", Color.YELLOW); + mEffect.setParameter("second_color", Color.DKGRAY); + break; + + case R.id.filllight: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_FILLLIGHT); + mEffect.setParameter("strength", .8f); + break; + + case R.id.fisheye: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_FISHEYE); + mEffect.setParameter("scale", .5f); + break; + + case R.id.flipvert: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_FLIP); + mEffect.setParameter("vertical", true); + break; + + case R.id.fliphor: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_FLIP); + mEffect.setParameter("horizontal", true); + break; + + case R.id.grain: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_GRAIN); + mEffect.setParameter("strength", 1.0f); + break; + + case R.id.grayscale: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_GRAYSCALE); + break; + + case R.id.lomoish: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_LOMOISH); + break; + + case R.id.negative: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_NEGATIVE); + break; + + case R.id.posterize: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_POSTERIZE); + break; + + case R.id.rotate: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_ROTATE); + mEffect.setParameter("angle", 180); + break; + + case R.id.saturate: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_SATURATE); + mEffect.setParameter("scale", .5f); + break; + + case R.id.sepia: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_SEPIA); + break; + + case R.id.sharpen: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_SHARPEN); + break; + + case R.id.temperature: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_TEMPERATURE); + mEffect.setParameter("scale", .9f); + break; + + case R.id.tint: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_TINT); + mEffect.setParameter("tint", Color.MAGENTA); + break; + + case R.id.vignette: + mEffect = effectFactory.createEffect( + EffectFactory.EFFECT_VIGNETTE); + mEffect.setParameter("scale", .5f); + break; + + default: + break; + + } + } + + private void applyEffect() { + mEffect.apply(mTextures[0], mImageWidth, mImageHeight, mTextures[1]); + } + + private void renderResult() { + if (mCurrentEffect != R.id.none) { + // if no effect is chosen, just render the original bitmap + mTexRenderer.renderTexture(mTextures[1]); + } + else { + // render the result of applyEffect() + mTexRenderer.renderTexture(mTextures[0]); + } + } + + @Override + public void onDrawFrame(GL10 gl) { + if (!mInitialized) { + //Only need to do this once + mEffectContext = EffectContext.createWithCurrentGlContext(); + mTexRenderer.init(); + loadTextures(); + mInitialized = true; + } + if (mCurrentEffect != R.id.none) { + //if an effect is chosen initialize it and apply it to the texture + initEffect(); + applyEffect(); + } + renderResult(); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + if (mTexRenderer != null) { + mTexRenderer.updateViewSize(width, height); + } + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.options_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + setCurrentEffect(item.getItemId()); + mEffectView.requestRender(); + return true; + } +} diff --git a/samples/HelloEffects/src/com/example/android/mediafx/TextureRenderer.java b/samples/HelloEffects/src/com/example/android/mediafx/TextureRenderer.java new file mode 100644 index 000000000..9ef47bcda --- /dev/null +++ b/samples/HelloEffects/src/com/example/android/mediafx/TextureRenderer.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.mediafx; + +import android.opengl.GLES20; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +public class TextureRenderer { + + private int mProgram; + private int mTexSamplerHandle; + private int mTexCoordHandle; + private int mPosCoordHandle; + + private FloatBuffer mTexVertices; + private FloatBuffer mPosVertices; + + private int mViewWidth; + private int mViewHeight; + + private int mTexWidth; + private int mTexHeight; + + private static final String VERTEX_SHADER = + "attribute vec4 a_position;\n" + + "attribute vec2 a_texcoord;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " gl_Position = a_position;\n" + + " v_texcoord = a_texcoord;\n" + + "}\n"; + + private static final String FRAGMENT_SHADER = + "precision mediump float;\n" + + "uniform sampler2D tex_sampler;\n" + + "varying vec2 v_texcoord;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(tex_sampler, v_texcoord);\n" + + "}\n"; + + private static final float[] TEX_VERTICES = { + 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f + }; + + private static final float[] POS_VERTICES = { + -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f + }; + + private static final int FLOAT_SIZE_BYTES = 4; + + public void init() { + // Create program + mProgram = GLToolbox.createProgram(VERTEX_SHADER, FRAGMENT_SHADER); + + // Bind attributes and uniforms + mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram, + "tex_sampler"); + mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texcoord"); + mPosCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_position"); + + // Setup coordinate buffers + mTexVertices = ByteBuffer.allocateDirect( + TEX_VERTICES.length * FLOAT_SIZE_BYTES) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + mTexVertices.put(TEX_VERTICES).position(0); + mPosVertices = ByteBuffer.allocateDirect( + POS_VERTICES.length * FLOAT_SIZE_BYTES) + .order(ByteOrder.nativeOrder()).asFloatBuffer(); + mPosVertices.put(POS_VERTICES).position(0); + } + + public void tearDown() { + GLES20.glDeleteProgram(mProgram); + } + + public void updateTextureSize(int texWidth, int texHeight) { + mTexWidth = texWidth; + mTexHeight = texHeight; + computeOutputVertices(); + } + + public void updateViewSize(int viewWidth, int viewHeight) { + mViewWidth = viewWidth; + mViewHeight = viewHeight; + computeOutputVertices(); + } + + public void renderTexture(int texId) { + // Bind default FBO + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + + // Use our shader program + GLES20.glUseProgram(mProgram); + GLToolbox.checkGlError("glUseProgram"); + + // Set viewport + GLES20.glViewport(0, 0, mViewWidth, mViewHeight); + GLToolbox.checkGlError("glViewport"); + + // Disable blending + GLES20.glDisable(GLES20.GL_BLEND); + + // Set the vertex attributes + GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false, + 0, mTexVertices); + GLES20.glEnableVertexAttribArray(mTexCoordHandle); + GLES20.glVertexAttribPointer(mPosCoordHandle, 2, GLES20.GL_FLOAT, false, + 0, mPosVertices); + GLES20.glEnableVertexAttribArray(mPosCoordHandle); + GLToolbox.checkGlError("vertex attribute setup"); + + // Set the input texture + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLToolbox.checkGlError("glActiveTexture"); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId); + GLToolbox.checkGlError("glBindTexture"); + GLES20.glUniform1i(mTexSamplerHandle, 0); + + // Draw + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + } + + private void computeOutputVertices() { + if (mPosVertices != null) { + float imgAspectRatio = mTexWidth / (float)mTexHeight; + float viewAspectRatio = mViewWidth / (float)mViewHeight; + float relativeAspectRatio = viewAspectRatio / imgAspectRatio; + float x0, y0, x1, y1; + if (relativeAspectRatio > 1.0f) { + x0 = -1.0f / relativeAspectRatio; + y0 = -1.0f; + x1 = 1.0f / relativeAspectRatio; + y1 = 1.0f; + } else { + x0 = -1.0f; + y0 = -relativeAspectRatio; + x1 = 1.0f; + y1 = relativeAspectRatio; + } + float[] coords = new float[] { x0, y0, x1, y0, x0, y1, x1, y1 }; + mPosVertices.put(coords).position(0); + } + } +}