diff options
Diffstat (limited to 'src/com/android/gallery3d/ui')
-rw-r--r-- | src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java | 2 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/BasicTexture.java | 4 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/BitmapScreenNail.java | 191 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/GLCanvas.java | 7 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/GLCanvasImpl.java | 82 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/GLRoot.java | 3 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/GLView.java | 39 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/PhotoView.java | 25 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/PositionController.java | 2 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java | 2 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java | 3 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/TileImageView.java | 7 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/TileImageViewAdapter.java | 32 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/TiledScreenNail.java | 216 | ||||
-rw-r--r-- | src/com/android/gallery3d/ui/TiledTexture.java | 298 |
15 files changed, 668 insertions, 245 deletions
diff --git a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java index d5337f00d..9b3f29f2b 100644 --- a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java +++ b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java @@ -71,7 +71,7 @@ public class AlbumSetSlotRenderer extends AbstractSlotRenderer { mWaitLoadingTexture = new ColorTexture(mPlaceholderColor); mWaitLoadingTexture.setSize(1, 1); mCameraOverlay = new ResourceTexture(activity, - R.drawable.frame_overlay_gallery_camera); + R.drawable.ic_cameraalbum_overlay); } public void setPressedIndex(int index) { diff --git a/src/com/android/gallery3d/ui/BasicTexture.java b/src/com/android/gallery3d/ui/BasicTexture.java index 7b8e30de4..99cf0571c 100644 --- a/src/com/android/gallery3d/ui/BasicTexture.java +++ b/src/com/android/gallery3d/ui/BasicTexture.java @@ -42,8 +42,8 @@ abstract class BasicTexture implements Texture { protected int mWidth = UNSPECIFIED; protected int mHeight = UNSPECIFIED; - private int mTextureWidth; - private int mTextureHeight; + protected int mTextureWidth; + protected int mTextureHeight; private boolean mHasBorder; diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java index bf31bcbdf..741eefbe3 100644 --- a/src/com/android/gallery3d/ui/BitmapScreenNail.java +++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java @@ -19,209 +19,40 @@ package com.android.gallery3d.ui; import android.graphics.Bitmap; import android.graphics.RectF; -import com.android.gallery3d.common.Utils; -import com.android.gallery3d.data.BitmapPool; -import com.android.gallery3d.data.MediaItem; - -// This is a ScreenNail wraps a Bitmap. There are some extra functions: -// -// - If we need to draw before the bitmap is available, we draw a rectange of -// placeholder color (gray). -// -// - When the the bitmap is available, and we have drawn the placeholder color -// before, we will do a fade-in animation. public class BitmapScreenNail implements ScreenNail { - @SuppressWarnings("unused") - private static final String TAG = "BitmapScreenNail"; - - // The duration of the fading animation in milliseconds - private static final int DURATION = 180; - - private static int sMaxSide = 640; - - // These are special values for mAnimationStartTime - private static final long ANIMATION_NOT_NEEDED = -1; - private static final long ANIMATION_NEEDED = -2; - private static final long ANIMATION_DONE = -3; - - private int mWidth; - private int mHeight; - private Bitmap mBitmap; - private BitmapTexture mTexture; - private long mAnimationStartTime = ANIMATION_NOT_NEEDED; + private final BitmapTexture mBitmapTexture; public BitmapScreenNail(Bitmap bitmap) { - mWidth = bitmap.getWidth(); - mHeight = bitmap.getHeight(); - mBitmap = bitmap; - // We create mTexture lazily, so we don't incur the cost if we don't - // actually need it. - } - - public BitmapScreenNail(int width, int height) { - setSize(width, height); - } - - // This gets overridden by bitmap_screennail_placeholder - // in GalleryUtils.initialize - private static int mPlaceholderColor = 0xFF222222; - private static boolean mDrawPlaceholder = true; - - public static void setPlaceholderColor(int color) { - mPlaceholderColor = color; - } - - private void setSize(int width, int height) { - if (width == 0 || height == 0) { - width = sMaxSide; - height = sMaxSide * 3 / 4; - } - float scale = Math.min(1, (float) sMaxSide / Math.max(width, height)); - mWidth = Math.round(scale * width); - mHeight = Math.round(scale * height); - } - - private static void recycleBitmap(BitmapPool pool, Bitmap bitmap) { - if (pool == null || bitmap == null) return; - pool.recycle(bitmap); - } - - // Combines the two ScreenNails. - // Returns the used one and recycle the unused one. - public ScreenNail combine(ScreenNail other) { - if (other == null) { - return this; - } - - if (!(other instanceof BitmapScreenNail)) { - recycle(); - return other; - } - - // Now both are BitmapScreenNail. Move over the information about width, - // height, and Bitmap, then recycle the other. - BitmapScreenNail newer = (BitmapScreenNail) other; - mWidth = newer.mWidth; - mHeight = newer.mHeight; - if (newer.mBitmap != null) { - recycleBitmap(MediaItem.getThumbPool(), mBitmap); - mBitmap = newer.mBitmap; - newer.mBitmap = null; - - if (mTexture != null) { - mTexture.recycle(); - mTexture = null; - } - } - - newer.recycle(); - return this; - } - - public void updatePlaceholderSize(int width, int height) { - if (mBitmap != null) return; - if (width == 0 || height == 0) return; - setSize(width, height); + mBitmapTexture = new BitmapTexture(bitmap); } @Override public int getWidth() { - return mWidth; + return mBitmapTexture.getWidth(); } @Override public int getHeight() { - return mHeight; + return mBitmapTexture.getHeight(); } @Override - public void noDraw() { + public void draw(GLCanvas canvas, int x, int y, int width, int height) { + mBitmapTexture.draw(canvas, x, y, width, height); } @Override - public void recycle() { - if (mTexture != null) { - mTexture.recycle(); - mTexture = null; - } - recycleBitmap(MediaItem.getThumbPool(), mBitmap); - mBitmap = null; - } - - public static void disableDrawPlaceholder() { - mDrawPlaceholder = false; - } - - public static void enableDrawPlaceholder() { - mDrawPlaceholder = true; + public void noDraw() { + // do nothing } @Override - public void draw(GLCanvas canvas, int x, int y, int width, int height) { - if (mBitmap == null) { - if (mAnimationStartTime == ANIMATION_NOT_NEEDED) { - mAnimationStartTime = ANIMATION_NEEDED; - } - if(mDrawPlaceholder) { - canvas.fillRect(x, y, width, height, mPlaceholderColor); - } - return; - } - - if (mTexture == null) { - mTexture = new BitmapTexture(mBitmap); - } - - if (mAnimationStartTime == ANIMATION_NEEDED) { - mAnimationStartTime = now(); - } - - if (isAnimating()) { - canvas.drawMixed(mTexture, mPlaceholderColor, getRatio(), x, y, - width, height); - } else { - mTexture.draw(canvas, x, y, width, height); - } + public void recycle() { + mBitmapTexture.recycle(); } @Override public void draw(GLCanvas canvas, RectF source, RectF dest) { - if (mBitmap == null) { - canvas.fillRect(dest.left, dest.top, dest.width(), dest.height(), - mPlaceholderColor); - return; - } - - if (mTexture == null) { - mTexture = new BitmapTexture(mBitmap); - } - - canvas.drawTexture(mTexture, source, dest); - } - - public boolean isAnimating() { - if (mAnimationStartTime < 0) return false; - if (now() - mAnimationStartTime >= DURATION) { - mAnimationStartTime = ANIMATION_DONE; - return false; - } - return true; - } - - private static long now() { - return AnimationTime.get(); - } - - private float getRatio() { - float r = (float)(now() - mAnimationStartTime) / DURATION; - return Utils.clamp(1.0f - r, 0.0f, 1.0f); - } - - public boolean isShowingPlaceholder() { - return (mBitmap == null) || isAnimating(); - } - - public static void setMaxSide(int size) { - sMaxSide = size; + canvas.drawTexture(mBitmapTexture, source, dest); } } diff --git a/src/com/android/gallery3d/ui/GLCanvas.java b/src/com/android/gallery3d/ui/GLCanvas.java index e3a32ef08..6f8baef7e 100644 --- a/src/com/android/gallery3d/ui/GLCanvas.java +++ b/src/com/android/gallery3d/ui/GLCanvas.java @@ -99,6 +99,13 @@ public interface GLCanvas { public void drawMixed(BasicTexture from, int toColor, float ratio, int x, int y, int w, int h); + // Draw a region of a texture and a specified color to the specified + // rectangle. The actual color used is from * (1 - ratio) + to * ratio. + // The region of the texture is defined by parameter "src". The target + // rectangle is specified by parameter "target". + public void drawMixed(BasicTexture from, int toColor, + float ratio, RectF src, RectF target); + // Gets the underlying GL instance. This is used only when direct access to // GL is needed. public GL11 getGLInstance(); diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/ui/GLCanvasImpl.java index d83daf3e4..45903b3cd 100644 --- a/src/com/android/gallery3d/ui/GLCanvasImpl.java +++ b/src/com/android/gallery3d/ui/GLCanvasImpl.java @@ -415,7 +415,7 @@ public class GLCanvasImpl implements GLCanvas { // This function changes the source coordinate to the texture coordinates. // It also clips the source and target coordinates if it is beyond the // bound of the texture. - private void convertCoordinate(RectF source, RectF target, + private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) { int width = texture.getWidth(); @@ -465,23 +465,7 @@ public class GLCanvasImpl implements GLCanvas { color[3] = alpha; } - private void drawMixed(BasicTexture from, int toColor, - float ratio, int x, int y, int width, int height, float alpha) { - // change from 0 to 0.01f to prevent getting divided by zero below - if (ratio <= 0.01f) { - drawTexture(from, x, y, width, height, alpha); - return; - } else if (ratio >= 1) { - fillRect(x, y, width, height, toColor); - return; - } - - mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() - || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); - - final GL11 gl = mGL; - if (!bindTexture(from)) return; - + private void setMixedColor(int toColor, float ratio, float alpha) { // // The formula we want: // alpha * ((1 - ratio) * from + ratio * to) @@ -495,9 +479,6 @@ public class GLCanvasImpl implements GLCanvas { float combo = alpha * (1 - ratio); float scale = alpha * ratio / (1 - combo); - // Interpolate the RGB and alpha values between both textures. - mGLState.setTexEnvMode(GL11.GL_COMBINE); - // Specify the interpolation factor via the alpha component of // GL_TEXTURE_ENV_COLORs. // RGB component are get from toColor and will used as SRC1 @@ -505,6 +486,7 @@ public class GLCanvasImpl implements GLCanvas { setTextureColor(((toColor >>> 16) & 0xff) * colorScale, ((toColor >>> 8) & 0xff) * colorScale, (toColor & 0xff) * colorScale, combo); + GL11 gl = mGL; gl.glTexEnvfv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, mTextureColor, 0); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_COMBINE_RGB, GL11.GL_INTERPOLATE); @@ -522,6 +504,64 @@ public class GLCanvasImpl implements GLCanvas { gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_SRC2_ALPHA, GL11.GL_CONSTANT); gl.glTexEnvf(GL11.GL_TEXTURE_ENV, GL11.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA); + } + + @Override + public void drawMixed(BasicTexture from, int toColor, float ratio, + RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) return; + + if (ratio <= 0.01f) { + drawTexture(from, source, target); + return; + } else if (ratio >= 1) { + fillRect(target.left, target.top, target.width(), target.height(), toColor); + return; + } + + float alpha = mAlpha; + + // Copy the input to avoid changing it. + mDrawTextureSourceRect.set(source); + mDrawTextureTargetRect.set(target); + source = mDrawTextureSourceRect; + target = mDrawTextureTargetRect; + + mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() + || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); + + if (!bindTexture(from)) return; + + // Interpolate the RGB and alpha values between both textures. + mGLState.setTexEnvMode(GL11.GL_COMBINE); + setMixedColor(toColor, ratio, alpha); + convertCoordinate(source, target, from); + setTextureCoords(source); + textureRect(target.left, target.top, target.width(), target.height()); + mGLState.setTexEnvMode(GL11.GL_REPLACE); + } + + private void drawMixed(BasicTexture from, int toColor, + float ratio, int x, int y, int width, int height, float alpha) { + // change from 0 to 0.01f to prevent getting divided by zero below + if (ratio <= 0.01f) { + drawTexture(from, x, y, width, height, alpha); + return; + } else if (ratio >= 1) { + fillRect(x, y, width, height, toColor); + return; + } + + mGLState.setBlendEnabled(mBlendEnabled && (!from.isOpaque() + || !Utils.isOpaque(toColor) || alpha < OPAQUE_ALPHA)); + + final GL11 gl = mGL; + if (!bindTexture(from)) return; + + // Interpolate the RGB and alpha values between both textures. + mGLState.setTexEnvMode(GL11.GL_COMBINE); + setMixedColor(toColor, ratio, alpha); + drawBoundTexture(from, x, y, width, height); mGLState.setTexEnvMode(GL11.GL_REPLACE); } diff --git a/src/com/android/gallery3d/ui/GLRoot.java b/src/com/android/gallery3d/ui/GLRoot.java index 1651b4361..13b610b23 100644 --- a/src/com/android/gallery3d/ui/GLRoot.java +++ b/src/com/android/gallery3d/ui/GLRoot.java @@ -16,6 +16,7 @@ package com.android.gallery3d.ui; +import android.content.Context; import android.graphics.Matrix; import com.android.gallery3d.anim.CanvasAnimation; @@ -45,4 +46,6 @@ public interface GLRoot { public void freeze(); public void unfreeze(); public void setLightsOutMode(boolean enabled); + + public Context getContext(); } diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java index 3924c6e9d..b91b71212 100644 --- a/src/com/android/gallery3d/ui/GLView.java +++ b/src/com/android/gallery3d/ui/GLView.java @@ -21,6 +21,7 @@ import android.os.SystemClock; import android.view.MotionEvent; import com.android.gallery3d.anim.CanvasAnimation; +import com.android.gallery3d.anim.StateTransitionAnimation; import com.android.gallery3d.common.Utils; import java.util.ArrayList; @@ -77,6 +78,11 @@ public class GLView { protected int mScrollHeight = 0; protected int mScrollWidth = 0; + public static final int ANIM_TIME_OPENING = 400; + private RawTexture mFadeOutTexture; + private float [] mBackgroundColor; + private StateTransitionAnimation mTransition = new StateTransitionAnimation(ANIM_TIME_OPENING); + public void startAnimation(CanvasAnimation animation) { GLRoot root = getGLRoot(); if (root == null) throw new IllegalStateException(); @@ -217,13 +223,46 @@ public class GLView { } protected void render(GLCanvas canvas) { + if (mTransition.calculate(AnimationTime.get())) invalidate(); + canvas.save(); renderBackground(canvas); + if (mTransition.isActive()) mTransition.applyForegroundTransformation(this, canvas); for (int i = 0, n = getComponentCount(); i < n; ++i) { renderChild(canvas, getComponent(i)); } + canvas.restore(); + } + + public void setFadeOutTexture(RawTexture texture) { + mFadeOutTexture = texture; + if (mFadeOutTexture != null) { + TiledScreenNail.disableDrawPlaceholder(); + } + mTransition.start(); + } + + public float [] getBackgroundColor() { + return mBackgroundColor; + } + + public void setBackgroundColor(float [] color) { + mBackgroundColor = color; } protected void renderBackground(GLCanvas view) { + if (mBackgroundColor != null) { + view.clearBuffer(mBackgroundColor); + } + if (mFadeOutTexture != null) { + if (!mTransition.isActive()) { + mFadeOutTexture.recycle(); + mFadeOutTexture = null; + TiledScreenNail.enableDrawPlaceholder(); + } else { + mTransition.applyBackground(this, view, mFadeOutTexture); + return; + } + } } protected void renderChild(GLCanvas canvas, GLView component) { diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index 5978159e4..edd7c72e3 100644 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -17,6 +17,7 @@ package com.android.gallery3d.ui; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Rect; @@ -546,10 +547,20 @@ public class PhotoView extends GLView { } private int getPanoramaRotation() { - // Panorama only support rotations of 0 and 90, so if it is greater - // than that flip the output surface texture to compensate - if (mDisplayRotation > 180) + // This function is magic + // The issue here is that Pano makes bad assumptions about rotation and + // orientation. The first is it assumes only two rotations are possible, + // 0 and 90. Thus, if display rotation is >= 180, we invert the output. + // The second is that it assumes landscape is a 90 rotation from portrait, + // however on landscape devices this is not true. Thus, if we are in portrait + // on a landscape device, we need to invert the output + int orientation = getGLRoot().getContext().getResources().getConfiguration().orientation; + boolean invertPortrait = (orientation == Configuration.ORIENTATION_PORTRAIT + && (mDisplayRotation == 90 || mDisplayRotation == 270)); + boolean invert = (mDisplayRotation >= 180); + if (invert != invertPortrait) { return (mCompensation + 180) % 360; + } return mCompensation; } @@ -839,8 +850,8 @@ public class PhotoView extends GLView { } private boolean isScreenNailAnimating() { - return (mScreenNail instanceof BitmapScreenNail) - && ((BitmapScreenNail) mScreenNail).isAnimating(); + return (mScreenNail instanceof TiledScreenNail) + && ((TiledScreenNail) mScreenNail).isAnimating(); } @Override @@ -1781,8 +1792,8 @@ public class PhotoView extends GLView { MediaItem item = mModel.getMediaItem(i); if (item == null) continue; ScreenNail sc = mModel.getScreenNail(i); - if (!(sc instanceof BitmapScreenNail) - || ((BitmapScreenNail) sc).isShowingPlaceholder()) continue; + if (!(sc instanceof TiledScreenNail) + || ((TiledScreenNail) sc).isShowingPlaceholder()) continue; // Now, sc is BitmapScreenNail and is not showing placeholder Rect rect = new Rect(getPhotoRect(i)); diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java index fffa7e038..0111847eb 100644 --- a/src/com/android/gallery3d/ui/PositionController.java +++ b/src/com/android/gallery3d/ui/PositionController.java @@ -67,7 +67,7 @@ class PositionController { SNAPBACK_ANIMATION_TIME, // ANIM_KIND_SNAPBACK 400, // ANIM_KIND_SLIDE 300, // ANIM_KIND_ZOOM - PhotoPage.ANIM_TIME_OPENING, // ANIM_KIND_OPENING + GLView.ANIM_TIME_OPENING, // ANIM_KIND_OPENING 0, // ANIM_KIND_FLING (the duration is calculated dynamically) 0, // ANIM_KIND_FLING_X (see the comment above) 0, // ANIM_KIND_DELETE (the duration is calculated dynamically) diff --git a/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java b/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java index 812e831e1..7cab753c3 100644 --- a/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java +++ b/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java @@ -6,7 +6,7 @@ import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; public class PreparePageFadeoutTexture implements OnGLIdleListener { - private static final long TIMEOUT = FadeTexture.DURATION; + private static final long TIMEOUT = 500; public static final String KEY_FADE_TEXTURE = "fade_texture"; private RawTexture mTexture; diff --git a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java index 1930e3877..7cb894845 100644 --- a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java +++ b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java @@ -109,6 +109,7 @@ public abstract class SurfaceTextureScreenNail implements ScreenNail, canvas.translate(cx, cy); canvas.scale(1, -1, 1); canvas.translate(-cx, -cy); + updateTransformMatrix(mTransform); canvas.drawTexture(mExtTexture, mTransform, x, y, width, height); canvas.restore(); } @@ -119,6 +120,8 @@ public abstract class SurfaceTextureScreenNail implements ScreenNail, throw new UnsupportedOperationException(); } + protected void updateTransformMatrix(float[] matrix) {} + @Override abstract public void noDraw(); diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java index 5ce06bec4..8f26981fe 100644 --- a/src/com/android/gallery3d/ui/TileImageView.java +++ b/src/com/android/gallery3d/ui/TileImageView.java @@ -462,8 +462,8 @@ public class TileImageView extends GLView { } private boolean isScreenNailAnimating() { - return (mScreenNail instanceof BitmapScreenNail) - && ((BitmapScreenNail) mScreenNail).isAnimating(); + return (mScreenNail instanceof TiledScreenNail) + && ((TiledScreenNail) mScreenNail).isAnimating(); } private void uploadBackgroundTiles(GLCanvas canvas) { @@ -575,8 +575,10 @@ public class TileImageView extends GLView { } if (tile == null) break; if (!tile.isContentValid()) { + boolean hasBeenLoaded = tile.isLoaded(); Utils.assertTrue(tile.mTileState == STATE_DECODED); tile.updateContent(canvas); + if (!hasBeenLoaded) tile.draw(canvas, 0, 0); --quota; } } @@ -621,7 +623,6 @@ public class TileImageView extends GLView { } } - // TODO: avoid drawing the unused part of the textures. static boolean drawTile( Tile tile, GLCanvas canvas, RectF source, RectF target) { while (true) { diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java index 08d337921..45e2ce218 100644 --- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java +++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java @@ -40,51 +40,25 @@ public class TileImageViewAdapter implements TileImageView.Model { public TileImageViewAdapter() { } - public TileImageViewAdapter( - Bitmap bitmap, BitmapRegionDecoder regionDecoder) { - Utils.checkNotNull(bitmap); - updateScreenNail(new BitmapScreenNail(bitmap), true); - mRegionDecoder = regionDecoder; - mImageWidth = regionDecoder.getWidth(); - mImageHeight = regionDecoder.getHeight(); - mLevelCount = calculateLevelCount(); - } - public synchronized void clear() { - updateScreenNail(null, false); + mScreenNail = null; mImageWidth = 0; mImageHeight = 0; mLevelCount = 0; mRegionDecoder = null; } - public synchronized void setScreenNail(Bitmap bitmap, int width, int height) { - Utils.checkNotNull(bitmap); - updateScreenNail(new BitmapScreenNail(bitmap), true); - mImageWidth = width; - mImageHeight = height; - mRegionDecoder = null; - mLevelCount = 0; - } - + // Caller is responsible to recycle the ScreenNail public synchronized void setScreenNail( ScreenNail screenNail, int width, int height) { Utils.checkNotNull(screenNail); - updateScreenNail(screenNail, false); + mScreenNail = screenNail; mImageWidth = width; mImageHeight = height; mRegionDecoder = null; mLevelCount = 0; } - private void updateScreenNail(ScreenNail screenNail, boolean own) { - if (mScreenNail != null && mOwnScreenNail) { - mScreenNail.recycle(); - } - mScreenNail = screenNail; - mOwnScreenNail = own; - } - public synchronized void setRegionDecoder(BitmapRegionDecoder decoder) { mRegionDecoder = Utils.checkNotNull(decoder); mImageWidth = decoder.getWidth(); diff --git a/src/com/android/gallery3d/ui/TiledScreenNail.java b/src/com/android/gallery3d/ui/TiledScreenNail.java new file mode 100644 index 000000000..d2b34e3bf --- /dev/null +++ b/src/com/android/gallery3d/ui/TiledScreenNail.java @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.ui; + +import android.graphics.Bitmap; +import android.graphics.RectF; + +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.data.BitmapPool; +import com.android.gallery3d.data.MediaItem; + +// This is a ScreenNail wraps a Bitmap. There are some extra functions: +// +// - If we need to draw before the bitmap is available, we draw a rectange of +// placeholder color (gray). +// +// - When the the bitmap is available, and we have drawn the placeholder color +// before, we will do a fade-in animation. +public class TiledScreenNail implements ScreenNail { + @SuppressWarnings("unused") + private static final String TAG = "TiledScreenNail"; + + // The duration of the fading animation in milliseconds + private static final int DURATION = 180; + + private static int sMaxSide = 640; + + // These are special values for mAnimationStartTime + private static final long ANIMATION_NOT_NEEDED = -1; + private static final long ANIMATION_NEEDED = -2; + private static final long ANIMATION_DONE = -3; + + private int mWidth; + private int mHeight; + private long mAnimationStartTime = ANIMATION_NOT_NEEDED; + + private Bitmap mBitmap; + private TiledTexture mTexture; + + public TiledScreenNail(Bitmap bitmap) { + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + mBitmap = bitmap; + mTexture = new TiledTexture(bitmap); + } + + public TiledScreenNail(int width, int height) { + setSize(width, height); + } + + // This gets overridden by bitmap_screennail_placeholder + // in GalleryUtils.initialize + private static int mPlaceholderColor = 0xFF222222; + private static boolean mDrawPlaceholder = true; + + public static void setPlaceholderColor(int color) { + mPlaceholderColor = color; + } + + private void setSize(int width, int height) { + if (width == 0 || height == 0) { + width = sMaxSide; + height = sMaxSide * 3 / 4; + } + float scale = Math.min(1, (float) sMaxSide / Math.max(width, height)); + mWidth = Math.round(scale * width); + mHeight = Math.round(scale * height); + } + + private static void recycleBitmap(BitmapPool pool, Bitmap bitmap) { + if (pool == null || bitmap == null) return; + pool.recycle(bitmap); + } + + // Combines the two ScreenNails. + // Returns the used one and recycle the unused one. + public ScreenNail combine(ScreenNail other) { + if (other == null) { + return this; + } + + if (!(other instanceof TiledScreenNail)) { + recycle(); + return other; + } + + // Now both are TiledScreenNail. Move over the information about width, + // height, and Bitmap, then recycle the other. + TiledScreenNail newer = (TiledScreenNail) other; + mWidth = newer.mWidth; + mHeight = newer.mHeight; + if (newer.mTexture != null) { + recycleBitmap(MediaItem.getThumbPool(), mBitmap); + if (mTexture != null) mTexture.recycle(); + mBitmap = newer.mBitmap; + mTexture = newer.mTexture; + newer.mBitmap = null; + newer.mTexture = null; + } + newer.recycle(); + return this; + } + + public void updatePlaceholderSize(int width, int height) { + if (mBitmap != null) return; + if (width == 0 || height == 0) return; + setSize(width, height); + } + + @Override + public int getWidth() { + return mWidth; + } + + @Override + public int getHeight() { + return mHeight; + } + + @Override + public void noDraw() { + } + + @Override + public void recycle() { + if (mTexture != null) { + mTexture.recycle(); + mTexture = null; + } + recycleBitmap(MediaItem.getThumbPool(), mBitmap); + mBitmap = null; + } + + public static void disableDrawPlaceholder() { + mDrawPlaceholder = false; + } + + public static void enableDrawPlaceholder() { + mDrawPlaceholder = true; + } + + @Override + public void draw(GLCanvas canvas, int x, int y, int width, int height) { + if (mTexture == null || !mTexture.isReady()) { + if (mAnimationStartTime == ANIMATION_NOT_NEEDED) { + mAnimationStartTime = ANIMATION_NEEDED; + } + if(mDrawPlaceholder) { + canvas.fillRect(x, y, width, height, mPlaceholderColor); + } + return; + } + + if (mAnimationStartTime == ANIMATION_NEEDED) { + mAnimationStartTime = AnimationTime.get(); + } + + if (isAnimating()) { + mTexture.drawMixed(canvas, mPlaceholderColor, getRatio(), x, y, + width, height); + } else { + mTexture.draw(canvas, x, y, width, height); + } + } + + @Override + public void draw(GLCanvas canvas, RectF source, RectF dest) { + if (mTexture == null || !mTexture.isReady()) { + canvas.fillRect(dest.left, dest.top, dest.width(), dest.height(), + mPlaceholderColor); + return; + } + + mTexture.draw(canvas, source, dest); + } + + public boolean isAnimating() { + if (mAnimationStartTime < 0) return false; + if (AnimationTime.get() - mAnimationStartTime >= DURATION) { + mAnimationStartTime = ANIMATION_DONE; + return false; + } + return true; + } + + private float getRatio() { + float r = (float) (AnimationTime.get() - mAnimationStartTime) / DURATION; + return Utils.clamp(1.0f - r, 0.0f, 1.0f); + } + + public boolean isShowingPlaceholder() { + return (mBitmap == null) || isAnimating(); + } + + public TiledTexture getTexture() { + return mTexture; + } + + public static void setMaxSide(int size) { + sMaxSide = size; + } +} diff --git a/src/com/android/gallery3d/ui/TiledTexture.java b/src/com/android/gallery3d/ui/TiledTexture.java new file mode 100644 index 000000000..6e9ad9ea8 --- /dev/null +++ b/src/com/android/gallery3d/ui/TiledTexture.java @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.ui; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffXfermode; +import android.graphics.RectF; + +import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; + +import java.util.ArrayDeque; +import java.util.ArrayList; + +// This class is similar to BitmapTexture, except the bitmap is +// split into tiles. By doing so, we may increase the time required to +// upload the whole bitmap but we reduce the time of uploading each tile +// so it make the animation more smooth and prevents jank. +public class TiledTexture { + private static final int CONTENT_SIZE = 254; + private static final int BORDER_SIZE = 1; + private static final int TILE_SIZE = CONTENT_SIZE + 2 * BORDER_SIZE; + private static final int INIT_CAPACITY = 8; + + private static Tile sFreeTileHead = null; + private static final Object sFreeTileLock = new Object(); + + private static Bitmap sUploadBitmap; + private static Canvas sCanvas; + private static Paint sPaint; + + private int mUploadIndex = 0; + + private final Tile[] mTiles; + private final int mWidth; + private final int mHeight; + private final RectF mSrcRect = new RectF(); + private final RectF mDestRect = new RectF(); + + public static class Uploader implements OnGLIdleListener { + private final ArrayDeque<TiledTexture> mTextures = + new ArrayDeque<TiledTexture>(INIT_CAPACITY); + + private final GLRoot mGlRoot; + private boolean mIsQueued = false; + + public Uploader(GLRoot glRoot) { + mGlRoot = glRoot; + } + + public synchronized void clear() { + mTextures.clear(); + } + + public synchronized void addTexture(TiledTexture t) { + if (t.isReady()) return; + mTextures.addLast(t); + + if (mIsQueued) return; + mIsQueued = true; + mGlRoot.addOnGLIdleListener(this); + } + + + @Override + public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { + ArrayDeque<TiledTexture> deque = mTextures; + synchronized (this) { + if (!deque.isEmpty()) { + TiledTexture t = deque.peekFirst(); + if (t.uploadNextTile(canvas)) { + deque.removeFirst(); + mGlRoot.requestRender(); + } + } + mIsQueued = !mTextures.isEmpty(); + + // return true to keep this listener in the queue + return mIsQueued; + } + } + } + + private static class Tile extends UploadedTexture { + public int offsetX; + public int offsetY; + public Bitmap bitmap; + public Tile nextFreeTile; + public int contentWidth; + public int contentHeight; + + @Override + public void setSize(int width, int height) { + contentWidth = width; + contentHeight = height; + mWidth = width + 2 * BORDER_SIZE; + mHeight = height + 2 * BORDER_SIZE; + mTextureWidth = TILE_SIZE; + mTextureHeight = TILE_SIZE; + } + + @Override + protected Bitmap onGetBitmap() { + int x = BORDER_SIZE - offsetX; + int y = BORDER_SIZE - offsetY; + int r = bitmap.getWidth() - x; + int b = bitmap.getHeight() - y ; + sCanvas.drawBitmap(bitmap, x, y, null); + bitmap = null; + + // draw borders if need + if (x > 0) sCanvas.drawLine(x - 1, 0, x - 1, TILE_SIZE, sPaint); + if (y > 0) sCanvas.drawLine(0, y - 1, TILE_SIZE, y - 1, sPaint); + if (r < CONTENT_SIZE) sCanvas.drawLine(r, 0, r, TILE_SIZE, sPaint); + if (b < CONTENT_SIZE) sCanvas.drawLine(0, b, TILE_SIZE, b, sPaint); + + return sUploadBitmap; + } + + @Override + protected void onFreeBitmap(Bitmap bitmap) { + // do nothing + } + } + + private static void freeTile(Tile tile) { + tile.invalidateContent(); + tile.bitmap = null; + synchronized (sFreeTileLock) { + tile.nextFreeTile = sFreeTileHead; + sFreeTileHead = tile; + } + } + + private static Tile obtainTile() { + synchronized (sFreeTileLock) { + Tile result = sFreeTileHead; + if (result == null) return new Tile(); + sFreeTileHead = result.nextFreeTile; + result.nextFreeTile = null; + return result; + } + } + + private boolean uploadNextTile(GLCanvas canvas) { + if (mUploadIndex == mTiles.length) return true; + Tile next = mTiles[mUploadIndex++]; + boolean hasBeenLoad = next.isLoaded(); + next.updateContent(canvas); + + // It will take some time for a texture to be drawn for the first + // time. When scrolling, we need to draw several tiles on the screen + // at the same time. It may cause a UI jank even these textures has + // been uploaded. + if (!hasBeenLoad) next.draw(canvas, 0, 0); + return mUploadIndex == mTiles.length; + } + + public TiledTexture(Bitmap bitmap) { + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + ArrayList<Tile> list = new ArrayList<Tile>(); + + for (int x = 0, w = mWidth; x < w; x += CONTENT_SIZE) { + for (int y = 0, h = mHeight; y < h; y += CONTENT_SIZE) { + Tile tile = obtainTile(); + tile.offsetX = x; + tile.offsetY = y; + tile.bitmap = bitmap; + tile.setSize( + Math.min(CONTENT_SIZE, mWidth - x), + Math.min(CONTENT_SIZE, mHeight - y)); + list.add(tile); + } + } + mTiles = list.toArray(new Tile[list.size()]); + } + + public boolean isReady() { + return mUploadIndex == mTiles.length; + } + + public void recycle() { + for (int i = 0, n = mTiles.length; i < n; ++i) { + freeTile(mTiles[i]); + } + } + + public static void freeResources() { + sUploadBitmap = null; + sCanvas = null; + sPaint = null; + } + + public static void prepareResources() { + sUploadBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Config.ARGB_8888); + sCanvas = new Canvas(sUploadBitmap); + sPaint = new Paint(Paint.FILTER_BITMAP_FLAG); + sPaint.setColor(Color.TRANSPARENT); + sPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); + } + + // We want to draw the "source" on the "target". + // This method is to find the "output" rectangle which is + // the corresponding area of the "src". + // (x,y) target + // (x0,y0) source +---------------+ + // +----------+ | | + // | src | | output | + // | +--+ | linear map | +----+ | + // | +--+ | ----------> | | | | + // | | by (scaleX, scaleY) | +----+ | + // +----------+ | | + // Texture +---------------+ + // Canvas + private static void mapRect(RectF output, + RectF src, float x0, float y0, float x, float y, float scaleX, + float scaleY) { + output.set(x + (src.left - x0) * scaleX, + y + (src.top - y0) * scaleY, + x + (src.right - x0) * scaleX, + y + (src.bottom - y0) * scaleY); + } + + // Draws a mixed color of this texture and a specified color onto the + // a rectangle. The used color is: from * (1 - ratio) + to * ratio. + public void drawMixed(GLCanvas canvas, int color, float ratio, + int x, int y, int width, int height) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float scaleX = (float) width / mWidth ; + float scaleY = (float) height / mHeight; + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawMixed(t, color, ratio, mSrcRect, mDestRect); + } + } + + // Draws the texture on to the specified rectangle. + public void draw(GLCanvas canvas, int x, int y, int width, int height) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float scaleX = (float) width / mWidth ; + float scaleY = (float) height / mHeight; + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + mapRect(dest, src, 0, 0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawTexture(t, mSrcRect, mDestRect); + } + } + + // Draws a sub region of this texture on to the specified rectangle. + public void draw(GLCanvas canvas, RectF source, RectF target) { + RectF src = mSrcRect; + RectF dest = mDestRect; + float x0 = source.left; + float y0 = source.top; + float x = target.left; + float y = target.top; + float scaleX = target.width() / source.width(); + float scaleY = target.height() / source.height(); + + for (int i = 0, n = mTiles.length; i < n; ++i) { + Tile t = mTiles[i]; + src.set(0, 0, t.contentWidth, t.contentHeight); + src.offset(t.offsetX, t.offsetY); + if (!src.intersect(source)) continue; + mapRect(dest, src, x0, y0, x, y, scaleX, scaleY); + src.offset(BORDER_SIZE - t.offsetX, BORDER_SIZE - t.offsetY); + canvas.drawTexture(t, src, dest); + } + } +} |