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