OSDN Git Service

Revert "Move Gallery2 to androidx."
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / filtershow / imageshow / ImageShow.java
index 77eaf4d..2022ffd 100644 (file)
@@ -16,6 +16,8 @@
 
 package com.android.gallery3d.filtershow.imageshow;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -27,9 +29,12 @@ import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Shader;
 import android.graphics.drawable.NinePatchDrawable;
+import android.support.v4.widget.EdgeEffectCompat;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.GestureDetector;
 import android.view.GestureDetector.OnDoubleTapListener;
 import android.view.GestureDetector.OnGestureListener;
@@ -48,7 +53,6 @@ import com.android.gallery3d.filtershow.tools.SaveImage;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collection;
 
 public class ImageShow extends View implements OnGestureListener,
         ScaleGestureDetector.OnScaleGestureListener,
@@ -91,6 +95,21 @@ public class ImageShow extends View implements OnGestureListener,
     Point mOriginalTranslation = new Point();
     float mOriginalScale;
     float mStartFocusX, mStartFocusY;
+
+    private EdgeEffectCompat mEdgeEffect = null;
+    private static final int EDGE_LEFT = 1;
+    private static final int EDGE_TOP = 2;
+    private static final int EDGE_RIGHT = 3;
+    private static final int EDGE_BOTTOM = 4;
+    private int mCurrentEdgeEffect = 0;
+    private int mEdgeSize = 100;
+
+    private static final int mAnimationSnapDelay = 200;
+    private static final int mAnimationZoomDelay = 400;
+    private ValueAnimator mAnimatorScale = null;
+    private ValueAnimator mAnimatorTranslateX = null;
+    private ValueAnimator mAnimatorTranslateY = null;
+
     private enum InteractionMode {
         NONE,
         SCALE,
@@ -163,11 +182,14 @@ public class ImageShow extends View implements OnGestureListener,
             Bitmap mask = BitmapFactory.decodeResource(res, R.drawable.spot_mask);
             sMask = convertToAlphaMask(mask);
         }
