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.ui; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Bitmap.Config; 21 import android.opengl.GLUtils; 22 23 import com.android.gallery3d.common.Utils; 24 25 import java.util.HashMap; 26 27 import javax.microedition.khronos.opengles.GL11; 28 import 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(). 44 abstract 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 56 // indicate this textures is being uploaded in background 57 private boolean mIsUploading = false; 58 private boolean mOpaque = true; 59 private boolean mThrottled = false; 60 private static int sUploadedCount; 61 private static final int UPLOAD_LIMIT = 100; 62 63 protected Bitmap mBitmap; 64 private int mBorder; 65 UploadedTexture()66 protected UploadedTexture() { 67 this(false); 68 } 69 UploadedTexture(boolean hasBorder)70 protected UploadedTexture(boolean hasBorder) { 71 super(null, 0, STATE_UNLOADED); 72 if (hasBorder) { 73 setBorder(true); 74 mBorder = 1; 75 } 76 } 77 setIsUploading(boolean uploading)78 protected void setIsUploading(boolean uploading) { 79 mIsUploading = uploading; 80 } 81 isUploading()82 public boolean isUploading() { 83 return mIsUploading; 84 } 85 86 private static class BorderKey implements Cloneable { 87 public boolean vertical; 88 public Config config; 89 public int length; 90 91 @Override hashCode()92 public int hashCode() { 93 int x = config.hashCode() ^ length; 94 return vertical ? x : -x; 95 } 96 97 @Override equals(Object object)98 public boolean equals(Object object) { 99 if (!(object instanceof BorderKey)) return false; 100 BorderKey o = (BorderKey) object; 101 return vertical == o.vertical 102 && config == o.config && length == o.length; 103 } 104 105 @Override clone()106 public BorderKey clone() { 107 try { 108 return (BorderKey) super.clone(); 109 } catch (CloneNotSupportedException e) { 110 throw new AssertionError(e); 111 } 112 } 113 } 114 setThrottled(boolean throttled)115 protected void setThrottled(boolean throttled) { 116 mThrottled = throttled; 117 } 118 getBorderLine( boolean vertical, Config config, int length)119 private static Bitmap getBorderLine( 120 boolean vertical, Config config, int length) { 121 BorderKey key = sBorderKey; 122 key.vertical = vertical; 123 key.config = config; 124 key.length = length; 125 Bitmap bitmap = sBorderLines.get(key); 126 if (bitmap == null) { 127 bitmap = vertical 128 ? Bitmap.createBitmap(1, length, config) 129 : Bitmap.createBitmap(length, 1, config); 130 sBorderLines.put(key.clone(), bitmap); 131 } 132 return bitmap; 133 } 134 getBitmap()135 private Bitmap getBitmap() { 136 if (mBitmap == null) { 137 mBitmap = onGetBitmap(); 138 int w = mBitmap.getWidth() + mBorder * 2; 139 int h = mBitmap.getHeight() + mBorder * 2; 140 if (mWidth == UNSPECIFIED) { 141 setSize(w, h); 142 } 143 } 144 return mBitmap; 145 } 146 freeBitmap()147 private void freeBitmap() { 148 Utils.assertTrue(mBitmap != null); 149 onFreeBitmap(mBitmap); 150 mBitmap = null; 151 } 152 153 @Override getWidth()154 public int getWidth() { 155 if (mWidth == UNSPECIFIED) getBitmap(); 156 return mWidth; 157 } 158 159 @Override getHeight()160 public int getHeight() { 161 if (mWidth == UNSPECIFIED) getBitmap(); 162 return mHeight; 163 } 164 onGetBitmap()165 protected abstract Bitmap onGetBitmap(); 166 onFreeBitmap(Bitmap bitmap)167 protected abstract void onFreeBitmap(Bitmap bitmap); 168 invalidateContent()169 protected void invalidateContent() { 170 if (mBitmap != null) freeBitmap(); 171 mContentValid = false; 172 mWidth = UNSPECIFIED; 173 mHeight = UNSPECIFIED; 174 } 175 176 /** 177 * Whether the content on GPU is valid. 178 */ isContentValid()179 public boolean isContentValid() { 180 return isLoaded() && mContentValid; 181 } 182 183 /** 184 * Updates the content on GPU's memory. 185 * @param canvas 186 */ updateContent(GLCanvas canvas)187 public void updateContent(GLCanvas canvas) { 188 if (!isLoaded()) { 189 if (mThrottled && ++sUploadedCount > UPLOAD_LIMIT) { 190 return; 191 } 192 uploadToCanvas(canvas); 193 } else if (!mContentValid) { 194 Bitmap bitmap = getBitmap(); 195 int format = GLUtils.getInternalFormat(bitmap); 196 int type = GLUtils.getType(bitmap); 197 canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId); 198 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder, 199 bitmap, format, type); 200 freeBitmap(); 201 mContentValid = true; 202 } 203 } 204 resetUploadLimit()205 public static void resetUploadLimit() { 206 sUploadedCount = 0; 207 } 208 uploadLimitReached()209 public static boolean uploadLimitReached() { 210 return sUploadedCount > UPLOAD_LIMIT; 211 } 212 213 static int[] sTextureId = new int[1]; 214 static float[] sCropRect = new float[4]; 215 uploadToCanvas(GLCanvas canvas)216 private void uploadToCanvas(GLCanvas canvas) { 217 GL11 gl = canvas.getGLInstance(); 218 219 Bitmap bitmap = getBitmap(); 220 if (bitmap != null) { 221 try { 222 int bWidth = bitmap.getWidth(); 223 int bHeight = bitmap.getHeight(); 224 int width = bWidth + mBorder * 2; 225 int height = bHeight + mBorder * 2; 226 int texWidth = getTextureWidth(); 227 int texHeight = getTextureHeight(); 228 229 Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight); 230 231 // Define a vertically flipped crop rectangle for 232 // OES_draw_texture. 233 // The four values in sCropRect are: left, bottom, width, and 234 // height. Negative value of width or height means flip. 235 sCropRect[0] = mBorder; 236 sCropRect[1] = mBorder + bHeight; 237 sCropRect[2] = bWidth; 238 sCropRect[3] = -bHeight; 239 240 // Upload the bitmap to a new texture. 241 GLId.glGenTextures(1, sTextureId, 0); 242 gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]); 243 gl.glTexParameterfv(GL11.GL_TEXTURE_2D, 244 GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); 245 gl.glTexParameteri(GL11.GL_TEXTURE_2D, 246 GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); 247 gl.glTexParameteri(GL11.GL_TEXTURE_2D, 248 GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); 249 gl.glTexParameterf(GL11.GL_TEXTURE_2D, 250 GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); 251 gl.glTexParameterf(GL11.GL_TEXTURE_2D, 252 GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); 253 254 if (bWidth == texWidth && bHeight == texHeight) { 255 GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0); 256 } else { 257 int format = GLUtils.getInternalFormat(bitmap); 258 int type = GLUtils.getType(bitmap); 259 Config config = bitmap.getConfig(); 260 261 gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, 262 texWidth, texHeight, 0, format, type, null); 263 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 264 mBorder, mBorder, bitmap, format, type); 265 266 if (mBorder > 0) { 267 // Left border 268 Bitmap line = getBorderLine(true, config, texHeight); 269 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 270 0, 0, line, format, type); 271 272 // Top border 273 line = getBorderLine(false, config, texWidth); 274 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 275 0, 0, line, format, type); 276 } 277 278 // Right border 279 if (mBorder + bWidth < texWidth) { 280 Bitmap line = getBorderLine(true, config, texHeight); 281 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 282 mBorder + bWidth, 0, line, format, type); 283 } 284 285 // Bottom border 286 if (mBorder + bHeight < texHeight) { 287 Bitmap line = getBorderLine(false, config, texWidth); 288 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 289 0, mBorder + bHeight, line, format, type); 290 } 291 } 292 } finally { 293 freeBitmap(); 294 } 295 // Update texture state. 296 setAssociatedCanvas(canvas); 297 mId = sTextureId[0]; 298 mState = STATE_LOADED; 299 mContentValid = true; 300 } else { 301 mState = STATE_ERROR; 302 throw new RuntimeException("Texture load fail, no bitmap"); 303 } 304 } 305 306 @Override onBind(GLCanvas canvas)307 protected boolean onBind(GLCanvas canvas) { 308 updateContent(canvas); 309 return isContentValid(); 310 } 311 312 @Override getTarget()313 protected int getTarget() { 314 return GL11.GL_TEXTURE_2D; 315 } 316 setOpaque(boolean isOpaque)317 public void setOpaque(boolean isOpaque) { 318 mOpaque = isOpaque; 319 } 320 isOpaque()321 public boolean isOpaque() { 322 return mOpaque; 323 } 324 325 @Override recycle()326 public void recycle() { 327 super.recycle(); 328 if (mBitmap != null) freeBitmap(); 329 } 330 } 331