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