+        mEdgeEffect = new EdgeEffectCompat(context);
+        mEdgeSize = res.getDimensionPixelSize(R.dimen.edge_glow_size);
     }
 
     public void attach() {
         MasterImage.getImage().addObserver(this);
         bindAsImageLoadListener();
+        MasterImage.getImage().resetGeometryImages(false);
     }
 
     public void detach() {
@@ -231,55 +253,84 @@ public class ImageShow extends View implements OnGestureListener,
 
     @Override
     public void onDraw(Canvas canvas) {
+        mPaint.reset();
+        mPaint.setAntiAlias(true);
+        mPaint.setFilterBitmap(true);
         MasterImage.getImage().setImageShowSize(
                 getWidth() - 2*mShadowMargin,
                 getHeight() - 2*mShadowMargin);
 
-        float cx = canvas.getWidth()/2.0f;
-        float cy = canvas.getHeight()/2.0f;
-        float scaleFactor = MasterImage.getImage().getScaleFactor();
-        Point translation = MasterImage.getImage().getTranslation();
+        MasterImage img = MasterImage.getImage();
+        // Hide the loading indicator as needed
+        if (mActivity.isLoadingVisible() && getFilteredImage() != null) {
+            if ((img.getLoadedPreset() == null)
+                    || (img.getLoadedPreset() != null
+                    && img.getLoadedPreset().equals(img.getCurrentPreset()))) {
+                mActivity.stopLoadingIndicator();
+            } else if (img.getLoadedPreset() != null) {
+                return;
+            }
+            mActivity.stopLoadingIndicator();
+        }
 
         canvas.save();
 
         mShadowDrawn = false;
 
-        canvas.save();
-        // TODO: center scale on gesture
-        canvas.scale(scaleFactor, scaleFactor, cx, cy);
-        canvas.translate(translation.x, translation.y);
         Bitmap highresPreview = MasterImage.getImage().getHighresImage();
+        Bitmap fullHighres = MasterImage.getImage().getPartialImage();
 
         boolean isDoingNewLookAnimation = MasterImage.getImage().onGoingNewLookAnimation();
 
-        if (!isDoingNewLookAnimation && highresPreview != null) {
-            drawImage(canvas, highresPreview, true);
+        if (highresPreview == null || isDoingNewLookAnimation) {
+            drawImageAndAnimate(canvas, getFilteredImage());
         } else {
-            drawImage(canvas, getFilteredImage(), true);
+            drawImageAndAnimate(canvas, highresPreview);
         }
+
+        drawHighresImage(canvas, fullHighres);
+        drawCompareImage(canvas, getGeometryOnlyImage());
+
         canvas.restore();
 
-        Bitmap partialPreview = MasterImage.getImage().getPartialImage();
-        if (!isDoingNewLookAnimation && partialPreview != null) {
+        if (!mEdgeEffect.isFinished()) {
             canvas.save();
-            Rect originalBounds = MasterImage.getImage().getOriginalBounds();
-            Collection<FilterRepresentation> geo = MasterImage.getImage().getPreset()
-                    .getGeometryFilters();
-
-            Matrix compensation = GeometryMathUtils.getPartialToScreenMatrix(geo,
-                    originalBounds, getWidth(), getHeight(),
-                    partialPreview.getWidth(), partialPreview.getHeight());
-            canvas.drawBitmap(partialPreview, compensation, null);
+            float dx = (getHeight() - getWidth()) / 2f;
+            if (getWidth() > getHeight()) {
+                dx = - (getWidth() - getHeight()) / 2f;
+            }
+            if (mCurrentEdgeEffect == EDGE_BOTTOM) {
+                canvas.rotate(180, getWidth()/2, getHeight()/2);
+            } else if (mCurrentEdgeEffect == EDGE_RIGHT) {
+                canvas.rotate(90, getWidth()/2, getHeight()/2);
+                canvas.translate(0, dx);
+            } else if (mCurrentEdgeEffect == EDGE_LEFT) {
+                canvas.rotate(270, getWidth()/2, getHeight()/2);
+                canvas.translate(0, dx);
+            }
+            if (mCurrentEdgeEffect != 0) {
+                mEdgeEffect.draw(canvas);
+            }
             canvas.restore();
+            invalidate();
+        } else {
+            mCurrentEdgeEffect = 0;
         }
+    }
 
-        canvas.save();
-        canvas.scale(scaleFactor, scaleFactor, cx, cy);
-        canvas.translate(translation.x, translation.y);
-        drawPartialImage(canvas, getGeometryOnlyImage());
-        canvas.restore();
-
-        canvas.restore();
+    private void drawHighresImage(Canvas canvas, Bitmap fullHighres) {
+        Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
+        if (fullHighres != null && originalToScreen != null) {
+            Matrix screenToOriginal = new Matrix();
+            originalToScreen.invert(screenToOriginal);
+            Rect rBounds = new Rect();
+            rBounds.set(MasterImage.getImage().getPartialBounds());
+            if (fullHighres != null) {
+                originalToScreen.preTranslate(rBounds.left, rBounds.top);
+                canvas.clipRect(mImageBounds);
+                canvas.drawBitmap(fullHighres, originalToScreen, mPaint);
+            }
+        }
     }
 
     public void resetImageCaches(ImageShow caller) {
@@ -298,52 +349,83 @@ public class ImageShow extends View implements OnGestureListener,
         return MasterImage.getImage().getFilteredImage();
     }
 
-    public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) {
+    public void drawImageAndAnimate(Canvas canvas,
+                                    Bitmap image) {
         if (image == null) {
             return;
         }
+        MasterImage master = MasterImage.getImage();
+        Matrix m = master.computeImageToScreen(image, 0, false);
+        if (m == null) {
+            return;
+        }
 
-        Rect d = computeImageBounds(image.getWidth(), image.getHeight());
+        canvas.save();
 
-        if (updateBounds) {
-            mImageBounds = d;
+        RectF d = new RectF(0, 0, image.getWidth(), image.getHeight());
+        m.mapRect(d);
+        d.roundOut(mImageBounds);
+
+        boolean showAnimatedImage = master.onGoingNewLookAnimation();
+        if (!showAnimatedImage && mDidStartAnimation) {
+            // animation ended, but do we have the correct image to show?
+            if (master.getPreset().equals(master.getCurrentPreset())) {
+                // we do, let's stop showing the animated image
+                mDidStartAnimation = false;
+                MasterImage.getImage().resetAnimBitmap();
+            } else {
+                showAnimatedImage = true;
+            }
+        } else if (showAnimatedImage) {
+            mDidStartAnimation = true;
         }
 
-        float centerX = mShadowMargin + (getWidth() - 2 * mShadowMargin) / 2;
-        float centerY = mShadowMargin + (getHeight() - 2 * mShadowMargin) / 2;
+        if (showAnimatedImage) {
+            canvas.save();
+
+            // Animation uses the image before the change
+            Bitmap previousImage = master.getPreviousImage();
+            Matrix mp = master.computeImageToScreen(previousImage, 0, false);
+            RectF dp = new RectF(0, 0, previousImage.getWidth(), previousImage.getHeight());
+            mp.mapRect(dp);
+            Rect previousBounds = new Rect();
+            dp.roundOut(previousBounds);
+            float centerX = dp.centerX();
+            float centerY = dp.centerY();
+            boolean needsToDrawImage = true;
 
-        MasterImage master = MasterImage.getImage();
-        canvas.save();
-        if (master.onGoingNewLookAnimation()
-                || mDidStartAnimation) {
-            mDidStartAnimation = true;
             if (master.getCurrentLookAnimation()
-                    == MasterImage.CIRCLE_ANIMATION
-                    && MasterImage.getImage().getPreviousImage() != null) {
+                    == MasterImage.CIRCLE_ANIMATION) {
                 float maskScale = MasterImage.getImage().getMaskScale();
-                if (maskScale > 0.0f) {
+                if (maskScale >= 0.0f) {
                     float maskW = sMask.getWidth() / 2.0f;
                     float maskH = sMask.getHeight() / 2.0f;
-                    float x = centerX - maskW * maskScale;
-                    float y = centerY - maskH * maskScale;
+                    Point point = mActivity.hintTouchPoint(this);
+                    float maxMaskScale = 2 * Math.max(getWidth(), getHeight())
+                            / Math.min(maskW, maskH);
+                    maskScale = maskScale * maxMaskScale;
+                    float x = point.x - maskW * maskScale;
+                    float y = point.y - maskH * maskScale;
 
                     // Prepare the shader
                     mShaderMatrix.reset();
                     mShaderMatrix.setScale(1.0f / maskScale, 1.0f / maskScale);
-                    mShaderMatrix.preTranslate(-x + d.left, -y + d.top);
-                    float scaleImageX = d.width() / (float) image.getWidth();
-                    float scaleImageY = d.height() / (float) image.getHeight();
+                    mShaderMatrix.preTranslate(-x + mImageBounds.left, -y + mImageBounds.top);
+                    float scaleImageX = mImageBounds.width() / (float) image.getWidth();
+                    float scaleImageY = mImageBounds.height() / (float) image.getHeight();
                     mShaderMatrix.preScale(scaleImageX, scaleImageY);
                     mMaskPaint.reset();
-                    mMaskPaint.setShader(createShader(image));
-                    mMaskPaint.getShader().setLocalMatrix(mShaderMatrix);
+                    Shader maskShader = createShader(image);
+                    maskShader.setLocalMatrix(mShaderMatrix);
+                    mMaskPaint.setShader(maskShader);
 
-                    drawImage(canvas, MasterImage.getImage().getPreviousImage());
+                    drawShadow(canvas, mImageBounds); // as needed
+                    canvas.drawBitmap(previousImage, m, mPaint);
+                    canvas.clipRect(mImageBounds);
                     canvas.translate(x, y);
                     canvas.scale(maskScale, maskScale);
                     canvas.drawBitmap(sMask, 0, 0, mMaskPaint);
-                } else {
-                    drawImage(canvas, image);
+                    needsToDrawImage = false;
                 }
             } else if (master.getCurrentLookAnimation()
                     == MasterImage.ROTATE_ANIMATION) {
@@ -356,7 +438,6 @@ public class ImageShow extends View implements OnGestureListener,
                         + (finalScale * master.getAnimFraction());
                 canvas.rotate(master.getAnimRotationValue(), centerX, centerY);
                 canvas.scale(finalScale, finalScale, centerX, centerY);
-                drawImage(canvas, master.getPreviousImage());
             } else if (master.getCurrentLookAnimation()
                     == MasterImage.MIRROR_ANIMATION) {
                 if (master.getCurrentFilterRepresentation()
@@ -392,33 +473,22 @@ public class ImageShow extends View implements OnGestureListener,
                         }
                     }
                 }
-                drawImage(canvas, master.getPreviousImage());
             }
+
+            if (needsToDrawImage) {
+                drawShadow(canvas, previousBounds); // as needed
+                canvas.drawBitmap(previousImage, mp, mPaint);
+            }
+
+            canvas.restore();
         } else {
-            drawImage(canvas, image);
+            drawShadow(canvas, mImageBounds); // as needed
+            canvas.drawBitmap(image, m, mPaint);
         }
 
-        if (!master.onGoingNewLookAnimation()
-                && mDidStartAnimation
-                && !master.getPreviousPreset().equals(master.getCurrentPreset())) {
-            mDidStartAnimation = false;
-            MasterImage.getImage().resetAnimBitmap();
-        }
         canvas.restore();
     }
 
-    private void drawImage(Canvas canvas, Bitmap image) {
-        Rect d = computeImageBounds(image.getWidth(), image.getHeight());
-        float scaleImageX = d.width() / (float) image.getWidth();
-        float scaleImageY = d.height() / (float) image.getHeight();
-        Matrix imageMatrix = new Matrix();
-        imageMatrix.postScale(scaleImageX, scaleImageY);
-        imageMatrix.postTranslate(d.left, d.top);
-        drawShadow(canvas, d);
-        canvas.clipRect(d);
-        canvas.drawBitmap(image, imageMatrix, mPaint);
-    }
-
     private Rect computeImageBounds(int imageWidth, int imageHeight) {
         float scale = GeometryMathUtils.scale(imageWidth, imageHeight,
                 getWidth(), getHeight());
@@ -443,8 +513,9 @@ public class ImageShow extends View implements OnGestureListener,
         }
     }
 
-    public void drawPartialImage(Canvas canvas, Bitmap image) {
-        boolean showsOriginal = MasterImage.getImage().showsOriginal();
+    public void drawCompareImage(Canvas canvas, Bitmap image) {
+        MasterImage master = MasterImage.getImage();
+        boolean showsOriginal = master.showsOriginal();
         if (!showsOriginal && !mTouchShowOriginal)
             return;
         canvas.save();
@@ -472,8 +543,20 @@ public class ImageShow extends View implements OnGestureListener,
 
             Rect d = new Rect(mImageBounds.left, mImageBounds.top,
                     mImageBounds.left + px, mImageBounds.top + py);
+            if (mShowOriginalDirection == UNVEIL_HORIZONTAL) {
+                if (mTouchDown.x - mTouch.x > 0) {
+                    d.set(mImageBounds.left + px, mImageBounds.top,
+                            mImageBounds.right, mImageBounds.top + py);
+                }
+            } else {
+                if (mTouchDown.y - mTouch.y > 0) {
+                    d.set(mImageBounds.left, mImageBounds.top + py,
+                            mImageBounds.left + px, mImageBounds.bottom);
+                }
+            }
             canvas.clipRect(d);
-            drawImage(canvas, image, false);
+            Matrix m = master.computeImageToScreen(image, 0, false);
+            canvas.drawBitmap(image, m, mPaint);
             Paint paint = new Paint();
             paint.setColor(Color.BLACK);
             paint.setStrokeWidth(3);
@@ -566,7 +649,6 @@ public class ImageShow extends View implements OnGestureListener,
                 Point translation = MasterImage.getImage().getTranslation();
                 translation.x = (int) (originalTranslation.x + translateX);
                 translation.y = (int) (originalTranslation.y + translateY);
-                constrainTranslation(translation, scaleFactor);
                 MasterImage.getImage().setTranslation(translation);
                 mTouchShowOriginal = false;
             } else if (enableComparison() && !mOriginalDisabled
@@ -577,7 +659,9 @@ public class ImageShow extends View implements OnGestureListener,
             }
         }
 
-        if (action == MotionEvent.ACTION_UP) {
+        if (action == MotionEvent.ACTION_UP
+                || action == MotionEvent.ACTION_CANCEL
+                || action == MotionEvent.ACTION_OUTSIDE) {
             mInteractionMode = InteractionMode.NONE;
             mTouchShowOriginal = false;
             mTouchDown.x = 0;
@@ -589,10 +673,67 @@ public class ImageShow extends View implements OnGestureListener,
                 MasterImage.getImage().resetTranslation();
             }
         }
+
+        float scaleFactor = MasterImage.getImage().getScaleFactor();
+        Point translation = MasterImage.getImage().getTranslation();
+        constrainTranslation(translation, scaleFactor);
+        MasterImage.getImage().setTranslation(translation);
+
         invalidate();
         return true;
     }
 
+    private void startAnimTranslation(int fromX, int toX,
+                                      int fromY, int toY, int delay) {
+        if (fromX == toX && fromY == toY) {
+            return;
+        }
+        if (mAnimatorTranslateX != null) {
+            mAnimatorTranslateX.cancel();
+        }
+        if (mAnimatorTranslateY != null) {
+            mAnimatorTranslateY.cancel();
+        }
+        mAnimatorTranslateX = ValueAnimator.ofInt(fromX, toX);
+        mAnimatorTranslateY = ValueAnimator.ofInt(fromY, toY);
+        mAnimatorTranslateX.setDuration(delay);
+        mAnimatorTranslateY.setDuration(delay);
+        mAnimatorTranslateX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                Point translation = MasterImage.getImage().getTranslation();
+                translation.x = (Integer) animation.getAnimatedValue();
+                MasterImage.getImage().setTranslation(translation);
+                invalidate();
+            }
+        });
+        mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                Point translation = MasterImage.getImage().getTranslation();
+                translation.y = (Integer) animation.getAnimatedValue();
+                MasterImage.getImage().setTranslation(translation);
+                invalidate();
+            }
+        });
+        mAnimatorTranslateX.start();
+        mAnimatorTranslateY.start();
+    }
+
+    private void applyTranslationConstraints() {
+        float scaleFactor = MasterImage.getImage().getScaleFactor();
+        Point translation = MasterImage.getImage().getTranslation();
+        int x = translation.x;
+        int y = translation.y;
+        constrainTranslation(translation, scaleFactor);
+
+        if (x != translation.x || y != translation.y) {
+            startAnimTranslation(x, translation.x,
+                                 y, translation.y,
+                                 mAnimationSnapDelay);
+        }
+    }
+
     protected boolean enableComparison() {
         return true;
     }
