UploadedTexture.java revision cb4fb7c19f20405fb5e08513e6297dffce824118
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 17package com.android.gallery3d.ui; 18 19import android.graphics.Bitmap; 20import android.graphics.Bitmap.Config; 21import android.opengl.GLUtils; 22 23import com.android.gallery3d.common.Utils; 24 25import java.util.HashMap; 26 27import 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; 61 private int mBorder; 62 63 protected UploadedTexture() { 64 this(false); 65 } 66 67 protected UploadedTexture(boolean hasBorder) { 68 super(null, 0, STATE_UNLOADED); 69 if (hasBorder) { 70 setBorder(true); 71 mBorder = 1; 72 } 73 } 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(); 127 int w = mBitmap.getWidth() + mBorder * 2; 128 int h = mBitmap.getHeight() + mBorder * 2; 129 if (mWidth == UNSPECIFIED) { 130 setSize(w, h); 131 } else if (mWidth != w || mHeight != h) { 132 throw new IllegalStateException(String.format( 133 "cannot change size: this = %s, orig = %sx%s, new = %sx%s", 134 toString(), mWidth, mHeight, w, h)); 135 } 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; 165 mWidth = UNSPECIFIED; 166 mHeight = UNSPECIFIED; 167 } 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); 191 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder, 192 bitmap, format, type); 193 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 { 215 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(); 221 // Define a vertically flipped crop rectangle for 222 // OES_draw_texture. 223 // 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; 229 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 244 if (bWidth == texWidth && bHeight == texHeight) { 245 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, 252 texWidth, texHeight, 0, format, type, null); 253 GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, 254 mBorder, mBorder, bitmap, format, type); 255 256 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); 266 } 267 268 // 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); 273 } 274 275 // 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 } 281 } 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} 316