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