diff options
author | Owen Lin <owenlin@google.com> | 2012-05-17 15:59:25 -0700 |
---|---|---|
committer | Owen Lin <owenlin@google.com> | 2012-05-22 11:55:06 -0700 |
commit | c2c0b01fbbf243eee1a11c33ba6dd1ce918a9ce5 (patch) | |
tree | 4e24bd8f5149504f3b0a305882a4453897250830 /src | |
parent | 49affdc4e274098a34e4eb2dbe4a89a750f1ba7f (diff) | |
download | android_packages_apps_Gallery2-c2c0b01fbbf243eee1a11c33ba6dd1ce918a9ce5.tar.gz android_packages_apps_Gallery2-c2c0b01fbbf243eee1a11c33ba6dd1ce918a9ce5.tar.bz2 android_packages_apps_Gallery2-c2c0b01fbbf243eee1a11c33ba6dd1ce918a9ce5.zip |
Reuse bitmap to prevent GC in TiledImageView.
bug:5948093
Change-Id: Iad89c5809bde0de5409752330f607aab153dceba
Diffstat (limited to 'src')
5 files changed, 99 insertions, 79 deletions
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java index 29154a082..6a4961eb2 100644 --- a/src/com/android/gallery3d/app/PhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java @@ -23,6 +23,7 @@ import android.os.Message; import com.android.gallery3d.common.BitmapUtils; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.BitmapPool; import com.android.gallery3d.data.ContentListener; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.LocalMediaItem; @@ -490,8 +491,8 @@ public class PhotoDataAdapter implements PhotoPage.Model { } public Bitmap getTile(int level, int x, int y, int tileSize, - int borderSize) { - return mTileProvider.getTile(level, x, y, tileSize, borderSize); + int borderSize, BitmapPool pool) { + return mTileProvider.getTile(level, x, y, tileSize, borderSize, pool); } public boolean isEmpty() { diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java index 320118e89..d4c9b1d30 100644 --- a/src/com/android/gallery3d/ui/BitmapTileProvider.java +++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java @@ -21,6 +21,7 @@ import android.graphics.Bitmap.Config; import android.graphics.Canvas; import com.android.gallery3d.common.BitmapUtils; +import com.android.gallery3d.data.BitmapPool; import java.util.ArrayList; @@ -49,44 +50,46 @@ public class BitmapTileProvider implements TileImageView.Model { mConfig = Config.ARGB_8888; } + @Override public ScreenNail getScreenNail() { return mScreenNail; } + @Override public int getImageHeight() { return mImageHeight; } + @Override public int getImageWidth() { return mImageWidth; } + @Override public int getLevelCount() { return mMipmaps.length; } + @Override public Bitmap getTile(int level, int x, int y, int tileSize, - int borderSize) { + int borderSize, BitmapPool pool) { x >>= level; y >>= level; int size = tileSize + 2 * borderSize; - Bitmap result = Bitmap.createBitmap(size, size, mConfig); + + Bitmap result = pool == null ? null : pool.getBitmap(); + if (result == null) { + result = Bitmap.createBitmap(size, size, mConfig); + } else { + result.eraseColor(0); + } + Bitmap mipmap = mMipmaps[level]; Canvas canvas = new Canvas(result); int offsetX = -x + borderSize; int offsetY = -y + borderSize; canvas.drawBitmap(mipmap, offsetX, offsetY, null); - - // If the valid region (covered by mipmap or border) is smaller than the - // result bitmap, subset it. - int endX = offsetX + mipmap.getWidth() + borderSize; - int endY = offsetY + mipmap.getHeight() + borderSize; - if (endX < size || endY < size) { - return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX), - Math.min(size, endY)); - } else { - return result; - } + return result; } public void recycle() { diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java index fb0e333a1..2f8f22333 100644 --- a/src/com/android/gallery3d/ui/TileImageView.java +++ b/src/com/android/gallery3d/ui/TileImageView.java @@ -25,6 +25,7 @@ import android.util.LongSparseArray; import com.android.gallery3d.app.GalleryContext; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.BitmapPool; import com.android.gallery3d.data.DecodeUtils; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.ThreadPool; @@ -43,8 +44,12 @@ public class TileImageView extends GLView { // texture to avoid seams between tiles. private static final int TILE_SIZE = 254; private static final int TILE_BORDER = 1; + private static final int BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2; private static final int UPLOAD_LIMIT = 1; + private static final BitmapPool sTilePool = + new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128); + /* * This is the tile state in the CPU side. * Life of a Tile: @@ -96,9 +101,9 @@ public class TileImageView extends GLView { private final LongSparseArray<Tile> mActiveTiles = new LongSparseArray<Tile>(); // The following three queue is guarded by TileImageView.this - private TileQueue mRecycledQueue = new TileQueue(); - private TileQueue mUploadQueue = new TileQueue(); - private TileQueue mDecodeQueue = new TileQueue(); + private final TileQueue mRecycledQueue = new TileQueue(); + private final TileQueue mUploadQueue = new TileQueue(); + private final TileQueue mDecodeQueue = new TileQueue(); // The width and height of the full-sized bitmap protected int mImageWidth = SIZE_UNKNOWN; @@ -116,7 +121,7 @@ public class TileImageView extends GLView { private final TileUploader mTileUploader = new TileUploader(); private boolean mIsTextureFreed; private Future<Void> mTileDecoder; - private ThreadPool mThreadPool; + private final ThreadPool mThreadPool; private boolean mBackgroundTileUploaded; public static interface Model { @@ -138,7 +143,7 @@ public class TileImageView extends GLView { // // The method would be called in another thread. public Bitmap getTile(int level, int x, int y, int tileSize, - int borderSize); + int borderSize, BitmapPool pool); } public TileImageView(GalleryContext context) { @@ -373,6 +378,7 @@ public class TileImageView extends GLView { } } setScreenNail(null); + sTilePool.clear(); } public void prepareTextures() { @@ -480,7 +486,10 @@ public class TileImageView extends GLView { synchronized (this) { if (tile.mTileState == STATE_RECYCLING) { tile.mTileState = STATE_RECYCLED; - tile.mDecodedTile = null; + if (tile.mDecodedTile != null) { + sTilePool.recycle(tile.mDecodedTile); + tile.mDecodedTile = null; + } mRecycledQueue.push(tile); return false; } @@ -505,7 +514,10 @@ public class TileImageView extends GLView { return; } tile.mTileState = STATE_RECYCLED; - tile.mDecodedTile = null; + if (tile.mDecodedTile != null) { + sTilePool.recycle(tile.mDecodedTile); + tile.mDecodedTile = null; + } mRecycledQueue.push(tile); } @@ -626,12 +638,12 @@ public class TileImageView extends GLView { } private class Tile extends UploadedTexture { - int mX; - int mY; - int mTileLevel; - Tile mNext; - Bitmap mDecodedTile; - volatile int mTileState = STATE_ACTIVATED; + public int mX; + public int mY; + public int mTileLevel; + public Tile mNext; + public Bitmap mDecodedTile; + public volatile int mTileState = STATE_ACTIVATED; public Tile(int x, int y, int level) { mX = x; @@ -641,7 +653,7 @@ public class TileImageView extends GLView { @Override protected void onFreeBitmap(Bitmap bitmap) { - bitmap.recycle(); + sTilePool.recycle(bitmap); } boolean decode() { @@ -649,7 +661,7 @@ public class TileImageView extends GLView { // by (1 << mTilelevel) from a region in the original image. try { mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile( - mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER)); + mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER, sTilePool)); } catch (Throwable t) { Log.w(TAG, "fail to decode tile", t); } @@ -659,6 +671,13 @@ public class TileImageView extends GLView { @Override protected Bitmap onGetBitmap() { Utils.assertTrue(mTileState == STATE_DECODED); + + // We need to override the width and height, so that we won't + // draw beyond the boundaries. + int rightEdge = ((mImageWidth - mX) >> mTileLevel) + TILE_BORDER; + int bottomEdge = ((mImageHeight - mY) >> mTileLevel) + TILE_BORDER; + setSize(Math.min(BITMAP_SIZE, rightEdge), Math.min(BITMAP_SIZE, bottomEdge)); + Bitmap bitmap = mDecodedTile; mDecodedTile = null; mTileState = STATE_ACTIVATED; diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java index 5c9281202..0b4ac0375 100644 --- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java +++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java @@ -20,10 +20,10 @@ import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; import android.graphics.Rect; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.BitmapPool; public class TileImageViewAdapter implements TileImageView.Model { private static final String TAG = "TileImageViewAdapter"; @@ -94,70 +94,68 @@ public class TileImageViewAdapter implements TileImageView.Model { (float) mImageWidth / mScreenNail.getWidth())); } + // Gets a sub image on a rectangle of the current photo. For example, + // getTile(1, 50, 50, 100, 3, pool) means to get the region located + // at (50, 50) with sample level 1 (ie, down sampled by 2^1) and the + // target tile size (after sampling) 100 with border 3. + // + // From this spec, we can infer the actual tile size to be + // 100 + 3x2 = 106, and the size of the region to be extracted from the + // photo to be 200 with border 6. + // + // As a result, we should decode region (50-6, 50-6, 250+6, 250+6) or + // (44, 44, 256, 256) from the original photo and down sample it to 106. @Override public Bitmap getTile(int level, int x, int y, int tileSize, - int borderSize) { - // wantRegion is the rectangle on the original image we want. askRegion - // is the rectangle on the original image that we will ask from - // mRegionDecoder. Both are in the coordinates of the original image, - // not the coordinates of the scaled-down images. - Rect wantRegion = new Rect(); - Rect askRegion = new Rect(); - + int borderSize, BitmapPool pool) { int b = borderSize << level; - wantRegion.set(x - b, y - b, x + (tileSize << level) + b, - y + (tileSize << level) + b); + int t = tileSize << level; + + Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b); + boolean needClear; BitmapRegionDecoder regionDecoder = null; + synchronized (this) { regionDecoder = mRegionDecoder; if (regionDecoder == null) return null; - // askRegion is the intersection of wantRegion and the original image. - askRegion.set(0, 0, mImageWidth, mImageHeight); + + // We need to clear a reused bitmap, if wantRegion is not fully + // within the image. + needClear = !new Rect(0, 0, mImageWidth, mImageHeight) + .contains(wantRegion); } - Utils.assertTrue(askRegion.intersect(wantRegion)); + Bitmap bitmap = pool == null ? null : pool.getBitmap(); + if (bitmap != null) { + if (needClear) bitmap.eraseColor(0); + } else { + int s = tileSize + 2 * borderSize; + bitmap = Bitmap.createBitmap(s, s, Config.ARGB_8888); + } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Config.ARGB_8888; options.inPreferQualityOverSpeed = true; options.inSampleSize = (1 << level); - - Bitmap bitmap; - - // In CropImage, we may call the decodeRegion() concurrently. - synchronized (regionDecoder) { - bitmap = regionDecoder.decodeRegion(askRegion, options); + options.inBitmap = bitmap; + + try { + // In CropImage, we may call the decodeRegion() concurrently. + synchronized (regionDecoder) { + bitmap = regionDecoder.decodeRegion(wantRegion, options); + } + } finally { + if (options.inBitmap != bitmap && options.inBitmap != null) { + if (pool != null) pool.recycle(options.inBitmap); + options.inBitmap = null; + } } if (bitmap == null) { Log.w(TAG, "fail in decoding region"); - return null; - } - - if (wantRegion.equals(askRegion)) return bitmap; - - // Now the wantRegion does not match the askRegion. This means we are at - // a boundary tile, and we need to add paddings. Create a new Bitmap - // and copy over. - int size = tileSize + 2 * borderSize; - Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888); - Canvas canvas = new Canvas(result); - int offsetX = (askRegion.left - wantRegion.left) >> level; - int offsetY = (askRegion.top - wantRegion.top) >> level; - canvas.drawBitmap(bitmap, offsetX, offsetY, null); - - // If the valid region (covered by bitmap or border) is smaller than the - // result bitmap, subset it. - int endX = offsetX + bitmap.getWidth() + borderSize; - int endY = offsetY + bitmap.getHeight() + borderSize; - bitmap.recycle(); - if (endX < size || endY < size) { - return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX), - Math.min(size, endY)); - } else { - return result; } + return bitmap; } @Override diff --git a/src/com/android/gallery3d/ui/UploadedTexture.java b/src/com/android/gallery3d/ui/UploadedTexture.java index 85aa1c4b2..24c8d1db6 100644 --- a/src/com/android/gallery3d/ui/UploadedTexture.java +++ b/src/com/android/gallery3d/ui/UploadedTexture.java @@ -128,10 +128,6 @@ abstract class UploadedTexture extends BasicTexture { int h = mBitmap.getHeight() + mBorder * 2; if (mWidth == UNSPECIFIED) { setSize(w, h); - } else if (mWidth != w || mHeight != h) { - throw new IllegalStateException(String.format( - "cannot change size: this = %s, orig = %sx%s, new = %sx%s", - toString(), mWidth, mHeight, w, h)); } } return mBitmap; @@ -218,6 +214,9 @@ abstract class UploadedTexture extends BasicTexture { int height = bHeight + mBorder * 2; int texWidth = getTextureWidth(); int texHeight = getTextureHeight(); + + Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight); + // Define a vertically flipped crop rectangle for // OES_draw_texture. // The four values in sCropRect are: left, bottom, width, and |