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 73 public Uploader(GLRoot glRoot) { 74 mGlRoot = glRoot; 75 } 76 77 public synchronized void clear() { 78 mTextures.clear(); 79 } 80 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 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 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 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 156 protected void onFreeBitmap(Bitmap bitmap) { 157 // do nothing 158 } 159 } 160 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 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 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 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 222 public boolean isReady() { 223 return mUploadIndex == mTiles.length; 224 } 225 226 // Can be called in UI thread. 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 235 public static void freeResources() { 236 sUploadBitmap = null; 237 sCanvas = null; 238 sBitmapPaint = null; 239 sPaint = null; 240 } 241 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 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. 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 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. 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 338 public int getWidth() { 339 return mWidth; 340 } 341 342 @Override 343 public int getHeight() { 344 return mHeight; 345 } 346 347 @Override 348 public void draw(GLCanvas canvas, int x, int y) { 349 draw(canvas, x, y, mWidth, mHeight); 350 } 351 352 @Override 353 public boolean isOpaque() { 354 return false; 355 } 356 } 357