Loading src/com/android/photos/BitmapRegionTileSource.java +112 −16 Original line number Diff line number Diff line Loading @@ -16,66 +16,131 @@ package com.android.photos; import android.content.Context; 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 android.os.Build; import android.os.Build.VERSION_CODES; import android.util.Log; import com.android.gallery3d.glrenderer.BasicTexture; import com.android.gallery3d.glrenderer.BitmapTexture; import com.android.photos.views.TiledImageRenderer; import java.io.IOException; /** * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using * {@link BitmapRegionDecoder} to wrap a local file */ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { BitmapRegionDecoder mDecoder; private static final String TAG = "BitmapRegionTileSource"; private static final boolean REUSE_BITMAP = Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; private static final int MAX_PREVIEW_SIZE = 1024; public BitmapRegionTileSource(String path) { BitmapRegionDecoder mDecoder; int mWidth; int mHeight; int mTileSize; private BasicTexture mPreview; private final int mRotation; // For use only by getTile private Rect mWantRegion = new Rect(); private Rect mOverlapRegion = new Rect(); private BitmapFactory.Options mOptions; private Canvas mCanvas; public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) { mTileSize = TiledImageRenderer.suggestedTileSize(context); mRotation = rotation; try { mDecoder = BitmapRegionDecoder.newInstance(path, true); mWidth = mDecoder.getWidth(); mHeight = mDecoder.getHeight(); } catch (IOException e) { Log.w("BitmapRegionTileSource", "ctor failed", e); } mOptions = new BitmapFactory.Options(); mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; mOptions.inPreferQualityOverSpeed = true; mOptions.inTempStorage = new byte[16 * 1024]; if (previewSize != 0) { previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE); // Although this is the same size as the Bitmap that is likely already // loaded, the lifecycle is different and interactions are on a different // thread. Thus to simplify, this source will decode its own bitmap. int sampleSize = (int) Math.ceil(Math.max( mWidth / (float) previewSize, mHeight / (float) previewSize)); mOptions.inSampleSize = Math.max(sampleSize, 1); Bitmap preview = mDecoder.decodeRegion( new Rect(0, 0, mWidth, mHeight), mOptions); if (preview.getWidth() <= MAX_PREVIEW_SIZE && preview.getHeight() <= MAX_PREVIEW_SIZE) { mPreview = new BitmapTexture(preview); } else { Log.w(TAG, String.format( "Failed to create preview of apropriate size! " + " in: %dx%d, sample: %d, out: %dx%d", mWidth, mHeight, sampleSize, preview.getWidth(), preview.getHeight())); } } } @Override public int getTileSize() { return 256; return mTileSize; } @Override public int getImageWidth() { return mDecoder.getWidth(); return mWidth; } @Override public int getImageHeight() { return mDecoder.getHeight(); return mHeight; } @Override public BasicTexture getPreview() { return mPreview; } @Override public int getRotation() { return mRotation; } @Override public Bitmap getTile(int level, int x, int y, Bitmap bitmap) { int tileSize = getTileSize(); int t = tileSize << level; if (!REUSE_BITMAP) { return getTileWithoutReusingBitmap(level, x, y, tileSize); } Rect wantRegion = new Rect(x, y, x + t, y + t); int t = tileSize << level; mWantRegion.set(x, y, x + t, y + t); if (bitmap == null) { bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888); } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inPreferQualityOverSpeed = true; options.inSampleSize = (1 << level); options.inBitmap = bitmap; mOptions.inSampleSize = (1 << level); mOptions.inBitmap = bitmap; try { // In CropImage, we may call the decodeRegion() concurrently. bitmap = mDecoder.decodeRegion(wantRegion, options); bitmap = mDecoder.decodeRegion(mWantRegion, mOptions); } finally { if (options.inBitmap != bitmap && options.inBitmap != null) { options.inBitmap = null; if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) { mOptions.inBitmap = null; } } Loading @@ -84,4 +149,35 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { } return bitmap; } private Bitmap getTileWithoutReusingBitmap( int level, int x, int y, int tileSize) { int t = tileSize << level; mWantRegion.set(x, y, x + t, y + t); mOverlapRegion.set(0, 0, mWidth, mHeight); mOptions.inSampleSize = (1 << level); Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions); if (bitmap == null) { Log.w(TAG, "fail in decoding region"); } if (mWantRegion.equals(mOverlapRegion)) { return bitmap; } Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888); if (mCanvas == null) { mCanvas = new Canvas(); } mCanvas.setBitmap(result); mCanvas.drawBitmap(bitmap, (mOverlapRegion.left - mWantRegion.left) >> level, (mOverlapRegion.top - mWantRegion.top) >> level, null); mCanvas.setBitmap(null); return result; } } src/com/android/photos/FullscreenViewer.java +1 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,7 @@ public class FullscreenViewer extends Activity { String path = getIntent().getData().toString(); mTextureView = new TiledImageView(this); mTextureView.setTileSource(new BitmapRegionTileSource(path)); mTextureView.setTileSource(new BitmapRegionTileSource(this, path, 0, 0), null); setContentView(mTextureView); } Loading src/com/android/photos/views/BlockingGLTextureView.java +22 −11 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.photos.views; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.SurfaceTexture; import android.opengl.GLSurfaceView.Renderer; Loading @@ -32,7 +31,9 @@ import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL10; /** * A TextureView that supports blocking rendering for synchronous drawing */ public class BlockingGLTextureView extends TextureView implements SurfaceTextureListener { Loading Loading @@ -90,7 +91,9 @@ public class BlockingGLTextureView extends TextureView protected void finalize() throws Throwable { try { destroy(); } catch (Throwable t) {} } catch (Throwable t) { // Ignore } super.finalize(); } Loading Loading @@ -135,8 +138,8 @@ public class BlockingGLTextureView extends TextureView } EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList); } /** Loading Loading @@ -368,19 +371,24 @@ public class BlockingGLTextureView extends TextureView exec(FINISH); try { join(); } catch (InterruptedException e) {} } catch (InterruptedException e) { // Ignore } } private void exec(int msgid) { synchronized (mLock) { if (mExecMsgId != INVALID) { throw new IllegalArgumentException("Message already set - multithreaded access?"); throw new IllegalArgumentException( "Message already set - multithreaded access?"); } mExecMsgId = msgid; mLock.notify(); try { mLock.wait(); } catch (InterruptedException e) {} } catch (InterruptedException e) { // Ignore } } } Loading Loading @@ -415,12 +423,15 @@ public class BlockingGLTextureView extends TextureView while (mExecMsgId == INVALID) { try { mLock.wait(); } catch (InterruptedException e) {} } catch (InterruptedException e) { // Ignore } } handleMessageLocked(mExecMsgId); mExecMsgId = INVALID; mLock.notify(); } mExecMsgId = FINISH; } } } Loading src/com/android/photos/views/TiledImageRenderer.java +138 −64 Original line number Diff line number Diff line Loading @@ -23,14 +23,19 @@ import android.graphics.RectF; import android.support.v4.util.LongSparseArray; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pools.Pool; import android.util.Pools.SynchronizedPool; import android.view.View; import android.view.WindowManager; import com.android.gallery3d.common.Utils; import com.android.gallery3d.glrenderer.BasicTexture; import com.android.gallery3d.glrenderer.GLCanvas; import com.android.gallery3d.glrenderer.UploadedTexture; import com.android.photos.data.GalleryBitmapPool; /** * Handles laying out, decoding, and drawing of tiles in GL */ public class TiledImageRenderer { public static final int SIZE_UNKNOWN = -1; Loading Loading @@ -62,12 +67,13 @@ public class TiledImageRenderer { private static final int STATE_RECYCLING = 0x20; private static final int STATE_RECYCLED = 0x40; private static GalleryBitmapPool sTilePool = GalleryBitmapPool.getInstance(); private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64); // TILE_SIZE must be 2^N private int mTileSize; private TileSource mModel; private BasicTexture mPreview; protected int mLevelCount; // cache the value of mScaledBitmaps.length // The mLevel variable indicates which level of bitmap we should use. Loading Loading @@ -116,22 +122,39 @@ public class TiledImageRenderer { private int mViewWidth, mViewHeight; private View mParent; /** * Interface for providing tiles to a {@link TiledImageRenderer} */ public static interface TileSource { /** * If the source does not care about the tile size, it should use * {@link TiledImageRenderer#suggestedTileSize(Context)} */ public int getTileSize(); public int getImageWidth(); public int getImageHeight(); public int getRotation(); // The tile returned by this method can be specified this way: Assuming // the image size is (width, height), first take the intersection of (0, // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If // in extending the region, we found some part of the region is outside // the image, those pixels are filled with black. // // If level > 0, it does the same operation on a down-scaled version of // the original image (down-scaled by a factor of 2^level), but (x, y) // still refers to the coordinate on the original image. // // The method would be called by the decoder thread. /** * Return a Preview image if available. This will be used as the base layer * if higher res tiles are not yet available */ public BasicTexture getPreview(); /** * The tile returned by this method can be specified this way: Assuming * the image size is (width, height), first take the intersection of (0, * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If * in extending the region, we found some part of the region is outside * the image, those pixels are filled with black. * * If level > 0, it does the same operation on a down-scaled version of * the original image (down-scaled by a factor of 2^level), but (x, y) * still refers to the coordinate on the original image. * * The method would be called by the decoder thread. */ public Bitmap getTile(int level, int x, int y, Bitmap reuse); } Loading Loading @@ -173,19 +196,23 @@ public class TiledImageRenderer { if (mRotation != rotation) { mRotation = rotation; mLayoutTiles = true; invalidate(); } } private static int calulateLevelCount(TileSource source) { private void calculateLevelCount() { if (mPreview != null) { mLevelCount = Math.max(0, Utils.ceilLog2( mImageWidth / (float) mPreview.getWidth())); } else { int levels = 1; int maxDim = Math.max(source.getImageWidth(), source.getImageHeight()); int t = source.getTileSize(); int maxDim = Math.max(mImageWidth, mImageHeight); int t = mTileSize; while (t < maxDim) { t <<= 1; levels++; } return levels; mLevelCount = levels; } } public void notifyModelInvalidated() { Loading @@ -194,14 +221,15 @@ public class TiledImageRenderer { mImageWidth = 0; mImageHeight = 0; mLevelCount = 0; mPreview = null; } else { mImageWidth = mModel.getImageWidth(); mImageHeight = mModel.getImageHeight(); mLevelCount = calulateLevelCount(mModel); mPreview = mModel.getPreview(); mTileSize = mModel.getTileSize(); calculateLevelCount(); } mLayoutTiles = true; invalidate(); } public void setViewSize(int width, int height) { Loading @@ -211,12 +239,13 @@ public class TiledImageRenderer { public void setPosition(int centerX, int centerY, float scale) { if (mCenterX == centerX && mCenterY == centerY && mScale == scale) return; && mScale == scale) { return; } mCenterX = centerX; mCenterY = centerY; mScale = scale; mLayoutTiles = true; invalidate(); } // Prepare the tiles we want to use for display. Loading Loading @@ -265,7 +294,9 @@ public class TiledImageRenderer { } // If rotation is transient, don't update the tile. if (mRotation % 90 != 0) return; if (mRotation % 90 != 0) { return; } synchronized (mQueueLock) { mDecodeQueue.clean(); Loading Loading @@ -305,7 +336,7 @@ public class TiledImageRenderer { mDecodeQueue.clean(); mUploadQueue.clean(); // TODO disable decoder // TODO(xx): disable decoder int n = mActiveTiles.size(); for (int i = 0; i < n; i++) { Tile tile = mActiveTiles.valueAt(i); Loading Loading @@ -357,6 +388,7 @@ public class TiledImageRenderer { public void freeTextures() { mLayoutTiles = true; mTileDecoder.finishAndWait(); synchronized (mQueueLock) { mUploadQueue.clean(); mDecodeQueue.clean(); Loading @@ -375,10 +407,10 @@ public class TiledImageRenderer { mActiveTiles.clear(); mTileRange.set(0, 0, 0, 0); if (sTilePool != null) sTilePool.clear(); while (sTilePool.acquire() != null) {} } public void draw(GLCanvas canvas) { public boolean draw(GLCanvas canvas) { layoutTiles(); uploadTiles(canvas); Loading @@ -388,7 +420,9 @@ public class TiledImageRenderer { int level = mLevel; int rotation = mRotation; int flags = 0; if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX; if (rotation != 0) { flags |= GLCanvas.SAVE_FLAG_MATRIX; } if (flags != 0) { canvas.save(flags); Loading @@ -412,9 +446,15 @@ public class TiledImageRenderer { drawTile(canvas, tx, ty, level, x, y, length); } } } else if (mPreview != null) { mPreview.draw(canvas, mOffsetX, mOffsetY, Math.round(mImageWidth * mScale), Math.round(mImageHeight * mScale)); } } finally { if (flags != 0) canvas.restore(); if (flags != 0) { canvas.restore(); } } if (mRenderComplete) { Loading @@ -424,6 +464,7 @@ public class TiledImageRenderer { } else { invalidate(); } return mRenderComplete || mPreview != null; } private void uploadBackgroundTiles(GLCanvas canvas) { Loading @@ -437,17 +478,6 @@ public class TiledImageRenderer { } } private void queueForUpload(Tile tile) { synchronized (mQueueLock) { mUploadQueue.push(tile); } invalidate(); // TODO // if (mTileUploader.mActive.compareAndSet(false, true)) { // getGLRoot().addOnGLIdleListener(mTileUploader); // } } private void queueForDecode(Tile tile) { synchronized (mQueueLock) { if (tile.mTileState == STATE_ACTIVATED) { Loading @@ -459,9 +489,11 @@ public class TiledImageRenderer { } } private boolean decodeTile(Tile tile) { private void decodeTile(Tile tile) { synchronized (mQueueLock) { if (tile.mTileState != STATE_IN_QUEUE) return false; if (tile.mTileState != STATE_IN_QUEUE) { return; } tile.mTileState = STATE_DECODING; } boolean decodeComplete = tile.decode(); Loading @@ -469,15 +501,19 @@ public class TiledImageRenderer { if (tile.mTileState == STATE_RECYCLING) { tile.mTileState = STATE_RECYCLED; if (tile.mDecodedTile != null) { if (sTilePool != null) sTilePool.put(tile.mDecodedTile); sTilePool.release(tile.mDecodedTile); tile.mDecodedTile = null; } mRecycledQueue.push(tile); return false; return; } tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL; return decodeComplete; if (!decodeComplete) { return; } mUploadQueue.push(tile); } invalidate(); } private Tile obtainTile(int x, int y, int level) { Loading @@ -500,7 +536,7 @@ public class TiledImageRenderer { } tile.mTileState = STATE_RECYCLED; if (tile.mDecodedTile != null) { if (sTilePool != null) sTilePool.put(tile.mDecodedTile); sTilePool.release(tile.mDecodedTile); tile.mDecodedTile = null; } mRecycledQueue.push(tile); Loading Loading @@ -538,11 +574,16 @@ public class TiledImageRenderer { synchronized (mQueueLock) { tile = mUploadQueue.pop(); } if (tile == null) break; if (tile == null) { break; } if (!tile.isContentValid()) { Utils.assertTrue(tile.mTileState == STATE_DECODED); if (tile.mTileState == STATE_DECODED) { tile.updateContent(canvas); --quota; } else { Log.w(TAG, "Tile in upload queue has invalid state: " + tile.mTileState); } } } if (tile != null) { Loading Loading @@ -574,7 +615,17 @@ public class TiledImageRenderer { queueForDecode(tile); } } drawTile(tile, canvas, source, target); if (drawTile(tile, canvas, source, target)) { return; } } if (mPreview != null) { int size = mTileSize << level; float scaleX = (float) mPreview.getWidth() / mImageWidth; float scaleY = (float) mPreview.getHeight() / mImageHeight; source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX, (ty + size) * scaleY); canvas.drawTexture(mPreview, source, target); } } Loading @@ -588,7 +639,9 @@ public class TiledImageRenderer { // Parent can be divided to four quads and tile is one of the four. Tile parent = tile.getParentTile(); if (parent == null) return false; if (parent == null) { return false; } if (tile.mX == parent.mX) { source.left /= 2f; source.right /= 2f; Loading Loading @@ -623,14 +676,17 @@ public class TiledImageRenderer { @Override protected void onFreeBitmap(Bitmap bitmap) { if (sTilePool != null) sTilePool.put(bitmap); sTilePool.release(bitmap); } boolean decode() { // Get a tile from the original image. The tile is down-scaled // by (1 << mTilelevel) from a region in the original image. try { Bitmap reuse = sTilePool.get(mTileSize, mTileSize); Bitmap reuse = sTilePool.acquire(); if (reuse != null && reuse.getWidth() != mTileSize) { reuse = null; } mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse); } catch (Throwable t) { Log.w(TAG, "fail to decode tile", t); Loading Loading @@ -676,7 +732,9 @@ public class TiledImageRenderer { } public Tile getParentTile() { if (mTileLevel + 1 == mLevelCount) return null; if (mTileLevel + 1 == mLevelCount) { return null; } int size = mTileSize << (mTileLevel + 1); int x = size * (mX / size); int y = size * (mY / size); Loading @@ -695,17 +753,34 @@ public class TiledImageRenderer { public Tile pop() { Tile tile = mHead; if (tile != null) mHead = tile.mNext; if (tile != null) { mHead = tile.mNext; } return tile; } public boolean push(Tile tile) { if (contains(tile)) { Log.w(TAG, "Attempting to add a tile already in the queue!"); return false; } boolean wasEmpty = mHead == null; tile.mNext = mHead; mHead = tile; return wasEmpty; } private boolean contains(Tile tile) { Tile other = mHead; while (other != null) { if (other == tile) { return true; } other = other.mNext; } return false; } public void clean() { mHead = null; } Loading Loading @@ -739,11 +814,10 @@ public class TiledImageRenderer { try { while (!isInterrupted()) { Tile tile = waitForTile(); if (decodeTile(tile)) { queueForUpload(tile); } decodeTile(tile); } } catch (InterruptedException ex) { // We were finished } } Loading src/com/android/photos/views/TiledImageView.java +208 −95 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
src/com/android/photos/BitmapRegionTileSource.java +112 −16 Original line number Diff line number Diff line Loading @@ -16,66 +16,131 @@ package com.android.photos; import android.content.Context; 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 android.os.Build; import android.os.Build.VERSION_CODES; import android.util.Log; import com.android.gallery3d.glrenderer.BasicTexture; import com.android.gallery3d.glrenderer.BitmapTexture; import com.android.photos.views.TiledImageRenderer; import java.io.IOException; /** * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using * {@link BitmapRegionDecoder} to wrap a local file */ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { BitmapRegionDecoder mDecoder; private static final String TAG = "BitmapRegionTileSource"; private static final boolean REUSE_BITMAP = Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; private static final int MAX_PREVIEW_SIZE = 1024; public BitmapRegionTileSource(String path) { BitmapRegionDecoder mDecoder; int mWidth; int mHeight; int mTileSize; private BasicTexture mPreview; private final int mRotation; // For use only by getTile private Rect mWantRegion = new Rect(); private Rect mOverlapRegion = new Rect(); private BitmapFactory.Options mOptions; private Canvas mCanvas; public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) { mTileSize = TiledImageRenderer.suggestedTileSize(context); mRotation = rotation; try { mDecoder = BitmapRegionDecoder.newInstance(path, true); mWidth = mDecoder.getWidth(); mHeight = mDecoder.getHeight(); } catch (IOException e) { Log.w("BitmapRegionTileSource", "ctor failed", e); } mOptions = new BitmapFactory.Options(); mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; mOptions.inPreferQualityOverSpeed = true; mOptions.inTempStorage = new byte[16 * 1024]; if (previewSize != 0) { previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE); // Although this is the same size as the Bitmap that is likely already // loaded, the lifecycle is different and interactions are on a different // thread. Thus to simplify, this source will decode its own bitmap. int sampleSize = (int) Math.ceil(Math.max( mWidth / (float) previewSize, mHeight / (float) previewSize)); mOptions.inSampleSize = Math.max(sampleSize, 1); Bitmap preview = mDecoder.decodeRegion( new Rect(0, 0, mWidth, mHeight), mOptions); if (preview.getWidth() <= MAX_PREVIEW_SIZE && preview.getHeight() <= MAX_PREVIEW_SIZE) { mPreview = new BitmapTexture(preview); } else { Log.w(TAG, String.format( "Failed to create preview of apropriate size! " + " in: %dx%d, sample: %d, out: %dx%d", mWidth, mHeight, sampleSize, preview.getWidth(), preview.getHeight())); } } } @Override public int getTileSize() { return 256; return mTileSize; } @Override public int getImageWidth() { return mDecoder.getWidth(); return mWidth; } @Override public int getImageHeight() { return mDecoder.getHeight(); return mHeight; } @Override public BasicTexture getPreview() { return mPreview; } @Override public int getRotation() { return mRotation; } @Override public Bitmap getTile(int level, int x, int y, Bitmap bitmap) { int tileSize = getTileSize(); int t = tileSize << level; if (!REUSE_BITMAP) { return getTileWithoutReusingBitmap(level, x, y, tileSize); } Rect wantRegion = new Rect(x, y, x + t, y + t); int t = tileSize << level; mWantRegion.set(x, y, x + t, y + t); if (bitmap == null) { bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888); } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inPreferQualityOverSpeed = true; options.inSampleSize = (1 << level); options.inBitmap = bitmap; mOptions.inSampleSize = (1 << level); mOptions.inBitmap = bitmap; try { // In CropImage, we may call the decodeRegion() concurrently. bitmap = mDecoder.decodeRegion(wantRegion, options); bitmap = mDecoder.decodeRegion(mWantRegion, mOptions); } finally { if (options.inBitmap != bitmap && options.inBitmap != null) { options.inBitmap = null; if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) { mOptions.inBitmap = null; } } Loading @@ -84,4 +149,35 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { } return bitmap; } private Bitmap getTileWithoutReusingBitmap( int level, int x, int y, int tileSize) { int t = tileSize << level; mWantRegion.set(x, y, x + t, y + t); mOverlapRegion.set(0, 0, mWidth, mHeight); mOptions.inSampleSize = (1 << level); Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions); if (bitmap == null) { Log.w(TAG, "fail in decoding region"); } if (mWantRegion.equals(mOverlapRegion)) { return bitmap; } Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888); if (mCanvas == null) { mCanvas = new Canvas(); } mCanvas.setBitmap(result); mCanvas.drawBitmap(bitmap, (mOverlapRegion.left - mWantRegion.left) >> level, (mOverlapRegion.top - mWantRegion.top) >> level, null); mCanvas.setBitmap(null); return result; } }
src/com/android/photos/FullscreenViewer.java +1 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,7 @@ public class FullscreenViewer extends Activity { String path = getIntent().getData().toString(); mTextureView = new TiledImageView(this); mTextureView.setTileSource(new BitmapRegionTileSource(path)); mTextureView.setTileSource(new BitmapRegionTileSource(this, path, 0, 0), null); setContentView(mTextureView); } Loading
src/com/android/photos/views/BlockingGLTextureView.java +22 −11 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ package com.android.photos.views; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.SurfaceTexture; import android.opengl.GLSurfaceView.Renderer; Loading @@ -32,7 +31,9 @@ import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL10; /** * A TextureView that supports blocking rendering for synchronous drawing */ public class BlockingGLTextureView extends TextureView implements SurfaceTextureListener { Loading Loading @@ -90,7 +91,9 @@ public class BlockingGLTextureView extends TextureView protected void finalize() throws Throwable { try { destroy(); } catch (Throwable t) {} } catch (Throwable t) { // Ignore } super.finalize(); } Loading Loading @@ -135,8 +138,8 @@ public class BlockingGLTextureView extends TextureView } EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; return egl.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attribList); } /** Loading Loading @@ -368,19 +371,24 @@ public class BlockingGLTextureView extends TextureView exec(FINISH); try { join(); } catch (InterruptedException e) {} } catch (InterruptedException e) { // Ignore } } private void exec(int msgid) { synchronized (mLock) { if (mExecMsgId != INVALID) { throw new IllegalArgumentException("Message already set - multithreaded access?"); throw new IllegalArgumentException( "Message already set - multithreaded access?"); } mExecMsgId = msgid; mLock.notify(); try { mLock.wait(); } catch (InterruptedException e) {} } catch (InterruptedException e) { // Ignore } } } Loading Loading @@ -415,12 +423,15 @@ public class BlockingGLTextureView extends TextureView while (mExecMsgId == INVALID) { try { mLock.wait(); } catch (InterruptedException e) {} } catch (InterruptedException e) { // Ignore } } handleMessageLocked(mExecMsgId); mExecMsgId = INVALID; mLock.notify(); } mExecMsgId = FINISH; } } } Loading
src/com/android/photos/views/TiledImageRenderer.java +138 −64 Original line number Diff line number Diff line Loading @@ -23,14 +23,19 @@ import android.graphics.RectF; import android.support.v4.util.LongSparseArray; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pools.Pool; import android.util.Pools.SynchronizedPool; import android.view.View; import android.view.WindowManager; import com.android.gallery3d.common.Utils; import com.android.gallery3d.glrenderer.BasicTexture; import com.android.gallery3d.glrenderer.GLCanvas; import com.android.gallery3d.glrenderer.UploadedTexture; import com.android.photos.data.GalleryBitmapPool; /** * Handles laying out, decoding, and drawing of tiles in GL */ public class TiledImageRenderer { public static final int SIZE_UNKNOWN = -1; Loading Loading @@ -62,12 +67,13 @@ public class TiledImageRenderer { private static final int STATE_RECYCLING = 0x20; private static final int STATE_RECYCLED = 0x40; private static GalleryBitmapPool sTilePool = GalleryBitmapPool.getInstance(); private static Pool<Bitmap> sTilePool = new SynchronizedPool<Bitmap>(64); // TILE_SIZE must be 2^N private int mTileSize; private TileSource mModel; private BasicTexture mPreview; protected int mLevelCount; // cache the value of mScaledBitmaps.length // The mLevel variable indicates which level of bitmap we should use. Loading Loading @@ -116,22 +122,39 @@ public class TiledImageRenderer { private int mViewWidth, mViewHeight; private View mParent; /** * Interface for providing tiles to a {@link TiledImageRenderer} */ public static interface TileSource { /** * If the source does not care about the tile size, it should use * {@link TiledImageRenderer#suggestedTileSize(Context)} */ public int getTileSize(); public int getImageWidth(); public int getImageHeight(); public int getRotation(); // The tile returned by this method can be specified this way: Assuming // the image size is (width, height), first take the intersection of (0, // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If // in extending the region, we found some part of the region is outside // the image, those pixels are filled with black. // // If level > 0, it does the same operation on a down-scaled version of // the original image (down-scaled by a factor of 2^level), but (x, y) // still refers to the coordinate on the original image. // // The method would be called by the decoder thread. /** * Return a Preview image if available. This will be used as the base layer * if higher res tiles are not yet available */ public BasicTexture getPreview(); /** * The tile returned by this method can be specified this way: Assuming * the image size is (width, height), first take the intersection of (0, * 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If * in extending the region, we found some part of the region is outside * the image, those pixels are filled with black. * * If level > 0, it does the same operation on a down-scaled version of * the original image (down-scaled by a factor of 2^level), but (x, y) * still refers to the coordinate on the original image. * * The method would be called by the decoder thread. */ public Bitmap getTile(int level, int x, int y, Bitmap reuse); } Loading Loading @@ -173,19 +196,23 @@ public class TiledImageRenderer { if (mRotation != rotation) { mRotation = rotation; mLayoutTiles = true; invalidate(); } } private static int calulateLevelCount(TileSource source) { private void calculateLevelCount() { if (mPreview != null) { mLevelCount = Math.max(0, Utils.ceilLog2( mImageWidth / (float) mPreview.getWidth())); } else { int levels = 1; int maxDim = Math.max(source.getImageWidth(), source.getImageHeight()); int t = source.getTileSize(); int maxDim = Math.max(mImageWidth, mImageHeight); int t = mTileSize; while (t < maxDim) { t <<= 1; levels++; } return levels; mLevelCount = levels; } } public void notifyModelInvalidated() { Loading @@ -194,14 +221,15 @@ public class TiledImageRenderer { mImageWidth = 0; mImageHeight = 0; mLevelCount = 0; mPreview = null; } else { mImageWidth = mModel.getImageWidth(); mImageHeight = mModel.getImageHeight(); mLevelCount = calulateLevelCount(mModel); mPreview = mModel.getPreview(); mTileSize = mModel.getTileSize(); calculateLevelCount(); } mLayoutTiles = true; invalidate(); } public void setViewSize(int width, int height) { Loading @@ -211,12 +239,13 @@ public class TiledImageRenderer { public void setPosition(int centerX, int centerY, float scale) { if (mCenterX == centerX && mCenterY == centerY && mScale == scale) return; && mScale == scale) { return; } mCenterX = centerX; mCenterY = centerY; mScale = scale; mLayoutTiles = true; invalidate(); } // Prepare the tiles we want to use for display. Loading Loading @@ -265,7 +294,9 @@ public class TiledImageRenderer { } // If rotation is transient, don't update the tile. if (mRotation % 90 != 0) return; if (mRotation % 90 != 0) { return; } synchronized (mQueueLock) { mDecodeQueue.clean(); Loading Loading @@ -305,7 +336,7 @@ public class TiledImageRenderer { mDecodeQueue.clean(); mUploadQueue.clean(); // TODO disable decoder // TODO(xx): disable decoder int n = mActiveTiles.size(); for (int i = 0; i < n; i++) { Tile tile = mActiveTiles.valueAt(i); Loading Loading @@ -357,6 +388,7 @@ public class TiledImageRenderer { public void freeTextures() { mLayoutTiles = true; mTileDecoder.finishAndWait(); synchronized (mQueueLock) { mUploadQueue.clean(); mDecodeQueue.clean(); Loading @@ -375,10 +407,10 @@ public class TiledImageRenderer { mActiveTiles.clear(); mTileRange.set(0, 0, 0, 0); if (sTilePool != null) sTilePool.clear(); while (sTilePool.acquire() != null) {} } public void draw(GLCanvas canvas) { public boolean draw(GLCanvas canvas) { layoutTiles(); uploadTiles(canvas); Loading @@ -388,7 +420,9 @@ public class TiledImageRenderer { int level = mLevel; int rotation = mRotation; int flags = 0; if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX; if (rotation != 0) { flags |= GLCanvas.SAVE_FLAG_MATRIX; } if (flags != 0) { canvas.save(flags); Loading @@ -412,9 +446,15 @@ public class TiledImageRenderer { drawTile(canvas, tx, ty, level, x, y, length); } } } else if (mPreview != null) { mPreview.draw(canvas, mOffsetX, mOffsetY, Math.round(mImageWidth * mScale), Math.round(mImageHeight * mScale)); } } finally { if (flags != 0) canvas.restore(); if (flags != 0) { canvas.restore(); } } if (mRenderComplete) { Loading @@ -424,6 +464,7 @@ public class TiledImageRenderer { } else { invalidate(); } return mRenderComplete || mPreview != null; } private void uploadBackgroundTiles(GLCanvas canvas) { Loading @@ -437,17 +478,6 @@ public class TiledImageRenderer { } } private void queueForUpload(Tile tile) { synchronized (mQueueLock) { mUploadQueue.push(tile); } invalidate(); // TODO // if (mTileUploader.mActive.compareAndSet(false, true)) { // getGLRoot().addOnGLIdleListener(mTileUploader); // } } private void queueForDecode(Tile tile) { synchronized (mQueueLock) { if (tile.mTileState == STATE_ACTIVATED) { Loading @@ -459,9 +489,11 @@ public class TiledImageRenderer { } } private boolean decodeTile(Tile tile) { private void decodeTile(Tile tile) { synchronized (mQueueLock) { if (tile.mTileState != STATE_IN_QUEUE) return false; if (tile.mTileState != STATE_IN_QUEUE) { return; } tile.mTileState = STATE_DECODING; } boolean decodeComplete = tile.decode(); Loading @@ -469,15 +501,19 @@ public class TiledImageRenderer { if (tile.mTileState == STATE_RECYCLING) { tile.mTileState = STATE_RECYCLED; if (tile.mDecodedTile != null) { if (sTilePool != null) sTilePool.put(tile.mDecodedTile); sTilePool.release(tile.mDecodedTile); tile.mDecodedTile = null; } mRecycledQueue.push(tile); return false; return; } tile.mTileState = decodeComplete ? STATE_DECODED : STATE_DECODE_FAIL; return decodeComplete; if (!decodeComplete) { return; } mUploadQueue.push(tile); } invalidate(); } private Tile obtainTile(int x, int y, int level) { Loading @@ -500,7 +536,7 @@ public class TiledImageRenderer { } tile.mTileState = STATE_RECYCLED; if (tile.mDecodedTile != null) { if (sTilePool != null) sTilePool.put(tile.mDecodedTile); sTilePool.release(tile.mDecodedTile); tile.mDecodedTile = null; } mRecycledQueue.push(tile); Loading Loading @@ -538,11 +574,16 @@ public class TiledImageRenderer { synchronized (mQueueLock) { tile = mUploadQueue.pop(); } if (tile == null) break; if (tile == null) { break; } if (!tile.isContentValid()) { Utils.assertTrue(tile.mTileState == STATE_DECODED); if (tile.mTileState == STATE_DECODED) { tile.updateContent(canvas); --quota; } else { Log.w(TAG, "Tile in upload queue has invalid state: " + tile.mTileState); } } } if (tile != null) { Loading Loading @@ -574,7 +615,17 @@ public class TiledImageRenderer { queueForDecode(tile); } } drawTile(tile, canvas, source, target); if (drawTile(tile, canvas, source, target)) { return; } } if (mPreview != null) { int size = mTileSize << level; float scaleX = (float) mPreview.getWidth() / mImageWidth; float scaleY = (float) mPreview.getHeight() / mImageHeight; source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX, (ty + size) * scaleY); canvas.drawTexture(mPreview, source, target); } } Loading @@ -588,7 +639,9 @@ public class TiledImageRenderer { // Parent can be divided to four quads and tile is one of the four. Tile parent = tile.getParentTile(); if (parent == null) return false; if (parent == null) { return false; } if (tile.mX == parent.mX) { source.left /= 2f; source.right /= 2f; Loading Loading @@ -623,14 +676,17 @@ public class TiledImageRenderer { @Override protected void onFreeBitmap(Bitmap bitmap) { if (sTilePool != null) sTilePool.put(bitmap); sTilePool.release(bitmap); } boolean decode() { // Get a tile from the original image. The tile is down-scaled // by (1 << mTilelevel) from a region in the original image. try { Bitmap reuse = sTilePool.get(mTileSize, mTileSize); Bitmap reuse = sTilePool.acquire(); if (reuse != null && reuse.getWidth() != mTileSize) { reuse = null; } mDecodedTile = mModel.getTile(mTileLevel, mX, mY, reuse); } catch (Throwable t) { Log.w(TAG, "fail to decode tile", t); Loading Loading @@ -676,7 +732,9 @@ public class TiledImageRenderer { } public Tile getParentTile() { if (mTileLevel + 1 == mLevelCount) return null; if (mTileLevel + 1 == mLevelCount) { return null; } int size = mTileSize << (mTileLevel + 1); int x = size * (mX / size); int y = size * (mY / size); Loading @@ -695,17 +753,34 @@ public class TiledImageRenderer { public Tile pop() { Tile tile = mHead; if (tile != null) mHead = tile.mNext; if (tile != null) { mHead = tile.mNext; } return tile; } public boolean push(Tile tile) { if (contains(tile)) { Log.w(TAG, "Attempting to add a tile already in the queue!"); return false; } boolean wasEmpty = mHead == null; tile.mNext = mHead; mHead = tile; return wasEmpty; } private boolean contains(Tile tile) { Tile other = mHead; while (other != null) { if (other == tile) { return true; } other = other.mNext; } return false; } public void clean() { mHead = null; } Loading Loading @@ -739,11 +814,10 @@ public class TiledImageRenderer { try { while (!isInterrupted()) { Tile tile = waitForTile(); if (decodeTile(tile)) { queueForUpload(tile); } decodeTile(tile); } } catch (InterruptedException ex) { // We were finished } } Loading
src/com/android/photos/views/TiledImageView.java +208 −95 File changed.Preview size limit exceeded, changes collapsed. Show changes