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 int x = BORDER_SIZE - offsetX; 133 int y = BORDER_SIZE - offsetY; 134 int r = bitmap.getWidth() + x; 135 int b = bitmap.getHeight() + y; 136 sCanvas.drawBitmap(bitmap, x, y, sBitmapPaint); 137 bitmap = null; 138 139 // draw borders if need 140 if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint); 141 if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint); 142 if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint); 143 if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint); 144 145 return sUploadBitmap; 146 } 147 148 @Override 149 protected void onFreeBitmap(Bitmap bitmap) { 150 // do nothing 151 } 152 } 153 154 private static void freeTile(Tile tile) { 155 tile.invalidateContent(); 156 tile.bitmap = null; 157 synchronized (sFreeTileLock) { 158 tile.nextFreeTile = sFreeTileHead; 159 sFreeTileHead = tile; 160 } 161 } 162 163 private static Tile obtainTile() { 164 synchronized (sFreeTileLock) { 165 Tile result = sFreeTileHead; 166 if (result == null) return new Tile(); 167 sFreeTileHead = result.nextFreeTile; 168 result.nextFreeTile = null; 169 return result; 170 } 171 } 172 173 private boolean uploadNextTile(GLCanvas canvas) { 174 if (mUploadIndex == mTiles.length) return true; 175 176 synchronized (mTiles) { 177 Tile next = mTiles[mUploadIndex++]; 178 179 // Make sure tile has not already been recycled by the time 180 // this is called (race condition in onGLIdle) 181 if (next.bitmap != null) { 182 boolean hasBeenLoad = next.isLoaded(); 183 next.updateContent(canvas); 184 185 // It will take some time for a texture to be drawn for the first 186 // time. When scrolling, we need to draw several tiles on the screen 187 // at the same time. It may cause a UI jank even these textures has 188 // been uploaded. 189 if (!hasBeenLoad) next.draw(canvas, 0, 0); 190 } 191 } 192 return mUploadIndex == mTiles.length; 193 } 194 195 public TiledTexture(Bitmap bitmap) { 196 mWidth = bitmap.getWidth(); 197 mHeight = bitmap.getHeight(); 198 ArrayList<Tile> list = new ArrayList<Tile>(); 199 200 for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) { 201 for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) { 202 Tile tile = obtainTile(); 203 tile.offsetX = x; 204 tile.offsetY = y; 205 tile.bitmap = bitmap; 206 tile.setSize( 207 Math.min(CONTENT_SIZE, mWidth - x), 208 Math.min(CONTENT_SIZE, mHeight - y)); 209 list.add(tile); 210 } 211 } 212 mTiles = list.toArray(new Tile[list.size()]); 213 } 214 215 public boolean isReady() { 216 return mUploadIndex == mTiles.length; 217 } 218 219 // Can be called in UI thread. 220 public void recycle() { 221 synchronized (mTiles) { 222 for (int i = 0, n = mTiles.length; i < n; ++i) { 223 freeTile(mTiles[i]); 224 } 225 } 226 } 227 228 public static void freeResources() { 229 sUploadBitmap = null; 230 sCanvas = null; 231 sBitmapPaint = null; 232 sPaint = null; 233 } 234 235 public static void prepareResources() { 236 sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888); 237 sCanvas = new Canvas(sUploadBitmap); 238 sBitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 239 sBitmapPaint.setXfermode(new PorterDuffXfermode(Mode.SRC)); 240 sPaint = new Paint(); 241 sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC)); 242 sPaint.setColor(Color.TRANSPARENT); 243 } 244 245 // We want to draw the "source" on the "target". 246 // This method is to find the "output" rectangle which is 247 // the corresponding area of the "src". 248 // (x,y) target 249 // (x0,y0) source +---------------+ 250 // +----------+ | | 251 // | src | | output | 252 // | +--+ | linear map | +----+ | 253 // | +--+ | ----------> | | | | 254 // | | by (scaleX, scaleY) | +----+ | 255 // +----------+ | | 256 // Texture +---------------+ 257 // Canvas 258 private static void mapRect(RectF output, 259 RectF src, float x0, float y0, float x, float y, float scaleX, 260 float scaleY) { 261 output.set(x + (src.left - x0) * scaleX, 262 y + (src.top - y0) * scaleY, 263 x + (src.right - x0) * scaleX, 264 y + (src.bottom - y0) * scaleY); 265 } 266 267 // Draws a mixed color of this texture and a specified color onto the 268 // a rectangle. The used color is: from * (1 - ratio) + to * ratio. 269 public void drawMixed(GLCanvas canvas, int color, float ratio, 270 int x, int y, int width, int height) { 271 RectF src = mSrcRect; 272 RectF dest = mDestRect; 273 float scaleX = (float) width / mWidth; 274 float scaleY = (float) height / mHeight; 275 synchronized (mTiles) { 276 for (int i = 0, n = mTiles.length; i < n; ++i) { 277 Tile t = mTiles[i]; 278 src.set(0, 0, t.contentWidth, t.contentHeight); 279 src.offset(t.offsetX, t.offsetY); 280 mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); 281 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); 282 canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); 283 } 284 } 285 } 286 287 // Draws the texture on to the specified rectangle. 288 @Override 289 public void draw(GLCanvas canvas, int x, int y, int width, int height) { 290 RectF src = mSrcRect; 291 RectF dest = mDestRect; 292 float scaleX = (float) width / mWidth; 293 float scaleY = (float) height / mHeight; 294 synchronized (mTiles) { 295 for (int i = 0, n = mTiles.length; i < n; ++i) { 296 Tile t = mTiles[i]; 297 src.set(0, 0, t.contentWidth, t.contentHeight); 298 src.offset(t.offsetX, t.offsetY); 299 mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); 300 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); 301 canvas.drawTexture(t, mSrcRect, mDestRect); 302 } 303 } 304 } 305 306 // Draws a sub region of this texture on to the specified rectangle. 307 public void draw(GLCanvas canvas, RectF source, RectF target) { 308 RectF src = mSrcRect; 309 RectF dest = mDestRect; 310 float x0 = source.left; 311 float y0 = source.top; 312 float x = target.left; 313 float y = target.top; 314 float scaleX = target.width() / source.width(); 315 float scaleY = target.height() / source.height(); 316 317 synchronized (mTiles) { 318 for (int i = 0, n = mTiles.length; i < n; ++i) { 319 Tile t = mTiles[i]; 320 src.set(0, 0, t.contentWidth, t.contentHeight); 321 src.offset(t.offsetX, t.offsetY); 322 if (!src.intersect(source)) continue; 323 mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); 324 src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); 325 canvas.drawTexture(t, src, dest); 326 } 327 } 328 } 329 330 @Override 331 public int getWidth() { 332 return mWidth; 333 } 334 335 @Override 336 public int getHeight() { 337 return mHeight; 338 } 339 340 @Override 341 public void draw(GLCanvas canvas, int x, int y) { 342 draw(canvas, x, y, mWidth, mHeight); 343 } 344 345 @Override 346 public boolean isOpaque() { 347 return false; 348 } 349 } 350