Home | History | Annotate | Download | only in imageshow
      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 
    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 
    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 
    138     public FilterShowActivity getActivity() {
    139         return mActivity;
    140     }
    141 
    142     public boolean hasModifications() {
    143         return MasterImage.getImage().hasModifications();
    144     }
    145 
    146     public void resetParameter() {
    147         // TODO: implement reset
    148     }
    149 
    150     public void onNewValue(int parameter) {
    151         invalidate();
    152     }
    153 
    154     public ImageShow(Context context, AttributeSet attrs, int defStyle) {
    155         super(context, attrs, defStyle);
    156         setupImageShow(context);
    157     }
    158 
    159     public ImageShow(Context context, AttributeSet attrs) {
    160         super(context, attrs);
    161         setupImageShow(context);
    162 
    163     }
    164 
    165     public ImageShow(Context context) {
    166         super(context);
    167         setupImageShow(context);
    168     }
    169 
    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 
    189     public void attach() {
    190         MasterImage.getImage().addObserver(this);
    191         bindAsImageLoadListener();
    192         MasterImage.getImage().resetGeometryImages(false);
    193     }
    194 
    195     public void detach() {
    196         MasterImage.getImage().removeObserver(this);
    197         mMaskPaint.reset();
    198     }
    199 
    200     public void setupGestureDetector(Context context) {
    201         mGestureDetector = new GestureDetector(context, this);
    202         mScaleGestureDetector = new ScaleGestureDetector(context, this);
    203     }
    204 
    205     @Override
    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 
    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      */
    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      */
    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 
    250     public ImagePreset getImagePreset() {
    251         return MasterImage.getImage().getPreset();
    252     }
    253 
    254     @Override
    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 
    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 
    336     public void resetImageCaches(ImageShow caller) {
    337         MasterImage.getImage().invalidatePreview();
    338     }
    339 
    340     public Bitmap getFiltersOnlyImage() {
    341         return MasterImage.getImage().getFiltersOnlyImage();
    342     }
    343 
    344     public Bitmap getGeometryOnlyImage() {
    345         return MasterImage.getImage().getGeometryOnlyImage();
    346     }
    347 
    348     public Bitmap getFilteredImage() {
    349         return MasterImage.getImage().getFilteredImage();
    350     }
    351 
    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 
    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 
    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 
    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 
    590     public void bindAsImageLoadListener() {
    591         MasterImage.getImage().addListener(this);
    592     }
    593 
    594     public void updateImage() {
    595         invalidate();
    596     }
    597 
    598     public void imageLoaded() {
    599         updateImage();
    600     }
    601 
    602     public void saveImage(FilterShowActivity filterShowActivity, File file) {
    603         SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
    604     }
    605 
    606 
    607     public boolean scaleInProgress() {
    608         return mScaleGestureDetector.isInProgress();
    609     }
    610 
    611     @Override
    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 
    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 
    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 
    737     protected boolean enableComparison() {
    738         return true;
    739     }
    740 
    741     @Override
    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 
    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