1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package com.android.gallery3d.filtershow.imageshow;
18 
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;
45 
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;
53 
54 import java.io.File;
55 import java.util.ArrayList;
56 
57 public class ImageShow extends View implements OnGestureListener,
58         ScaleGestureDetector.OnScaleGestureListener,
59         OnDoubleTapListener {
60 
61     private static final String LOGTAG = "ImageShow";
62     private static final boolean ENABLE_ZOOMED_COMPARISON = false;
63 
64     protected Paint mPaint = new Paint();
65     protected int mTextSize;
66     protected int mTextPadding;
67 
68     protected int mBackgroundColor;
69 
70     private GestureDetector mGestureDetector = null;
71     private ScaleGestureDetector mScaleGestureDetector = null;
72 
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;
81 
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;
86 
87     private Point mTouchDown = new Point();
88     private Point mTouch = new Point();
89     private boolean mFinishedScalingOperation = false;
90 
91     private int mOriginalTextMargin;
92     private int mOriginalTextSize;
93     private String mOriginalText;
94     private boolean mZoomIn = false;
95     Point mOriginalTranslation = new Point();
96     float mOriginalScale;
97     float mStartFocusX, mStartFocusY;
98 
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;
106 
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;
112 
113     private enum InteractionMode {
114         NONE,
115         SCALE,
116         MOVE
117     }
118     InteractionMode mInteractionMode = InteractionMode.NONE;
119 
120     private static Bitmap sMask;
121     private Paint mMaskPaint = new Paint();
122     private Matrix mShaderMatrix = new Matrix();
123     private boolean mDidStartAnimation = false;
124 
convertToAlphaMask(Bitmap b)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);
129         return a;
130     }
131 
createShader(Bitmap b)132     private static Shader createShader(Bitmap b) {
133         return new BitmapShader(b, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
134     }
135 
136     private FilterShowActivity mActivity = null;
137 
getActivity()138     public FilterShowActivity getActivity() {
139         return mActivity;
140     }
141 
hasModifications()142     public boolean hasModifications() {
143         return MasterImage.getImage().hasModifications();
144     }
145 
resetParameter()146     public void resetParameter() {
147         // TODO: implement reset
148     }
149 
onNewValue(int parameter)150     public void onNewValue(int parameter) {
151         invalidate();
152     }
153 
ImageShow(Context context, AttributeSet attrs, int defStyle)154     public ImageShow(Context context, AttributeSet attrs, int defStyle) {
155         super(context, attrs, defStyle);
156         setupImageShow(context);
157     }
158 
ImageShow(Context context, AttributeSet attrs)159     public ImageShow(Context context, AttributeSet attrs) {
160         super(context, attrs);
161         setupImageShow(context);
162 
163     }
164 
ImageShow(Context context)165     public ImageShow(Context context) {
166         super(context);
167         setupImageShow(context);
168     }
169 
setupImageShow(Context 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;
181         if (sMask == null) {
182             Bitmap mask = BitmapFactory.decodeResource(res, R.drawable.spot_mask);
183             sMask = convertToAlphaMask(mask);
184         }
185         mEdgeEffect = new EdgeEffectCompat(context);
186         mEdgeSize = res.getDimensionPixelSize(R.dimen.edge_glow_size);
187     }
188 
attach()189     public void attach() {
190         MasterImage.getImage().addObserver(this);
191         bindAsImageLoadListener();
192         MasterImage.getImage().resetGeometryImages(false);
193     }
194 
detach()195     public void detach() {
196         MasterImage.getImage().removeObserver(this);
197         mMaskPaint.reset();
198     }
199 
setupGestureDetector(Context context)200     public void setupGestureDetector(Context context) {
201         mGestureDetector = new GestureDetector(context, this);
202         mScaleGestureDetector = new ScaleGestureDetector(context, this);
203     }
204 
205     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)206     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
207         int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
208         int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
209         setMeasuredDimension(parentWidth, parentHeight);
210     }
211 
getCurrentFilter()212     public ImageFilter getCurrentFilter() {
213         return MasterImage.getImage().getCurrentFilter();
214     }
215 
216     /* consider moving the following 2 methods into a subclass */
217     /**
218      * This function calculates a Image to Screen Transformation matrix
219      *
220      * @param reflectRotation set true if you want the rotation encoded
221      * @return Image to Screen transformation matrix
222      */
getImageToScreenMatrix(boolean reflectRotation)223     protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
224         MasterImage master = MasterImage.getImage();
225         if (master.getOriginalBounds() == null) {
226             return new Matrix();
227         }
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);
234         return m;
235     }
236 
237     /**
238      * This function calculates a to Screen Image Transformation matrix
239      *
240      * @param reflectRotation set true if you want the rotation encoded
241      * @return Screen to Image transformation matrix
242      */
getScreenToImageMatrix(boolean reflectRotation)243     protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
244         Matrix m = getImageToScreenMatrix(reflectRotation);
245         Matrix invert = new Matrix();
246         m.invert(invert);
247         return invert;
248     }
249 
getImagePreset()250     public ImagePreset getImagePreset() {
251         return MasterImage.getImage().getPreset();
252     }
253 
254     @Override
onDraw(Canvas canvas)255     public void onDraw(Canvas canvas) {
256         mPaint.reset();
257         mPaint.setAntiAlias(true);
258         mPaint.setFilterBitmap(true);
259         MasterImage.getImage().setImageShowSize(
260                 getWidth() - 2*mShadowMargin,
261                 getHeight() - 2*mShadowMargin);
262 
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) {
271                 return;
272             }
273             mActivity.stopLoadingIndicator();
274         }
275 
276         canvas.save();
277 
278         mShadowDrawn = false;
279 
280         Bitmap highresPreview = MasterImage.getImage().getHighresImage();
281         Bitmap fullHighres = MasterImage.getImage().getPartialImage();
282 
283         boolean isDoingNewLookAnimation = MasterImage.getImage().onGoingNewLookAnimation();
284 
285         if (highresPreview == null || isDoingNewLookAnimation) {
286             drawImageAndAnimate(canvas, getFilteredImage());
287         } else {
288             drawImageAndAnimate(canvas, highresPreview);
289         }
290 
291         drawHighresImage(canvas, fullHighres);
292         drawCompareImage(canvas, getGeometryOnlyImage());
293 
294         canvas.restore();
295 
296         if (!mEdgeEffect.isFinished()) {
297             canvas.save();
298             float dx = (getHeight() - getWidth()) / 2f;
299             if (getWidth() > getHeight()) {
300                 dx = - (getWidth() - getHeight()) / 2f;
301             }
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);
310             }
311             if (mCurrentEdgeEffect != 0) {
312                 mEdgeEffect.draw(canvas);
313             }
314             canvas.restore();
315             invalidate();
316         } else {
317             mCurrentEdgeEffect = 0;
318         }
319     }
320 
drawHighresImage(Canvas canvas, Bitmap fullHighres)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);
332             }
333         }
334     }
335 
resetImageCaches(ImageShow caller)336     public void resetImageCaches(ImageShow caller) {
337         MasterImage.getImage().invalidatePreview();
338     }
339 
getFiltersOnlyImage()340     public Bitmap getFiltersOnlyImage() {
341         return MasterImage.getImage().getFiltersOnlyImage();
342     }
343 
getGeometryOnlyImage()344     public Bitmap getGeometryOnlyImage() {
345         return MasterImage.getImage().getGeometryOnlyImage();
346     }
347 
getFilteredImage()348     public Bitmap getFilteredImage() {
349         return MasterImage.getImage().getFilteredImage();
350     }
351 
drawImageAndAnimate(Canvas canvas, Bitmap image)352     public void drawImageAndAnimate(Canvas canvas,
353                                     Bitmap image) {
354         if (image == null) {
355             return;
356         }
357         MasterImage master = MasterImage.getImage();
358         Matrix m = master.computeImageToScreen(image, 0, false);
359         if (m == null) {
360             return;
361         }
362 
363         canvas.save();
364 
365         RectF d = new RectF(0, 0, image.getWidth(), image.getHeight());
366         m.mapRect(d);
367         d.roundOut(mImageBounds);
368 
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();
376             } else {
377                 showAnimatedImage = true;
378             }
379         } else if (showAnimatedImage) {
380             mDidStartAnimation = true;
381         }
382 
383         if (showAnimatedImage) {
384             canvas.save();
385 
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());
390             mp.mapRect(dp);
391             Rect previousBounds = new Rect();
392             dp.roundOut(previousBounds);
393             float centerX = dp.centerX();
394             float centerY = dp.centerY();
395             boolean needsToDrawImage = true;
396 
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;
409 
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);
417                     mMaskPaint.reset();
418                     Shader maskShader = createShader(image);
419                     maskShader.setLocalMatrix(mShaderMatrix);
420                     mMaskPaint.setShader(maskShader);
421 
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;
429                 }
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();
447 
448                     ImagePreset preset = master.getPreset();
449                     ArrayList<FilterRepresentation> geometry =
450                             (ArrayList<FilterRepresentation>) preset.getGeometryFilters();
451                     GeometryMathUtils.GeometryHolder holder = null;
452                     holder = GeometryMathUtils.unpackGeometry(geometry);
453 
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);
461                         } else {
462                             canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY);
463                         }
464                     } else {
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);
471                         } else {
472                             canvas.scale(1, master.getAnimRotationValue(), centerX, centerY);
473                         }
474                     }
475                 }
476             }
477 
478             if (needsToDrawImage) {
479                 drawShadow(canvas, previousBounds); // as needed
480                 canvas.drawBitmap(previousImage, mp, mPaint);
481             }
482 
483             canvas.restore();
484         } else {
485             drawShadow(canvas, mImageBounds); // as needed
486             canvas.drawBitmap(image, m, mPaint);
487         }
488 
489         canvas.restore();
490     }
491 
computeImageBounds(int imageWidth, int imageHeight)492     private Rect computeImageBounds(int imageWidth, int imageHeight) {
493         float scale = GeometryMathUtils.scale(imageWidth, imageHeight,
494                 getWidth(), getHeight());
495 
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);
504     }
505 
drawShadow(Canvas canvas, Rect d)506     private void drawShadow(Canvas canvas, Rect d) {
507         if (!mShadowDrawn) {
508             mShadowBounds.set(d.left - mShadowMargin, d.top - mShadowMargin,
509                     d.right + mShadowMargin, d.bottom + mShadowMargin);
510             mShadow.setBounds(mShadowBounds);
511             mShadow.draw(canvas);
512             mShadowDrawn = true;
513         }
514     }
515 
drawCompareImage(Canvas canvas, Bitmap image)516     public void drawCompareImage(Canvas canvas, Bitmap image) {
517         MasterImage master = MasterImage.getImage();
518         boolean showsOriginal = master.showsOriginal();
519         if (!showsOriginal && !mTouchShowOriginal)
520             return;
521         canvas.save();
522         if (image != null) {
523             if (mShowOriginalDirection == 0) {
524                 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
525                     mShowOriginalDirection = UNVEIL_VERTICAL;
526                 } else {
527                     mShowOriginalDirection = UNVEIL_HORIZONTAL;
528                 }
529             }
530 
531             int px = 0;
532             int py = 0;
533             if (mShowOriginalDirection == UNVEIL_VERTICAL) {
534                 px = mImageBounds.width();
535                 py = mTouch.y - mImageBounds.top;
536             } else {
537                 px = mTouch.x - mImageBounds.left;
538                 py = mImageBounds.height();
539                 if (showsOriginal) {
540                     px = mImageBounds.width();
541                 }
542             }
543 
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);
550                 }
551             } else {
552                 if (mTouchDown.y - mTouch.y > 0) {
553                     d.set(mImageBounds.left, mImageBounds.top + py,
554                             mImageBounds.left + px, mImageBounds.bottom);
555                 }
556             }
557             canvas.clipRect(d);
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);
563 
564             if (mShowOriginalDirection == UNVEIL_VERTICAL) {
565                 canvas.drawLine(mImageBounds.left, mTouch.y,
566                         mImageBounds.right, mTouch.y, paint);
567             } else {
568                 canvas.drawLine(mTouch.x, mImageBounds.top,
569                         mTouch.x, mImageBounds.bottom, paint);
570             }
571 
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);
586         }
587         canvas.restore();
588     }
589 
bindAsImageLoadListener()590     public void bindAsImageLoadListener() {
591         MasterImage.getImage().addListener(this);
592     }
593 
updateImage()594     public void updateImage() {
595         invalidate();
596     }
597 
imageLoaded()598     public void imageLoaded() {
599         updateImage();
600     }
601 
saveImage(FilterShowActivity filterShowActivity, File file)602     public void saveImage(FilterShowActivity filterShowActivity, File file) {
603         SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
604     }
605 
606 
scaleInProgress()607     public boolean scaleInProgress() {
608         return mScaleGestureDetector.isInProgress();
609     }
610 
611     @Override
onTouchEvent(MotionEvent event)612     public boolean onTouchEvent(MotionEvent event) {
613         super.onTouchEvent(event);
614         int action = event.getAction();
615         action = action & MotionEvent.ACTION_MASK;
616 
617         mGestureDetector.onTouchEvent(event);
618         boolean scaleInProgress = scaleInProgress();
619         mScaleGestureDetector.onTouchEvent(event);
620         if (mInteractionMode == InteractionMode.SCALE) {
621             return true;
622         }
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;
627         }
628 
629         int ex = (int) event.getX();
630         int ey = (int) event.getY();
631         if (action == MotionEvent.ACTION_DOWN) {
632             mInteractionMode = InteractionMode.MOVE;
633             mTouchDown.x = ex;
634             mTouchDown.y = ey;
635             mTouchShowOriginalDate = System.currentTimeMillis();
636             mShowOriginalDirection = 0;
637             MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
638         }
639 
640         if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
641             mTouch.x = ex;
642             mTouch.y = ey;
643 
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;
659             }
660         }
661 
662         if (action == MotionEvent.ACTION_UP
663                 || action == MotionEvent.ACTION_CANCEL
664                 || action == MotionEvent.ACTION_OUTSIDE) {
665             mInteractionMode = InteractionMode.NONE;
666             mTouchShowOriginal = false;
667             mTouchDown.x = 0;
668             mTouchDown.y = 0;
669             mTouch.x = 0;
670             mTouch.y = 0;
671             if (MasterImage.getImage().getScaleFactor() <= 1) {
672                 MasterImage.getImage().setScaleFactor(1);
673                 MasterImage.getImage().resetTranslation();
674             }
675         }
676 
677         float scaleFactor = MasterImage.getImage().getScaleFactor();
678         Point translation = MasterImage.getImage().getTranslation();
679         constrainTranslation(translation, scaleFactor);
680         MasterImage.getImage().setTranslation(translation);
681 
682         invalidate();
683         return true;
684     }
685 
startAnimTranslation(int fromX, int toX, int fromY, int toY, int delay)686     private void startAnimTranslation(int fromX, int toX,
687                                       int fromY, int toY, int delay) {
688         if (fromX == toX && fromY == toY) {
689             return;
690         }
691         if (mAnimatorTranslateX != null) {
692             mAnimatorTranslateX.cancel();
693         }
694         if (mAnimatorTranslateY != null) {
695             mAnimatorTranslateY.cancel();
696         }
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() {
702             @Override
703             public void onAnimationUpdate(ValueAnimator animation) {
704                 Point translation = MasterImage.getImage().getTranslation();
705                 translation.x = (Integer) animation.getAnimatedValue();
706                 MasterImage.getImage().setTranslation(translation);
707                 invalidate();
708             }
709         });
710         mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
711             @Override
712             public void onAnimationUpdate(ValueAnimator animation) {
713                 Point translation = MasterImage.getImage().getTranslation();
714                 translation.y = (Integer) animation.getAnimatedValue();
715                 MasterImage.getImage().setTranslation(translation);
716                 invalidate();
717             }
718         });
719         mAnimatorTranslateX.start();
720         mAnimatorTranslateY.start();
721     }
722 
applyTranslationConstraints()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);
729 
730         if (x != translation.x || y != translation.y) {
731             startAnimTranslation(x, translation.x,
732                                  y, translation.y,
733                                  mAnimationSnapDelay);
734         }
735     }
736 
enableComparison()737     protected boolean enableComparison() {
738         return true;
739     }
740 
741     @Override
onDoubleTap(MotionEvent arg0)742     public boolean onDoubleTap(MotionEvent arg0) {
743         mZoomIn = !mZoomIn;
744         float scale = 1.0f;
745         final float x = arg0.getX();
746         final float y = arg0.getY();
747         if (mZoomIn) {
748             scale = MasterImage.getImage().getMaxScaleFactor();
749         }
750         if (scale != MasterImage.getImage().getScaleFactor()) {
751             if (mAnimatorScale != null) {
752                 mAnimatorScale.cancel();
753             }
754             mAnimatorScale = ValueAnimator.ofFloat(
755                     MasterImage.getImage().getScaleFactor(),
756                     scale
757             );
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;
763             if (scale != 1.0f) {
764                 translation.x = (int) (mOriginalTranslation.x + translateX);
765                 translation.y = (int) (mOriginalTranslation.y + translateY);
766             } else {
767                 translation.x = 0;
768                 translation.y = 0;
769             }
770             constrainTranslation(translation, scale);
771 
772             startAnimTranslation(startTranslateX, translation.x,
773                                  startTranslateY, translation.y,
774                                  mAnimationZoomDelay);
775             mAnimatorScale.setDuration(mAnimationZoomDelay);
776             mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
777                 @Override
778                 public void onAnimationUpdate(ValueAnimator animation) {
779                     MasterImage.getImage().setScaleFactor((Float) animation.getAnimatedValue());
780                     invalidate();
781                 }
782             });
783             mAnimatorScale.addListener(new Animator.AnimatorListener() {
784                 @Override
785                 public void onAnimationStart(Animator animation) {
786                 }
787 
788                 @Override
789                 public void onAnimationEnd(Animator animation) {
790                     applyTranslationConstraints();
791                     MasterImage.getImage().needsUpdatePartialPreview();
792                     invalidate();
793                 }
794 
795                 @Override
796                 public void onAnimationCancel(Animator animation) {
797 
798                 }
799 
800                 @Override
801                 public void onAnimationRepeat(Animator animation) {
802 
803                 }
804             });
805             mAnimatorScale.start();
806         }
807         return true;
808     }
809 
constrainTranslation(Point translation, float scale)810     private void constrainTranslation(Point translation, float scale) {
811         int currentEdgeEffect = 0;
812         if (scale <= 1) {
813             mCurrentEdgeEffect = 0;
814             mEdgeEffect.finish();
815             return;
816         }
817 
818         Matrix originalToScreen = MasterImage.getImage().originalImageToScreen();
819         Rect originalBounds = MasterImage.getImage().getOriginalBounds();
820         RectF screenPos = new RectF(originalBounds);
821         originalToScreen.mapRect(screenPos);
822 
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;
827 
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;
837             }
838         } else {
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);
842         }
843 
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;
853             }
854         } else {
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);
858         }
859 
860         if (mCurrentEdgeEffect != currentEdgeEffect) {
861             if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) {
862                 mCurrentEdgeEffect = currentEdgeEffect;
863                 mEdgeEffect.finish();
864             }
865             mEdgeEffect.setSize(getWidth(), mEdgeSize);
866         }
867         if (currentEdgeEffect != 0) {
868             mEdgeEffect.onPull(mEdgeSize);
869         }
870     }
871 
872     @Override
873     public boolean onDoubleTapEvent(MotionEvent arg0) {
874         return false;
875     }
876 
877     @Override
878     public boolean onSingleTapConfirmed(MotionEvent arg0) {
879         return false;
880     }
881 
882     @Override
883     public boolean onDown(MotionEvent arg0) {
884         return false;
885     }
886 
887     @Override
888     public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
889         if (mActivity == null) {
890             return false;
891         }
892         if (endEvent.getPointerCount() == 2) {
893             return false;
894         }
895         return true;
896     }
897 
898     @Override
899     public void onLongPress(MotionEvent arg0) {
900     }
901 
902     @Override
903     public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
904         return false;
905     }
906 
907     @Override
908     public void onShowPress(MotionEvent arg0) {
909     }
910 
911     @Override
912     public boolean onSingleTapUp(MotionEvent arg0) {
913         return false;
914     }
915 
916     public boolean useUtilityPanel() {
917         return false;
918     }
919 
920     public void openUtilityPanel(final LinearLayout accessoryViewList) {
921     }
922 
923     @Override
924     public boolean onScale(ScaleGestureDetector detector) {
925         MasterImage img = MasterImage.getImage();
926         float scaleFactor = img.getScaleFactor();
927 
928         scaleFactor = scaleFactor * detector.getScaleFactor();
929         if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
930             scaleFactor = MasterImage.getImage().getMaxScaleFactor();
931         }
932         if (scaleFactor < 1.0f) {
933             scaleFactor = 1.0f;
934         }
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);
945         invalidate();
946         return true;
947     }
948 
949     @Override
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;
958         return true;
959     }
960 
961     @Override
962     public void onScaleEnd(ScaleGestureDetector detector) {
963         mInteractionMode = InteractionMode.NONE;
964         if (MasterImage.getImage().getScaleFactor() < 1) {
965             MasterImage.getImage().setScaleFactor(1);
966             invalidate();
967         }
968     }
969 
970     public boolean didFinishScalingOperation() {
971         if (mFinishedScalingOperation) {
972             mFinishedScalingOperation = false;
973             return true;
974         }
975         return false;
976     }
977 
978 }
979