2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.gallery3d.filtershow.imageshow;
19 import android.animation.Animator;
20 import android.animation.ValueAnimator;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.BitmapShader;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.Matrix;
29 import android.graphics.Paint;
30 import android.graphics.Point;
31 import android.graphics.Rect;
32 import android.graphics.RectF;
33 import android.graphics.Shader;
34 import android.graphics.drawable.NinePatchDrawable;
35 import androidx.core.widget.EdgeEffectCompat;
36 import android.util.AttributeSet;
37 import android.util.Log;
38 import android.view.GestureDetector;
39 import android.view.GestureDetector.OnDoubleTapListener;
40 import android.view.GestureDetector.OnGestureListener;
41 import android.view.MotionEvent;
42 import android.view.ScaleGestureDetector;
43 import android.view.View;
44 import android.widget.LinearLayout;
46 import com.android.gallery3d.R;
47 import com.android.gallery3d.filtershow.FilterShowActivity;
48 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
49 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
50 import com.android.gallery3d.filtershow.filters.ImageFilter;
51 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
52 import com.android.gallery3d.filtershow.tools.SaveImage;
55 import java.util.ArrayList;
57 public class ImageShow extends View implements OnGestureListener,
58 ScaleGestureDetector.OnScaleGestureListener,
61 private static final String LOGTAG = "ImageShow";
62 private static final boolean ENABLE_ZOOMED_COMPARISON = false;
64 protected Paint mPaint = new Paint();
65 protected int mTextSize;
66 protected int mTextPadding;
68 protected int mBackgroundColor;
70 private GestureDetector mGestureDetector = null;
71 private ScaleGestureDetector mScaleGestureDetector = null;
73 protected Rect mImageBounds = new Rect();
74 private boolean mOriginalDisabled = false;
75 private boolean mTouchShowOriginal = false;
76 private long mTouchShowOriginalDate = 0;
77 private final long mTouchShowOriginalDelayMin = 200; // 200ms
78 private int mShowOriginalDirection = 0;
79 private static int UNVEIL_HORIZONTAL = 1;
80 private static int UNVEIL_VERTICAL = 2;
82 private NinePatchDrawable mShadow = null;
83 private Rect mShadowBounds = new Rect();
84 private int mShadowMargin = 15; // not scaled, fixed in the asset
85 private boolean mShadowDrawn = false;
87 private Point mTouchDown = new Point();
88 private Point mTouch = new Point();
89 private boolean mFinishedScalingOperation = false;
91 private int mOriginalTextMargin;
92 private int mOriginalTextSize;
93 private String mOriginalText;
94 private boolean mZoomIn = false;
95 Point mOriginalTranslation = new Point();
97 float mStartFocusX, mStartFocusY;
99 private EdgeEffectCompat mEdgeEffect = null;
100 private static final int EDGE_LEFT = 1;
101 private static final int EDGE_TOP = 2;
102 private static final int EDGE_RIGHT = 3;
103 private static final int EDGE_BOTTOM = 4;
104 private int mCurrentEdgeEffect = 0;
105 private int mEdgeSize = 100;
107 private static final int mAnimationSnapDelay = 200;
108 private static final int mAnimationZoomDelay = 400;
109 private ValueAnimator mAnimatorScale = null;
110 private ValueAnimator mAnimatorTranslateX = null;
111 private ValueAnimator mAnimatorTranslateY = null;
113 private enum InteractionMode {
118 InteractionMode mInteractionMode = InteractionMode.NONE;
120 private static Bitmap sMask;
121 private Paint mMaskPaint = new Paint();
122 private Matrix mShaderMatrix = new Matrix();
123 private boolean mDidStartAnimation = false;
125 private static Bitmap convertToAlphaMask(Bitmap b) {
126 Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8);
127 Canvas c = new Canvas(a);
128 c.drawBitmap(b, 0.0f, 0.0f, null);
132 private static Shader createShader(Bitmap b) {
133 return new BitmapShader(b, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
136 private FilterShowActivity mActivity = null;
138 public FilterShowActivity getActivity() {
142 public boolean hasModifications() {
143 return MasterImage.getImage().hasModifications();
146 public void resetParameter() {
147 // TODO: implement reset
150 public void onNewValue(int parameter) {
154 public ImageShow(Context context, AttributeSet attrs, int defStyle) {
155 super(context, attrs, defStyle);
156 setupImageShow(context);
159 public ImageShow(Context context, AttributeSet attrs) {
160 super(context, attrs);
161 setupImageShow(context);
165 public ImageShow(Context context) {
167 setupImageShow(context);
170 private void setupImageShow(Context context) {
171 Resources res = context.getResources();
172 mTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_text_size);
173 mTextPadding = res.getDimensionPixelSize(R.dimen.photoeditor_text_padding);
174 mOriginalTextMargin = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_margin);
175 mOriginalTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_size);
176 mBackgroundColor = res.getColor(R.color.background_screen);
177 mOriginalText = res.getString(R.string.original_picture_text);
178 mShadow = (NinePatchDrawable) res.getDrawable(R.drawable.geometry_shadow);
179 setupGestureDetector(context);
180 mActivity = (FilterShowActivity) context;
182 Bitmap mask = BitmapFactory.decodeResource(res, R.drawable.spot_mask);
183 sMask = convertToAlphaMask(mask);
185 mEdgeEffect = new EdgeEffectCompat(context);
186 mEdgeSize = res.getDimensionPixelSize(R.dimen.edge_glow_size);
189 public void attach() {
190 MasterImage.getImage().addObserver(this);
191 bindAsImageLoadListener();
192 MasterImage.getImage().resetGeometryImages(false);
195 public void detach() {
196 MasterImage.getImage().removeObserver(this);
200 public void setupGestureDetector(Context context) {
201 mGestureDetector = new GestureDetector(context, this);
202 mScaleGestureDetector = new ScaleGestureDetector(context, this);
206 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
207 int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
208 int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
209 setMeasuredDimension(parentWidth, parentHeight);
212 public ImageFilter getCurrentFilter() {
213 return MasterImage.getImage().getCurrentFilter();
216 /* consider moving the following 2 methods into a subclass */
218 * This function calculates a Image to Screen Transformation matrix
220 * @param reflectRotation set true if you want the rotation encoded
221 * @return Image to Screen transformation matrix
223 protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
224 MasterImage master = MasterImage.getImage();
225 if (master.getOriginalBounds() == null) {
228 Matrix m = GeometryMathUtils.getImageToScreenMatrix(master.getPreset().getGeometryFilters(),
229 reflectRotation, master.getOriginalBounds(), getWidth(), getHeight());
230 Point translate = master.getTranslation();
231 float scaleFactor = master.getScaleFactor();
232 m.postTranslate(translate.x, translate.y);
233 m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
238 * This function calculates a to Screen Image Transformation matrix
240 * @param reflectRotation set true if you want the rotation encoded
241 * @return Screen to Image transformation matrix
243 protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
244 Matrix m = getImageToScreenMatrix(reflectRotation);
245 Matrix invert = new Matrix();
250 public ImagePreset getImagePreset() {
251 return MasterImage.getImage().getPreset();
255 public void onDraw(Canvas canvas) {
257 mPaint.setAntiAlias(true);
258 mPaint.setFilterBitmap(true);
259 MasterImage.getImage().setImageShowSize(
260 getWidth() - 2*mShadowMargin,
261 getHeight() - 2*mShadowMargin);
263 MasterImage img = MasterImage.getImage();
264 // Hide the loading indicator as needed
265 if (mActivity.isLoadingVisible() && getFilteredImage() != null) {
266 if ((img.getLoadedPreset() == null)
267 || (img.getLoadedPreset() != null
268 && img.getLoadedPreset().equals(img.getCurrentPreset()))) {
269 mActivity.stopLoadingIndicator();
270 } else if (img.getLoadedPreset() != null) {
273 mActivity.stopLoadingIndicator();
278 mShadowDrawn = false;
280 Bitmap highresPreview = MasterImage.getImage().getHighresImage();
281 Bitmap fullHighres = MasterImage.getImage().getPartialImage();
283 boolean isDoingNewLookAnimation = MasterImage.getImage().onGoingNewLookAnimation();
285 if (highresPreview == null || isDoingNewLookAnimation) {
286 drawImageAndAnimate(canvas, getFilteredImage());
288 drawImageAndAnimate(canvas, highresPreview);
291 drawHighresImage(canvas, fullHighres);
292 drawCompareImage(canvas, getGeometryOnlyImage());
296 if (!mEdgeEffect.isFinished()) {
298 float dx = (getHeight() - getWidth()) / 2f;
299 if (getWidth() > getHeight()) {
300 dx = - (getWidth() - getHeight()) / 2f;
302 if (mCurrentEdgeEffect == EDGE_BOTTOM) {
303 canvas.rotate(180, getWidth()/2, getHeight()/2);
304 } else if (mCurrentEdgeEffect == EDGE_RIGHT) {
305 canvas.rotate(90, getWidth()/2, getHeight()/2);
306 canvas.translate(0, dx);
307 } else if (mCurrentEdgeEffect == EDGE_LEFT) {
308 canvas.rotate(270, getWidth()/2, getHeight()/2);
309 canvas.translate(0, dx);
311 if (mCurrentEdgeEffect != 0) {
312 mEdgeEffect.draw(canvas);
317 mCurrentEdgeEffect = 0;
321 private void drawHighresImage(Canvas canvas, Bitmap fullHighres) {
322 Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
323 if (fullHighres != null && originalToScreen != null) {
324 Matrix screenToOriginal = new Matrix();
325 originalToScreen.invert(screenToOriginal);
326 Rect rBounds = new Rect();
327 rBounds.set(MasterImage.getImage().getPartialBounds());
328 if (fullHighres != null) {
329 originalToScreen.preTranslate(rBounds.left, rBounds.top);
330 canvas.clipRect(mImageBounds);
331 canvas.drawBitmap(fullHighres, originalToScreen, mPaint);
336 public void resetImageCaches(ImageShow caller) {
337 MasterImage.getImage().invalidatePreview();
340 public Bitmap getFiltersOnlyImage() {
341 return MasterImage.getImage().getFiltersOnlyImage();
344 public Bitmap getGeometryOnlyImage() {
345 return MasterImage.getImage().getGeometryOnlyImage();
348 public Bitmap getFilteredImage() {
349 return MasterImage.getImage().getFilteredImage();
352 public void drawImageAndAnimate(Canvas canvas,
357 MasterImage master = MasterImage.getImage();
358 Matrix m = master.computeImageToScreen(image, 0, false);
365 RectF d = new RectF(0, 0, image.getWidth(), image.getHeight());
367 d.roundOut(mImageBounds);
369 boolean showAnimatedImage = master.onGoingNewLookAnimation();
370 if (!showAnimatedImage && mDidStartAnimation) {
371 // animation ended, but do we have the correct image to show?
372 if (master.getPreset().equals(master.getCurrentPreset())) {
373 // we do, let's stop showing the animated image
374 mDidStartAnimation = false;
375 MasterImage.getImage().resetAnimBitmap();
377 showAnimatedImage = true;
379 } else if (showAnimatedImage) {
380 mDidStartAnimation = true;
383 if (showAnimatedImage) {
386 // Animation uses the image before the change
387 Bitmap previousImage = master.getPreviousImage();
388 Matrix mp = master.computeImageToScreen(previousImage, 0, false);
389 RectF dp = new RectF(0, 0, previousImage.getWidth(), previousImage.getHeight());
391 Rect previousBounds = new Rect();
392 dp.roundOut(previousBounds);
393 float centerX = dp.centerX();
394 float centerY = dp.centerY();
395 boolean needsToDrawImage = true;
397 if (master.getCurrentLookAnimation()
398 == MasterImage.CIRCLE_ANIMATION) {
399 float maskScale = MasterImage.getImage().getMaskScale();
400 if (maskScale >= 0.0f) {
401 float maskW = sMask.getWidth() / 2.0f;
402 float maskH = sMask.getHeight() / 2.0f;
403 Point point = mActivity.hintTouchPoint(this);
404 float maxMaskScale = 2 * Math.max(getWidth(), getHeight())
405 / Math.min(maskW, maskH);
406 maskScale = maskScale * maxMaskScale;
407 float x = point.x - maskW * maskScale;
408 float y = point.y - maskH * maskScale;
410 // Prepare the shader
411 mShaderMatrix.reset();
412 mShaderMatrix.setScale(1.0f / maskScale, 1.0f / maskScale);
413 mShaderMatrix.preTranslate(-x + mImageBounds.left, -y + mImageBounds.top);
414 float scaleImageX = mImageBounds.width() / (float) image.getWidth();
415 float scaleImageY = mImageBounds.height() / (float) image.getHeight();
416 mShaderMatrix.preScale(scaleImageX, scaleImageY);
418 Shader maskShader = createShader(image);
419 maskShader.setLocalMatrix(mShaderMatrix);
420 mMaskPaint.setShader(maskShader);
422 drawShadow(canvas, mImageBounds); // as needed
423 canvas.drawBitmap(previousImage, m, mPaint);
424 canvas.clipRect(mImageBounds);
425 canvas.translate(x, y);
426 canvas.scale(maskScale, maskScale);
427 canvas.drawBitmap(sMask, 0, 0, mMaskPaint);
428 needsToDrawImage = false;
430 } else if (master.getCurrentLookAnimation()
431 == MasterImage.ROTATE_ANIMATION) {
432 Rect d1 = computeImageBounds(master.getPreviousImage().getHeight(),
433 master.getPreviousImage().getWidth());
434 Rect d2 = computeImageBounds(master.getPreviousImage().getWidth(),
435 master.getPreviousImage().getHeight());
436 float finalScale = d1.width() / (float) d2.height();
437 finalScale = (1.0f * (1.0f - master.getAnimFraction()))
438 + (finalScale * master.getAnimFraction());
439 canvas.rotate(master.getAnimRotationValue(), centerX, centerY);
440 canvas.scale(finalScale, finalScale, centerX, centerY);
441 } else if (master.getCurrentLookAnimation()
442 == MasterImage.MIRROR_ANIMATION) {
443 if (master.getCurrentFilterRepresentation()
444 instanceof FilterMirrorRepresentation) {
445 FilterMirrorRepresentation rep =
446 (FilterMirrorRepresentation) master.getCurrentFilterRepresentation();
448 ImagePreset preset = master.getPreset();
449 ArrayList<FilterRepresentation> geometry =
450 (ArrayList<FilterRepresentation>) preset.getGeometryFilters();
451 GeometryMathUtils.GeometryHolder holder = null;
452 holder = GeometryMathUtils.unpackGeometry(geometry);
454 if (holder.rotation.value() == 90 || holder.rotation.value() == 270) {
455 if (rep.isHorizontal() && !rep.isVertical()) {
456 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
457 } else if (rep.isVertical() && !rep.isHorizontal()) {
458 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
459 } else if (rep.isHorizontal() && rep.isVertical()) {
460 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
462 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
465 if (rep.isHorizontal() && !rep.isVertical()) {
466 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
467 } else if (rep.isVertical() && !rep.isHorizontal()) {
468 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
469 } else if (rep.isHorizontal() && rep.isVertical()) {
470 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
472 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
478 if (needsToDrawImage) {
479 drawShadow(canvas, previousBounds); // as needed
480 canvas.drawBitmap(previousImage, mp, mPaint);
485 drawShadow(canvas, mImageBounds); // as needed
486 canvas.drawBitmap(image, m, mPaint);
492 private Rect computeImageBounds(int imageWidth, int imageHeight) {
493 float scale = GeometryMathUtils.scale(imageWidth, imageHeight,
494 getWidth(), getHeight());
496 float w = imageWidth * scale;
497 float h = imageHeight * scale;
498 float ty = (getHeight() - h) / 2.0f;
499 float tx = (getWidth() - w) / 2.0f;
500 return new Rect((int) tx + mShadowMargin,
501 (int) ty + mShadowMargin,
502 (int) (w + tx) - mShadowMargin,
503 (int) (h + ty) - mShadowMargin);
506 private void drawShadow(Canvas canvas, Rect d) {
508 mShadowBounds.set(d.left - mShadowMargin, d.top - mShadowMargin,
509 d.right + mShadowMargin, d.bottom + mShadowMargin);
510 mShadow.setBounds(mShadowBounds);
511 mShadow.draw(canvas);
516 public void drawCompareImage(Canvas canvas, Bitmap image) {
517 MasterImage master = MasterImage.getImage();
518 boolean showsOriginal = master.showsOriginal();
519 if (!showsOriginal && !mTouchShowOriginal)
523 if (mShowOriginalDirection == 0) {
524 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
525 mShowOriginalDirection = UNVEIL_VERTICAL;
527 mShowOriginalDirection = UNVEIL_HORIZONTAL;
533 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
534 px = mImageBounds.width();
535 py = mTouch.y - mImageBounds.top;
537 px = mTouch.x - mImageBounds.left;
538 py = mImageBounds.height();
540 px = mImageBounds.width();
544 Rect d = new Rect(mImageBounds.left, mImageBounds.top,
545 mImageBounds.left + px, mImageBounds.top + py);
546 if (mShowOriginalDirection == UNVEIL_HORIZONTAL) {
547 if (mTouchDown.x - mTouch.x > 0) {
548 d.set(mImageBounds.left + px, mImageBounds.top,
549 mImageBounds.right, mImageBounds.top + py);
552 if (mTouchDown.y - mTouch.y > 0) {
553 d.set(mImageBounds.left, mImageBounds.top + py,
554 mImageBounds.left + px, mImageBounds.bottom);
558 Matrix m = master.computeImageToScreen(image, 0, false);
559 canvas.drawBitmap(image, m, mPaint);
560 Paint paint = new Paint();
561 paint.setColor(Color.BLACK);
562 paint.setStrokeWidth(3);
564 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
565 canvas.drawLine(mImageBounds.left, mTouch.y,
566 mImageBounds.right, mTouch.y, paint);
568 canvas.drawLine(mTouch.x, mImageBounds.top,
569 mTouch.x, mImageBounds.bottom, paint);
572 Rect bounds = new Rect();
573 paint.setAntiAlias(true);
574 paint.setTextSize(mOriginalTextSize);
575 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
576 paint.setColor(Color.BLACK);
577 paint.setStyle(Paint.Style.STROKE);
578 paint.setStrokeWidth(3);
579 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
580 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
581 paint.setStyle(Paint.Style.FILL);
582 paint.setStrokeWidth(1);
583 paint.setColor(Color.WHITE);
584 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
585 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
590 public void bindAsImageLoadListener() {
591 MasterImage.getImage().addListener(this);
594 public void updateImage() {
598 public void imageLoaded() {
602 public void saveImage(FilterShowActivity filterShowActivity, File file) {
603 SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
607 public boolean scaleInProgress() {
608 return mScaleGestureDetector.isInProgress();
612 public boolean onTouchEvent(MotionEvent event) {
613 super.onTouchEvent(event);
614 int action = event.getAction();
615 action = action & MotionEvent.ACTION_MASK;
617 mGestureDetector.onTouchEvent(event);
618 boolean scaleInProgress = scaleInProgress();
619 mScaleGestureDetector.onTouchEvent(event);
620 if (mInteractionMode == InteractionMode.SCALE) {
623 if (!scaleInProgress() && scaleInProgress) {
624 // If we were scaling, the scale will stop but we will
625 // still issue an ACTION_UP. Let the subclasses know.
626 mFinishedScalingOperation = true;
629 int ex = (int) event.getX();
630 int ey = (int) event.getY();
631 if (action == MotionEvent.ACTION_DOWN) {
632 mInteractionMode = InteractionMode.MOVE;
635 mTouchShowOriginalDate = System.currentTimeMillis();
636 mShowOriginalDirection = 0;
637 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
640 if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
644 float scaleFactor = MasterImage.getImage().getScaleFactor();
645 if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
646 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
647 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
648 Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
649 Point translation = MasterImage.getImage().getTranslation();
650 translation.x = (int) (originalTranslation.x + translateX);
651 translation.y = (int) (originalTranslation.y + translateY);
652 MasterImage.getImage().setTranslation(translation);
653 mTouchShowOriginal = false;
654 } else if (enableComparison() && !mOriginalDisabled
655 && (System.currentTimeMillis() - mTouchShowOriginalDate
656 > mTouchShowOriginalDelayMin)
657 && event.getPointerCount() == 1) {
658 mTouchShowOriginal = true;
662 if (action == MotionEvent.ACTION_UP
663 || action == MotionEvent.ACTION_CANCEL
664 || action == MotionEvent.ACTION_OUTSIDE) {
665 mInteractionMode = InteractionMode.NONE;
666 mTouchShowOriginal = false;
671 if (MasterImage.getImage().getScaleFactor() <= 1) {
672 MasterImage.getImage().setScaleFactor(1);
673 MasterImage.getImage().resetTranslation();
677 float scaleFactor = MasterImage.getImage().getScaleFactor();
678 Point translation = MasterImage.getImage().getTranslation();
679 constrainTranslation(translation, scaleFactor);
680 MasterImage.getImage().setTranslation(translation);
686 private void startAnimTranslation(int fromX, int toX,
687 int fromY, int toY, int delay) {
688 if (fromX == toX && fromY == toY) {
691 if (mAnimatorTranslateX != null) {
692 mAnimatorTranslateX.cancel();
694 if (mAnimatorTranslateY != null) {
695 mAnimatorTranslateY.cancel();
697 mAnimatorTranslateX = ValueAnimator.ofInt(fromX, toX);
698 mAnimatorTranslateY = ValueAnimator.ofInt(fromY, toY);
699 mAnimatorTranslateX.setDuration(delay);
700 mAnimatorTranslateY.setDuration(delay);
701 mAnimatorTranslateX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
703 public void onAnimationUpdate(ValueAnimator animation) {
704 Point translation = MasterImage.getImage().getTranslation();
705 translation.x = (Integer) animation.getAnimatedValue();
706 MasterImage.getImage().setTranslation(translation);
710 mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
712 public void onAnimationUpdate(ValueAnimator animation) {
713 Point translation = MasterImage.getImage().getTranslation();
714 translation.y = (Integer) animation.getAnimatedValue();
715 MasterImage.getImage().setTranslation(translation);
719 mAnimatorTranslateX.start();
720 mAnimatorTranslateY.start();
723 private void applyTranslationConstraints() {
724 float scaleFactor = MasterImage.getImage().getScaleFactor();
725 Point translation = MasterImage.getImage().getTranslation();
726 int x = translation.x;
727 int y = translation.y;
728 constrainTranslation(translation, scaleFactor);
730 if (x != translation.x || y != translation.y) {
731 startAnimTranslation(x, translation.x,
733 mAnimationSnapDelay);
737 protected boolean enableComparison() {
742 public boolean onDoubleTap(MotionEvent arg0) {
745 final float x = arg0.getX();
746 final float y = arg0.getY();
748 scale = MasterImage.getImage().getMaxScaleFactor();
750 if (scale != MasterImage.getImage().getScaleFactor()) {
751 if (mAnimatorScale != null) {
752 mAnimatorScale.cancel();
754 mAnimatorScale = ValueAnimator.ofFloat(
755 MasterImage.getImage().getScaleFactor(),
758 float translateX = (getWidth() / 2 - x);
759 float translateY = (getHeight() / 2 - y);
760 Point translation = MasterImage.getImage().getTranslation();
761 int startTranslateX = translation.x;
762 int startTranslateY = translation.y;
764 translation.x = (int) (mOriginalTranslation.x + translateX);
765 translation.y = (int) (mOriginalTranslation.y + translateY);
770 constrainTranslation(translation, scale);
772 startAnimTranslation(startTranslateX, translation.x,
773 startTranslateY, translation.y,
774 mAnimationZoomDelay);
775 mAnimatorScale.setDuration(mAnimationZoomDelay);
776 mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
778 public void onAnimationUpdate(ValueAnimator animation) {
779 MasterImage.getImage().setScaleFactor((Float) animation.getAnimatedValue());
783 mAnimatorScale.addListener(new Animator.AnimatorListener() {
785 public void onAnimationStart(Animator animation) {
789 public void onAnimationEnd(Animator animation) {
790 applyTranslationConstraints();
791 MasterImage.getImage().needsUpdatePartialPreview();
796 public void onAnimationCancel(Animator animation) {
801 public void onAnimationRepeat(Animator animation) {
805 mAnimatorScale.start();
810 private void constrainTranslation(Point translation, float scale) {
811 int currentEdgeEffect = 0;
813 mCurrentEdgeEffect = 0;
814 mEdgeEffect.finish();
818 Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
819 Rect originalBounds = MasterImage.getImage().getOriginalBounds();
820 RectF screenPos = new RectF(originalBounds);
821 originalToScreen.mapRect(screenPos);
823 boolean rightConstraint = screenPos.right < getWidth() - mShadowMargin;
824 boolean leftConstraint = screenPos.left > mShadowMargin;
825 boolean topConstraint = screenPos.top > mShadowMargin;
826 boolean bottomConstraint = screenPos.bottom < getHeight() - mShadowMargin;
828 if (screenPos.width() > getWidth()) {
829 if (rightConstraint && !leftConstraint) {
830 float tx = screenPos.right - translation.x * scale;
831 translation.x = (int) ((getWidth() - mShadowMargin - tx) / scale);
832 currentEdgeEffect = EDGE_RIGHT;
833 } else if (leftConstraint && !rightConstraint) {
834 float tx = screenPos.left - translation.x * scale;
835 translation.x = (int) ((mShadowMargin - tx) / scale);
836 currentEdgeEffect = EDGE_LEFT;
839 float tx = screenPos.right - translation.x * scale;
840 float dx = (getWidth() - 2 * mShadowMargin - screenPos.width()) / 2f;
841 translation.x = (int) ((getWidth() - mShadowMargin - tx - dx) / scale);
844 if (screenPos.height() > getHeight()) {
845 if (bottomConstraint && !topConstraint) {
846 float ty = screenPos.bottom - translation.y * scale;
847 translation.y = (int) ((getHeight() - mShadowMargin - ty) / scale);
848 currentEdgeEffect = EDGE_BOTTOM;
849 } else if (topConstraint && !bottomConstraint) {
850 float ty = screenPos.top - translation.y * scale;
851 translation.y = (int) ((mShadowMargin - ty) / scale);
852 currentEdgeEffect = EDGE_TOP;
855 float ty = screenPos.bottom - translation.y * scale;
856 float dy = (getHeight()- 2 * mShadowMargin - screenPos.height()) / 2f;
857 translation.y = (int) ((getHeight() - mShadowMargin - ty - dy) / scale);
860 if (mCurrentEdgeEffect != currentEdgeEffect) {
861 if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) {
862 mCurrentEdgeEffect = currentEdgeEffect;
863 mEdgeEffect.finish();
865 mEdgeEffect.setSize(getWidth(), mEdgeSize);
867 if (currentEdgeEffect != 0) {
868 mEdgeEffect.onPull(mEdgeSize);
873 public boolean onDoubleTapEvent(MotionEvent arg0) {
878 public boolean onSingleTapConfirmed(MotionEvent arg0) {
883 public boolean onDown(MotionEvent arg0) {
888 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
889 if (mActivity == null) {
892 if (endEvent.getPointerCount() == 2) {
899 public void onLongPress(MotionEvent arg0) {
903 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
908 public void onShowPress(MotionEvent arg0) {
912 public boolean onSingleTapUp(MotionEvent arg0) {
916 public boolean useUtilityPanel() {
920 public void openUtilityPanel(final LinearLayout accessoryViewList) {
924 public boolean onScale(ScaleGestureDetector detector) {
925 MasterImage img = MasterImage.getImage();
926 float scaleFactor = img.getScaleFactor();
928 scaleFactor = scaleFactor * detector.getScaleFactor();
929 if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
930 scaleFactor = MasterImage.getImage().getMaxScaleFactor();
932 if (scaleFactor < 1.0f) {
935 MasterImage.getImage().setScaleFactor(scaleFactor);
936 scaleFactor = img.getScaleFactor();
937 float focusx = detector.getFocusX();
938 float focusy = detector.getFocusY();
939 float translateX = (focusx - mStartFocusX) / scaleFactor;
940 float translateY = (focusy - mStartFocusY) / scaleFactor;
941 Point translation = MasterImage.getImage().getTranslation();
942 translation.x = (int) (mOriginalTranslation.x + translateX);
943 translation.y = (int) (mOriginalTranslation.y + translateY);
944 MasterImage.getImage().setTranslation(translation);
950 public boolean onScaleBegin(ScaleGestureDetector detector) {
951 Point pos = MasterImage.getImage().getTranslation();
952 mOriginalTranslation.x = pos.x;
953 mOriginalTranslation.y = pos.y;
954 mOriginalScale = MasterImage.getImage().getScaleFactor();
955 mStartFocusX = detector.getFocusX();
956 mStartFocusY = detector.getFocusY();
957 mInteractionMode = InteractionMode.SCALE;
962 public void onScaleEnd(ScaleGestureDetector detector) {
963 mInteractionMode = InteractionMode.NONE;
964 if (MasterImage.getImage().getScaleFactor() < 1) {
965 MasterImage.getImage().setScaleFactor(1);
970 public boolean didFinishScalingOperation() {
971 if (mFinishedScalingOperation) {
972 mFinishedScalingOperation = false;