Yuli Huang | 6a12ad7 | 2011-09-12 22:25:30 +0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2010 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.gallery3d.photoeditor; |
| 18 | |
| 19 | import android.graphics.Bitmap; |
| 20 | import android.opengl.GLES20; |
| 21 | import android.opengl.GLUtils; |
Chih-Chung Chang | fd91413 | 2012-02-11 07:19:47 +0800 | [diff] [blame] | 22 | import android.util.FloatMath; |
Yuli Huang | 6a12ad7 | 2011-09-12 22:25:30 +0800 | [diff] [blame] | 23 | |
| 24 | import java.nio.ByteBuffer; |
| 25 | import java.nio.ByteOrder; |
| 26 | import java.nio.FloatBuffer; |
| 27 | |
| 28 | /** |
| 29 | * Utils for GL renderer. |
| 30 | */ |
| 31 | public class RendererUtils { |
| 32 | |
| 33 | public static class RenderContext { |
| 34 | private int shaderProgram; |
| 35 | private int texSamplerHandle; |
| 36 | private int texCoordHandle; |
| 37 | private int posCoordHandle; |
| 38 | private FloatBuffer texVertices; |
| 39 | private FloatBuffer posVertices; |
| 40 | } |
| 41 | |
| 42 | private static final float[] TEX_VERTICES = { |
| 43 | 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f |
| 44 | }; |
| 45 | |
| 46 | private static final float[] POS_VERTICES = { |
| 47 | -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f |
| 48 | }; |
| 49 | |
| 50 | private static final String VERTEX_SHADER = |
| 51 | "attribute vec4 a_position;\n" + |
| 52 | "attribute vec2 a_texcoord;\n" + |
| 53 | "varying vec2 v_texcoord;\n" + |
| 54 | "void main() {\n" + |
| 55 | " gl_Position = a_position;\n" + |
| 56 | " v_texcoord = a_texcoord;\n" + |
| 57 | "}\n"; |
| 58 | |
| 59 | private static final String FRAGMENT_SHADER = |
| 60 | "precision mediump float;\n" + |
| 61 | "uniform sampler2D tex_sampler;\n" + |
| 62 | "varying vec2 v_texcoord;\n" + |
| 63 | "void main() {\n" + |
| 64 | " gl_FragColor = texture2D(tex_sampler, v_texcoord);\n" + |
| 65 | "}\n"; |
| 66 | |
| 67 | private static final int FLOAT_SIZE_BYTES = 4; |
| 68 | private static final float DEGREE_TO_RADIAN = (float) Math.PI / 180.0f; |
| 69 | |
| 70 | public static int createTexture() { |
| 71 | int[] textures = new int[1]; |
| 72 | GLES20.glGenTextures(textures.length, textures, 0); |
| 73 | checkGlError("glGenTextures"); |
| 74 | return textures[0]; |
| 75 | } |
| 76 | |
| 77 | public static int createTexture(Bitmap bitmap) { |
| 78 | int texture = createTexture(); |
| 79 | |
| 80 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); |
| 81 | GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); |
| 82 | GLES20.glTexParameteri( |
| 83 | GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); |
| 84 | GLES20.glTexParameteri( |
| 85 | GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); |
| 86 | GLES20.glTexParameteri( |
| 87 | GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); |
| 88 | GLES20.glTexParameteri( |
| 89 | GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); |
| 90 | checkGlError("texImage2D"); |
| 91 | |
| 92 | return texture; |
| 93 | } |
| 94 | |
| 95 | public static Bitmap saveTexture(int texture, int width, int height) { |
| 96 | int[] frame = new int[1]; |
| 97 | GLES20.glGenFramebuffers(1, frame, 0); |
| 98 | checkGlError("glGenFramebuffers"); |
| 99 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frame[0]); |
| 100 | checkGlError("glBindFramebuffer"); |
| 101 | GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, |
| 102 | GLES20.GL_TEXTURE_2D, texture, 0); |
| 103 | checkGlError("glFramebufferTexture2D"); |
| 104 | |
| 105 | ByteBuffer buffer = ByteBuffer.allocate(width * height * 4); |
| 106 | GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer); |
| 107 | checkGlError("glReadPixels"); |
| 108 | Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
| 109 | bitmap.copyPixelsFromBuffer(buffer); |
| 110 | |
| 111 | GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); |
| 112 | checkGlError("glBindFramebuffer"); |
| 113 | GLES20.glDeleteFramebuffers(1, frame, 0); |
| 114 | checkGlError("glDeleteFramebuffer"); |
| 115 | return bitmap; |
| 116 | } |
| 117 | |
| 118 | public static void clearTexture(int texture) { |
| 119 | int[] textures = new int[1]; |
| 120 | textures[0] = texture; |
| 121 | GLES20.glDeleteTextures(textures.length, textures, 0); |
| 122 | checkGlError("glDeleteTextures"); |
| 123 | } |
| 124 | |
Yuli Huang | 7b62f48 | 2011-10-20 18:29:05 +0800 | [diff] [blame] | 125 | private static float[] getFitVertices(int srcWidth, int srcHeight, int dstWidth, |
| 126 | int dstHeight) { |
Yuli Huang | 6a12ad7 | 2011-09-12 22:25:30 +0800 | [diff] [blame] | 127 | float srcAspectRatio = ((float) srcWidth) / srcHeight; |
| 128 | float dstAspectRatio = ((float) dstWidth) / dstHeight; |
| 129 | float relativeAspectRatio = dstAspectRatio / srcAspectRatio; |
| 130 | |
Yuli Huang | 7b62f48 | 2011-10-20 18:29:05 +0800 | [diff] [blame] | 131 | float[] vertices = new float[8]; |
Yuli Huang | 6a12ad7 | 2011-09-12 22:25:30 +0800 | [diff] [blame] | 132 | System.arraycopy(POS_VERTICES, 0, vertices, 0, vertices.length); |
| 133 | if (relativeAspectRatio > 1.0f) { |
| 134 | // Screen is wider than the camera, scale down X |
| 135 | vertices[0] /= relativeAspectRatio; |
| 136 | vertices[2] /= relativeAspectRatio; |
| 137 | vertices[4] /= relativeAspectRatio; |
| 138 | vertices[6] /= relativeAspectRatio; |
| 139 | } else { |
| 140 | vertices[1] *= relativeAspectRatio; |
| 141 | vertices[3] *= relativeAspectRatio; |
| 142 | vertices[5] *= relativeAspectRatio; |
| 143 | vertices[7] *= relativeAspectRatio; |
| 144 | } |
Yuli Huang | 7b62f48 | 2011-10-20 18:29:05 +0800 | [diff] [blame] | 145 | return vertices; |
| 146 | } |
| 147 | |
| 148 | public static void setRenderToFit(RenderContext context, int srcWidth, int srcHeight, |
| 149 | int dstWidth, int dstHeight) { |
| 150 | context.posVertices = createVerticesBuffer( |
| 151 | getFitVertices(srcWidth, srcHeight, dstWidth, dstHeight)); |
Yuli Huang | 6a12ad7 | 2011-09-12 22:25:30 +0800 | [diff] [blame] | 152 | } |
| 153 | |
| 154 | public static void setRenderToRotate(RenderContext context, int srcWidth, int srcHeight, |
| 155 | int dstWidth, int dstHeight, float degrees) { |
Yuli Huang | 7b62f48 | 2011-10-20 18:29:05 +0800 | [diff] [blame] | 156 | float radian = -degrees * DEGREE_TO_RADIAN; |
Chih-Chung Chang | fd91413 | 2012-02-11 07:19:47 +0800 | [diff] [blame] | 157 | float cosTheta = FloatMath.cos(radian); |
| 158 | float sinTheta = FloatMath.sin(radian); |
Yuli Huang | 6a12ad7 | 2011-09-12 22:25:30 +0800 | [diff] [blame] | 159 | float cosWidth = cosTheta * srcWidth; |
| 160 | float sinWidth = sinTheta * srcWidth; |
| 161 | float cosHeight = cosTheta * srcHeight; |
| 162 | float sinHeight = sinTheta * srcHeight; |
| 163 | |
| 164 | float[] vertices = new float[8]; |
| 165 | vertices[0] = -cosWidth + sinHeight; |
| 166 | vertices[1] = -sinWidth - cosHeight; |
| 167 | vertices[2] = cosWidth + sinHeight; |
| 168 | vertices[3] = sinWidth - cosHeight; |
| 169 | vertices[4] = -vertices[2]; |
| 170 | vertices[5] = -vertices[3]; |
| 171 | vertices[6] = -vertices[0]; |
| 172 | vertices[7] = -vertices[1]; |
| 173 | |
| 174 | float maxWidth = Math.max(Math.abs(vertices[0]), Math.abs(vertices[2])); |
| 175 | float maxHeight = Math.max(Math.abs(vertices[1]), Math.abs(vertices[3])); |
| 176 | float scale = Math.min(dstWidth / maxWidth, dstHeight / maxHeight); |
| 177 | |
| 178 | for (int i = 0; i < 8; i += 2) { |
| 179 | vertices[i] *= scale / dstWidth; |
| 180 | vertices[i + 1] *= scale / dstHeight; |
| 181 | } |
| 182 | context.posVertices = createVerticesBuffer(vertices); |
| 183 | } |
| 184 | |
Yuli Huang | 7b62f48 | 2011-10-20 18:29:05 +0800 | [diff] [blame] | 185 | public static void setRenderToFlip(RenderContext context, int srcWidth, int srcHeight, |
| 186 | int dstWidth, int dstHeight, float horizontalDegrees, float verticalDegrees) { |
| 187 | // Calculate the base flip coordinates. |
| 188 | float[] base = getFitVertices(srcWidth, srcHeight, dstWidth, dstHeight); |
| 189 | int horizontalRounds = (int) horizontalDegrees / 180; |
| 190 | if (horizontalRounds % 2 != 0) { |
| 191 | base[0] = -base[0]; |
| 192 | base[4] = base[0]; |
| 193 | base[2] = -base[2]; |
| 194 | base[6] = base[2]; |
| 195 | } |
| 196 | int verticalRounds = (int) verticalDegrees / 180; |
| 197 | if (verticalRounds % 2 != 0) { |
| 198 | base[1] = -base[1]; |
| 199 | base[3] = base[1]; |
| 200 | base[5] = -base[5]; |
| 201 | base[7] = base[5]; |
| 202 | } |
| 203 | |
| 204 | float length = 5; |
| 205 | float[] vertices = new float[8]; |
| 206 | System.arraycopy(base, 0, vertices, 0, vertices.length); |
| 207 | if (horizontalDegrees % 180f != 0) { |
| 208 | float radian = (horizontalDegrees - horizontalRounds * 180) * DEGREE_TO_RADIAN; |
Chih-Chung Chang | fd91413 | 2012-02-11 07:19:47 +0800 | [diff] [blame] | 209 | float cosTheta = FloatMath.cos(radian); |
| 210 | float sinTheta = FloatMath.sin(radian); |
Yuli Huang | 7b62f48 | 2011-10-20 18:29:05 +0800 | [diff] [blame] | 211 | |
| 212 | float scale = length / (length + sinTheta * base[0]); |
| 213 | vertices[0] = cosTheta * base[0] * scale; |
| 214 | vertices[1] = base[1] * scale; |
| 215 | vertices[4] = vertices[0]; |
| 216 | vertices[5] = base[5] * scale; |
| 217 | |
| 218 | scale = length / (length + sinTheta * base[2]); |
| 219 | vertices[2] = cosTheta * base[2] * scale; |
| 220 | vertices[3] = base[3] * scale; |
| 221 | vertices[6] = vertices[2]; |
| 222 | vertices[7] = base[7] * scale; |
| 223 | } |
| 224 | |
| 225 | if (verticalDegrees % 180f != 0) { |
| 226 | float radian = (verticalDegrees - verticalRounds * 180) * DEGREE_TO_RADIAN; |
Chih-Chung Chang | fd91413 | 2012-02-11 07:19:47 +0800 | [diff] [blame] | 227 | float cosTheta = FloatMath.cos(radian); |
| 228 | float sinTheta = FloatMath.sin(radian); |
Yuli Huang | 7b62f48 | 2011-10-20 18:29:05 +0800 | [diff] [blame] | 229 | |
| 230 | float scale = length / (length + sinTheta * base[1]); |
| 231 | vertices[0] = base[0] * scale; |
| 232 | vertices[1] = cosTheta * base[1] * scale; |
| 233 | vertices[2] = base[2] * scale; |
| 234 | vertices[3] = vertices[1]; |
| 235 | |
| 236 | scale = length / (length + sinTheta * base[5]); |
| 237 | vertices[4] = base[4] * scale; |
| 238 | vertices[5] = cosTheta * base[5] * scale; |
| 239 | vertices[6] = base[6] * scale; |
| 240 | vertices[7] = vertices[5]; |
| 241 | } |
| 242 | context.posVertices = createVerticesBuffer(vertices); |
| 243 | } |
| 244 | |
Yuli Huang | eb83d83 | 2011-10-11 21:23:22 +0800 | [diff] [blame] | 245 | public static void renderBackground() { |
| 246 | GLES20.glClearColor(0, 0, 0, 1); |
| 247 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); |
| 248 | } |
| 249 | |
Yuli Huang | 6a12ad7 | 2011-09-12 22:25:30 +0800 | [diff] [blame] | 250 | public static void renderTexture( |
| 251 | RenderContext context, int texture, int viewWidth, int viewHeight) { |
| 252 | // Use our shader program |
| 253 | GLES20.glUseProgram(context.shaderProgram); |
| 254 | checkGlError("glUseProgram"); |
| 255 | |
| 256 | // Set viewport |
| 257 | GLES20.glViewport(0, 0, viewWidth, viewHeight); |
| 258 | checkGlError("glViewport"); |
| 259 | |
| 260 | // Disable blending |
| 261 | GLES20.glDisable(GLES20.GL_BLEND); |
| 262 | |
| 263 | // Set the vertex attributes |
| 264 | GLES20.glVertexAttribPointer( |
| 265 | context.texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, context.texVertices); |
| 266 | GLES20.glEnableVertexAttribArray(context.texCoordHandle); |
| 267 | GLES20.glVertexAttribPointer( |
| 268 | context.posCoordHandle, 2, GLES20.GL_FLOAT, false, 0, context.posVertices); |
| 269 | GLES20.glEnableVertexAttribArray(context.posCoordHandle); |
| 270 | checkGlError("vertex attribute setup"); |
| 271 | |
| 272 | // Set the input texture |
| 273 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); |
| 274 | checkGlError("glActiveTexture"); |
| 275 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture); |
| 276 | checkGlError("glBindTexture"); |
| 277 | GLES20.glUniform1i(context.texSamplerHandle, 0); |
| 278 | |
| 279 | // Draw! |
Yuli Huang | 6a12ad7 | 2011-09-12 22:25:30 +0800 | [diff] [blame] | 280 | GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); |
| 281 | } |
| 282 | |
| 283 | public static RenderContext createProgram() { |
| 284 | int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER); |
| 285 | if (vertexShader == 0) { |
| 286 | return null; |
| 287 | } |
| 288 | int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER); |
| 289 | if (pixelShader == 0) { |
| 290 | return null; |
| 291 | } |
| 292 | |
| 293 | int program = GLES20.glCreateProgram(); |
| 294 | if (program != 0) { |
| 295 | GLES20.glAttachShader(program, vertexShader); |
| 296 | checkGlError("glAttachShader"); |
| 297 | GLES20.glAttachShader(program, pixelShader); |
| 298 | checkGlError("glAttachShader"); |
| 299 | GLES20.glLinkProgram(program); |
| 300 | int[] linkStatus = new int[1]; |
| 301 | GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); |
| 302 | if (linkStatus[0] != GLES20.GL_TRUE) { |
| 303 | String info = GLES20.glGetProgramInfoLog(program); |
| 304 | GLES20.glDeleteProgram(program); |
| 305 | program = 0; |
| 306 | throw new RuntimeException("Could not link program: " + info); |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | // Bind attributes and uniforms |
| 311 | RenderContext context = new RenderContext(); |
| 312 | context.texSamplerHandle = GLES20.glGetUniformLocation(program, "tex_sampler"); |
| 313 | context.texCoordHandle = GLES20.glGetAttribLocation(program, "a_texcoord"); |
| 314 | context.posCoordHandle = GLES20.glGetAttribLocation(program, "a_position"); |
| 315 | context.texVertices = createVerticesBuffer(TEX_VERTICES); |
| 316 | context.posVertices = createVerticesBuffer(POS_VERTICES); |
| 317 | |
| 318 | context.shaderProgram = program; |
| 319 | return context; |
| 320 | } |
| 321 | |
| 322 | private static int loadShader(int shaderType, String source) { |
| 323 | int shader = GLES20.glCreateShader(shaderType); |
| 324 | if (shader != 0) { |
| 325 | GLES20.glShaderSource(shader, source); |
| 326 | GLES20.glCompileShader(shader); |
| 327 | int[] compiled = new int[1]; |
| 328 | GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); |
| 329 | if (compiled[0] == 0) { |
| 330 | String info = GLES20.glGetShaderInfoLog(shader); |
| 331 | GLES20.glDeleteShader(shader); |
| 332 | shader = 0; |
| 333 | throw new RuntimeException("Could not compile shader " + shaderType + ":" + info); |
| 334 | } |
| 335 | } |
| 336 | return shader; |
| 337 | } |
| 338 | |
| 339 | private static FloatBuffer createVerticesBuffer(float[] vertices) { |
| 340 | if (vertices.length != 8) { |
| 341 | throw new RuntimeException("Number of vertices should be four."); |
| 342 | } |
| 343 | |
| 344 | FloatBuffer buffer = ByteBuffer.allocateDirect( |
| 345 | vertices.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer(); |
| 346 | buffer.put(vertices).position(0); |
| 347 | return buffer; |
| 348 | } |
| 349 | |
| 350 | private static void checkGlError(String op) { |
| 351 | int error; |
| 352 | while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { |
| 353 | throw new RuntimeException(op + ": glError " + error); |
| 354 | } |
| 355 | } |
| 356 | } |