1 /* 2 * Copyright (C) 2013 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.photos.views; 18 19 import android.content.Context; 20 import android.graphics.Bitmap; 21 import android.graphics.Rect; 22 import android.graphics.RectF; 23 import android.util.DisplayMetrics; 24 import android.util.Log; 25 import android.util.LongSparseArray; 26 import android.util.Pools.Pool; 27 import android.util.Pools.SynchronizedPool; 28 import android.view.View; 29 30 import com.android.gallery3d.common.Utils; 31 import com.android.gallery3d.glrenderer.BasicTexture; 32 import com.android.gallery3d.glrenderer.GLCanvas; 33 import com.android.gallery3d.glrenderer.UploadedTexture; 34 35 /** 36 * Handles laying out, decoding, and drawing of tiles in GL 37 */ 38 public class TiledImageRenderer { 39 public static final int SIZE_UNKNOWN = -1; 40 41 private static final String TAG = "TiledImageRenderer"; 42 private static final int UPLOAD_LIMIT = 1; 43 44 /* 45 * This is the tile state in the CPU side. 46 * Life of a Tile: 47 * ACTIVATED (initial state) 48 * --> IN_QUEUE - by queueForDecode() 49 * --> RECYCLED - by recycleTile() 50 * IN_QUEUE --> DECODING - by decodeTile() 51 * --> RECYCLED - by recycleTile) 52 * DECODING --> RECYCLING - by recycleTile() 53 * --> DECODED - by decodeTile() 54 * --> DECODE_FAIL - by decodeTile() 55 * RECYCLING --> RECYCLED - by decodeTile() 56 * DECODED --> ACTIVATED - (after the decoded bitmap is uploaded) 57 * DECODED --> RECYCLED - by recycleTile() 58 * DECODE_FAIL -> RECYCLED - by recycleTile() 59 * RECYCLED --> ACTIVATED - by obtainTile() 60 */ 61 private static final int STATE_ACTIVATED = 0x01; 62 private static final int STATE_IN_QUEUE = 0x02; 63 private static final int STATE_DECODING = 0x04; 64 private static final int STATE_DECODED = 0x08; 65 private static final int STATE_DECODE_FAIL = 0x10; 66 private static final int STATE_RECYCLING = 0x20; 67 private static final int STATE_RECYCLED = 0x40; 68 69 private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64); 70 71 // TILE_SIZE must be 2^N 72 private int mTileSize; 73 74 private TileSource mModel; 75 private BasicTexture mPreview; 76 protected int mLevelCount; // cache the value of mScaledBitmaps.length 77 78 // The mLevel variable indicates which level of bitmap we should use. 79 // Level 0 means the original full-sized bitmap, and a larger value means 80 // a smaller scaled bitmap (The width and height of each scaled bitmap is 81 // half size of the previous one). If the value is in [0, mLevelCount), we 82 // use the bitmap in mScaledBitmaps[mLevel] for display, otherwise the value 83 // is mLevelCount 84 private int mLevel = 0; 85 86 private int mOffsetX; 87 private int mOffsetY; 88 89 private int mUploadQuota; 90 private boolean mRenderComplete; 91 92 private final RectF mSourceRect = new RectF(); 93 private final RectF mTargetRect = new RectF(); 94 95 private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>(); 96 97 // The following three queue are guarded by mQueueLock 98 private final Object mQueueLock = new Object(); 99 private final TileQueue mRecycledQueue = new TileQueue(); 100 private final TileQueue mUploadQueue = new TileQueue(); 101 private final TileQueue mDecodeQueue = new TileQueue(); 102 103 // The width and height of the full-sized bitmap 104 protected int mImageWidth = SIZE_UNKNOWN; 105 protected int mImageHeight = SIZE_UNKNOWN; 106 107 protected int mCenterX; 108 protected int mCenterY; 109 protected float mScale; 110 protected int mRotation; 111 112 private boolean mLayoutTiles; 113 114 // Temp variables to avoid memory allocation 115 private final Rect mTileRange = new Rect(); 116 private final Rect mActiveRange[] = {new Rect(), new Rect()}; 117 118 private TileDecoder mTileDecoder; 119 private boolean mBackgroundTileUploaded; 120 121 private int mViewWidth, mViewHeight; 122 private View mParent; 123 124 /** 125 * Interface for providing tiles to a {@link TiledImageRenderer} 126 */ 127 public static interface TileSource { 128 129 /** 130 * If the source does not care about the tile size, it should use 131 * {@link TiledImageRenderer#suggestedTileSize(Context)} 132 */ getTileSize()133 public int getTileSize(); getImageWidth()134 public int getImageWidth(); getImageHeight()135 public int getImageHeight(); getRotation()136 public int getRotation(); 137 138 /** 139 * Return a Preview image if available. This will be used as the base layer 140 * if higher res tiles are not yet available 141 */ getPreview()142 public BasicTexture getPreview(); 143 144 /** 145 * The tile returned by this method can be specified this way: Assuming 146 * the image size is (width, height), first take the intersection of (0, 147 * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If 148 * in extending the region, we found some part of the region is outside 149 * the image, those pixels are filled with black. 150 * 151 * If level > 0, it does the same operation on a down-scaled version of 152 * the original image (down-scaled by a factor of 2^level), but (x, y) 153 * still refers to the coordinate on the original image. 154 * 155 * The method would be called by the decoder thread. 156 */ getTile(int level, int x, int y, Bitmap reuse)157 public Bitmap getTile(int level, int x, int y, Bitmap reuse); 158 } 159 suggestedTileSize(Context context)160 public static int suggestedTileSize(Context context) { 161 return isHighResolution(context) ? 512 : 256; 162 } 163 isHighResolution(Context context)164 private static boolean isHighResolution(Context context) { 165 DisplayMetrics metrics = new DisplayMetrics(); 166 context.getDisplayNoVerify().getRealMetrics(metrics); 167 return metrics.heightPixels > 2048 || metrics.widthPixels > 2048; 168 } 169 TiledImageRenderer(View parent)170 public TiledImageRenderer(View parent) { 171 mParent = parent; 172 mTileDecoder = new TileDecoder(); 173 mTileDecoder.start(); 174 } 175 getViewWidth()176 public int getViewWidth() { 177 return mViewWidth; 178 } 179 getViewHeight()180 public int getViewHeight() { 181 return mViewHeight; 182 } 183 invalidate()184 private void invalidate() { 185 mParent.postInvalidate(); 186 } 187 setModel(TileSource model, int rotation)188 public void setModel(TileSource model, int rotation) { 189 if (mModel != model) { 190 mModel = model; 191 notifyModelInvalidated(); 192 } 193 if (mRotation != rotation) { 194 mRotation = rotation; 195 mLayoutTiles = true; 196 } 197 } 198 calculateLevelCount()199 private void calculateLevelCount() { 200 if (mPreview != null) { 201 mLevelCount = Math.max(0, Utils.ceilLog2( 202 mImageWidth / (float) mPreview.getWidth())); 203 } else { 204 int levels = 1; 205 int maxDim = Math.max(mImageWidth, mImageHeight); 206 int t = mTileSize; 207 while (t < maxDim) { 208 t <<= 1; 209 levels++; 210 } 211 mLevelCount = levels; 212 } 213 } 214 notifyModelInvalidated()215 public void notifyModelInvalidated() { 216 invalidateTiles(); 217 if (mModel == null) { 218 mImageWidth = 0; 219 mImageHeight = 0; 220 mLevelCount = 0; 221 mPreview = null; 222 } else { 223 mImageWidth = mModel.getImageWidth(); 224 mImageHeight = mModel.getImageHeight(); 225 mPreview = mModel.getPreview(); 226 mTileSize = mModel.getTileSize(); 227 calculateLevelCount(); 228 } 229 mLayoutTiles = true; 230 } 231 setViewSize(int width, int height)232 public void setViewSize(int width, int height) { 233 mViewWidth = width; 234 mViewHeight = height; 235 } 236 setPosition(int centerX, int centerY, float scale)237 public void setPosition(int centerX, int centerY, float scale) { 238 if (mCenterX == centerX && mCenterY == centerY 239 && mScale == scale) { 240 return; 241 } 242 mCenterX = centerX; 243 mCenterY = centerY; 244 mScale = scale; 245 mLayoutTiles = true; 246 } 247 248 // Prepare the tiles we want to use for display. 249 // 250 // 1. Decide the tile level we want to use for display. 251 // 2. Decide the tile levels we want to keep as texture (in addition to 252 // the one we use for display). 253 // 3. Recycle unused tiles. 254 // 4. Activate the tiles we want. layoutTiles()255 private void layoutTiles() { 256 if (mViewWidth == 0 || mViewHeight == 0 || !mLayoutTiles) { 257 return; 258 } 259 mLayoutTiles = false; 260 261 // The tile levels we want to keep as texture is in the range 262 // [fromLevel, endLevel). 263 int fromLevel; 264 int endLevel; 265 266 // We want to use a texture larger than or equal to the display size. 267 mLevel = Utils.clamp(Utils.floorLog2(1f / mScale), 0, mLevelCount); 268 269 // We want to keep one more tile level as texture in addition to what 270 // we use for display. So it can be faster when the scale moves to the 271 // next level. We choose the level closest to the current scale. 272 if (mLevel != mLevelCount) { 273 Rect range = mTileRange; 274 getRange(range, mCenterX, mCenterY, mLevel, mScale, mRotation); 275 mOffsetX = Math.round(mViewWidth / 2f + (range.left - mCenterX) * mScale); 276 mOffsetY = Math.round(mViewHeight / 2f + (range.top - mCenterY) * mScale); 277 fromLevel = mScale * (1 << mLevel) > 0.75f ? mLevel - 1 : mLevel; 278 } else { 279 // Activate the tiles of the smallest two levels. 280 fromLevel = mLevel - 2; 281 mOffsetX = Math.round(mViewWidth / 2f - mCenterX * mScale); 282 mOffsetY = Math.round(mViewHeight / 2f - mCenterY * mScale); 283 } 284 285 fromLevel = Math.max(0, Math.min(fromLevel, mLevelCount - 2)); 286 endLevel = Math.min(fromLevel + 2, mLevelCount); 287 288 Rect range[] = mActiveRange; 289 for (int i = fromLevel; i < endLevel; ++i) { 290 getRange(range[i - fromLevel], mCenterX, mCenterY, i, mRotation); 291 } 292 293 // If rotation is transient, don't update the tile. 294 if (mRotation % 90 != 0) { 295 return; 296 } 297 298 synchronized (mQueueLock) { 299 mDecodeQueue.clean(); 300 mUploadQueue.clean(); 301 mBackgroundTileUploaded = false; 302 303 // Recycle unused tiles: if the level of the active tile is outside the 304 // range [fromLevel, endLevel) or not in the visible range. 305 int n = mActiveTiles.size(); 306 for (int i = 0; i < n; i++) { 307 Tile tile = mActiveTiles.valueAt(i); 308 int level = tile.mTileLevel; 309 if (level < fromLevel || level >= endLevel 310 || !range[level - fromLevel].contains(tile.mX, tile.mY)) { 311 mActiveTiles.removeAt(i); 312 i--; 313 n--; 314 recycleTile(tile); 315 } 316 } 317 } 318 319 for (int i = fromLevel; i < endLevel; ++i) { 320 int size = mTileSize << i; 321 Rect r = range[i - fromLevel]; 322 for (int y = r.top, bottom = r.bottom; y < bottom; y += size) { 323 for (int x = r.left, right = r.right; x < right; x += size) { 324 activateTile(x, y, i); 325 } 326 } 327 } 328 invalidate(); 329 } 330 invalidateTiles()331 private void invalidateTiles() { 332 synchronized (mQueueLock) { 333 mDecodeQueue.clean(); 334 mUploadQueue.clean(); 335 336 // TODO(xx): disable decoder 337 int n = mActiveTiles.size(); 338 for (int i = 0; i < n; i++) { 339 Tile tile = mActiveTiles.valueAt(i); 340 recycleTile(tile); 341 } 342 mActiveTiles.clear(); 343 } 344 } 345 getRange(Rect out, int cX, int cY, int level, int rotation)346 private void getRange(Rect out, int cX, int cY, int level, int rotation) { 347 getRange(out, cX, cY, level, 1f / (1 << (level + 1)), rotation); 348 } 349 350 // If the bitmap is scaled by the given factor "scale", return the 351 // rectangle containing visible range. The left-top coordinate returned is 352 // aligned to the tile boundary. 353 // 354 // (cX, cY) is the point on the original bitmap which will be put in the 355 // center of the ImageViewer. getRange(Rect out, int cX, int cY, int level, float scale, int rotation)356 private void getRange(Rect out, 357 int cX, int cY, int level, float scale, int rotation) { 358 359 double radians = Math.toRadians(-rotation); 360 double w = mViewWidth; 361 double h = mViewHeight; 362 363 double cos = Math.cos(radians); 364 double sin = Math.sin(radians); 365 int width = (int) Math.ceil(Math.max( 366 Math.abs(cos * w - sin * h), Math.abs(cos * w + sin * h))); 367 int height = (int) Math.ceil(Math.max( 368 Math.abs(sin * w + cos * h), Math.abs(sin * w - cos * h))); 369 370 int left = (int) Math.floor(cX - width / (2f * scale)); 371 int top = (int) Math.floor(cY - height / (2f * scale)); 372 int right = (int) Math.ceil(left + width / scale); 373 int bottom = (int) Math.ceil(top + height / scale); 374 375 // align the rectangle to tile boundary 376 int size = mTileSize << level; 377 left = Math.max(0, size * (left / size)); 378 top = Math.max(0, size * (top / size)); 379 right = Math.min(mImageWidth, right); 380 bottom = Math.min(mImageHeight, bottom); 381 382 out.set(left, top, right, bottom); 383 } 384 freeTextures()385 public void freeTextures() { 386 mLayoutTiles = true; 387 388 mTileDecoder.finishAndWait(); 389 synchronized (mQueueLock) { 390 mUploadQueue.clean(); 391 mDecodeQueue.clean(); 392 Tile tile = mRecycledQueue.pop(); 393 while (tile != null) { 394 tile.recycle(); 395 tile = mRecycledQueue.pop(); 396 } 397 } 398 399 int n = mActiveTiles.size(); 400 for (int i = 0; i < n; i++) { 401 Tile texture = mActiveTiles.valueAt(i); 402 texture.recycle(); 403 } 404 mActiveTiles.clear(); 405 mTileRange.set(0, 0, 0, 0); 406 407 while (sTilePool.acquire() != null) {} 408 } 409 draw(GLCanvas canvas)410 public boolean draw(GLCanvas canvas) { 411 layoutTiles(); 412 uploadTiles(canvas); 413 414 mUploadQuota = UPLOAD_LIMIT; 415 mRenderComplete = true; 416 417 int level = mLevel; 418 int rotation = mRotation; 419 int flags = 0; 420 if (rotation != 0) { 421 flags |= GLCanvas.SAVE_FLAG_MATRIX; 422 } 423 424 if (flags != 0) { 425 canvas.save(flags); 426 if (rotation != 0) { 427 int centerX = mViewWidth / 2, centerY = mViewHeight / 2; 428 canvas.translate(centerX, centerY); 429 canvas.rotate(rotation, 0, 0, 1); 430 canvas.translate(-centerX, -centerY); 431 } 432 } 433 try { 434 if (level != mLevelCount) { 435 int size = (mTileSize << level); 436 float length = size * mScale; 437 Rect r = mTileRange; 438 439 for (int ty = r.top, i = 0; ty < r.bottom; ty += size, i++) { 440 float y = mOffsetY + i * length; 441 for (int tx = r.left, j = 0; tx < r.right; tx += size, j++) { 442 float x = mOffsetX + j * length; 443 drawTile(canvas, tx, ty, level, x, y, length); 444 } 445 } 446 } else if (mPreview != null) { 447 mPreview.draw(canvas, mOffsetX, mOffsetY, 448 Math.round(mImageWidth * mScale), 449 Math.round(mImageHeight * mScale)); 450 } 451 } finally { 452 if (flags != 0) { 453 canvas.restore(); 454 } 455 } 456 457 if (mRenderComplete) { 458 if (!mBackgroundTileUploaded) { 459 uploadBackgroundTiles(canvas); 460 } 461 } else { 462 invalidate(); 463 } 464 return mRenderComplete || mPreview != null; 465 } 466 uploadBackgroundTiles(GLCanvas canvas)467 private void uploadBackgroundTiles(GLCanvas canvas) { 468 mBackgroundTileUploaded = true; 469 int n = mActiveTiles.size(); 470 for (int i = 0; i < n; i++) { 471 Tile tile = mActiveTiles.valueAt(i); 472 if (!tile.isContentValid()) { 473 queueForDecode(tile); 474 } 475 } 476 } 477 queueForDecode(Tile tile)478 private void queueForDecode(Tile tile) { 479 synchronized (mQueueLock) { 480 if (tile.mTileState == STATE_ACTIVATED) { 481 tile.mTileState = STATE_IN_QUEUE; 482 if (mDecodeQueue.push(tile)) { 483 mQueueLock.notifyAll(); 484 } 485 } 486 } 487 } 488 decodeTile(Tile tile)489 private void decodeTile(Tile tile) { 490 synchronized (mQueueLock) { 491 if (tile.mTileState != STATE_IN_QUEUE) { 492 return; 493 } 494 tile.mTileState = STATE_DECODING; 495 } 496 boolean decodeComplete = tile.decode(); 497 synchronized (mQueueLock) { 498 if (tile.mTileState == STATE_RECYCLING) { 499 tile.mTileState = STATE_RECYCLED; 500 if (tile.mDecodedTile != null) { 501 sTilePool.release(tile.mDecodedTile); 502 tile.mDecodedTile = null; 503 } 504 mRecycledQueue.push(tile); 505 return; 506 } 507 tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL; 508 if (!decodeComplete) { 509 return; 510 } 511 mUploadQueue.push(tile); 512 } 513 invalidate(); 514 } 515 obtainTile(int x, int y, int level)516 private Tile obtainTile(int x, int y, int level) { 517 synchronized (mQueueLock) { 518 Tile tile = mRecycledQueue.pop(); 519 if (tile != null) { 520 tile.mTileState = STATE_ACTIVATED; 521 tile.update(x, y, level); 522 return tile; 523 } 524 return new Tile(x, y, level); 525 } 526 } 527 recycleTile(Tile tile)528 private void recycleTile(Tile tile) { 529 synchronized (mQueueLock) { 530 if (tile.mTileState == STATE_DECODING) { 531 tile.mTileState = STATE_RECYCLING; 532 return; 533 } 534 tile.mTileState = STATE_RECYCLED; 535 if (tile.mDecodedTile != null) { 536 sTilePool.release(tile.mDecodedTile); 537 tile.mDecodedTile = null; 538 } 539 mRecycledQueue.push(tile); 540 } 541 } 542 activateTile(int x, int y, int level)543 private void activateTile(int x, int y, int level) { 544 long key = makeTileKey(x, y, level); 545 Tile tile = mActiveTiles.get(key); 546 if (tile != null) { 547 if (tile.mTileState == STATE_IN_QUEUE) { 548 tile.mTileState = STATE_ACTIVATED; 549 } 550 return; 551 } 552 tile = obtainTile(x, y, level); 553 mActiveTiles.put(key, tile); 554 } 555 getTile(int x, int y, int level)556 private Tile getTile(int x, int y, int level) { 557 return mActiveTiles.get(makeTileKey(x, y, level)); 558 } 559 makeTileKey(int x, int y, int level)560 private static long makeTileKey(int x, int y, int level) { 561 long result = x; 562 result = (result << 16) | y; 563 result = (result << 16) | level; 564 return result; 565 } 566 uploadTiles(GLCanvas canvas)567 private void uploadTiles(GLCanvas canvas) { 568 int quota = UPLOAD_LIMIT; 569 Tile tile = null; 570 while (quota > 0) { 571 synchronized (mQueueLock) { 572 tile = mUploadQueue.pop(); 573 } 574 if (tile == null) { 575 break; 576 } 577 if (!tile.isContentValid()) { 578 if (tile.mTileState == STATE_DECODED) { 579 tile.updateContent(canvas); 580 --quota; 581 } else { 582 Log.w(TAG, "Tile in upload queue has invalid state: " + tile.mTileState); 583 } 584 } 585 } 586 if (tile != null) { 587 invalidate(); 588 } 589 } 590 591 // Draw the tile to a square at canvas that locates at (x, y) and 592 // has a side length of length. drawTile(GLCanvas canvas, int tx, int ty, int level, float x, float y, float length)593 private void drawTile(GLCanvas canvas, 594 int tx, int ty, int level, float x, float y, float length) { 595 RectF source = mSourceRect; 596 RectF target = mTargetRect; 597 target.set(x, y, x + length, y + length); 598 source.set(0, 0, mTileSize, mTileSize); 599 600 Tile tile = getTile(tx, ty, level); 601 if (tile != null) { 602 if (!tile.isContentValid()) { 603 if (tile.mTileState == STATE_DECODED) { 604 if (mUploadQuota > 0) { 605 --mUploadQuota; 606 tile.updateContent(canvas); 607 } else { 608 mRenderComplete = false; 609 } 610 } else if (tile.mTileState != STATE_DECODE_FAIL){ 611 mRenderComplete = false; 612 queueForDecode(tile); 613 } 614 } 615 if (drawTile(tile, canvas, source, target)) { 616 return; 617 } 618 } 619 if (mPreview != null) { 620 int size = mTileSize << level; 621 float scaleX = (float) mPreview.getWidth() / mImageWidth; 622 float scaleY = (float) mPreview.getHeight() / mImageHeight; 623 source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX, 624 (ty + size) * scaleY); 625 canvas.drawTexture(mPreview, source, target); 626 } 627 } 628 drawTile( Tile tile, GLCanvas canvas, RectF source, RectF target)629 private boolean drawTile( 630 Tile tile, GLCanvas canvas, RectF source, RectF target) { 631 while (true) { 632 if (tile.isContentValid()) { 633 canvas.drawTexture(tile, source, target); 634 return true; 635 } 636 637 // Parent can be divided to four quads and tile is one of the four. 638 Tile parent = tile.getParentTile(); 639 if (parent == null) { 640 return false; 641 } 642 if (tile.mX == parent.mX) { 643 source.left /= 2f; 644 source.right /= 2f; 645 } else { 646 source.left = (mTileSize + source.left) / 2f; 647 source.right = (mTileSize + source.right) / 2f; 648 } 649 if (tile.mY == parent.mY) { 650 source.top /= 2f; 651 source.bottom /= 2f; 652 } else { 653 source.top = (mTileSize + source.top) / 2f; 654 source.bottom = (mTileSize + source.bottom) / 2f; 655 } 656 tile = parent; 657 } 658 } 659 660 private class Tile extends UploadedTexture { 661 public int mX; 662 public int mY; 663 public int mTileLevel; 664 public Tile mNext; 665 public Bitmap mDecodedTile; 666 public volatile int mTileState = STATE_ACTIVATED; 667 Tile(int x, int y, int level)668 public Tile(int x, int y, int level) { 669 mX = x; 670 mY = y; 671 mTileLevel = level; 672 } 673 674 @Override onFreeBitmap(Bitmap bitmap)675 protected void onFreeBitmap(Bitmap bitmap) { 676 sTilePool.release(bitmap); 677 } 678 decode()679 boolean decode() { 680 // Get a tile from the original image. The tile is down-scaled 681 // by (1 << mTilelevel) from a region in the original image. 682 try { 683 Bitmap reuse = sTilePool.acquire(); 684 if (reuse != null && reuse.getWidth() != mTileSize) { 685 reuse = null; 686 } 687 mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse); 688 } catch (Throwable t) { 689 Log.w(TAG, "fail to decode tile", t); 690 } 691 return mDecodedTile != null; 692 } 693 694 @Override onGetBitmap()695 protected Bitmap onGetBitmap() { 696 Utils.assertTrue(mTileState == STATE_DECODED); 697 698 // We need to override the width and height, so that we won't 699 // draw beyond the boundaries. 700 int rightEdge = ((mImageWidth - mX) >> mTileLevel); 701 int bottomEdge = ((mImageHeight - mY) >> mTileLevel); 702 setSize(Math.min(mTileSize, rightEdge), Math.min(mTileSize, bottomEdge)); 703 704 Bitmap bitmap = mDecodedTile; 705 mDecodedTile = null; 706 mTileState = STATE_ACTIVATED; 707 return bitmap; 708 } 709 710 // We override getTextureWidth() and getTextureHeight() here, so the 711 // texture can be re-used for different tiles regardless of the actual 712 // size of the tile (which may be small because it is a tile at the 713 // boundary). 714 @Override getTextureWidth()715 public int getTextureWidth() { 716 return mTileSize; 717 } 718 719 @Override getTextureHeight()720 public int getTextureHeight() { 721 return mTileSize; 722 } 723 update(int x, int y, int level)724 public void update(int x, int y, int level) { 725 mX = x; 726 mY = y; 727 mTileLevel = level; 728 invalidateContent(); 729 } 730 getParentTile()731 public Tile getParentTile() { 732 if (mTileLevel + 1 == mLevelCount) { 733 return null; 734 } 735 int size = mTileSize << (mTileLevel + 1); 736 int x = size * (mX / size); 737 int y = size * (mY / size); 738 return getTile(x, y, mTileLevel + 1); 739 } 740 741 @Override toString()742 public String toString() { 743 return String.format("tile(%s, %s, %s / %s)", 744 mX / mTileSize, mY / mTileSize, mLevel, mLevelCount); 745 } 746 } 747 748 private static class TileQueue { 749 private Tile mHead; 750 pop()751 public Tile pop() { 752 Tile tile = mHead; 753 if (tile != null) { 754 mHead = tile.mNext; 755 } 756 return tile; 757 } 758 push(Tile tile)759 public boolean push(Tile tile) { 760 if (contains(tile)) { 761 Log.w(TAG, "Attempting to add a tile already in the queue!"); 762 return false; 763 } 764 boolean wasEmpty = mHead == null; 765 tile.mNext = mHead; 766 mHead = tile; 767 return wasEmpty; 768 } 769 contains(Tile tile)770 private boolean contains(Tile tile) { 771 Tile other = mHead; 772 while (other != null) { 773 if (other == tile) { 774 return true; 775 } 776 other = other.mNext; 777 } 778 return false; 779 } 780 clean()781 public void clean() { 782 mHead = null; 783 } 784 } 785 786 private class TileDecoder extends Thread { 787 finishAndWait()788 public void finishAndWait() { 789 interrupt(); 790 try { 791 join(); 792 } catch (InterruptedException e) { 793 Log.w(TAG, "Interrupted while waiting for TileDecoder thread to finish!"); 794 } 795 } 796 waitForTile()797 private Tile waitForTile() throws InterruptedException { 798 synchronized (mQueueLock) { 799 while (true) { 800 Tile tile = mDecodeQueue.pop(); 801 if (tile != null) { 802 return tile; 803 } 804 mQueueLock.wait(); 805 } 806 } 807 } 808 809 @Override run()810 public void run() { 811 try { 812 while (!isInterrupted()) { 813 Tile tile = waitForTile(); 814 decodeTile(tile); 815 } 816 } catch (InterruptedException ex) { 817 // We were finished 818 } 819 } 820 821 } 822 } 823