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 android.support.v4.widget.EdgeEffectCompat;
36 import android.util.AttributeSet;
37 import android.view.GestureDetector;
38 import android.view.GestureDetector.OnDoubleTapListener;
39 import android.view.GestureDetector.OnGestureListener;
40 import android.view.MotionEvent;
41 import android.view.ScaleGestureDetector;
42 import android.view.View;
43 import android.widget.LinearLayout;
45 import com.android.gallery3d.R;
46 import com.android.gallery3d.filtershow.FilterShowActivity;
47 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
48 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
49 import com.android.gallery3d.filtershow.filters.ImageFilter;
50 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
51 import com.android.gallery3d.filtershow.tools.SaveImage;
54 import java.util.ArrayList;
56 public class ImageShow extends View implements OnGestureListener,
57 ScaleGestureDetector.OnScaleGestureListener,
60 private static final String LOGTAG = "ImageShow";
61 private static final boolean ENABLE_ZOOMED_COMPARISON = false;
63 protected Paint mPaint = new Paint();
64 protected int mTextSize;
65 protected int mTextPadding;
67 protected int mBackgroundColor;
69 private GestureDetector mGestureDetector = null;
70 private ScaleGestureDetector mScaleGestureDetector = null;
72 protected Rect mImageBounds = new Rect();
73 private boolean mOriginalDisabled = false;
74 private boolean mTouchShowOriginal = false;
75 private long mTouchShowOriginalDate = 0;
76 private final long mTouchShowOriginalDelayMin = 200; // 200ms
77 private int mShowOriginalDirection = 0;
78 private static int UNVEIL_HORIZONTAL = 1;
79 private static int UNVEIL_VERTICAL = 2;
81 private NinePatchDrawable mShadow = null;
82 private Rect mShadowBounds = new Rect();
83 private int mShadowMargin = 15; // not scaled, fixed in the asset
84 private boolean mShadowDrawn = false;
86 private Point mTouchDown = new Point();
87 private Point mTouch = new Point();
88 private boolean mFinishedScalingOperation = false;
90 private int mOriginalTextMargin;
91 private int mOriginalTextSize;
92 private String mOriginalText;
93 private boolean mZoomIn = false;
94 Point mOriginalTranslation = new Point();
96 float mStartFocusX, mStartFocusY;
98 private EdgeEffectCompat mEdgeEffect = null;
99 private static final int EDGE_LEFT = 1;
100 private static final int EDGE_TOP = 2;
101 private static final int EDGE_RIGHT = 3;
102 private static final int EDGE_BOTTOM = 4;
103 private int mCurrentEdgeEffect = 0;
104 private int mEdgeSize = 100;
106 private static final int mAnimationSnapDelay = 200;
107 private static final int mAnimationZoomDelay = 400;
108 private ValueAnimator mAnimatorScale = null;
109 private ValueAnimator mAnimatorTranslateX = null;
110 private ValueAnimator mAnimatorTranslateY = null;
112 private enum InteractionMode {
117 InteractionMode mInteractionMode = InteractionMode.NONE;
119 private static Bitmap sMask;
120 private Paint mMaskPaint = new Paint();
121 private Matrix mShaderMatrix = new Matrix();
122 private boolean mDidStartAnimation = false;
124 private static Bitmap convertToAlphaMask(Bitmap b) {
125 Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8);
126 Canvas c = new Canvas(a);
127 c.drawBitmap(b, 0.0f, 0.0f, null);
131 private static Shader createShader(Bitmap b) {
132 return new BitmapShader(b, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
135 private FilterShowActivity mActivity = null;
137 public FilterShowActivity getActivity() {
141 public boolean hasModifications() {
142 return MasterImage.getImage().hasModifications();
145 public void resetParameter() {
146 // TODO: implement reset
149 public void onNewValue(int parameter) {
153 public ImageShow(Context context, AttributeSet attrs, int defStyle) {
154 super(context, attrs, defStyle);
155 setupImageShow(context);
158 public ImageShow(Context context, AttributeSet attrs) {
159 super(context, attrs);
160 setupImageShow(context);
164 public ImageShow(Context context) {
166 setupImageShow(context);
169 private void setupImageShow(Context context) {
170 Resources res = context.getResources();
171 mTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_text_size);
172 mTextPadding = res.getDimensionPixelSize(R.dimen.photoeditor_text_padding);
173 mOriginalTextMargin = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_margin);
174 mOriginalTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_size);
175 mBackgroundColor = res.getColor(R.color.background_screen);
176 mOriginalText = res.getString(R.string.original_picture_text);
177 mShadow = (NinePatchDrawable) res.getDrawable(R.drawable.geometry_shadow);
178 setupGestureDetector(context);
179 mActivity = (FilterShowActivity) context;
181 Bitmap mask = BitmapFactory.decodeResource(res, R.drawable.spot_mask);
182 sMask = convertToAlphaMask(mask);
184 mEdgeEffect = new EdgeEffectCompat(context);
185 mEdgeSize = res.getDimensionPixelSize(R.dimen.edge_glow_size);
188 public void attach() {
189 MasterImage.getImage().addObserver(this);
190 bindAsImageLoadListener();
191 MasterImage.getImage().resetGeometryImages(false);
194 public void detach() {
195 MasterImage.getImage().removeObserver(this);
199 public void setupGestureDetector(Context context) {
200 mGestureDetector = new GestureDetector(context, this);
201 mScaleGestureDetector = new ScaleGestureDetector(context, this);
205 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
206 int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
207 int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
208 setMeasuredDimension(parentWidth, parentHeight);
211 public ImageFilter getCurrentFilter() {
212 return MasterImage.getImage().getCurrentFilter();
215 /* consider moving the following 2 methods into a subclass */
217 * This function calculates a Image to Screen Transformation matrix
219 * @param reflectRotation set true if you want the rotation encoded
220 * @return Image to Screen transformation matrix
222 protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
223 MasterImage master = MasterImage.getImage();
224 if (master.getOriginalBounds() == null) {
227 Matrix m = GeometryMathUtils.getImageToScreenMatrix(master.getPreset().getGeometryFilters(),
228 reflectRotation, master.getOriginalBounds(), getWidth(), getHeight());
229 Point translate = master.getTranslation();
230 float scaleFactor = master.getScaleFactor();
231 m.postTranslate(translate.x, translate.y);
232 m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
237 * This function calculates a to Screen Image Transformation matrix
239 * @param reflectRotation set true if you want the rotation encoded
240 * @return Screen to Image transformation matrix
242 protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
243 Matrix m = getImageToScreenMatrix(reflectRotation);
244 Matrix invert = new Matrix();
249 public ImagePreset getImagePreset() {
250 return MasterImage.getImage().getPreset();
254 public void onDraw(Canvas canvas) {
256 mPaint.setAntiAlias(true);
257 mPaint.setFilterBitmap(true);
258 MasterImage.getImage().setImageShowSize(
259 getWidth() - 2*mShadowMargin,
260 getHeight() - 2*mShadowMargin);
262 MasterImage img = MasterImage.getImage();
263 // Hide the loading indicator as needed
264 if (mActivity.isLoadingVisible() && getFilteredImage() != null) {
265 if ((img.getLoadedPreset() == null)
266 || (img.getLoadedPreset() != null
267 && img.getLoadedPreset().equals(img.getCurrentPreset()))) {
268 mActivity.stopLoadingIndicator();
269 } else if (img.getLoadedPreset() != null) {
272 mActivity.stopLoadingIndicator();
277 mShadowDrawn = false;
279 Bitmap highresPreview = MasterImage.getImage().getHighresImage();
280 Bitmap fullHighres = MasterImage.getImage().getPartialImage();
282 boolean isDoingNewLookAnimation = MasterImage.getImage().onGoingNewLookAnimation();
284 if (highresPreview == null || isDoingNewLookAnimation) {
285 drawImageAndAnimate(canvas, getFilteredImage());
287 drawImageAndAnimate(canvas, highresPreview);
290 drawHighresImage(canvas, fullHighres);
291 drawCompareImage(canvas, getGeometryOnlyImage());
295 if (!mEdgeEffect.isFinished()) {
297 float dx = (getHeight() - getWidth()) / 2f;
298 if (getWidth() > getHeight()) {
299 dx = - (getWidth() - getHeight()) / 2f;
301 if (mCurrentEdgeEffect == EDGE_BOTTOM) {
302 canvas.rotate(180, getWidth()/2, getHeight()/2);
303 } else if (mCurrentEdgeEffect == EDGE_RIGHT) {
304 canvas.rotate(90, getWidth()/2, getHeight()/2);
305 canvas.translate(0, dx);
306 } else if (mCurrentEdgeEffect == EDGE_LEFT) {
307 canvas.rotate(270, getWidth()/2, getHeight()/2);
308 canvas.translate(0, dx);
310 if (mCurrentEdgeEffect != 0) {
311 mEdgeEffect.draw(canvas);
316 mCurrentEdgeEffect = 0;
320 private void drawHighresImage(Canvas canvas, Bitmap fullHighres) {
321 Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
322 if (fullHighres != null && originalToScreen != null) {
323 Matrix screenToOriginal = new Matrix();
324 originalToScreen.invert(screenToOriginal);
325 Rect rBounds = new Rect();
326 rBounds.set(MasterImage.getImage().getPartialBounds());
327 if (fullHighres != null) {
328 originalToScreen.preTranslate(rBounds.left, rBounds.top);
329 canvas.clipRect(mImageBounds);
330 canvas.drawBitmap(fullHighres, originalToScreen, mPaint);
335 public void resetImageCaches(ImageShow caller) {
336 MasterImage.getImage().invalidatePreview();
339 public Bitmap getFiltersOnlyImage() {
340 return MasterImage.getImage().getFiltersOnlyImage();
343 public Bitmap getGeometryOnlyImage() {
344 return MasterImage.getImage().getGeometryOnlyImage();
347 public Bitmap getFilteredImage() {
348 return MasterImage.getImage().getFilteredImage();
351 public void drawImageAndAnimate(Canvas canvas,
356 MasterImage master = MasterImage.getImage();
357 Matrix m = master.computeImageToScreen(image, 0, false);
364 RectF d = new RectF(0, 0, image.getWidth(), image.getHeight());
366 d.roundOut(mImageBounds);
368 if (master.onGoingNewLookAnimation()
369 || mDidStartAnimation) {
370 mDidStartAnimation = true;
373 // Animation uses the image before the change
374 Bitmap previousImage = master.getPreviousImage();
375 Matrix mp = master.computeImageToScreen(previousImage, 0, false);
376 RectF dp = new RectF(0, 0, previousImage.getWidth(), previousImage.getHeight());
378 Rect previousBounds = new Rect();
379 dp.roundOut(previousBounds);
380 float centerX = dp.centerX();
381 float centerY = dp.centerY();
382 boolean needsToDrawImage = true;
384 if (master.getCurrentLookAnimation()
385 == MasterImage.CIRCLE_ANIMATION) {
386 float maskScale = MasterImage.getImage().getMaskScale();
387 if (maskScale >= 0.0f) {
388 float maskW = sMask.getWidth() / 2.0f;
389 float maskH = sMask.getHeight() / 2.0f;
391 Point point = mActivity.hintTouchPoint(this);
392 float x = point.x - maskW * maskScale;
393 float y = point.y - maskH * maskScale;
395 // Prepare the shader
396 mShaderMatrix.reset();
397 mShaderMatrix.setScale(1.0f / maskScale, 1.0f / maskScale);
398 mShaderMatrix.preTranslate(-x + mImageBounds.left, -y + mImageBounds.top);
399 float scaleImageX = mImageBounds.width() / (float) image.getWidth();
400 float scaleImageY = mImageBounds.height() / (float) image.getHeight();
401 mShaderMatrix.preScale(scaleImageX, scaleImageY);
403 mMaskPaint.setShader(createShader(image));
404 mMaskPaint.getShader().setLocalMatrix(mShaderMatrix);
406 drawShadow(canvas, mImageBounds); // as needed
407 canvas.drawBitmap(previousImage, m, mPaint);
408 canvas.clipRect(mImageBounds);
409 canvas.translate(x, y);
410 canvas.scale(maskScale, maskScale);
411 canvas.drawBitmap(sMask, 0, 0, mMaskPaint);
412 needsToDrawImage = false;
414 } else if (master.getCurrentLookAnimation()
415 == MasterImage.ROTATE_ANIMATION) {
416 Rect d1 = computeImageBounds(master.getPreviousImage().getHeight(),
417 master.getPreviousImage().getWidth());
418 Rect d2 = computeImageBounds(master.getPreviousImage().getWidth(),
419 master.getPreviousImage().getHeight());
420 float finalScale = d1.width() / (float) d2.height();
421 finalScale = (1.0f * (1.0f - master.getAnimFraction()))
422 + (finalScale * master.getAnimFraction());
423 canvas.rotate(master.getAnimRotationValue(), centerX, centerY);
424 canvas.scale(finalScale, finalScale, centerX, centerY);
425 } else if (master.getCurrentLookAnimation()
426 == MasterImage.MIRROR_ANIMATION) {
427 if (master.getCurrentFilterRepresentation()
428 instanceof FilterMirrorRepresentation) {
429 FilterMirrorRepresentation rep =
430 (FilterMirrorRepresentation) master.getCurrentFilterRepresentation();
432 ImagePreset preset = master.getPreset();
433 ArrayList<FilterRepresentation> geometry =
434 (ArrayList<FilterRepresentation>) preset.getGeometryFilters();
435 GeometryMathUtils.GeometryHolder holder = null;
436 holder = GeometryMathUtils.unpackGeometry(geometry);
438 if (holder.rotation.value() == 90 || holder.rotation.value() == 270) {
439 if (rep.isHorizontal() && !rep.isVertical()) {
440 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
441 } else if (rep.isVertical() && !rep.isHorizontal()) {
442 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
443 } else if (rep.isHorizontal() && rep.isVertical()) {
444 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
446 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
449 if (rep.isHorizontal() && !rep.isVertical()) {
450 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
451 } else if (rep.isVertical() && !rep.isHorizontal()) {
452 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
453 } else if (rep.isHorizontal() && rep.isVertical()) {
454 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
456 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
462 if (needsToDrawImage) {
463 drawShadow(canvas, previousBounds); // as needed
464 canvas.drawBitmap(previousImage, mp, mPaint);
469 drawShadow(canvas, mImageBounds); // as needed
470 canvas.drawBitmap(image, m, mPaint);
473 if (!master.onGoingNewLookAnimation()
474 && mDidStartAnimation
475 && !master.getPreviousPreset().equals(master.getCurrentPreset())) {
476 mDidStartAnimation = false;
477 MasterImage.getImage().resetAnimBitmap();
483 private Rect computeImageBounds(int imageWidth, int imageHeight) {
484 float scale = GeometryMathUtils.scale(imageWidth, imageHeight,
485 getWidth(), getHeight());
487 float w = imageWidth * scale;
488 float h = imageHeight * scale;
489 float ty = (getHeight() - h) / 2.0f;
490 float tx = (getWidth() - w) / 2.0f;
491 return new Rect((int) tx + mShadowMargin,
492 (int) ty + mShadowMargin,
493 (int) (w + tx) - mShadowMargin,
494 (int) (h + ty) - mShadowMargin);
497 private void drawShadow(Canvas canvas, Rect d) {
499 mShadowBounds.set(d.left - mShadowMargin, d.top - mShadowMargin,
500 d.right + mShadowMargin, d.bottom + mShadowMargin);
501 mShadow.setBounds(mShadowBounds);
502 mShadow.draw(canvas);
507 public void drawCompareImage(Canvas canvas, Bitmap image) {
508 MasterImage master = MasterImage.getImage();
509 boolean showsOriginal = master.showsOriginal();
510 if (!showsOriginal && !mTouchShowOriginal)
514 if (mShowOriginalDirection == 0) {
515 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
516 mShowOriginalDirection = UNVEIL_VERTICAL;
518 mShowOriginalDirection = UNVEIL_HORIZONTAL;
524 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
525 px = mImageBounds.width();
526 py = mTouch.y - mImageBounds.top;
528 px = mTouch.x - mImageBounds.left;
529 py = mImageBounds.height();
531 px = mImageBounds.width();
535 Rect d = new Rect(mImageBounds.left, mImageBounds.top,
536 mImageBounds.left + px, mImageBounds.top + py);
537 if (mShowOriginalDirection == UNVEIL_HORIZONTAL) {
538 if (mTouchDown.x - mTouch.x > 0) {
539 d.set(mImageBounds.left + px, mImageBounds.top,
540 mImageBounds.right, mImageBounds.top + py);
543 if (mTouchDown.y - mTouch.y > 0) {
544 d.set(mImageBounds.left, mImageBounds.top + py,
545 mImageBounds.left + px, mImageBounds.bottom);
549 Matrix m = master.computeImageToScreen(image, 0, false);
550 canvas.drawBitmap(image, m, mPaint);
551 Paint paint = new Paint();
552 paint.setColor(Color.BLACK);
553 paint.setStrokeWidth(3);
555 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
556 canvas.drawLine(mImageBounds.left, mTouch.y,
557 mImageBounds.right, mTouch.y, paint);
559 canvas.drawLine(mTouch.x, mImageBounds.top,
560 mTouch.x, mImageBounds.bottom, paint);
563 Rect bounds = new Rect();
564 paint.setAntiAlias(true);
565 paint.setTextSize(mOriginalTextSize);
566 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
567 paint.setColor(Color.BLACK);
568 paint.setStyle(Paint.Style.STROKE);
569 paint.setStrokeWidth(3);
570 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
571 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
572 paint.setStyle(Paint.Style.FILL);
573 paint.setStrokeWidth(1);
574 paint.setColor(Color.WHITE);
575 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
576 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
581 public void bindAsImageLoadListener() {
582 MasterImage.getImage().addListener(this);
585 public void updateImage() {
589 public void imageLoaded() {
593 public void saveImage(FilterShowActivity filterShowActivity, File file) {
594 SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
598 public boolean scaleInProgress() {
599 return mScaleGestureDetector.isInProgress();
603 public boolean onTouchEvent(MotionEvent event) {
604 super.onTouchEvent(event);
605 int action = event.getAction();
606 action = action & MotionEvent.ACTION_MASK;
608 mGestureDetector.onTouchEvent(event);
609 boolean scaleInProgress = scaleInProgress();
610 mScaleGestureDetector.onTouchEvent(event);
611 if (mInteractionMode == InteractionMode.SCALE) {
614 if (!scaleInProgress() && scaleInProgress) {
615 // If we were scaling, the scale will stop but we will
616 // still issue an ACTION_UP. Let the subclasses know.
617 mFinishedScalingOperation = true;
620 int ex = (int) event.getX();
621 int ey = (int) event.getY();
622 if (action == MotionEvent.ACTION_DOWN) {
623 mInteractionMode = InteractionMode.MOVE;
626 mTouchShowOriginalDate = System.currentTimeMillis();
627 mShowOriginalDirection = 0;
628 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
631 if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
635 float scaleFactor = MasterImage.getImage().getScaleFactor();
636 if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
637 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
638 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
639 Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
640 Point translation = MasterImage.getImage().getTranslation();
641 translation.x = (int) (originalTranslation.x + translateX);
642 translation.y = (int) (originalTranslation.y + translateY);
643 MasterImage.getImage().setTranslation(translation);
644 mTouchShowOriginal = false;
645 } else if (enableComparison() && !mOriginalDisabled
646 && (System.currentTimeMillis() - mTouchShowOriginalDate
647 > mTouchShowOriginalDelayMin)
648 && event.getPointerCount() == 1) {
649 mTouchShowOriginal = true;
653 if (action == MotionEvent.ACTION_UP
654 || action == MotionEvent.ACTION_CANCEL
655 || action == MotionEvent.ACTION_OUTSIDE) {
656 mInteractionMode = InteractionMode.NONE;
657 mTouchShowOriginal = false;
662 if (MasterImage.getImage().getScaleFactor() <= 1) {
663 MasterImage.getImage().setScaleFactor(1);
664 MasterImage.getImage().resetTranslation();
668 float scaleFactor = MasterImage.getImage().getScaleFactor();
669 Point translation = MasterImage.getImage().getTranslation();
670 constrainTranslation(translation, scaleFactor);
671 MasterImage.getImage().setTranslation(translation);
677 private void startAnimTranslation(int fromX, int toX,
678 int fromY, int toY, int delay) {
679 if (fromX == toX && fromY == toY) {
682 if (mAnimatorTranslateX != null) {
683 mAnimatorTranslateX.cancel();
685 if (mAnimatorTranslateY != null) {
686 mAnimatorTranslateY.cancel();
688 mAnimatorTranslateX = ValueAnimator.ofInt(fromX, toX);
689 mAnimatorTranslateY = ValueAnimator.ofInt(fromY, toY);
690 mAnimatorTranslateX.setDuration(delay);
691 mAnimatorTranslateY.setDuration(delay);
692 mAnimatorTranslateX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
694 public void onAnimationUpdate(ValueAnimator animation) {
695 Point translation = MasterImage.getImage().getTranslation();
696 translation.x = (Integer) animation.getAnimatedValue();
697 MasterImage.getImage().setTranslation(translation);
701 mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
703 public void onAnimationUpdate(ValueAnimator animation) {
704 Point translation = MasterImage.getImage().getTranslation();
705 translation.y = (Integer) animation.getAnimatedValue();
706 MasterImage.getImage().setTranslation(translation);
710 mAnimatorTranslateX.start();
711 mAnimatorTranslateY.start();
714 private void applyTranslationConstraints() {
715 float scaleFactor = MasterImage.getImage().getScaleFactor();
716 Point translation = MasterImage.getImage().getTranslation();
717 int x = translation.x;
718 int y = translation.y;
719 constrainTranslation(translation, scaleFactor);
721 if (x != translation.x || y != translation.y) {
722 startAnimTranslation(x, translation.x,
724 mAnimationSnapDelay);
728 protected boolean enableComparison() {
733 public boolean onDoubleTap(MotionEvent arg0) {
736 final float x = arg0.getX();
737 final float y = arg0.getY();
739 scale = MasterImage.getImage().getMaxScaleFactor();
741 if (scale != MasterImage.getImage().getScaleFactor()) {
742 if (mAnimatorScale != null) {
743 mAnimatorScale.cancel();
745 mAnimatorScale = ValueAnimator.ofFloat(
746 MasterImage.getImage().getScaleFactor(),
749 float translateX = (getWidth() / 2 - x);
750 float translateY = (getHeight() / 2 - y);
751 Point translation = MasterImage.getImage().getTranslation();
752 int startTranslateX = translation.x;
753 int startTranslateY = translation.y;
755 translation.x = (int) (mOriginalTranslation.x + translateX);
756 translation.y = (int) (mOriginalTranslation.y + translateY);
761 constrainTranslation(translation, scale);
763 startAnimTranslation(startTranslateX, translation.x,
764 startTranslateY, translation.y,
765 mAnimationZoomDelay);
766 mAnimatorScale.setDuration(mAnimationZoomDelay);
767 mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
769 public void onAnimationUpdate(ValueAnimator animation) {
770 MasterImage.getImage().setScaleFactor((Float) animation.getAnimatedValue());
774 mAnimatorScale.addListener(new Animator.AnimatorListener() {
776 public void onAnimationStart(Animator animation) {
780 public void onAnimationEnd(Animator animation) {
781 applyTranslationConstraints();
782 MasterImage.getImage().needsUpdatePartialPreview();
787 public void onAnimationCancel(Animator animation) {
792 public void onAnimationRepeat(Animator animation) {
796 mAnimatorScale.start();
801 private void constrainTranslation(Point translation, float scale) {
802 int currentEdgeEffect = 0;
804 mCurrentEdgeEffect = 0;
805 mEdgeEffect.finish();
809 Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
810 Rect originalBounds = MasterImage.getImage().getOriginalBounds();
811 RectF screenPos = new RectF(originalBounds);
812 originalToScreen.mapRect(screenPos);
814 boolean rightConstraint = screenPos.right < getWidth() - mShadowMargin;
815 boolean leftConstraint = screenPos.left > mShadowMargin;
816 boolean topConstraint = screenPos.top > mShadowMargin;
817 boolean bottomConstraint = screenPos.bottom < getHeight() - mShadowMargin;
819 if (screenPos.width() > getWidth()) {
820 if (rightConstraint && !leftConstraint) {
821 float tx = screenPos.right - translation.x * scale;
822 translation.x = (int) ((getWidth() - mShadowMargin - tx) / scale);
823 currentEdgeEffect = EDGE_RIGHT;
824 } else if (leftConstraint && !rightConstraint) {
825 float tx = screenPos.left - translation.x * scale;
826 translation.x = (int) ((mShadowMargin - tx) / scale);
827 currentEdgeEffect = EDGE_LEFT;
830 float tx = screenPos.right - translation.x * scale;
831 float dx = (getWidth() - 2 * mShadowMargin - screenPos.width()) / 2f;
832 translation.x = (int) ((getWidth() - mShadowMargin - tx - dx) / scale);
835 if (screenPos.height() > getHeight()) {
836 if (bottomConstraint && !topConstraint) {
837 float ty = screenPos.bottom - translation.y * scale;
838 translation.y = (int) ((getHeight() - mShadowMargin - ty) / scale);
839 currentEdgeEffect = EDGE_BOTTOM;
840 } else if (topConstraint && !bottomConstraint) {
841 float ty = screenPos.top - translation.y * scale;
842 translation.y = (int) ((mShadowMargin - ty) / scale);
843 currentEdgeEffect = EDGE_TOP;
846 float ty = screenPos.bottom - translation.y * scale;
847 float dy = (getHeight()- 2 * mShadowMargin - screenPos.height()) / 2f;
848 translation.y = (int) ((getHeight() - mShadowMargin - ty - dy) / scale);
851 if (mCurrentEdgeEffect != currentEdgeEffect) {
852 if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) {
853 mCurrentEdgeEffect = currentEdgeEffect;
854 mEdgeEffect.finish();
856 mEdgeEffect.setSize(getWidth(), mEdgeSize);
858 if (currentEdgeEffect != 0) {
859 mEdgeEffect.onPull(mEdgeSize);
864 public boolean onDoubleTapEvent(MotionEvent arg0) {
869 public boolean onSingleTapConfirmed(MotionEvent arg0) {
874 public boolean onDown(MotionEvent arg0) {
879 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
880 if (mActivity == null) {
883 if (endEvent.getPointerCount() == 2) {
890 public void onLongPress(MotionEvent arg0) {
894 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
899 public void onShowPress(MotionEvent arg0) {
903 public boolean onSingleTapUp(MotionEvent arg0) {
907 public boolean useUtilityPanel() {
911 public void openUtilityPanel(final LinearLayout accessoryViewList) {
915 public boolean onScale(ScaleGestureDetector detector) {
916 MasterImage img = MasterImage.getImage();
917 float scaleFactor = img.getScaleFactor();
919 scaleFactor = scaleFactor * detector.getScaleFactor();
920 if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
921 scaleFactor = MasterImage.getImage().getMaxScaleFactor();
923 if (scaleFactor < 1.0f) {
926 MasterImage.getImage().setScaleFactor(scaleFactor);
927 scaleFactor = img.getScaleFactor();
928 float focusx = detector.getFocusX();
929 float focusy = detector.getFocusY();
930 float translateX = (focusx - mStartFocusX) / scaleFactor;
931 float translateY = (focusy - mStartFocusY) / scaleFactor;
932 Point translation = MasterImage.getImage().getTranslation();
933 translation.x = (int) (mOriginalTranslation.x + translateX);
934 translation.y = (int) (mOriginalTranslation.y + translateY);
935 MasterImage.getImage().setTranslation(translation);
941 public boolean onScaleBegin(ScaleGestureDetector detector) {
942 Point pos = MasterImage.getImage().getTranslation();
943 mOriginalTranslation.x = pos.x;
944 mOriginalTranslation.y = pos.y;
945 mOriginalScale = MasterImage.getImage().getScaleFactor();
946 mStartFocusX = detector.getFocusX();
947 mStartFocusY = detector.getFocusY();
948 mInteractionMode = InteractionMode.SCALE;
953 public void onScaleEnd(ScaleGestureDetector detector) {
954 mInteractionMode = InteractionMode.NONE;
955 if (MasterImage.getImage().getScaleFactor() < 1) {
956 MasterImage.getImage().setScaleFactor(1);
961 public boolean didFinishScalingOperation() {
962 if (mFinishedScalingOperation) {
963 mFinishedScalingOperation = false;