@@ -601,34 +742,130 @@ public class ImageShow extends View implements OnGestureListener,
     public boolean onDoubleTap(MotionEvent arg0) {
         mZoomIn = !mZoomIn;
         float scale = 1.0f;
+        final float x = arg0.getX();
+        final float y = arg0.getY();
         if (mZoomIn) {
             scale = MasterImage.getImage().getMaxScaleFactor();
         }
         if (scale != MasterImage.getImage().getScaleFactor()) {
-            MasterImage.getImage().setScaleFactor(scale);
-            float translateX = (getWidth() / 2 - arg0.getX());
-            float translateY = (getHeight() / 2 - arg0.getY());
+            if (mAnimatorScale != null) {
+                mAnimatorScale.cancel();
+            }
+            mAnimatorScale = ValueAnimator.ofFloat(
+                    MasterImage.getImage().getScaleFactor(),
+                    scale
+            );
+            float translateX = (getWidth() / 2 - x);
+            float translateY = (getHeight() / 2 - y);
             Point translation = MasterImage.getImage().getTranslation();
-            translation.x = (int) (mOriginalTranslation.x + translateX);
-            translation.y = (int) (mOriginalTranslation.y + translateY);
+            int startTranslateX = translation.x;
+            int startTranslateY = translation.y;
+            if (scale != 1.0f) {
+                translation.x = (int) (mOriginalTranslation.x + translateX);
+                translation.y = (int) (mOriginalTranslation.y + translateY);
+            } else {
+                translation.x = 0;
+                translation.y = 0;
+            }
             constrainTranslation(translation, scale);
-            MasterImage.getImage().setTranslation(translation);
-            invalidate();
+
+            startAnimTranslation(startTranslateX, translation.x,
+                                 startTranslateY, translation.y,
+                                 mAnimationZoomDelay);
+            mAnimatorScale.setDuration(mAnimationZoomDelay);
+            mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    MasterImage.getImage().setScaleFactor((Float) animation.getAnimatedValue());
+                    invalidate();
+                }
+            });
+            mAnimatorScale.addListener(new Animator.AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    applyTranslationConstraints();
+                    MasterImage.getImage().needsUpdatePartialPreview();
+                    invalidate();
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animation) {
+
+                }
+            });
+            mAnimatorScale.start();
         }
         return true;
     }
 
     private void constrainTranslation(Point translation, float scale) {
-        float maxTranslationX = getWidth() / scale;
-        float maxTranslationY = getHeight() / scale;
-        if (Math.abs(translation.x) > maxTranslationX) {
-            translation.x = (int) (Math.signum(translation.x) *
-                    maxTranslationX);
-            if (Math.abs(translation.y) > maxTranslationY) {
-                translation.y = (int) (Math.signum(translation.y) *
-                        maxTranslationY);
+        int currentEdgeEffect = 0;
+        if (scale <= 1) {
+            mCurrentEdgeEffect = 0;
+            mEdgeEffect.finish();
+            return;
+        }
+
+        Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
+        Rect originalBounds = MasterImage.getImage().getOriginalBounds();
+        RectF screenPos = new RectF(originalBounds);
+        originalToScreen.mapRect(screenPos);
+
+        boolean rightConstraint = screenPos.right < getWidth() - mShadowMargin;
+        boolean leftConstraint = screenPos.left > mShadowMargin;
+        boolean topConstraint = screenPos.top > mShadowMargin;
+        boolean bottomConstraint = screenPos.bottom < getHeight() - mShadowMargin;
+
+        if (screenPos.width() > getWidth()) {
+            if (rightConstraint && !leftConstraint) {
+                float tx = screenPos.right - translation.x * scale;
+                translation.x = (int) ((getWidth() - mShadowMargin - tx) / scale);
+                currentEdgeEffect = EDGE_RIGHT;
+            } else if (leftConstraint && !rightConstraint) {
+                float tx = screenPos.left - translation.x * scale;
+                translation.x = (int) ((mShadowMargin - tx) / scale);
+                currentEdgeEffect = EDGE_LEFT;
+            }
+        } else {
+            float tx = screenPos.right - translation.x * scale;
+            float dx = (getWidth() - 2 * mShadowMargin - screenPos.width()) / 2f;
+            translation.x = (int) ((getWidth() - mShadowMargin - tx - dx) / scale);
+        }
+
+        if (screenPos.height() > getHeight()) {
+            if (bottomConstraint && !topConstraint) {
+                float ty = screenPos.bottom - translation.y * scale;
+                translation.y = (int) ((getHeight() - mShadowMargin - ty) / scale);
+                currentEdgeEffect = EDGE_BOTTOM;
+            } else if (topConstraint && !bottomConstraint) {
+                float ty = screenPos.top - translation.y * scale;
+                translation.y = (int) ((mShadowMargin - ty) / scale);
+                currentEdgeEffect = EDGE_TOP;
             }
+        } else {
+            float ty = screenPos.bottom - translation.y * scale;
+            float dy = (getHeight()- 2 * mShadowMargin - screenPos.height()) / 2f;
+            translation.y = (int) ((getHeight() - mShadowMargin - ty - dy) / scale);
+        }
 
+        if (mCurrentEdgeEffect != currentEdgeEffect) {
+            if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) {
+                mCurrentEdgeEffect = currentEdgeEffect;
+                mEdgeEffect.finish();
+            }
+            mEdgeEffect.setSize(getWidth(), mEdgeSize);
+        }
+        if (currentEdgeEffect != 0) {
+            mEdgeEffect.onPull(mEdgeSize);
         }
     }
 
@@ -692,8 +929,8 @@ public class ImageShow extends View implements OnGestureListener,
         if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
             scaleFactor = MasterImage.getImage().getMaxScaleFactor();
         }
-        if (scaleFactor < 0.5) {
-            scaleFactor = 0.5f;
+        if (scaleFactor < 1.0f) {
+            scaleFactor = 1.0f;
         }
         MasterImage.getImage().setScaleFactor(scaleFactor);
         scaleFactor = img.getScaleFactor();
@@ -704,9 +941,7 @@ public class ImageShow extends View implements OnGestureListener,
         Point translation = MasterImage.getImage().getTranslation();
         translation.x = (int) (mOriginalTranslation.x + translateX);
         translation.y = (int) (mOriginalTranslation.y + translateY);
-        constrainTranslation(translation, scaleFactor);
         MasterImage.getImage().setTranslation(translation);
-
         invalidate();
         return true;
     }