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;
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;
import java.io.File;
import java.util.ArrayList;
-import java.util.Collection;
public class ImageShow extends View implements OnGestureListener,
ScaleGestureDetector.OnScaleGestureListener,
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,
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() {
@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) {
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) {
+ (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()
}
}
}
- 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());
}
}
- 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();
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);
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
}
}
- 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;
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;
}
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);
}
}
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();
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;
}