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 android.util.AttributeSet;
36 import android.util.Log;
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;
44 import androidx.core.widget.EdgeEffectCompat;
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 PrimaryImage.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         PrimaryImage.getImage().addObserver(this);
191         bindAsImageLoadListener();
192         PrimaryImage.getImage().resetGeometryImages(false);
193     }
194 
detach()195     public void detach() {
196         PrimaryImage.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 PrimaryImage.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         PrimaryImage primary = PrimaryImage.getImage();
225         if (primary.getOriginalBounds() == null) {
226             return new Matrix();
227         }
228         Matrix m = GeometryMathUtils.getImageToScreenMatrix(
229                 primary.getPreset().getGeometryFilters(), reflectRotation,
230                 primary.getOriginalBounds(), getWidth(), getHeight());
231         Point translate = primary.getTranslation();
232         float scaleFactor = primary.getScaleFactor();
233         m.postTranslate(translate.x, translate.y);
234         m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
235         return m;
236     }
237 
238     /**
239      * This function calculates a to Screen Image Transformation matrix
240      *
241      * @param reflectRotation set true if you want the rotation encoded
242      * @return Screen to Image transformation matrix
243      */
getScreenToImageMatrix(boolean reflectRotation)244     protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
245         Matrix m = getImageToScreenMatrix(reflectRotation);
246         Matrix invert = new Matrix();
247         m.invert(invert);
248         return invert;
249     }
250 
getImagePreset()251     public ImagePreset getImagePreset() {
252         return PrimaryImage.getImage().getPreset();
253     }
254 
255     @Override
onDraw(Canvas canvas)256     public void onDraw(Canvas canvas) {
257         mPaint.reset();
258         mPaint.setAntiAlias(true);
259         mPaint.setFilterBitmap(true);
260         PrimaryImage.getImage().setImageShowSize(
261                 getWidth() - 2*mShadowMargin,
262                 getHeight() - 2*mShadowMargin);
263 
264         PrimaryImage img = PrimaryImage.getImage();
265         // Hide the loading indicator as needed
266         if (mActivity.isLoadingVisible() && getFilteredImage() != null) {
267             if ((img.getLoadedPreset() == null)
268                     || (img.getLoadedPreset() != null
269                     && img.getLoadedPreset().equals(img.getCurrentPreset()))) {
270                 mActivity.stopLoadingIndicator();
271             } else if (img.getLoadedPreset() != null) {
272                 return;
273             }
274             mActivity.stopLoadingIndicator();
275         }
276 
277         canvas.save();
278 
279         mShadowDrawn = false;
280 
281         Bitmap highresPreview = PrimaryImage.getImage().getHighresImage();
282         Bitmap fullHighres = PrimaryImage.getImage().getPartialImage();
283 
284         boolean isDoingNewLookAnimation = PrimaryImage.getImage().onGoingNewLookAnimation();
285 
286         if (highresPreview == null || isDoingNewLookAnimation) {
287             drawImageAndAnimate(canvas, getFilteredImage());
288         } else {
289             drawImageAndAnimate(canvas, highresPreview);
290         }
291 
292         drawHighresImage(canvas, fullHighres);
293         drawCompareImage(canvas, getGeometryOnlyImage());
294 
295         canvas.restore();
296 
297         if (!mEdgeEffect.isFinished()) {
298             canvas.save();
299             float dx = (getHeight() - getWidth()) / 2f;
300             if (getWidth() > getHeight()) {
301                 dx = - (getWidth() - getHeight()) / 2f;
302             }
303             if (mCurrentEdgeEffect == EDGE_BOTTOM) {
304                 canvas.rotate(180, getWidth()/2, getHeight()/2);
305             } else if (mCurrentEdgeEffect == EDGE_RIGHT) {
306                 canvas.rotate(90, getWidth()/2, getHeight()/2);
307                 canvas.translate(0, dx);
308             } else if (mCurrentEdgeEffect == EDGE_LEFT) {
309                 canvas.rotate(270, getWidth()/2, getHeight()/2);
310                 canvas.translate(0, dx);
311             }
312             if (mCurrentEdgeEffect != 0) {
313                 mEdgeEffect.draw(canvas);
314             }
315             canvas.restore();
316             invalidate();
317         } else {
318             mCurrentEdgeEffect = 0;
319         }
320     }
321 
drawHighresImage(Canvas canvas, Bitmap fullHighres)322     private void drawHighresImage(Canvas canvas, Bitmap fullHighres) {
323         Matrix originalToScreen = PrimaryImage.getImage().originalImageToScreen();
324         if (fullHighres != null && originalToScreen != null) {
325             Matrix screenToOriginal = new Matrix();
326             originalToScreen.invert(screenToOriginal);
327             Rect rBounds = new Rect();
328             rBounds.set(PrimaryImage.getImage().getPartialBounds());
329             if (fullHighres != null) {
330                 originalToScreen.preTranslate(rBounds.left, rBounds.top);
331                 canvas.clipRect(mImageBounds);
332                 canvas.drawBitmap(fullHighres, originalToScreen, mPaint);
333             }
334         }
335     }
336 
resetImageCaches(ImageShow caller)337     public void resetImageCaches(ImageShow caller) {
338         PrimaryImage.getImage().invalidatePreview();
339     }
340 
getFiltersOnlyImage()341     public Bitmap getFiltersOnlyImage() {
342         return PrimaryImage.getImage().getFiltersOnlyImage();
343     }
344 
getGeometryOnlyImage()345     public Bitmap getGeometryOnlyImage() {
346         return PrimaryImage.getImage().getGeometryOnlyImage();
347     }
348 
getFilteredImage()349     public Bitmap getFilteredImage() {
350         return PrimaryImage.getImage().getFilteredImage();
351     }
352 
drawImageAndAnimate(Canvas canvas, Bitmap image)353     public void drawImageAndAnimate(Canvas canvas,
354                                     Bitmap image) {
355         if (image == null) {
356             return;
357         }
358         PrimaryImage primary = PrimaryImage.getImage();
359         Matrix m = primary.computeImageToScreen(image, 0, false);
360         if (m == null) {
361             return;
362         }
363 
364         canvas.save();
365 
366         RectF d = new RectF(0, 0, image.getWidth(), image.getHeight());
367         m.mapRect(d);
368         d.roundOut(mImageBounds);
369 
370         boolean showAnimatedImage = primary.onGoingNewLookAnimation();
371         if (!showAnimatedImage && mDidStartAnimation) {
372             // animation ended, but do we have the correct image to show?
373             if (primary.getPreset().equals(primary.getCurrentPreset())) {
374                 // we do, let's stop showing the animated image
375                 mDidStartAnimation = false;
376                 PrimaryImage.getImage().resetAnimBitmap();
377             } else {
378                 showAnimatedImage = true;
379             }
380         } else if (showAnimatedImage) {
381             mDidStartAnimation = true;
382         }
383 
384         if (showAnimatedImage) {
385             canvas.save();
386 
387             // Animation uses the image before the change
388             Bitmap previousImage = primary.getPreviousImage();
389             Matrix mp = primary.computeImageToScreen(previousImage, 0, false);
390             RectF dp = new RectF(0, 0, previousImage.getWidth(), previousImage.getHeight());
391             mp.mapRect(dp);
392             Rect previousBounds = new Rect();
393             dp.roundOut(previousBounds);
394             float centerX = dp.centerX();
395             float centerY = dp.centerY();
396             boolean needsToDrawImage = true;
397 
398             if (primary.getCurrentLookAnimation()
399                     == PrimaryImage.CIRCLE_ANIMATION) {
400                 float maskScale = PrimaryImage.getImage().getMaskScale();
401                 if (maskScale >= 0.0f) {
402                     float maskW = sMask.getWidth() / 2.0f;
403                     float maskH = sMask.getHeight() / 2.0f;
404                     Point point = mActivity.hintTouchPoint(this);
405                     float maxMaskScale = 2 * Math.max(getWidth(), getHeight())
406                             / Math.min(maskW, maskH);
407                     maskScale = maskScale * maxMaskScale;
408                     float x = point.x - maskW * maskScale;
409                     float y = point.y - maskH * maskScale;
410 
411                     // Prepare the shader
412                     mShaderMatrix.reset();
413                     mShaderMatrix.setScale(1.0f / maskScale, 1.0f / maskScale);
414                     mShaderMatrix.preTranslate(-x + mImageBounds.left, -y + mImageBounds.top);
415                     float scaleImageX = mImageBounds.width() / (float) image.getWidth();
416                     float scaleImageY = mImageBounds.height() / (float) image.getHeight();
417                     mShaderMatrix.preScale(scaleImageX, scaleImageY);
418                     mMaskPaint.reset();
419                     Shader maskShader = createShader(image);
420                     maskShader.setLocalMatrix(mShaderMatrix);
421                     mMaskPaint.setShader(maskShader);
422 
423                     drawShadow(canvas, mImageBounds); // as needed
424                     canvas.drawBitmap(previousImage, m, mPaint);
425                     canvas.clipRect(mImageBounds);
426                     canvas.translate(x, y);
427                     canvas.scale(maskScale, maskScale);
428                     canvas.drawBitmap(sMask, 0, 0, mMaskPaint);
429                     needsToDrawImage = false;
430                 }
431             } else if (primary.getCurrentLookAnimation()
432                     == PrimaryImage.ROTATE_ANIMATION) {
433                 Rect d1 = computeImageBounds(primary.getPreviousImage().getHeight(),
434                         primary.getPreviousImage().getWidth());
435                 Rect d2 = computeImageBounds(primary.getPreviousImage().getWidth(),
436                         primary.getPreviousImage().getHeight());
437                 float finalScale = d1.width() / (float) d2.height();
438                 finalScale = (1.0f * (1.0f - primary.getAnimFraction()))
439                         + (finalScale * primary.getAnimFraction());
440                 canvas.rotate(primary.getAnimRotationValue(), centerX, centerY);
441                 canvas.scale(finalScale, finalScale, centerX, centerY);
442             } else if (primary.getCurrentLookAnimation()
443                     == PrimaryImage.MIRROR_ANIMATION) {
444                 if (primary.getCurrentFilterRepresentation()
445                         instanceof FilterMirrorRepresentation) {
446                     FilterMirrorRepresentation rep =
447                             (FilterMirrorRepresentation) primary.getCurrentFilterRepresentation();
448 
449                     ImagePreset preset = primary.getPreset();
450                     ArrayList<FilterRepresentation> geometry =
451                             (ArrayList<FilterRepresentation>) preset.getGeometryFilters();
452                     GeometryMathUtils.GeometryHolder holder = null;
453                     holder = GeometryMathUtils.unpackGeometry(geometry);
454 
455                     if (holder.rotation.value() == 90 || holder.rotation.value() == 270) {
456                         if (rep.isHorizontal() && !rep.isVertical()) {
457                             canvas.scale(1, primary.getAnimRotationValue(), centerX, centerY);
458                         } else if (rep.isVertical() && !rep.isHorizontal()) {
459                             canvas.scale(1, primary.getAnimRotationValue(), centerX, centerY);
460                         } else if (rep.isHorizontal() && rep.isVertical()) {
461                             canvas.scale(primary.getAnimRotationValue(), 1, centerX, centerY);
462                         } else {
463                             canvas.scale(primary.getAnimRotationValue(), 1, centerX, centerY);
464                         }
465                     } else {
466                         if (rep.isHorizontal() && !rep.isVertical()) {
467                             canvas.scale(primary.getAnimRotationValue(), 1, centerX, centerY);
468                         } else if (rep.isVertical() && !rep.isHorizontal()) {
469                             canvas.scale(primary.getAnimRotationValue(), 1, centerX, centerY);
470                         } else  if (rep.isHorizontal() && rep.isVertical()) {
471                             canvas.scale(1, primary.getAnimRotationValue(), centerX, centerY);
472                         } else {
473                             canvas.scale(1, primary.getAnimRotationValue(), centerX, centerY);
474                         }
475                     }
476                 }
477             }
478 
479             if (needsToDrawImage) {
480                 drawShadow(canvas, previousBounds); // as needed
481                 canvas.drawBitmap(previousImage, mp, mPaint);
482             }
483 
484             canvas.restore();
485         } else {
486             drawShadow(canvas, mImageBounds); // as needed
487             canvas.drawBitmap(image, m, mPaint);
488         }
489 
490         canvas.restore();
491     }
492 
computeImageBounds(int imageWidth, int imageHeight)493     private Rect computeImageBounds(int imageWidth, int imageHeight) {
494         float scale = GeometryMathUtils.scale(imageWidth, imageHeight,
495                 getWidth(), getHeight());
496 
497         float w = imageWidth * scale;
498         float h = imageHeight * scale;
499         float ty = (getHeight() - h) / 2.0f;
500         float tx = (getWidth() - w) / 2.0f;
501         return new Rect((int) tx + mShadowMargin,
502                 (int) ty + mShadowMargin,
503                 (int) (w + tx) - mShadowMargin,
504                 (int) (h + ty) - mShadowMargin);
505     }
506 
drawShadow(Canvas canvas, Rect d)507     private void drawShadow(Canvas canvas, Rect d) {
508         if (!mShadowDrawn) {
509             mShadowBounds.set(d.left - mShadowMargin, d.top - mShadowMargin,
510                     d.right + mShadowMargin, d.bottom + mShadowMargin);
511             mShadow.setBounds(mShadowBounds);
512             mShadow.draw(canvas);
513             mShadowDrawn = true;
514         }
515     }
516 
drawCompareImage(Canvas canvas, Bitmap image)517     public void drawCompareImage(Canvas canvas, Bitmap image) {
518         PrimaryImage primary = PrimaryImage.getImage();
519         boolean showsOriginal = primary.showsOriginal();
520         if (!showsOriginal && !mTouchShowOriginal)
521             return;
522         canvas.save();
523         if (image != null) {
524             if (mShowOriginalDirection == 0) {
525                 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
526                     mShowOriginalDirection = UNVEIL_VERTICAL;
527                 } else {
528                     mShowOriginalDirection = UNVEIL_HORIZONTAL;
529                 }
530             }
531 
532             int px = 0;
533             int py = 0;
534             if (mShowOriginalDirection == UNVEIL_VERTICAL) {
535                 px = mImageBounds.width();
536                 py = mTouch.y - mImageBounds.top;
537             } else {
538                 px = mTouch.x - mImageBounds.left;
539                 py = mImageBounds.height();
540                 if (showsOriginal) {
541                     px = mImageBounds.width();
542                 }
543             }
544 
545             Rect d = new Rect(mImageBounds.left, mImageBounds.top,
546                     mImageBounds.left + px, mImageBounds.top + py);
547             if (mShowOriginalDirection == UNVEIL_HORIZONTAL) {
548                 if (mTouchDown.x - mTouch.x > 0) {
549                     d.set(mImageBounds.left + px, mImageBounds.top,
550                             mImageBounds.right, mImageBounds.top + py);
551                 }
552             } else {
553                 if (mTouchDown.y - mTouch.y > 0) {
554                     d.set(mImageBounds.left, mImageBounds.top + py,
555                             mImageBounds.left + px, mImageBounds.bottom);
556                 }
557             }
558             canvas.clipRect(d);
559             Matrix m = primary.computeImageToScreen(image, 0, false);
560             canvas.drawBitmap(image, m, mPaint);
561             Paint paint = new Paint();
562             paint.setColor(Color.BLACK);
563             paint.setStrokeWidth(3);
564 
565             if (mShowOriginalDirection == UNVEIL_VERTICAL) {
566                 canvas.drawLine(mImageBounds.left, mTouch.y,
567                         mImageBounds.right, mTouch.y, paint);
568             } else {
569                 canvas.drawLine(mTouch.x, mImageBounds.top,
570                         mTouch.x, mImageBounds.bottom, paint);
571             }
572 
573             Rect bounds = new Rect();
574             paint.setAntiAlias(true);
575             paint.setTextSize(mOriginalTextSize);
576             paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
577             paint.setColor(Color.BLACK);
578             paint.setStyle(Paint.Style.STROKE);
579             paint.setStrokeWidth(3);
580             canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
581                     mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
582             paint.setStyle(Paint.Style.FILL);
583             paint.setStrokeWidth(1);
584             paint.setColor(Color.WHITE);
585             canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
586                     mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
587         }
588         canvas.restore();
589     }
590 
bindAsImageLoadListener()591     public void bindAsImageLoadListener() {
592         PrimaryImage.getImage().addListener(this);
593     }
594 
updateImage()595     public void updateImage() {
596         invalidate();
597     }
598 
imageLoaded()599     public void imageLoaded() {
600         updateImage();
601     }
602 
saveImage(FilterShowActivity filterShowActivity, File file)603     public void saveImage(FilterShowActivity filterShowActivity, File file) {
604         SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
605     }
606 
607 
scaleInProgress()608     public boolean scaleInProgress() {
609         return mScaleGestureDetector.isInProgress();
610     }
611 
612     @Override
onTouchEvent(MotionEvent event)613     public boolean onTouchEvent(MotionEvent event) {
614         super.onTouchEvent(event);
615         int action = event.getAction();
616         action = action & MotionEvent.ACTION_MASK;
617 
618         mGestureDetector.onTouchEvent(event);
619         boolean scaleInProgress = scaleInProgress();
620         mScaleGestureDetector.onTouchEvent(event);
621         if (mInteractionMode == InteractionMode.SCALE) {
622             return true;
623         }
624         if (!scaleInProgress() && scaleInProgress) {
625             // If we were scaling, the scale will stop but we will
626             // still issue an ACTION_UP. Let the subclasses know.
627             mFinishedScalingOperation = true;
628         }
629 
630         int ex = (int) event.getX();
631         int ey = (int) event.getY();
632         if (action == MotionEvent.ACTION_DOWN) {
633             mInteractionMode = InteractionMode.MOVE;
634             mTouchDown.x = ex;
635             mTouchDown.y = ey;
636             mTouchShowOriginalDate = System.currentTimeMillis();
637             mShowOriginalDirection = 0;
638             PrimaryImage.getImage().setOriginalTranslation(
639                     PrimaryImage.getImage().getTranslation());
640         }
641 
642         if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
643             mTouch.x = ex;
644             mTouch.y = ey;
645 
646             float scaleFactor = PrimaryImage.getImage().getScaleFactor();
647             if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
648                 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
649                 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
650                 Point originalTranslation = PrimaryImage.getImage().getOriginalTranslation();
651                 Point translation = PrimaryImage.getImage().getTranslation();
652                 translation.x = (int) (originalTranslation.x + translateX);
653                 translation.y = (int) (originalTranslation.y + translateY);
654                 PrimaryImage.getImage().setTranslation(translation);
655                 mTouchShowOriginal = false;
656             } else if (enableComparison() && !mOriginalDisabled
657                     && (System.currentTimeMillis() - mTouchShowOriginalDate
658                             > mTouchShowOriginalDelayMin)
659                     && event.getPointerCount() == 1) {
660                 mTouchShowOriginal = true;
661             }
662         }
663 
664         if (action == MotionEvent.ACTION_UP
665                 || action == MotionEvent.ACTION_CANCEL
666                 || action == MotionEvent.ACTION_OUTSIDE) {
667             mInteractionMode = InteractionMode.NONE;
668             mTouchShowOriginal = false;
669             mTouchDown.x = 0;
670             mTouchDown.y = 0;
671             mTouch.x = 0;
672             mTouch.y = 0;
673             if (PrimaryImage.getImage().getScaleFactor() <= 1) {
674                 PrimaryImage.getImage().setScaleFactor(1);
675                 PrimaryImage.getImage().resetTranslation();
676             }
677         }
678 
679         float scaleFactor = PrimaryImage.getImage().getScaleFactor();
680         Point translation = PrimaryImage.getImage().getTranslation();
681         constrainTranslation(translation, scaleFactor);
682         PrimaryImage.getImage().setTranslation(translation);
683 
684         invalidate();
685         return true;
686     }
687 
startAnimTranslation(int fromX, int toX, int fromY, int toY, int delay)688     private void startAnimTranslation(int fromX, int toX,
689                                       int fromY, int toY, int delay) {
690         if (fromX == toX && fromY == toY) {
691             return;
692         }
693         if (mAnimatorTranslateX != null) {
694             mAnimatorTranslateX.cancel();
695         }
696         if (mAnimatorTranslateY != null) {
697             mAnimatorTranslateY.cancel();
698         }
699         mAnimatorTranslateX = ValueAnimator.ofInt(fromX, toX);
700         mAnimatorTranslateY = ValueAnimator.ofInt(fromY, toY);
701         mAnimatorTranslateX.setDuration(delay);
702         mAnimatorTranslateY.setDuration(delay);
703         mAnimatorTranslateX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
704             @Override
705             public void onAnimationUpdate(ValueAnimator animation) {
706                 Point translation = PrimaryImage.getImage().getTranslation();
707                 translation.x = (Integer) animation.getAnimatedValue();
708                 PrimaryImage.getImage().setTranslation(translation);
709                 invalidate();
710             }
711         });
712         mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
713             @Override
714             public void onAnimationUpdate(ValueAnimator animation) {
715                 Point translation = PrimaryImage.getImage().getTranslation();
716                 translation.y = (Integer) animation.getAnimatedValue();
717                 PrimaryImage.getImage().setTranslation(translation);
718                 invalidate();
719             }
720         });
721         mAnimatorTranslateX.start();
722         mAnimatorTranslateY.start();
723     }
724 
applyTranslationConstraints()725     private void applyTranslationConstraints() {
726         float scaleFactor = PrimaryImage.getImage().getScaleFactor();
727         Point translation = PrimaryImage.getImage().getTranslation();
728         int x = translation.x;
729         int y = translation.y;
730         constrainTranslation(translation, scaleFactor);
731 
732         if (x != translation.x || y != translation.y) {
733             startAnimTranslation(x, translation.x,
734                                  y, translation.y,
735                                  mAnimationSnapDelay);
736         }
737     }
738 
enableComparison()739     protected boolean enableComparison() {
740         return true;
741     }
742 
743     @Override
onDoubleTap(MotionEvent arg0)744     public boolean onDoubleTap(MotionEvent arg0) {
745         mZoomIn = !mZoomIn;
746         float scale = 1.0f;
747         final float x = arg0.getX();
748         final float y = arg0.getY();
749         if (mZoomIn) {
750             scale = PrimaryImage.getImage().getMaxScaleFactor();
751         }
752         if (scale != PrimaryImage.getImage().getScaleFactor()) {
753             if (mAnimatorScale != null) {
754                 mAnimatorScale.cancel();
755             }
756             mAnimatorScale = ValueAnimator.ofFloat(
757                     PrimaryImage.getImage().getScaleFactor(),
758                     scale
759             );
760             float translateX = (getWidth() / 2 - x);
761             float translateY = (getHeight() / 2 - y);
762             Point translation = PrimaryImage.getImage().getTranslation();
763             int startTranslateX = translation.x;
764             int startTranslateY = translation.y;
765             if (scale != 1.0f) {
766                 translation.x = (int) (mOriginalTranslation.x + translateX);
767                 translation.y = (int) (mOriginalTranslation.y + translateY);
768             } else {
769                 translation.x = 0;
770                 translation.y = 0;
771             }
772             constrainTranslation(translation, scale);
773 
774             startAnimTranslation(startTranslateX, translation.x,
775                                  startTranslateY, translation.y,
776                                  mAnimationZoomDelay);
777             mAnimatorScale.setDuration(mAnimationZoomDelay);
778             mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
779                 @Override
780                 public void onAnimationUpdate(ValueAnimator animation) {
781                     PrimaryImage.getImage().setScaleFactor((Float) animation.getAnimatedValue());
782                     invalidate();
783                 }
784             });
785             mAnimatorScale.addListener(new Animator.AnimatorListener() {
786                 @Override
787                 public void onAnimationStart(Animator animation) {
788                 }
789 
790                 @Override
791                 public void onAnimationEnd(Animator animation) {
792                     applyTranslationConstraints();
793                     PrimaryImage.getImage().needsUpdatePartialPreview();
794                     invalidate();
795                 }
796 
797                 @Override
798                 public void onAnimationCancel(Animator animation) {
799 
800                 }
801 
802                 @Override
803                 public void onAnimationRepeat(Animator animation) {
804 
805                 }
806             });
807             mAnimatorScale.start();
808         }
809         return true;
810     }
811 
constrainTranslation(Point translation, float scale)812     private void constrainTranslation(Point translation, float scale) {
813         int currentEdgeEffect = 0;
814         if (scale <= 1) {
815             mCurrentEdgeEffect = 0;
816             mEdgeEffect.finish();
817             return;
818         }
819 
820         Matrix originalToScreen = PrimaryImage.getImage().originalImageToScreen();
821         Rect originalBounds = PrimaryImage.getImage().getOriginalBounds();
822         RectF screenPos = new RectF(originalBounds);
823         originalToScreen.mapRect(screenPos);
824 
825         boolean rightConstraint = screenPos.right < getWidth() - mShadowMargin;
826         boolean leftConstraint = screenPos.left > mShadowMargin;
827         boolean topConstraint = screenPos.top > mShadowMargin;
828         boolean bottomConstraint = screenPos.bottom < getHeight() - mShadowMargin;
829 
830         if (screenPos.width() > getWidth()) {
831             if (rightConstraint && !leftConstraint) {
832                 float tx = screenPos.right - translation.x * scale;
833                 translation.x = (int) ((getWidth() - mShadowMargin - tx) / scale);
834                 currentEdgeEffect = EDGE_RIGHT;
835             } else if (leftConstraint && !rightConstraint) {
836                 float tx = screenPos.left - translation.x * scale;
837                 translation.x = (int) ((mShadowMargin - tx) / scale);
838                 currentEdgeEffect = EDGE_LEFT;
839             }
840         } else {
841             float tx = screenPos.right - translation.x * scale;
842             float dx = (getWidth() - 2 * mShadowMargin - screenPos.width()) / 2f;
843             translation.x = (int) ((getWidth() - mShadowMargin - tx - dx) / scale);
844         }
845 
846         if (screenPos.height() > getHeight()) {
847             if (bottomConstraint && !topConstraint) {
848                 float ty = screenPos.bottom - translation.y * scale;
849                 translation.y = (int) ((getHeight() - mShadowMargin - ty) / scale);
850                 currentEdgeEffect = EDGE_BOTTOM;
851             } else if (topConstraint && !bottomConstraint) {
852                 float ty = screenPos.top - translation.y * scale;
853                 translation.y = (int) ((mShadowMargin - ty) / scale);
854                 currentEdgeEffect = EDGE_TOP;
855             }
856         } else {
857             float ty = screenPos.bottom - translation.y * scale;
858             float dy = (getHeight()- 2 * mShadowMargin - screenPos.height()) / 2f;
859             translation.y = (int) ((getHeight() - mShadowMargin - ty - dy) / scale);
860         }
861 
862         if (mCurrentEdgeEffect != currentEdgeEffect) {
863             if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) {
864                 mCurrentEdgeEffect = currentEdgeEffect;
865                 mEdgeEffect.finish();
866             }
867             mEdgeEffect.setSize(getWidth(), mEdgeSize);
868         }
869         if (currentEdgeEffect != 0) {
870             mEdgeEffect.onPull(mEdgeSize);
871         }
872     }
873 
874     @Override
875     public boolean onDoubleTapEvent(MotionEvent arg0) {
876         return false;
877     }
878 
879     @Override
880     public boolean onSingleTapConfirmed(MotionEvent arg0) {
881         return false;
882     }
883 
884     @Override
885     public boolean onDown(MotionEvent arg0) {
886         return false;
887     }
888 
889     @Override
890     public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
891         if (mActivity == null) {
892             return false;
893         }
894         if (endEvent.getPointerCount() == 2) {
895             return false;
896         }
897         return true;
898     }
899 
900     @Override
901     public void onLongPress(MotionEvent arg0) {
902     }
903 
904     @Override
905     public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
906         return false;
907     }
908 
909     @Override
910     public void onShowPress(MotionEvent arg0) {
911     }
912 
913     @Override
914     public boolean onSingleTapUp(MotionEvent arg0) {
915         return false;
916     }
917 
918     public boolean useUtilityPanel() {
919         return false;
920     }
921 
922     public void openUtilityPanel(final LinearLayout accessoryViewList) {
923     }
924 
925     @Override
926     public boolean onScale(ScaleGestureDetector detector) {
927         PrimaryImage img = PrimaryImage.getImage();
928         float scaleFactor = img.getScaleFactor();
929 
930         scaleFactor = scaleFactor * detector.getScaleFactor();
931         if (scaleFactor > PrimaryImage.getImage().getMaxScaleFactor()) {
932             scaleFactor = PrimaryImage.getImage().getMaxScaleFactor();
933         }
934         if (scaleFactor < 1.0f) {
935             scaleFactor = 1.0f;
936         }
937         PrimaryImage.getImage().setScaleFactor(scaleFactor);
938         scaleFactor = img.getScaleFactor();
939         float focusx = detector.getFocusX();
940         float focusy = detector.getFocusY();
941         float translateX = (focusx - mStartFocusX) / scaleFactor;
942         float translateY = (focusy - mStartFocusY) / scaleFactor;
943         Point translation = PrimaryImage.getImage().getTranslation();
944         translation.x = (int) (mOriginalTranslation.x + translateX);
945         translation.y = (int) (mOriginalTranslation.y + translateY);
946         PrimaryImage.getImage().setTranslation(translation);
947         invalidate();
948         return true;
949     }
950 
951     @Override
952     public boolean onScaleBegin(ScaleGestureDetector detector) {
953         Point pos = PrimaryImage.getImage().getTranslation();
954         mOriginalTranslation.x = pos.x;
955         mOriginalTranslation.y = pos.y;
956         mOriginalScale = PrimaryImage.getImage().getScaleFactor();
957         mStartFocusX = detector.getFocusX();
958         mStartFocusY = detector.getFocusY();
959         mInteractionMode = InteractionMode.SCALE;
960         return true;
961     }
962 
963     @Override
964     public void onScaleEnd(ScaleGestureDetector detector) {
965         mInteractionMode = InteractionMode.NONE;
966         if (PrimaryImage.getImage().getScaleFactor() < 1) {
967             PrimaryImage.getImage().setScaleFactor(1);
968             invalidate();
969         }
970     }
971 
972     public boolean didFinishScalingOperation() {
973         if (mFinishedScalingOperation) {
974             mFinishedScalingOperation = false;
975             return true;
976         }
977         return false;
978     }
979 
980 }
981