| /* |
| * Copyright (C) 2010 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.android.gallery3d.photoeditor; |
| |
| import android.graphics.Bitmap; |
| import android.opengl.GLES20; |
| import android.opengl.GLUtils; |
| import android.util.FloatMath; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.FloatBuffer; |
| |
| /** |
| * Utils for GL renderer. |
| */ |
| public class RendererUtils { |
| |
| public static class RenderContext { |
| private int shaderProgram; |
| private int texSamplerHandle; |
| private int texCoordHandle; |
| private int posCoordHandle; |
| private FloatBuffer texVertices; |
| private FloatBuffer posVertices; |
| } |
| |
| 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 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 int FLOAT_SIZE_BYTES = 4; |
| private static final float DEGREE_TO_RADIAN = (float) Math.PI / 180.0f; |
| |
| public static int createTexture() { |
| int[] textures = new int[1]; |
| GLES20.glGenTextures(textures.length, textures, 0); |
| checkGlError("glGenTextures"); |
| return textures[0]; |
| } |
| |
| public static int createTexture(Bitmap bitmap) { |
| int texture = createTexture(); |
| |
| GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); |
| GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); |
| 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); |
| checkGlError("texImage2D"); |
| |
| return texture; |
| } |
| |
| public static Bitmap saveTexture(int texture, int width, int height) { |
| int[] frame = new int[1]; |
| GLES20.glGenFramebuffers(1, frame, 0); |
| checkGlError("glGenFramebuffers"); |
| GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frame[0]); |
| checkGlError("glBindFramebuffer"); |
| GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, |
| GLES20.GL_TEXTURE_2D, texture, 0); |
| checkGlError("glFramebufferTexture2D"); |
| |
| ByteBuffer buffer = ByteBuffer.allocate(width * height * 4); |
| GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); |
| checkGlError("glReadPixels"); |
| Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
| bitmap.copyPixelsFromBuffer(buffer); |
| |
| GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); |
| checkGlError("glBindFramebuffer"); |
| GLES20.glDeleteFramebuffers(1, frame, 0); |
| checkGlError("glDeleteFramebuffer"); |
| return bitmap; |
| } |
| |
| public static void clearTexture(int texture) { |
| int[] textures = new int[1]; |
| textures[0] = texture; |
| GLES20.glDeleteTextures(textures.length, textures, 0); |
| checkGlError("glDeleteTextures"); |
| } |
| |
| private static float[] getFitVertices(int srcWidth, int srcHeight, int dstWidth, |
| int dstHeight) { |
| float srcAspectRatio = ((float) srcWidth) / srcHeight; |
| float dstAspectRatio = ((float) dstWidth) / dstHeight; |
| float relativeAspectRatio = dstAspectRatio / srcAspectRatio; |
| |
| float[] vertices = new float[8]; |
| System.arraycopy(POS_VERTICES, 0, vertices, 0, vertices.length); |
| if (relativeAspectRatio > 1.0f) { |
| // Screen is wider than the camera, scale down X |
| vertices[0] /= relativeAspectRatio; |
| vertices[2] /= relativeAspectRatio; |
| vertices[4] /= relativeAspectRatio; |
| vertices[6] /= relativeAspectRatio; |
| } else { |
| vertices[1] *= relativeAspectRatio; |
| vertices[3] *= relativeAspectRatio; |
| vertices[5] *= relativeAspectRatio; |
| vertices[7] *= relativeAspectRatio; |
| } |
| return vertices; |
| } |
| |
| public static void setRenderToFit(RenderContext context, int srcWidth, int srcHeight, |
| int dstWidth, int dstHeight) { |
| context.posVertices = createVerticesBuffer( |
| getFitVertices(srcWidth, srcHeight, dstWidth, dstHeight)); |
| } |
| |
| public static void setRenderToRotate(RenderContext context, int srcWidth, int srcHeight, |
| int dstWidth, int dstHeight, float degrees) { |
| float radian = -degrees * DEGREE_TO_RADIAN; |
| float cosTheta = FloatMath.cos(radian); |
| float sinTheta = FloatMath.sin(radian); |
| float cosWidth = cosTheta * srcWidth; |
| float sinWidth = sinTheta * srcWidth; |
| float cosHeight = cosTheta * srcHeight; |
| float sinHeight = sinTheta * srcHeight; |
| |
| float[] vertices = new float[8]; |
| vertices[0] = -cosWidth + sinHeight; |
| vertices[1] = -sinWidth - cosHeight; |
| vertices[2] = cosWidth + sinHeight; |
| vertices[3] = sinWidth - cosHeight; |
| vertices[4] = -vertices[2]; |
| vertices[5] = -vertices[3]; |
| vertices[6] = -vertices[0]; |
| vertices[7] = -vertices[1]; |
| |
| float maxWidth = Math.max(Math.abs(vertices[0]), Math.abs(vertices[2])); |
| float maxHeight = Math.max(Math.abs(vertices[1]), Math.abs(vertices[3])); |
| float scale = Math.min(dstWidth / maxWidth, dstHeight / maxHeight); |
| |
| for (int i = 0; i < 8; i += 2) { |
| vertices[i] *= scale / dstWidth; |
| vertices[i + 1] *= scale / dstHeight; |
| } |
| context.posVertices = createVerticesBuffer(vertices); |
| } |
| |
| public static void setRenderToFlip(RenderContext context, int srcWidth, int srcHeight, |
| int dstWidth, int dstHeight, float horizontalDegrees, float verticalDegrees) { |
| // Calculate the base flip coordinates. |
| float[] base = getFitVertices(srcWidth, srcHeight, dstWidth, dstHeight); |
| int horizontalRounds = (int) horizontalDegrees / 180; |
| if (horizontalRounds % 2 != 0) { |
| base[0] = -base[0]; |
| base[4] = base[0]; |
| base[2] = -base[2]; |
| base[6] = base[2]; |
| } |
| int verticalRounds = (int) verticalDegrees / 180; |
| if (verticalRounds % 2 != 0) { |
| base[1] = -base[1]; |
| base[3] = base[1]; |
| base[5] = -base[5]; |
| base[7] = base[5]; |
| } |
| |
| float length = 5; |
| float[] vertices = new float[8]; |
| System.arraycopy(base, 0, vertices, 0, vertices.length); |
| if (horizontalDegrees % 180f != 0) { |
| float radian = (horizontalDegrees - horizontalRounds * 180) * DEGREE_TO_RADIAN; |
| float cosTheta = FloatMath.cos(radian); |
| float sinTheta = FloatMath.sin(radian); |
| |
| float scale = length / (length + sinTheta * base[0]); |
| vertices[0] = cosTheta * base[0] * scale; |
| vertices[1] = base[1] * scale; |
| vertices[4] = vertices[0]; |
| vertices[5] = base[5] * scale; |
| |
| scale = length / (length + sinTheta * base[2]); |
| vertices[2] = cosTheta * base[2] * scale; |
| vertices[3] = base[3] * scale; |
| vertices[6] = vertices[2]; |
| vertices[7] = base[7] * scale; |
| } |
| |
| if (verticalDegrees % 180f != 0) { |
| float radian = (verticalDegrees - verticalRounds * 180) * DEGREE_TO_RADIAN; |
| float cosTheta = FloatMath.cos(radian); |
| float sinTheta = FloatMath.sin(radian); |
| |
| float scale = length / (length + sinTheta * base[1]); |
| vertices[0] = base[0] * scale; |
| vertices[1] = cosTheta * base[1] * scale; |
| vertices[2] = base[2] * scale; |
| vertices[3] = vertices[1]; |
| |
| scale = length / (length + sinTheta * base[5]); |
| vertices[4] = base[4] * scale; |
| vertices[5] = cosTheta * base[5] * scale; |
| vertices[6] = base[6] * scale; |
| vertices[7] = vertices[5]; |
| } |
| context.posVertices = createVerticesBuffer(vertices); |
| } |
| |
| public static void renderBackground() { |
| GLES20.glClearColor(0, 0, 0, 1); |
| GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
| } |
| |
| public static void renderTexture( |
| RenderContext context, int texture, int viewWidth, int viewHeight) { |
| // Use our shader program |
| GLES20.glUseProgram(context.shaderProgram); |
| checkGlError("glUseProgram"); |
| |
| // Set viewport |
| GLES20.glViewport(0, 0, viewWidth, viewHeight); |
| checkGlError("glViewport"); |
| |
| // Disable blending |
| GLES20.glDisable(GLES20.GL_BLEND); |
| |
| // Set the vertex attributes |
| GLES20.glVertexAttribPointer( |
| context.texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, context.texVertices); |
| GLES20.glEnableVertexAttribArray(context.texCoordHandle); |
| GLES20.glVertexAttribPointer( |
| context.posCoordHandle, 2, GLES20.GL_FLOAT, false, 0, context.posVertices); |
| GLES20.glEnableVertexAttribArray(context.posCoordHandle); |
| checkGlError("vertex attribute setup"); |
| |
| // Set the input texture |
| GLES20.glActiveTexture(GLES20.GL_TEXTURE0); |
| checkGlError("glActiveTexture"); |
| GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); |
| checkGlError("glBindTexture"); |
| GLES20.glUniform1i(context.texSamplerHandle, 0); |
| |
| // Draw! |
| GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); |
| } |
| |
| public static RenderContext createProgram() { |
| int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER); |
| if (vertexShader == 0) { |
| return null; |
| } |
| int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER); |
| if (pixelShader == 0) { |
| return null; |
| } |
| |
| 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); |
| } |
| } |
| |
| // Bind attributes and uniforms |
| RenderContext context = new RenderContext(); |
| context.texSamplerHandle = GLES20.glGetUniformLocation(program, "tex_sampler"); |
| context.texCoordHandle = GLES20.glGetAttribLocation(program, "a_texcoord"); |
| context.posCoordHandle = GLES20.glGetAttribLocation(program, "a_position"); |
| context.texVertices = createVerticesBuffer(TEX_VERTICES); |
| context.posVertices = createVerticesBuffer(POS_VERTICES); |
| |
| context.shaderProgram = program; |
| return context; |
| } |
| |
| private 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; |
| } |
| |
| private static FloatBuffer createVerticesBuffer(float[] vertices) { |
| if (vertices.length != 8) { |
| throw new RuntimeException("Number of vertices should be four."); |
| } |
| |
| FloatBuffer buffer = ByteBuffer.allocateDirect( |
| vertices.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer(); |
| buffer.put(vertices).position(0); |
| return buffer; |
| } |
| |
| private static void checkGlError(String op) { |
| int error; |
| while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { |
| throw new RuntimeException(op + ": glError " + error); |
| } |
| } |
| } |