blob: c4d8d82f8e5245e92fe6b8a15e1751bcae8fe2c7 [file] [log] [blame]
Owen Lina2fba682011-08-17 22:07:43 +08001/*
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
17package com.android.gallery3d.ui;
18
Owen Lina2fba682011-08-17 22:07:43 +080019import android.graphics.Bitmap;
20import android.graphics.Bitmap.Config;
21import android.opengl.GLUtils;
22
Owen Lin73a04ff2012-03-14 17:27:24 +080023import com.android.gallery3d.common.Utils;
24
Owen Lina2fba682011-08-17 22:07:43 +080025import java.util.HashMap;
Owen Lin73a04ff2012-03-14 17:27:24 +080026
Owen Lina2fba682011-08-17 22:07:43 +080027import javax.microedition.khronos.opengles.GL11;
28import javax.microedition.khronos.opengles.GL11Ext;
29
30// UploadedTextures use a Bitmap for the content of the texture.
31//
32// Subclasses should implement onGetBitmap() to provide the Bitmap and
33// implement onFreeBitmap(mBitmap) which will be called when the Bitmap
34// is not needed anymore.
35//
36// isContentValid() is meaningful only when the isLoaded() returns true.
37// It means whether the content needs to be updated.
38//
39// The user of this class should call recycle() when the texture is not
40// needed anymore.
41//
42// By default an UploadedTexture is opaque (so it can be drawn faster without
43// blending). The user or subclass can override it using setOpaque().
44abstract class UploadedTexture extends BasicTexture {
45
46 // To prevent keeping allocation the borders, we store those used borders here.
47 // Since the length will be power of two, it won't use too much memory.
48 private static HashMap<BorderKey, Bitmap> sBorderLines =
49 new HashMap<BorderKey, Bitmap>();
50 private static BorderKey sBorderKey = new BorderKey();
51
52 @SuppressWarnings("unused")
53 private static final String TAG = "Texture";
54 private boolean mContentValid = true;
55 private boolean mOpaque = true;
56 private boolean mThrottled = false;
57 private static int sUploadedCount;
58 private static final int UPLOAD_LIMIT = 100;
59
60 protected Bitmap mBitmap;
Chih-Chung Change3312ff2011-09-22 14:55:32 +080061 private int mBorder;
Owen Lina2fba682011-08-17 22:07:43 +080062
63 protected UploadedTexture() {
Chih-Chung Change3312ff2011-09-22 14:55:32 +080064 this(false);
65 }
66
67 protected UploadedTexture(boolean hasBorder) {
Owen Lina2fba682011-08-17 22:07:43 +080068 super(null, 0, STATE_UNLOADED);
Chih-Chung Change3312ff2011-09-22 14:55:32 +080069 if (hasBorder) {
70 setBorder(true);
71 mBorder = 1;
72 }
Owen Lina2fba682011-08-17 22:07:43 +080073 }
74
75 private static class BorderKey implements Cloneable {
76 public boolean vertical;
77 public Config config;
78 public int length;
79
80 @Override
81 public int hashCode() {
82 int x = config.hashCode() ^ length;
83 return vertical ? x : -x;
84 }
85
86 @Override
87 public boolean equals(Object object) {
88 if (!(object instanceof BorderKey)) return false;
89 BorderKey o = (BorderKey) object;
90 return vertical == o.vertical
91 && config == o.config && length == o.length;
92 }
93
94 @Override
95 public BorderKey clone() {
96 try {
97 return (BorderKey) super.clone();
98 } catch (CloneNotSupportedException e) {
99 throw new AssertionError(e);
100 }
101 }
102 }
103
104 protected void setThrottled(boolean throttled) {
105 mThrottled = throttled;
106 }
107
108 private static Bitmap getBorderLine(
109 boolean vertical, Config config, int length) {
110 BorderKey key = sBorderKey;
111 key.vertical = vertical;
112 key.config = config;
113 key.length = length;
114 Bitmap bitmap = sBorderLines.get(key);
115 if (bitmap == null) {
116 bitmap = vertical
117 ? Bitmap.createBitmap(1, length, config)
118 : Bitmap.createBitmap(length, 1, config);
119 sBorderLines.put(key.clone(), bitmap);
120 }
121 return bitmap;
122 }
123
124 private Bitmap getBitmap() {
125 if (mBitmap == null) {
126 mBitmap = onGetBitmap();
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800127 int w = mBitmap.getWidth() + mBorder * 2;
128 int h = mBitmap.getHeight() + mBorder * 2;
Owen Lina2fba682011-08-17 22:07:43 +0800129 if (mWidth == UNSPECIFIED) {
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800130 setSize(w, h);
131 } else if (mWidth != w || mHeight != h) {
Owen Lina2fba682011-08-17 22:07:43 +0800132 throw new IllegalStateException(String.format(
133 "cannot change size: this = %s, orig = %sx%s, new = %sx%s",
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800134 toString(), mWidth, mHeight, w, h));
Owen Lina2fba682011-08-17 22:07:43 +0800135 }
136 }
137 return mBitmap;
138 }
139
140 private void freeBitmap() {
141 Utils.assertTrue(mBitmap != null);
142 onFreeBitmap(mBitmap);
143 mBitmap = null;
144 }
145
146 @Override
147 public int getWidth() {
148 if (mWidth == UNSPECIFIED) getBitmap();
149 return mWidth;
150 }
151
152 @Override
153 public int getHeight() {
154 if (mWidth == UNSPECIFIED) getBitmap();
155 return mHeight;
156 }
157
158 protected abstract Bitmap onGetBitmap();
159
160 protected abstract void onFreeBitmap(Bitmap bitmap);
161
162 protected void invalidateContent() {
163 if (mBitmap != null) freeBitmap();
164 mContentValid = false;
Chih-Chung Chang4a01c6e2012-03-21 16:19:21 +0800165 mWidth = UNSPECIFIED;
166 mHeight = UNSPECIFIED;
Owen Lina2fba682011-08-17 22:07:43 +0800167 }
168
169 /**
170 * Whether the content on GPU is valid.
171 */
172 public boolean isContentValid(GLCanvas canvas) {
173 return isLoaded(canvas) && mContentValid;
174 }
175
176 /**
177 * Updates the content on GPU's memory.
178 * @param canvas
179 */
180 public void updateContent(GLCanvas canvas) {
181 if (!isLoaded(canvas)) {
182 if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) {
183 return;
184 }
185 uploadToCanvas(canvas);
186 } else if (!mContentValid) {
187 Bitmap bitmap = getBitmap();
188 int format = GLUtils.getInternalFormat(bitmap);
189 int type = GLUtils.getType(bitmap);
190 canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId);
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800191 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder,
192 bitmap, format, type);
Owen Lina2fba682011-08-17 22:07:43 +0800193 freeBitmap();
194 mContentValid = true;
195 }
196 }
197
198 public static void resetUploadLimit() {
199 sUploadedCount = 0;
200 }
201
202 public static boolean uploadLimitReached() {
203 return sUploadedCount > UPLOAD_LIMIT;
204 }
205
206 static int[] sTextureId = new int[1];
207 static float[] sCropRect = new float[4];
208
209 private void uploadToCanvas(GLCanvas canvas) {
210 GL11 gl = canvas.getGLInstance();
211
212 Bitmap bitmap = getBitmap();
213 if (bitmap != null) {
214 try {
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800215 int bWidth = bitmap.getWidth();
216 int bHeight = bitmap.getHeight();
217 int width = bWidth + mBorder * 2;
218 int height = bHeight + mBorder * 2;
219 int texWidth = getTextureWidth();
220 int texHeight = getTextureHeight();
Owen Lina2fba682011-08-17 22:07:43 +0800221 // Define a vertically flipped crop rectangle for
222 // OES_draw_texture.
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800223 // The four values in sCropRect are: left, bottom, width, and
224 // height. Negative value of width or height means flip.
225 sCropRect[0] = mBorder;
226 sCropRect[1] = mBorder + bHeight;
227 sCropRect[2] = bWidth;
228 sCropRect[3] = -bHeight;
Owen Lina2fba682011-08-17 22:07:43 +0800229
230 // Upload the bitmap to a new texture.
231 gl.glGenTextures(1, sTextureId, 0);
232 gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]);
233 gl.glTexParameterfv(GL11.GL_TEXTURE_2D,
234 GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0);
235 gl.glTexParameteri(GL11.GL_TEXTURE_2D,
236 GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
237 gl.glTexParameteri(GL11.GL_TEXTURE_2D,
238 GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);
239 gl.glTexParameterf(GL11.GL_TEXTURE_2D,
240 GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
241 gl.glTexParameterf(GL11.GL_TEXTURE_2D,
242 GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
243
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800244 if (bWidth == texWidth && bHeight == texHeight) {
Owen Lina2fba682011-08-17 22:07:43 +0800245 GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0);
246 } else {
247 int format = GLUtils.getInternalFormat(bitmap);
248 int type = GLUtils.getType(bitmap);
249 Config config = bitmap.getConfig();
250
251 gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format,
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800252 texWidth, texHeight, 0, format, type, null);
253 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
254 mBorder, mBorder, bitmap, format, type);
Owen Lina2fba682011-08-17 22:07:43 +0800255
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800256 if (mBorder > 0) {
257 // Left border
258 Bitmap line = getBorderLine(true, config, texHeight);
259 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
260 0, 0, line, format, type);
261
262 // Top border
263 line = getBorderLine(false, config, texWidth);
264 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
265 0, 0, line, format, type);
Owen Lina2fba682011-08-17 22:07:43 +0800266 }
267
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800268 // Right border
269 if (mBorder + bWidth < texWidth) {
270 Bitmap line = getBorderLine(true, config, texHeight);
271 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
272 mBorder + bWidth, 0, line, format, type);
Owen Lina2fba682011-08-17 22:07:43 +0800273 }
274
Chih-Chung Change3312ff2011-09-22 14:55:32 +0800275 // Bottom border
276 if (mBorder + bHeight < texHeight) {
277 Bitmap line = getBorderLine(false, config, texWidth);
278 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0,
279 0, mBorder + bHeight, line, format, type);
280 }
Owen Lina2fba682011-08-17 22:07:43 +0800281 }
282 } finally {
283 freeBitmap();
284 }
285 // Update texture state.
286 setAssociatedCanvas(canvas);
287 mId = sTextureId[0];
288 mState = UploadedTexture.STATE_LOADED;
289 mContentValid = true;
290 } else {
291 mState = STATE_ERROR;
292 throw new RuntimeException("Texture load fail, no bitmap");
293 }
294 }
295
296 @Override
297 protected boolean onBind(GLCanvas canvas) {
298 updateContent(canvas);
299 return isContentValid(canvas);
300 }
301
302 public void setOpaque(boolean isOpaque) {
303 mOpaque = isOpaque;
304 }
305
306 public boolean isOpaque() {
307 return mOpaque;
308 }
309
310 @Override
311 public void recycle() {
312 super.recycle();
313 if (mBitmap != null) freeBitmap();
314 }
315}