summaryrefslogtreecommitdiffstats
path: root/src/com/android/photos
diff options
context:
space:
mode:
authorJohn Reck <jreck@google.com>2013-05-29 17:07:21 -0700
committerJohn Reck <jreck@google.com>2013-05-30 09:26:42 -0700
commitf5ef4801465a48ce3e33e1a75568060b2cf61185 (patch)
tree0b537ed68efcb334ca0b0c77cf3dcaf7b157c89b /src/com/android/photos
parent41234723257555e6c003341f0ae184db5ee1f887 (diff)
downloadandroid_packages_apps_Gallery2-f5ef4801465a48ce3e33e1a75568060b2cf61185.tar.gz
android_packages_apps_Gallery2-f5ef4801465a48ce3e33e1a75568060b2cf61185.tar.bz2
android_packages_apps_Gallery2-f5ef4801465a48ce3e33e1a75568060b2cf61185.zip
Copy Tiling changes from G+
Change-Id: Id229728182a002c29699884289f1354b6cb6e714
Diffstat (limited to 'src/com/android/photos')
-rw-r--r--src/com/android/photos/BitmapRegionTileSource.java128
-rw-r--r--src/com/android/photos/FullscreenViewer.java2
-rw-r--r--src/com/android/photos/views/BlockingGLTextureView.java33
-rw-r--r--src/com/android/photos/views/TiledImageRenderer.java202
-rw-r--r--src/com/android/photos/views/TiledImageView.java303
5 files changed, 481 insertions, 187 deletions
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
index 1c7115191..d7d52f67a 100644
--- a/src/com/android/photos/BitmapRegionTileSource.java
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -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;
}
}
@@ -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;
+ }
}
diff --git a/src/com/android/photos/FullscreenViewer.java b/src/com/android/photos/FullscreenViewer.java
index 50ea1ba71..a3761395e 100644
--- a/src/com/android/photos/FullscreenViewer.java
+++ b/src/com/android/photos/FullscreenViewer.java
@@ -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);
}
diff --git a/src/com/android/photos/views/BlockingGLTextureView.java b/src/com/android/photos/views/BlockingGLTextureView.java
index c38f8f7a4..8a0505185 100644
--- a/src/com/android/photos/views/BlockingGLTextureView.java
+++ b/src/com/android/photos/views/BlockingGLTextureView.java
@@ -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;
@@ -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 {
@@ -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();
}
@@ -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);
}
/**
@@ -161,7 +164,7 @@ public class BlockingGLTextureView extends TextureView
* We can now initialize EGL for that display
*/
int[] version = new int[2];
- if(!mEgl.eglInitialize(mEglDisplay, version)) {
+ if (!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
mEglConfig = chooseEglConfig();
@@ -251,7 +254,7 @@ public class BlockingGLTextureView extends TextureView
* @return the EGL error code from eglSwapBuffers.
*/
public int swap() {
- if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
+ if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError();
}
return EGL10.EGL_SUCCESS;
@@ -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
+ }
}
}
@@ -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;
}
}
}
diff --git a/src/com/android/photos/views/TiledImageRenderer.java b/src/com/android/photos/views/TiledImageRenderer.java
index a1f7107f2..c4e493b34 100644
--- a/src/com/android/photos/views/TiledImageRenderer.java
+++ b/src/com/android/photos/views/TiledImageRenderer.java
@@ -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;
@@ -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.
@@ -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();
-
- // 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 int getRotation();
+
+ /**
+ * 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);
}
@@ -173,19 +196,23 @@ public class TiledImageRenderer {
if (mRotation != rotation) {
mRotation = rotation;
mLayoutTiles = true;
- invalidate();
}
}
- private static int calulateLevelCount(TileSource source) {
- int levels = 1;
- int maxDim = Math.max(source.getImageWidth(), source.getImageHeight());
- int t = source.getTileSize();
- while (t < maxDim) {
- t <<= 1;
- levels++;
+ private void calculateLevelCount() {
+ if (mPreview != null) {
+ mLevelCount = Math.max(0, Utils.ceilLog2(
+ mImageWidth / (float) mPreview.getWidth()));
+ } else {
+ int levels = 1;
+ int maxDim = Math.max(mImageWidth, mImageHeight);
+ int t = mTileSize;
+ while (t < maxDim) {
+ t <<= 1;
+ levels++;
+ }
+ mLevelCount = levels;
}
- return levels;
}
public void notifyModelInvalidated() {
@@ -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) {
@@ -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.
@@ -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();
@@ -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);
@@ -357,6 +388,7 @@ public class TiledImageRenderer {
public void freeTextures() {
mLayoutTiles = true;
+ mTileDecoder.finishAndWait();
synchronized (mQueueLock) {
mUploadQueue.clean();
mDecodeQueue.clean();
@@ -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);
@@ -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);
@@ -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) {
@@ -424,6 +464,7 @@ public class TiledImageRenderer {
} else {
invalidate();
}
+ return mRenderComplete || mPreview != null;
}
private void uploadBackgroundTiles(GLCanvas canvas) {
@@ -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) {
@@ -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();
@@ -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) {
@@ -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);
@@ -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);
- tile.updateContent(canvas);
- --quota;
+ 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) {
@@ -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);
}
}
@@ -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;
@@ -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);
@@ -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);
@@ -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;
}
@@ -723,7 +798,7 @@ public class TiledImageRenderer {
}
private Tile waitForTile() throws InterruptedException {
- synchronized(mQueueLock) {
+ synchronized (mQueueLock) {
while (true) {
Tile tile = mDecodeQueue.pop();
if (tile != null) {
@@ -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
}
}
diff --git a/src/com/android/photos/views/TiledImageView.java b/src/com/android/photos/views/TiledImageView.java
index 6fe030dae..8bc07c051 100644
--- a/src/com/android/photos/views/TiledImageView.java
+++ b/src/com/android/photos/views/TiledImageView.java
@@ -16,29 +16,48 @@
package com.android.photos.views;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Align;
+import android.graphics.RectF;
+import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
+import android.os.Build;
import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.ScaleGestureDetector;
-import android.view.ScaleGestureDetector.OnScaleGestureListener;
+import android.view.Choreographer;
+import android.view.Choreographer.FrameCallback;
+import android.view.View;
import android.widget.FrameLayout;
+
+import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.GLES20Canvas;
import com.android.photos.views.TiledImageRenderer.TileSource;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
+/**
+ * Shows an image using {@link TiledImageRenderer} using either {@link GLSurfaceView}
+ * or {@link BlockingGLTextureView}.
+ */
+public class TiledImageView extends FrameLayout {
-public class TiledImageView extends FrameLayout implements OnScaleGestureListener {
+ private static final boolean USE_TEXTURE_VIEW = false;
+ private static final boolean IS_SUPPORTED =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
+ private static final boolean USE_CHOREOGRAPHER =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
private BlockingGLTextureView mTextureView;
- private float mLastX, mLastY;
+ private GLSurfaceView mGLSurfaceView;
+ private boolean mInvalPending = false;
+ private FrameCallback mFrameCallback;
private static class ImageRendererWrapper {
// Guarded by locks
@@ -46,20 +65,19 @@ public class TiledImageView extends FrameLayout implements OnScaleGestureListene
int centerX, centerY;
int rotation;
TileSource source;
+ Runnable isReadyCallback;
// GL thread only
TiledImageRenderer image;
}
- // TODO: left/right paging
- private ImageRendererWrapper mRenderers[] = new ImageRendererWrapper[1];
- private ImageRendererWrapper mFocusedRenderer;
+ private float[] mValues = new float[9];
// -------------------------
// Guarded by mLock
// -------------------------
private Object mLock = new Object();
- private ScaleGestureDetector mScaleGestureDetector;
+ private ImageRendererWrapper mRenderer;
public TiledImageView(Context context) {
this(context, null);
@@ -67,102 +85,99 @@ public class TiledImageView extends FrameLayout implements OnScaleGestureListene
public TiledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
- mTextureView = new BlockingGLTextureView(context);
- addView(mTextureView, new LayoutParams(
+ if (!IS_SUPPORTED) {
+ return;
+ }
+
+ mRenderer = new ImageRendererWrapper();
+ mRenderer.image = new TiledImageRenderer(this);
+ View view;
+ if (USE_TEXTURE_VIEW) {
+ mTextureView = new BlockingGLTextureView(context);
+ mTextureView.setRenderer(new TileRenderer());
+ view = mTextureView;
+ } else {
+ mGLSurfaceView = new GLSurfaceView(context);
+ mGLSurfaceView.setEGLContextClientVersion(2);
+ mGLSurfaceView.setRenderer(new TileRenderer());
+ mGLSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ view = mGLSurfaceView;
+ }
+ addView(view, new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- mTextureView.setRenderer(new TileRenderer());
- setTileSource(new ColoredTiles());
- mScaleGestureDetector = new ScaleGestureDetector(context, this);
+ //setTileSource(new ColoredTiles());
}
public void destroy() {
- mTextureView.destroy();
- }
-
- public void setTileSource(TileSource source) {
- synchronized (mLock) {
- for (int i = 0; i < mRenderers.length; i++) {
- ImageRendererWrapper renderer = mRenderers[i];
- if (renderer == null) {
- renderer = mRenderers[i] = new ImageRendererWrapper();
- }
- renderer.source = source;
- renderer.centerX = renderer.source.getImageWidth() / 2;
- renderer.centerY = renderer.source.getImageHeight() / 2;
- renderer.rotation = 0;
- renderer.scale = 0;
- renderer.image = new TiledImageRenderer(this);
- updateScaleIfNecessaryLocked(renderer);
- }
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (USE_TEXTURE_VIEW) {
+ mTextureView.destroy();
+ } else {
+ mGLSurfaceView.queueEvent(mFreeTextures);
}
- mFocusedRenderer = mRenderers[0];
- invalidate();
}
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- return true;
- }
+ private Runnable mFreeTextures = new Runnable() {
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- // Don't need the lock because this will only fire inside of onTouchEvent
- mFocusedRenderer.scale *= detector.getScaleFactor();
- invalidate();
- return true;
- }
+ @Override
+ public void run() {
+ mRenderer.image.freeTextures();
+ }
+ };
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
+ public void onPause() {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (!USE_TEXTURE_VIEW) {
+ mGLSurfaceView.onPause();
+ }
}
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
- final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
- final int skipIndex = pointerUp ? event.getActionIndex() : -1;
-
- // Determine focal point
- float sumX = 0, sumY = 0;
- final int count = event.getPointerCount();
- for (int i = 0; i < count; i++) {
- if (skipIndex == i) continue;
- sumX += event.getX(i);
- sumY += event.getY(i);
+ public void onResume() {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (!USE_TEXTURE_VIEW) {
+ mGLSurfaceView.onResume();
}
- final int div = pointerUp ? count - 1 : count;
- float x = sumX / div;
- float y = sumY / div;
+ }
+ public void setTileSource(TileSource source, Runnable isReadyCallback) {
+ if (!IS_SUPPORTED) {
+ return;
+ }
synchronized (mLock) {
- mScaleGestureDetector.onTouchEvent(event);
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- mFocusedRenderer.centerX += (mLastX - x) / mFocusedRenderer.scale;
- mFocusedRenderer.centerY += (mLastY - y) / mFocusedRenderer.scale;
- invalidate();
- break;
- }
+ mRenderer.source = source;
+ mRenderer.isReadyCallback = isReadyCallback;
+ mRenderer.centerX = source != null ? source.getImageWidth() / 2 : 0;
+ mRenderer.centerY = source != null ? source.getImageHeight() / 2 : 0;
+ mRenderer.rotation = source != null ? source.getRotation() : 0;
+ mRenderer.scale = 0;
+ updateScaleIfNecessaryLocked(mRenderer);
}
-
- mLastX = x;
- mLastY = y;
- return true;
+ invalidate();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
+ if (!IS_SUPPORTED) {
+ return;
+ }
synchronized (mLock) {
- for (ImageRendererWrapper renderer : mRenderers) {
- updateScaleIfNecessaryLocked(renderer);
- }
+ updateScaleIfNecessaryLocked(mRenderer);
}
}
private void updateScaleIfNecessaryLocked(ImageRendererWrapper renderer) {
- if (renderer.scale > 0 || getWidth() == 0) return;
+ if (renderer == null || renderer.source == null
+ || renderer.scale > 0 || getWidth() == 0) {
+ return;
+ }
renderer.scale = Math.min(
(float) getWidth() / (float) renderer.source.getImageWidth(),
(float) getHeight() / (float) renderer.source.getImageHeight());
@@ -170,14 +185,93 @@ public class TiledImageView extends FrameLayout implements OnScaleGestureListene
@Override
protected void dispatchDraw(Canvas canvas) {
- mTextureView.render();
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (USE_TEXTURE_VIEW) {
+ mTextureView.render();
+ }
super.dispatchDraw(canvas);
}
+ @SuppressLint("NewApi")
+ @Override
+ public void setTranslationX(float translationX) {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ super.setTranslationX(translationX);
+ }
+
@Override
public void invalidate() {
- super.invalidate();
- mTextureView.invalidate();
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (USE_TEXTURE_VIEW) {
+ super.invalidate();
+ mTextureView.invalidate();
+ } else {
+ if (USE_CHOREOGRAPHER) {
+ invalOnVsync();
+ } else {
+ mGLSurfaceView.requestRender();
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ private void invalOnVsync() {
+ if (!mInvalPending) {
+ mInvalPending = true;
+ if (mFrameCallback == null) {
+ mFrameCallback = new FrameCallback() {
+ @Override
+ public void doFrame(long frameTimeNanos) {
+ mInvalPending = false;
+ mGLSurfaceView.requestRender();
+ }
+ };
+ }
+ Choreographer.getInstance().postFrameCallback(mFrameCallback);
+ }
+ }
+
+ private RectF mTempRectF = new RectF();
+ public void positionFromMatrix(Matrix matrix) {
+ if (!IS_SUPPORTED) {
+ return;
+ }
+ if (mRenderer.source != null) {
+ final int rotation = mRenderer.source.getRotation();
+ final boolean swap = !(rotation % 180 == 0);
+ final int width = swap ? mRenderer.source.getImageHeight()
+ : mRenderer.source.getImageWidth();
+ final int height = swap ? mRenderer.source.getImageWidth()
+ : mRenderer.source.getImageHeight();
+ mTempRectF.set(0, 0, width, height);
+ matrix.mapRect(mTempRectF);
+ matrix.getValues(mValues);
+ int cx = width / 2;
+ int cy = height / 2;
+ float scale = mValues[Matrix.MSCALE_X];
+ int xoffset = Math.round((getWidth() - mTempRectF.width()) / 2 / scale);
+ int yoffset = Math.round((getHeight() - mTempRectF.height()) / 2 / scale);
+ if (rotation == 90 || rotation == 180) {
+ cx += (mTempRectF.left / scale) - xoffset;
+ } else {
+ cx -= (mTempRectF.left / scale) - xoffset;
+ }
+ if (rotation == 180 || rotation == 270) {
+ cy += (mTempRectF.top / scale) - yoffset;
+ } else {
+ cy -= (mTempRectF.top / scale) - yoffset;
+ }
+ mRenderer.scale = scale;
+ mRenderer.centerX = swap ? cy : cx;
+ mRenderer.centerY = swap ? cx : cy;
+ invalidate();
+ }
}
private class TileRenderer implements Renderer {
@@ -187,37 +281,46 @@ public class TiledImageView extends FrameLayout implements OnScaleGestureListene
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
mCanvas = new GLES20Canvas();
- for (ImageRendererWrapper renderer : mRenderers) {
- renderer.image.setModel(renderer.source, renderer.rotation);
- }
+ BasicTexture.invalidateAllTextures();
+ mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
mCanvas.setSize(width, height);
- for (ImageRendererWrapper renderer : mRenderers) {
- renderer.image.setViewSize(width, height);
- }
+ mRenderer.image.setViewSize(width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
mCanvas.clearBuffer();
+ Runnable readyCallback;
synchronized (mLock) {
- for (ImageRendererWrapper renderer : mRenderers) {
- renderer.image.setModel(renderer.source, renderer.rotation);
- renderer.image.setPosition(renderer.centerX, renderer.centerY, renderer.scale);
- }
+ readyCallback = mRenderer.isReadyCallback;
+ mRenderer.image.setModel(mRenderer.source, mRenderer.rotation);
+ mRenderer.image.setPosition(mRenderer.centerX, mRenderer.centerY,
+ mRenderer.scale);
}
- for (ImageRendererWrapper renderer : mRenderers) {
- renderer.image.draw(mCanvas);
+ boolean complete = mRenderer.image.draw(mCanvas);
+ if (complete && readyCallback != null) {
+ synchronized (mLock) {
+ // Make sure we don't trample on a newly set callback/source
+ // if it changed while we were rendering
+ if (mRenderer.isReadyCallback == readyCallback) {
+ mRenderer.isReadyCallback = null;
+ }
+ }
+ if (readyCallback != null) {
+ post(readyCallback);
+ }
}
}
}
+ @SuppressWarnings("unused")
private static class ColoredTiles implements TileSource {
- private static int[] COLORS = new int[] {
+ private static final int[] COLORS = new int[] {
Color.RED,
Color.BLUE,
Color.YELLOW,
@@ -246,6 +349,11 @@ public class TiledImageView extends FrameLayout implements OnScaleGestureListene
}
@Override
+ public int getRotation() {
+ return 0;
+ }
+
+ @Override
public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
int tileSize = getTileSize();
if (bitmap == null) {
@@ -265,5 +373,10 @@ public class TiledImageView extends FrameLayout implements OnScaleGestureListene
mCanvas.setBitmap(null);
return bitmap;
}
+
+ @Override
+ public BasicTexture getPreview() {
+ return null;
+ }
}
}