1/* 2 * Copyright (C) 2012 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.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.Paint; 24import android.graphics.PorterDuff.Mode; 25import android.graphics.PorterDuffXfermode; 26import android.graphics.RectF; 27import android.os.SystemClock; 28 29import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; 30 31import java.util.ArrayDeque; 32import java.util.ArrayList; 33 34// This class is similar to BitmapTexture, except the bitmap is 35// split into tiles. By doing so, we may increase the time required to 36// upload the whole bitmap but we reduce the time of uploading each tile 37// so it make the animation more smooth and prevents jank. 38public class TiledTexture implements Texture { 39 private static final int CONTENT_SIZE = 254; 40 private static final int BORDER_SIZE = 1; 41 private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE; 42 private static final int INIT_CAPACITY = 8; 43 44 // We are targeting at 60fps, so we have 16ms for each frame. 45 // In this 16ms, we use about 4~8 ms to upload tiles. 46 private static final long UPLOAD_TILE_LIMIT = 4; // ms 47 48 private static Tile sFreeTileHead = null; 49 private static final Object sFreeTileLock = new Object(); 50 51 private static Bitmap sUploadBitmap; 52 private static Canvas sCanvas; 53 private static Paint sBitmapPaint; 54 private static Paint sPaint; 55 56 private int mUploadIndex = 0; 57 58 private final Tile[] mTiles; // Can be modified in different threads. 59 // Should be protected by "synchronized." 60 private final int mWidth; 61 private final int mHeight; 62 private final RectF mSrcRect = new RectF(); 63 private final RectF mDestRect = new RectF(); 64 65 public static class Uploader implements OnGLIdleListener { 66 private final ArrayDeque<TiledTexture> mTextures = 67 new ArrayDeque<TiledTexture>(INIT_CAPACITY); 68 69 private final GLRoot mGlRoot; 70 private boolean mIsQueued = false; 71 72 public Uploader(GLRoot glRoot) { 73 mGlRoot = glRoot; 74 } 75 76 public synchronized void clear() { 77 mTextures.clear(); 78 } 79 80 public synchronized void addTexture(TiledTexture t) { 81 if (t.isReady()) return; 82 mTextures.addLast(t); 83 84 if (mIsQueued) return; 85 mIsQueued = true; 86 mGlRoot.addOnGLIdleListener(this); 87 } 88 89 @Override 90 public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { 91 ArrayDeque<TiledTexture> deque = mTextures; 92 synchronized (this) { 93 long now = SystemClock.uptimeMillis(); 94 long dueTime = now + UPLOAD_TILE_LIMIT; 95 while (now < dueTime && !deque.isEmpty()) { 96 TiledTexture t = deque.peekFirst(); 97 if (t.uploadNextTile(canvas)) { 98 deque.removeFirst(); 99 mGlRoot.requestRender(); 100 } 101 now = SystemClock.uptimeMillis(); 102 } 103 mIsQueued = !mTextures.isEmpty(); 104 105 // return true to keep this listener in the queue 106 return mIsQueued; 107 } 108 } 109 } 110 111 private static class Tile extends UploadedTexture { 112 public int offsetX; 113 public int offsetY; 114 public Bitmap bitmap; 115 public Tile nextFreeTile; 116 public int contentWidth; 117 public int contentHeight; 118 119 @Override 120 public void setSize(int width, int height) { 121 contentWidth = width; 122 contentHeight = height; 123 mWidth = width + 2 * BORDER_SIZE; 124 mHeight = height + 2 * BORDER_SIZE; 125 mTextureWidth = TILE_SIZE; 126 mTextureHeight = TILE_SIZE; 127 } 128 129 @Override 130 protected Bitmap onGetBitmap() { 131 int x = BORDER_SIZE - offsetX; 132 int y = BORDER_SIZE - offsetY; 133 int r = bitmap.getWidth() + x; 134 int b = bitmap.getHeight() + y; 135 sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint); 136 bitmap = null; 137 138 // draw borders if need 139 if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint); 140 if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint); 141 if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint); 142 if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint); 143 144 return sUploadBitmap; 145 } 146 147 @Override 148 protected void onFreeBitmap(Bitmap bitmap) { 149 // do nothing 150 } 151 } 152 153 private static void freeTile(Tile tile) { 154 tile.invalidateContent(); 155 tile.bitmap = null; 156 synchronized (sFreeTileLock) { 157 tile.nextFreeTile = sFreeTileHead; 158 sFreeTileHead = tile; 159 } 160 } 161 162 private static Tile obtainTile() { 163 synchronized (sFreeTileLock) { 164 Tile result = sFreeTileHead; 165 if (result == null) return new Tile(); 166 sFreeTileHead = result.nextFreeTile; 167 result.nextFreeTile = null; 168 return result; 169 } 170 } 171 172 private boolean uploadNextTile(GLCanvas canvas) { 173 if (mUploadIndex == mTiles.length) return true; 174 175 synchronized (mTiles) { 176 Tile next = mTiles[mUploadIndex++]; 177 178 // Make sure tile has not already been recycled by the time 179 // this is called (race condition in onGLIdle) 180 if (next.bitmap != null) { 181 boolean hasBeenLoad = next.isLoaded(); 182 next.updateContent(canvas); 183 184 // It will take some time for a texture to be drawn for the first 185 // time. When scrolling, we need to draw several tiles on the screen 186 // at the same time. It may cause a UI jank even these textures has 187 // been uploaded. 188 if (!hasBeenLoad) next.draw(canvas, 0, 0); 189 } 190 } 191 return mUploadIndex == mTiles.length; 192 } 193 194 public TiledTexture(Bitmap bitmap) { 195 mWidth = bitmap.getWidth(); 196 mHeight = bitmap.getHeight(); 197 ArrayList<Tile> list = new ArrayList<Tile>(); 198 199 for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) { 200 for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) { 201 Tile tile = obtainTile(); 202 tile.offsetX = x; 203 tile.offsetY = y; 204 tile.bitmap = bitmap; 205 tile.setSize( 206 Math.min(CONTENT_SIZE, mWidth - x), 207 Math.min(CONTENT_SIZE, mHeight - y)); 208 list.add(tile); 209 } 210 } 211 mTiles = list.toArray(new Tile[list.size()]); 212 } 213 214 public boolean isReady() { 215 return mUploadIndex == mTiles.length; 216 } 217 218 // Can be called in UI thread. 219 public void recycle() { 220 synchronized (mTiles) { 221 for (int i = 0, n = mTiles.length; i < n; ++i) { 222 freeTile(mTiles[i]); 223 } 224 } 225 } 226 227 public static void freeResources() { 228 sUploadBitmap = null; 229 sCanvas = null; 230 sBitmapPaint = null; 231 sPaint = null; 232 } 233 234 public static void prepareResources() { 235 sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888); 236 sCanvas = new Canvas(sUploadBitmap); 237 sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 238 sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC)); 239 sPaint = new Paint(); 240 sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC)); 241 sPaint.setColor(Color.TRANSPARENT); 242 } 243 244 // We want to draw the "source" on the "target". 245 // This method is to find the "output" rectangle which is 246 // the corresponding area of the "src". 247 // (x,y) target 248 // (x0,y0) source +---------------+ 249 // +----------+ | | 250 // | src | | output | 251 // | +--+ | linear map | +----+ | 252 // | +--+ | ----------> | | | | 253 // | | by (scaleX, scaleY) | +----+ | 254 // +----------+ | | 255 // Texture +---------------+ 256 // Canvas 257 private static void mapRect(RectF output, 258 RectF src, float x0, float y0, float x, float y, float scaleX, 259 float scaleY) { 260 output.set(x + (src.left - x0) * scaleX, 261 y + (src.top - y0) * scaleY, 262 x + (src.right - x0) * scaleX, 263 y + (src.bottom - y0) * scaleY); 264 } 265 266 // Draws a mixed color of this texture and a specified color onto the 267 // a rectangle. The used color is: from * (1 - ratio) + to * ratio. 268 public void drawMixed(GLCanvas canvas, int color, float ratio, 269 int x, int y, int width, int height) { 270 RectF src = mSrcRect; 271 RectF dest = mDestRect; 272 float scaleX = (float) width / mWidth; 273 float scaleY = (float) height / mHeight; 274 synchronized (mTiles) { 275 for (int i = 0, n = mTiles.length; i < n; ++i) { 276 Tile t = mTiles[i]; 277 src.set(0, 0, t.contentWidth, t.contentHeight); 278 src.offset(t.offsetX, t.offsetY); 279 mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); 280 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); 281 canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); 282 } 283 } 284 } 285 286 // Draws the texture on to the specified rectangle. 287 @Override 288 public void draw(GLCanvas canvas, int x, int y, int width, int height) { 289 RectF src = mSrcRect; 290 RectF dest = mDestRect; 291 float scaleX = (float) width / mWidth; 292 float scaleY = (float) height / mHeight; 293 synchronized (mTiles) { 294 for (int i = 0, n = mTiles.length; i < n; ++i) { 295 Tile t = mTiles[i]; 296 src.set(0, 0, t.contentWidth, t.contentHeight); 297 src.offset(t.offsetX, t.offsetY); 298 mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); 299 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); 300 canvas.drawTexture(t, mSrcRect, mDestRect); 301 } 302 } 303 } 304 305 // Draws a sub region of this texture on to the specified rectangle. 306 public void draw(GLCanvas canvas, RectF source, RectF target) { 307 RectF src = mSrcRect; 308 RectF dest = mDestRect; 309 float x0 = source.left; 310 float y0 = source.top; 311 float x = target.left; 312 float y = target.top; 313 float scaleX = target.width() / source.width(); 314 float scaleY = target.height() / source.height(); 315 316 synchronized (mTiles) { 317 for (int i = 0, n = mTiles.length; i < n; ++i) { 318 Tile t = mTiles[i]; 319 src.set(0, 0, t.contentWidth, t.contentHeight); 320 src.offset(t.offsetX, t.offsetY); 321 if (!src.intersect(source)) continue; 322 mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); 323 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); 324 canvas.drawTexture(t, src, dest); 325 } 326 } 327 } 328 329 @Override 330 public int getWidth() { 331 return mWidth; 332 } 333 334 @Override 335 public int getHeight() { 336 return mHeight; 337 } 338 339 @Override 340 public void draw(GLCanvas canvas, int x, int y) { 341 draw(canvas, x, y, mWidth, mHeight); 342 } 343 344 @Override 345 public boolean isOpaque() { 346 return false; 347 } 348} 349