Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cb4fb7c1 authored by Chih-Chung Chang's avatar Chih-Chung Chang
Browse files

New effect in photo browsing.

Change-Id: I12ca70f56f5c874292a7a6f38f0518bbc855ed70
parent 3910e0e7
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -362,8 +362,9 @@ public class PhotoDataAdapter implements PhotoPage.Model {
        return mTileProvider.getLevelCount();
    }

    public Bitmap getTile(int level, int x, int y, int tileSize) {
        return mTileProvider.getTile(level, x, y, tileSize);
    public Bitmap getTile(int level, int x, int y, int tileSize,
            int borderSize) {
        return mTileProvider.getTile(level, x, y, tileSize, borderSize);
    }

    public boolean isFailedToLoad() {
+21 −4
Original line number Diff line number Diff line
@@ -65,12 +65,29 @@ public class BitmapTileProvider implements TileImageView.Model {
        return mMipmaps.length;
    }

    public Bitmap getTile(int level, int x, int y, int tileSize) {
        Bitmap result = Bitmap.createBitmap(tileSize, tileSize, mConfig);
    public Bitmap getTile(int level, int x, int y, int tileSize,
            int borderSize) {
        x >>= level;
        y >>= level;
        int size = tileSize + 2 * borderSize;
        Bitmap result = Bitmap.createBitmap(size, size, mConfig);
        Bitmap mipmap = mMipmaps[level];
        Canvas canvas = new Canvas(result);
        canvas.drawBitmap(mMipmaps[level], -(x >> level), -(y >> level), null);
        int offsetX = -x + borderSize;
        int offsetY = -y + borderSize;
        canvas.drawBitmap(mipmap, offsetX, offsetY, null);

        // If the valid region (covered by mipmap or border) is smaller than the
        // result bitmap, subset it.
        int endX = offsetX + mipmap.getWidth() + borderSize;
        int endY = offsetY + mipmap.getHeight() + borderSize;
        if (endX < size || endY < size) {
            return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX),
                    Math.min(size, endY));
        } else {
            return result;
        }
    }

    public void recycle() {
        if (mRecycled) return;
+164 −29
Original line number Diff line number Diff line
@@ -19,13 +19,16 @@ package com.android.gallery3d.ui;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Message;
import android.view.MotionEvent;
import android.view.animation.AccelerateInterpolator;

import com.android.gallery3d.R;
import com.android.gallery3d.app.GalleryActivity;
import com.android.gallery3d.common.Utils;

public class PhotoView extends GLView {
    @SuppressWarnings("unused")
@@ -59,6 +62,14 @@ public class PhotoView extends GLView {
    private static final float SWIPE_THRESHOLD = 300f;

    private static final float DEFAULT_TEXT_SIZE = 20;
    private static float TRANSITION_SCALE_FACTOR = 0.74f;

    // Used to calculate the scaling factor for the fading animation.
    private ZInterpolator mScaleInterpolator = new ZInterpolator(0.5f);

    // Used to calculate the alpha factor for the fading animation.
    private AccelerateInterpolator mAlphaInterpolator =
            new AccelerateInterpolator(0.9f);

    public interface PhotoTapListener {
        public void onSingleTapUp(int x, int y);
@@ -91,6 +102,7 @@ public class PhotoView extends GLView {
    private int mImageRotation;

    private Rect mOpenAnimationRect;
    private Point mImageCenter = new Point();

    public PhotoView(GalleryActivity activity) {
        mTileView = new TileImageView(activity);
@@ -153,25 +165,50 @@ public class PhotoView extends GLView {
        mPhotoTapListener = listener;
    }

    private boolean setTileViewPosition(int centerX, int centerY, float scale) {
    private void setTileViewPosition(int centerX, int centerY, float scale) {
        TileImageView t = mTileView;

        // Calculate the move-out progress value.
        RectF bounds = mPositionController.getImageBounds();
        int left = Math.round(bounds.left);
        int right = Math.round(bounds.right);
        int width = getWidth();
        float progress = calculateMoveOutProgress(left, right, width);
        progress = Utils.clamp(progress, -1f, 1f);

        // We only want to apply the fading animation if the scrolling movement
        // is to the right.
        if (progress < 0) {
            if (right - left < width) {
                // If the picture is narrower than the view, keep it at the center
                // of the view.
                centerX = mPositionController.getImageWidth() / 2;
            } else {
                // If the picture is wider than the view (it's zoomed-in), keep
                // the left edge of the object align the the left edge of the view.
                centerX = Math.round(width / 2f / scale);
            }
            scale *= getScrollScale(progress);
            t.setAlpha(getScrollAlpha(progress));
        }

        // set the position of the tile view
        int inverseX = mPositionController.getImageWidth() - centerX;
        int inverseY = mPositionController.getImageHeight() - centerY;
        TileImageView t = mTileView;
        int rotation = mImageRotation;
        switch (rotation) {
            case 0: return t.setPosition(centerX, centerY, scale, 0);
            case 90: return t.setPosition(centerY, inverseX, scale, 90);
            case 180: return t.setPosition(inverseX, inverseY, scale, 180);
            case 270: return t.setPosition(inverseY, centerX, scale, 270);
            case 0: t.setPosition(centerX, centerY, scale, 0); break;
            case 90: t.setPosition(centerY, inverseX, scale, 90); break;
            case 180: t.setPosition(inverseX, inverseY, scale, 180); break;
            case 270: t.setPosition(inverseY, centerX, scale, 270); break;
            default: throw new IllegalArgumentException(String.valueOf(rotation));
        }
    }

    public void setPosition(int centerX, int centerY, float scale) {
        if (setTileViewPosition(centerX, centerY, scale)) {
        setTileViewPosition(centerX, centerY, scale);
        layoutScreenNails();
    }
    }

    private void updateScreenNailEntry(int which, ScreenNail screenNail) {
        if (mTransitionMode == TRANS_SWITCH_NEXT
@@ -202,6 +239,7 @@ public class PhotoView extends GLView {
            case 0: {
                // mImageWidth and mImageHeight will get updated
                mTileView.notifyModelInvalidated();
                mTileView.setAlpha(1.0f);

                mImageRotation = mModel.getImageRotation();
                if (((mImageRotation / 90) & 1) == 0) {
@@ -251,6 +289,7 @@ public class PhotoView extends GLView {

        if (mModel == null) {
            mTileView.notifyModelInvalidated();
            mTileView.setAlpha(1.0f);
            mImageRotation = 0;
            mPositionController.setImageSize(0, 0);
            updateLoadingState();
@@ -325,22 +364,38 @@ public class PhotoView extends GLView {

    @Override
    protected void render(GLCanvas canvas) {
        boolean drawScreenNail = (mTransitionMode != TRANS_SLIDE_IN_LEFT
                && mTransitionMode != TRANS_SLIDE_IN_RIGHT
                && mTransitionMode != TRANS_OPEN_ANIMATION);

        // Draw the next photo
        if (drawScreenNail) {
            ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT];
            nextNail.draw(canvas, true);
        }

        // Draw the current photo
        if (mLoadingState == LOADING_COMPLETE) {
            super.render(canvas);
        }

        // Draw the previous and the next photo
        if (mTransitionMode != TRANS_SLIDE_IN_LEFT
                && mTransitionMode != TRANS_SLIDE_IN_RIGHT
                && mTransitionMode != TRANS_OPEN_ANIMATION) {
            ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS];
            ScreenNailEntry nextNail = mScreenNails[ENTRY_NEXT];
        // If the photo is loaded, draw the message/icon at the center of it,
        // otherwise draw the message/icon at the center of the view.
        if (mLoadingState == LOADING_COMPLETE) {
            mTileView.getImageCenter(mImageCenter);
            renderMessage(canvas, mImageCenter.x, mImageCenter.y);
        } else {
            renderMessage(canvas, getWidth() / 2, getHeight() / 2);
        }

            prevNail.draw(canvas);
            nextNail.draw(canvas);
        // Draw the previous photo
        if (drawScreenNail) {
            ScreenNailEntry prevNail = mScreenNails[ENTRY_PREVIOUS];
            prevNail.draw(canvas, false);
        }
    }

    private void renderMessage(GLCanvas canvas, int x, int y) {
        // Draw the progress spinner and the text below it
        //
        // (x, y) is where we put the center of the spinner.
@@ -349,8 +404,6 @@ public class PhotoView extends GLView {
        // play icon is shown instead of the spinner.
        int w = getWidth();
        int h = getHeight();
        int x = Math.round(mPositionController.getImageBounds().centerX());
        int y = h / 2;
        int s = Math.min(getWidth(), getHeight()) / 6;

        if (mLoadingState == LOADING_TIMEOUT) {
@@ -688,30 +741,112 @@ public class PhotoView extends GLView {
            return mEnabled;
        }

        public void draw(GLCanvas canvas) {
        public void draw(GLCanvas canvas, boolean applyFadingAnimation) {
            if (mScreenNail == null) return;
            if (!mVisible) {
                mScreenNail.disableDraw();
                return;
            }

            int x = mOffsetX;
            int w = getWidth();
            int x = applyFadingAnimation ? w / 2 : mOffsetX;
            int y = getHeight() / 2;
            int flags = GLCanvas.SAVE_FLAG_MATRIX;

            if (mRotation != 0) {
                canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
            if (applyFadingAnimation) flags |= GLCanvas.SAVE_FLAG_ALPHA;
            canvas.save(flags);
            canvas.translate(x, y);
            if (applyFadingAnimation) {
                float progress = (float) (x - mOffsetX) / w;
                float alpha = getScrollAlpha(progress);
                float scale = getScrollScale(progress);
                canvas.multiplyAlpha(alpha);
                canvas.scale(scale, scale, 1);
            }
            if (mRotation != 0) {
                canvas.rotate(mRotation, 0, 0, 1);
                canvas.translate(-x, -y);
            }

            canvas.translate(-x, -y);
            mScreenNail.draw(canvas, x - mDrawWidth / 2, y - mDrawHeight / 2,
                    mDrawWidth, mDrawHeight);

            if (mRotation != 0) {
            canvas.restore();
        }
    }

    // Returns the scrolling progress value for an object moving out of a
    // view. The progress value measures how much the object has moving out of
    // the view. The object currently displays in [left, right), and the view is
    // at [0, viewWidth].
    //
    // The returned value is negative when the object is moving right, and
    // positive when the object is moving left. The value goes to -1 or 1 when
    // the object just moves out of the view completely. The value is 0 if the
    // object currently fills the view.
    private static float calculateMoveOutProgress(int left, int right,
            int viewWidth) {
        // w = object width
        // viewWidth = view width
        int w = right - left;

        // If the object width is smaller than the view width,
        //      |....view....|
        //                   |<-->|      progress = -1 when left = viewWidth
        // |<-->|                        progress = 1 when left = -w
        // So progress = 1 - 2 * (left + w) / (viewWidth + w)
        if (w < viewWidth) {
            return 1f - 2f * (left + w) / (viewWidth + w);
        }

        // If the object width is larger than the view width,
        //             |..view..|
        //                      |<--------->| progress = -1 when left = viewWidth
        //             |<--------->|          progress = 0 between left = 0
        //          |<--------->|                          and right = viewWidth
        // |<--------->|                      progress = 1 when right = 0
        if (left > 0) {
            return -left / (float) viewWidth;
        }

        if (right < viewWidth) {
            return (viewWidth - right) / (float) viewWidth;
        }

        return 0;
    }

    // Maps a scrolling progress value to the alpha factor in the fading
    // animation.
    private float getScrollAlpha(float scrollProgress) {
        return scrollProgress < 0 ? mAlphaInterpolator.getInterpolation(
                     1 - Math.abs(scrollProgress)) : 1.0f;
    }

    // Maps a scrolling progress value to the scaling factor in the fading
    // animation.
    private float getScrollScale(float scrollProgress) {
        float interpolatedProgress = mScaleInterpolator.getInterpolation(
                Math.abs(scrollProgress));
        float scale = (1 - interpolatedProgress) +
                interpolatedProgress * TRANSITION_SCALE_FACTOR;
        return scale;
    }


    // This interpolator emulates the rate at which the perceived scale of an
    // object changes as its distance from a camera increases. When this
    // interpolator is applied to a scale animation on a view, it evokes the
    // sense that the object is shrinking due to moving away from the camera.
    private static class ZInterpolator {
        private float focalLength;

        public ZInterpolator(float foc) {
            focalLength = foc;
        }

        public float getInterpolation(float input) {
            return (1.0f - focalLength / (focalLength + input)) /
                (1.0f - focalLength / (focalLength + 1.0f));
        }
    }

    public void pause() {
+76 −13
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.gallery3d.ui;

import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.FloatMath;
@@ -107,6 +108,7 @@ public class TileImageView extends GLView {
    protected int mCenterY;
    protected float mScale;
    protected int mRotation;
    protected float mAlpha = 1.0f;

    // Temp variables to avoid memory allocation
    private final Rect mTileRange = new Rect();
@@ -124,8 +126,20 @@ public class TileImageView extends GLView {
        public int getImageWidth();
        public int getImageHeight();

        // The method would be called in another thread
        public Bitmap getTile(int level, int x, int y, int tileSize);
        // 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). Then
        // extend this intersection region by borderSize pixels on each side. If
        // in extending the region, we found some part of the region are 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 in another thread.
        public Bitmap getTile(int level, int x, int y, int tileSize,
                int borderSize);
        public boolean isFailedToLoad();
    }

@@ -300,6 +314,30 @@ public class TileImageView extends GLView {
        out.set(left, top, right, bottom);
    }

    // Calculate where the center of the image is, in the view coordinates.
    public void getImageCenter(Point center) {
        // The width and height of this view.
        int viewW = getWidth();
        int viewH = getHeight();

        // The distance between the center of the view to the center of the
        // bitmap, in bitmap units. (mCenterX and mCenterY are the bitmap
        // coordinates correspond to the center of view)
        int distW, distH;
        if (mRotation % 180 == 0) {
            distW = mImageWidth / 2 - mCenterX;
            distH = mImageHeight / 2 - mCenterY;
        } else {
            distW = mImageHeight / 2 - mCenterY;
            distH = mImageWidth / 2 - mCenterX;
        }

        // Convert to view coordinates. mScale translates from bitmap units to
        // view units.
        center.x = Math.round(viewW / 2f + distW * mScale);
        center.y = Math.round(viewH / 2f + distH * mScale);
    }

    public boolean setPosition(int centerX, int centerY, float scale, int rotation) {
        if (mCenterX == centerX
                && mCenterY == centerY && mScale == scale) return false;
@@ -312,6 +350,13 @@ public class TileImageView extends GLView {
        return true;
    }

    public boolean setAlpha(float alpha) {
        if (mAlpha == alpha) return false;
        mAlpha = alpha;
        invalidate();
        return true;
    }

    public void freeTextures() {
        mIsTextureFreed = true;

@@ -359,14 +404,20 @@ public class TileImageView extends GLView {

        int level = mLevel;
        int rotation = mRotation;
        int flags = 0;
        if (rotation != 0) flags |= GLCanvas.SAVE_FLAG_MATRIX;
        if (mAlpha != 1.0f) flags |= GLCanvas.SAVE_FLAG_ALPHA;

        if (flags != 0) {
            canvas.save(flags);
            if (rotation != 0) {
            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
                int centerX = getWidth() / 2, centerY = getHeight() / 2;
                canvas.translate(centerX, centerY);
                canvas.rotate(rotation, 0, 0, 1);
                canvas.translate(-centerX, -centerY);
            }
            if (mAlpha != 1.0f) canvas.multiplyAlpha(mAlpha);
        }
        try {
            if (level != mLevelCount) {
                if (mScreenNail != null) {
@@ -390,7 +441,7 @@ public class TileImageView extends GLView {
                        Math.round(mImageHeight * mScale));
            }
        } finally {
            if (rotation != 0) canvas.restore();
            if (flags != 0) canvas.restore();
        }

        if (mRenderComplete) {
@@ -600,11 +651,9 @@ public class TileImageView extends GLView {
        boolean decode() {
            // Get a tile from the original image. The tile is down-scaled
            // by (1 << mTilelevel) from a region in the original image.
            int tileLength = (TILE_SIZE + 2 * TILE_BORDER);
            int borderLength = TILE_BORDER << mTileLevel;
            try {
                mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile(
                        mTileLevel, mX - borderLength, mY - borderLength, tileLength));
                        mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER));
            } catch (Throwable t) {
                Log.w(TAG, "fail to decode tile", t);
            }
@@ -620,6 +669,20 @@ public class TileImageView extends GLView {
            return bitmap;
        }

        // We override getTextureWidth() and getTextureHeight() here, so the
        // texture can be re-used for different tiles regardless of the actual
        // size of the tile (which may be small because it is a tile at the
        // boundary).
        @Override
        public int getTextureWidth() {
            return TILE_SIZE + TILE_BORDER * 2;
        }

        @Override
        public int getTextureHeight() {
            return TILE_SIZE + TILE_BORDER * 2;
        }

        public void update(int x, int y, int level) {
            mX = x;
            mY = y;
+38 −21
Original line number Diff line number Diff line
@@ -34,9 +34,6 @@ public class TileImageViewAdapter implements TileImageView.Model {
    protected int mLevelCount;
    protected boolean mFailedToLoad;

    private final Rect mIntersectRect = new Rect();
    private final Rect mRegionRect = new Rect();

    public TileImageViewAdapter() {
    }

@@ -90,16 +87,24 @@ public class TileImageViewAdapter implements TileImageView.Model {
    }

    @Override
    public synchronized Bitmap getTile(int level, int x, int y, int length) {
    public synchronized Bitmap getTile(int level, int x, int y, int tileSize,
            int borderSize) {
        if (mRegionDecoder == null) return null;

        Rect region = mRegionRect;
        Rect intersectRect = mIntersectRect;
        region.set(x, y, x + (length << level), y + (length << level));
        intersectRect.set(0, 0, mImageWidth, mImageHeight);
        // wantRegion is the rectangle on the original image we want. askRegion
        // is the rectangle on the original image that we will ask from
        // mRegionDecoder. Both are in the coordinates of the original image,
        // not the coordinates of the scaled-down images.
        Rect wantRegion = new Rect();
        Rect askRegion = new Rect();

        int b = borderSize << level;
        wantRegion.set(x - b, y - b, x + (tileSize << level) + b,
                y + (tileSize << level) + b);

        // Get the intersected rect of the requested region and the image.
        Utils.assertTrue(intersectRect.intersect(region));
        // askRegion is the intersection of wantRegion and the original image.
        askRegion.set(0, 0, mImageWidth, mImageHeight);
        Utils.assertTrue(askRegion.intersect(wantRegion));

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Config.ARGB_8888;
@@ -110,25 +115,37 @@ public class TileImageViewAdapter implements TileImageView.Model {

        // In CropImage, we may call the decodeRegion() concurrently.
        synchronized (mRegionDecoder) {
            bitmap = mRegionDecoder.decodeRegion(intersectRect, options);
            bitmap = mRegionDecoder.decodeRegion(askRegion, options);
        }

        // The returned region may not match with the targetLength.
        // If so, we fill black pixels on it.
        if (intersectRect.equals(region)) return bitmap;

        if (bitmap == null) {
            Log.w(TAG, "fail in decoding region");
            return null;
        }

        Bitmap tile = Bitmap.createBitmap(length, length, Config.ARGB_8888);
        Canvas canvas = new Canvas(tile);
        canvas.drawBitmap(bitmap,
                (intersectRect.left - region.left) >> level,
                (intersectRect.top - region.top) >> level, null);
        if (wantRegion.equals(askRegion)) return bitmap;

        // Now the wantRegion does not match the askRegion. This means we are at
        // a boundary tile, and we need to add paddings. Create a new Bitmap
        // and copy over.
        int size = tileSize + 2 * borderSize;
        Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        int offsetX = (askRegion.left - wantRegion.left) >> level;
        int offsetY = (askRegion.top - wantRegion.top) >> level;
        canvas.drawBitmap(bitmap, offsetX, offsetY, null);

        // If the valid region (covered by bitmap or border) is smaller than the
        // result bitmap, subset it.
        int endX = offsetX + bitmap.getWidth() + borderSize;
        int endY = offsetY + bitmap.getHeight() + borderSize;
        bitmap.recycle();
        return tile;
        if (endX < size || endY < size) {
            return Bitmap.createBitmap(result, 0, 0, Math.min(size, endX),
                    Math.min(size, endY));
        } else {
            return result;
        }
    }

    @Override
Loading