@Override
 public void drawTo(Canvas drawCanvas, float left, float top, Paint paint, boolean onlyDirty) {
   final Rect src = new Rect(0, 0, mTileSize, mTileSize);
   final Rect dst = new Rect(0, 0, mTileSize, mTileSize);
   drawCanvas.save();
   drawCanvas.translate(-left, -top);
   drawCanvas.clipRect(0, 0, mWidth, mHeight);
   for (int j = 0; j < mTilesY; j++) {
     for (int i = 0; i < mTilesX; i++) {
       dst.offsetTo(i * mTileSize, j * mTileSize);
       final int p = j * mTilesX + i;
       final Tile tile = mTiles[p];
       if (!onlyDirty || tile.dirty) {
         drawCanvas.drawBitmap(tile.getBitmap(), src, dst, paint);
         tile.dirty = false;
         if (mDebug) {
           mDrawCount++;
           dbgPaint.setColor(DEBUG_COLORS[tile.top % DEBUG_COLORS.length]);
           // drawCanvas.drawRect(dst, (mDrawCount % 2 == 0) ? dbgPaint1 : dbgPaint2);
           drawCanvas.drawRect(dst, dbgPaint);
           // drawCanvas.drawRect(dst, dbgStroke);
           drawCanvas.drawText(
               String.format("%d,%d v%d", tile.x, tile.y, tile.top),
               dst.left + 4,
               dst.bottom - 4,
               dbgTextPaint);
         }
       }
     }
   }
   drawCanvas.restore();
 }
 public void drawColor(int color, PorterDuff.Mode mode) {
   for (int i = 0; i < mTiles.length; i++) {
     final Tile tile = mTiles[i];
     getDrawingCanvas(tile).drawColor(color, mode);
     tile.dirty = true;
   }
 }
 public void drawRect(float l, float t, float r, float b, Paint paint) {
   final int tilel = max(0, (int) FloatMath.floor((l - INVALIDATE_PADDING) / mTileSize));
   final int tilet = max(0, (int) FloatMath.floor((t - INVALIDATE_PADDING) / mTileSize));
   final int tiler = min(mTilesX - 1, (int) FloatMath.floor((r + INVALIDATE_PADDING) / mTileSize));
   final int tileb = min(mTilesY - 1, (int) FloatMath.floor((b + INVALIDATE_PADDING) / mTileSize));
   for (int tiley = tilet; tiley <= tileb; tiley++) {
     for (int tilex = tilel; tilex <= tiler; tilex++) {
       final Tile tile = mTiles[tiley * mTilesX + tilex];
       getDrawingCanvas(tile).drawRect(l, t, r, b, paint);
       tile.dirty = true;
     }
   }
 }
 public void drawCircle(float x, float y, float r, Paint paint) {
   final float invalR = r + INVALIDATE_PADDING;
   final int tilel = max(0, (int) FloatMath.floor((x - invalR) / mTileSize));
   final int tilet = max(0, (int) FloatMath.floor((y - invalR) / mTileSize));
   final int tiler = min(mTilesX - 1, (int) FloatMath.floor((x + invalR) / mTileSize));
   final int tileb = min(mTilesY - 1, (int) FloatMath.floor((y + invalR) / mTileSize));
   for (int tiley = tilet; tiley <= tileb; tiley++) {
     for (int tilex = tilel; tilex <= tiler; tilex++) {
       final Tile tile = mTiles[tiley * mTilesX + tilex];
       getDrawingCanvas(tile).drawCircle(x, y, r, paint);
       tile.dirty = true;
     }
   }
 }
 @Override
 public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
   final int tilel = max(0, (int) FloatMath.floor((dst.left - INVALIDATE_PADDING) / mTileSize));
   final int tilet = max(0, (int) FloatMath.floor((dst.top - INVALIDATE_PADDING) / mTileSize));
   final int tiler =
       min(mTilesX - 1, (int) FloatMath.floor((dst.right + INVALIDATE_PADDING) / mTileSize));
   final int tileb =
       min(mTilesY - 1, (int) FloatMath.floor((dst.bottom + INVALIDATE_PADDING) / mTileSize));
   for (int tiley = tilet; tiley <= tileb; tiley++) {
     for (int tilex = tilel; tilex <= tiler; tilex++) {
       final Tile tile = mTiles[tiley * mTilesX + tilex];
       getDrawingCanvas(tile).drawBitmap(bitmap, src, dst, paint);
       tile.dirty = true;
     }
   }
 }
 @Override
 public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
   RectF dst = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
   matrix.mapRect(dst);
   final int tilel = max(0, (int) FloatMath.floor((dst.left - INVALIDATE_PADDING) / mTileSize));
   final int tilet = max(0, (int) FloatMath.floor((dst.top - INVALIDATE_PADDING) / mTileSize));
   final int tiler =
       min(mTilesX - 1, (int) FloatMath.floor((dst.right + INVALIDATE_PADDING) / mTileSize));
   final int tileb =
       min(mTilesY - 1, (int) FloatMath.floor((dst.bottom + INVALIDATE_PADDING) / mTileSize));
   for (int tiley = tilet; tiley <= tileb; tiley++) {
     for (int tilex = tilel; tilex <= tiler; tilex++) {
       final Tile tile = mTiles[tiley * mTilesX + tilex];
       getDrawingCanvas(tile).drawBitmap(bitmap, matrix, paint);
       tile.dirty = true;
     }
   }
 }
 public void step(int delta) {
   final int oldTop = mVersionInUse ? mNewVersion : mNewVersion - 1;
   int newTop = oldTop + delta; // step
   if (newTop < mBottomVersion) {
     if (newTop == mBottomVersion) return; // we're already at the end
     newTop = mBottomVersion;
   }
   if (mDebug) {
     Log.v(
         TAG,
         String.format(
             "step(%d): oldTop=%d newTop=%d bot=%d", delta, oldTop, newTop, mBottomVersion));
   }
   for (int i = 0; i < mTiles.length; i++) {
     final Tile tile = mTiles[i];
     if (tile.bottom == newTop) {
       // XXX: bottomed out; do something
     }
     tile.revert(newTop);
     tile.dirty = true; // XXX: only do this if the version changed, i.e. tile.top > mVersion
   }
   mNewVersion = newTop + 1;
   mVersionInUse = false;
 }