ImageShow.java revision 253b0816b2e979a1e4fef1d99df3345483580655
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
17package com.android.gallery3d.filtershow.imageshow;
18
19import android.content.Context;
20import android.graphics.Bitmap;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.Matrix;
24import android.graphics.Paint;
25import android.graphics.Point;
26import android.graphics.Rect;
27import android.graphics.RectF;
28import android.net.Uri;
29import android.os.Handler;
30import android.util.AttributeSet;
31import android.util.Log;
32import android.view.GestureDetector;
33import android.view.GestureDetector.OnDoubleTapListener;
34import android.view.GestureDetector.OnGestureListener;
35import android.view.MotionEvent;
36import android.view.ScaleGestureDetector;
37import android.view.View;
38import android.widget.LinearLayout;
39
40import com.android.gallery3d.filtershow.FilterShowActivity;
41import com.android.gallery3d.filtershow.PanelController;
42import com.android.gallery3d.filtershow.cache.ImageLoader;
43import com.android.gallery3d.filtershow.filters.ImageFilter;
44import com.android.gallery3d.filtershow.presets.ImagePreset;
45
46import java.io.File;
47
48public class ImageShow extends View implements OnGestureListener,
49        ScaleGestureDetector.OnScaleGestureListener,
50        OnDoubleTapListener {
51
52    private static final String LOGTAG = "ImageShow";
53    private static final boolean ENABLE_HISTORY_SWAP = false;
54    private static final boolean ENABLE_COMPARISON = false;
55
56    protected Paint mPaint = new Paint();
57    protected static int mTextSize = 24;
58    protected static int mTextPadding = 20;
59
60    protected ImageLoader mImageLoader = null;
61    private boolean mDirtyGeometry = false;
62
63    private Bitmap mBackgroundImage = null;
64    private final boolean USE_BACKGROUND_IMAGE = false;
65    private static int mBackgroundColor = Color.RED;
66
67    private GestureDetector mGestureDetector = null;
68    private ScaleGestureDetector mScaleGestureDetector = null;
69
70    protected Rect mImageBounds = new Rect();
71    private boolean mOriginalDisabled = false;
72    private boolean mTouchShowOriginal = false;
73    private long mTouchShowOriginalDate = 0;
74    private final long mTouchShowOriginalDelayMin = 200; // 200ms
75    private final long mTouchShowOriginalDelayMax = 300; // 300ms
76    private int mShowOriginalDirection = 0;
77    private static int UNVEIL_HORIZONTAL = 1;
78    private static int UNVEIL_VERTICAL = 2;
79
80    private Point mTouchDown = new Point();
81    private Point mTouch = new Point();
82    private boolean mFinishedScalingOperation = false;
83
84    private static int mOriginalTextMargin = 8;
85    private static int mOriginalTextSize = 26;
86    private static String mOriginalText = "Original";
87    private boolean mZoomIn = false;
88
89    protected GeometryMetadata getGeometry() {
90        return new GeometryMetadata(getImagePreset().mGeoData);
91    }
92
93    private String mToast = null;
94    private boolean mShowToast = false;
95    private boolean mImportantToast = false;
96
97    private PanelController mController = null;
98
99    private FilterShowActivity mActivity = null;
100
101    public static void setDefaultBackgroundColor(int value) {
102        mBackgroundColor = value;
103    }
104
105    public FilterShowActivity getActivity() {
106        return mActivity;
107    }
108
109    public int getDefaultBackgroundColor() {
110        return mBackgroundColor;
111    }
112
113    public static void setTextSize(int value) {
114        mTextSize = value;
115    }
116
117    public static void setTextPadding(int value) {
118        mTextPadding = value;
119    }
120
121    public static void setOriginalTextMargin(int value) {
122        mOriginalTextMargin = value;
123    }
124
125    public static void setOriginalTextSize(int value) {
126        mOriginalTextSize = value;
127    }
128
129    public static void setOriginalText(String text) {
130        mOriginalText = text;
131    }
132
133    private final Handler mHandler = new Handler();
134
135    public void select() {
136    }
137
138    public void unselect() {
139    }
140
141    public boolean hasModifications() {
142        if (getImagePreset() == null) {
143            return false;
144        }
145        return getImagePreset().hasModifications();
146    }
147
148    public void resetParameter() {
149        // TODO: implement reset
150    }
151
152    public void setPanelController(PanelController controller) {
153        mController = controller;
154    }
155
156    public PanelController getPanelController() {
157        return mController;
158    }
159
160    public void onNewValue(int parameter) {
161        if (getPanelController() != null) {
162            getPanelController().onNewValue(parameter);
163        }
164        invalidate();
165        mActivity.enableSave(hasModifications());
166    }
167
168    public Point getTouchPoint() {
169        return mTouch;
170    }
171
172    public ImageShow(Context context, AttributeSet attrs) {
173        super(context, attrs);
174
175        setupGestureDetector(context);
176        mActivity = (FilterShowActivity) context;
177        MasterImage.getImage().addObserver(this);
178    }
179
180    public ImageShow(Context context) {
181        super(context);
182
183        setupGestureDetector(context);
184        mActivity = (FilterShowActivity) context;
185        MasterImage.getImage().addObserver(this);
186    }
187
188    public void setupGestureDetector(Context context) {
189        mGestureDetector = new GestureDetector(context, this);
190        mScaleGestureDetector = new ScaleGestureDetector(context, this);
191    }
192
193    @Override
194    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
195        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
196        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
197        setMeasuredDimension(parentWidth, parentHeight);
198    }
199
200    public ImageFilter getCurrentFilter() {
201        return MasterImage.getImage().getCurrentFilter();
202    }
203
204    public void showToast(String text) {
205        showToast(text, false);
206    }
207
208    public void showToast(String text, boolean important) {
209        mToast = text;
210        mShowToast = true;
211        mImportantToast = important;
212        invalidate();
213
214        mHandler.postDelayed(new Runnable() {
215            @Override
216            public void run() {
217                mShowToast = false;
218                invalidate();
219            }
220        }, 400);
221    }
222
223    public Rect getImageBounds() {
224        Rect dst = new Rect();
225        getImagePreset().mGeoData.getPhotoBounds().roundOut(dst);
226        return dst;
227    }
228
229    public Rect getImageCropBounds() {
230        return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds());
231    }
232
233    /* consider moving the following 2 methods into a subclass */
234    /**
235     * This function calculates a Image to Screen Transformation matrix
236     *
237     * @param reflectRotation set true if you want the rotation encoded
238     * @return Image to Screen transformation matrix
239     */
240    protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
241        GeometryMetadata geo = getImagePreset().mGeoData;
242        if (geo == null || mImageLoader == null
243                || mImageLoader.getOriginalBounds() == null) {
244            return new Matrix();
245        }
246        Matrix m = geo.getOriginalToScreen(reflectRotation,
247                mImageLoader.getOriginalBounds().width(),
248                mImageLoader.getOriginalBounds().height(), getWidth(), getHeight());
249        Point translate = MasterImage.getImage().getTranslation();
250        float scaleFactor = MasterImage.getImage().getScaleFactor();
251        m.postTranslate(translate.x, translate.y);
252        m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
253        return m;
254    }
255
256    /**
257     * This function calculates a to Screen Image Transformation matrix
258     *
259     * @param reflectRotation set true if you want the rotation encoded
260     * @return Screen to Image transformation matrix
261     */
262    protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
263        Matrix m = getImageToScreenMatrix(reflectRotation);
264        Matrix invert = new Matrix();
265        m.invert(invert);
266        return invert;
267    }
268
269    public Rect getDisplayedImageBounds() {
270        return mImageBounds;
271    }
272
273    public ImagePreset getImagePreset() {
274        return MasterImage.getImage().getPreset();
275    }
276
277    public void drawToast(Canvas canvas) {
278        if (mShowToast && mToast != null) {
279            Paint paint = new Paint();
280            paint.setTextSize(128);
281            float textWidth = paint.measureText(mToast);
282            int toastX = (int) ((getWidth() - textWidth) / 2.0f);
283            int toastY = (int) (getHeight() / 3.0f);
284
285            paint.setARGB(255, 0, 0, 0);
286            canvas.drawText(mToast, toastX - 2, toastY - 2, paint);
287            canvas.drawText(mToast, toastX - 2, toastY, paint);
288            canvas.drawText(mToast, toastX, toastY - 2, paint);
289            canvas.drawText(mToast, toastX + 2, toastY + 2, paint);
290            canvas.drawText(mToast, toastX + 2, toastY, paint);
291            canvas.drawText(mToast, toastX, toastY + 2, paint);
292            if (mImportantToast) {
293                paint.setARGB(255, 200, 0, 0);
294            } else {
295                paint.setARGB(255, 255, 255, 255);
296            }
297            canvas.drawText(mToast, toastX, toastY, paint);
298        }
299    }
300
301    @Override
302    public void onDraw(Canvas canvas) {
303        MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
304
305        float cx = canvas.getWidth()/2.0f;
306        float cy = canvas.getHeight()/2.0f;
307        float scaleFactor = MasterImage.getImage().getScaleFactor();
308        Point translation = MasterImage.getImage().getTranslation();
309
310        Matrix scalingMatrix = new Matrix();
311        scalingMatrix.postScale(scaleFactor, scaleFactor, cx, cy);
312        scalingMatrix.preTranslate(translation.x, translation.y);
313
314        RectF unscaledClipRect = new RectF(mImageBounds);
315        scalingMatrix.mapRect(unscaledClipRect, unscaledClipRect);
316
317        canvas.save();
318        if (!unscaledClipRect.isEmpty()) {
319            canvas.clipRect(unscaledClipRect);
320        }
321
322        canvas.save();
323        // TODO: center scale on gesture
324        canvas.scale(scaleFactor, scaleFactor, cx, cy);
325        canvas.translate(translation.x, translation.y);
326        drawBackground(canvas);
327        drawImage(canvas, getFilteredImage());
328        canvas.restore();
329
330        if (showTitle() && getImagePreset() != null) {
331            mPaint.setARGB(200, 0, 0, 0);
332            mPaint.setTextSize(mTextSize);
333
334            Rect textRect = new Rect(0, 0, getWidth(), mTextSize + mTextPadding);
335            canvas.drawRect(textRect, mPaint);
336            mPaint.setARGB(255, 200, 200, 200);
337            canvas.drawText(getImagePreset().name(), mTextPadding,
338                    1.5f * mTextPadding, mPaint);
339        }
340
341        Bitmap partialPreview = MasterImage.getImage().getPartialImage();
342        if (partialPreview != null) {
343            Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight());
344            Rect dest = new Rect(0, 0, getWidth(), getHeight());
345            canvas.drawBitmap(partialPreview, src, dest, mPaint);
346        }
347
348        canvas.save();
349        canvas.scale(scaleFactor, scaleFactor, cx, cy);
350        canvas.translate(translation.x, translation.y);
351        drawPartialImage(canvas, getGeometryOnlyImage());
352        canvas.restore();
353
354        canvas.restore();
355
356        drawToast(canvas);
357    }
358
359    public void resetImageCaches(ImageShow caller) {
360        if (mImageLoader == null) {
361            return;
362        }
363        MasterImage.getImage().updatePresets(true);
364    }
365
366    public Bitmap getFiltersOnlyImage() {
367        return MasterImage.getImage().getFiltersOnlyImage();
368    }
369
370    public Bitmap getGeometryOnlyImage() {
371        return MasterImage.getImage().getGeometryOnlyImage();
372    }
373
374    public Bitmap getFilteredImage() {
375        return MasterImage.getImage().getFilteredImage();
376    }
377
378    public void drawImage(Canvas canvas, Bitmap image) {
379        if (image != null) {
380            Rect s = new Rect(0, 0, image.getWidth(),
381                    image.getHeight());
382
383            float scale = GeometryMath.scale(image.getWidth(), image.getHeight(), getWidth(),
384                    getHeight());
385
386            float w = image.getWidth() * scale;
387            float h = image.getHeight() * scale;
388            float ty = (getHeight() - h) / 2.0f;
389            float tx = (getWidth() - w) / 2.0f;
390
391            Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
392                    (int) (h + ty));
393            mImageBounds = d;
394            canvas.drawBitmap(image, s, d, mPaint);
395        }
396    }
397
398    public void drawPartialImage(Canvas canvas, Bitmap image) {
399        if (!mTouchShowOriginal)
400            return;
401        canvas.save();
402        if (image != null) {
403            if (mShowOriginalDirection == 0) {
404                if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
405                    mShowOriginalDirection = UNVEIL_VERTICAL;
406                } else {
407                    mShowOriginalDirection = UNVEIL_HORIZONTAL;
408                }
409            }
410
411            int px = 0;
412            int py = 0;
413            if (mShowOriginalDirection == UNVEIL_VERTICAL) {
414                px = mImageBounds.width();
415                py = (int) (mTouch.y - mImageBounds.top);
416            } else {
417                px = (int) (mTouch.x - mImageBounds.left);
418                py = mImageBounds.height();
419            }
420
421            Rect d = new Rect(mImageBounds.left, mImageBounds.top,
422                    mImageBounds.left + px, mImageBounds.top + py);
423            canvas.clipRect(d);
424            drawImage(canvas, image);
425            Paint paint = new Paint();
426            paint.setColor(Color.BLACK);
427            paint.setStrokeWidth(3);
428
429            if (mShowOriginalDirection == UNVEIL_VERTICAL) {
430                canvas.drawLine(mImageBounds.left, mTouch.y,
431                        mImageBounds.right, mTouch.y, paint);
432            } else {
433                canvas.drawLine(mTouch.x, mImageBounds.top,
434                        mTouch.x, mImageBounds.bottom, paint);
435            }
436
437            Rect bounds = new Rect();
438            paint.setAntiAlias(true);
439            paint.setTextSize(mOriginalTextSize);
440            paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
441            paint.setColor(Color.BLACK);
442            paint.setStyle(Paint.Style.STROKE);
443            paint.setStrokeWidth(3);
444            canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
445                    mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
446            paint.setStyle(Paint.Style.FILL);
447            paint.setStrokeWidth(1);
448            paint.setColor(Color.WHITE);
449            canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
450                    mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
451        }
452        canvas.restore();
453    }
454
455    public void drawBackground(Canvas canvas) {
456        if (USE_BACKGROUND_IMAGE) {
457            if (mBackgroundImage == null) {
458                mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources());
459            }
460            if (mBackgroundImage != null) {
461                Rect s = new Rect(0, 0, mBackgroundImage.getWidth(),
462                        mBackgroundImage.getHeight());
463                Rect d = new Rect(0, 0, getWidth(), getHeight());
464                canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
465            }
466        } else {
467            canvas.drawColor(mBackgroundColor);
468        }
469    }
470
471    public boolean showTitle() {
472        return false;
473    }
474
475    public void setImageLoader(ImageLoader loader) {
476        mImageLoader = loader;
477        if (mImageLoader != null) {
478            mImageLoader.addListener(this);
479            MasterImage.getImage().setImageLoader(mImageLoader);
480        }
481    }
482
483    private void setDirtyGeometryFlag() {
484        mDirtyGeometry = true;
485    }
486
487    protected void clearDirtyGeometryFlag() {
488        mDirtyGeometry = false;
489    }
490
491    protected boolean getDirtyGeometryFlag() {
492        return mDirtyGeometry;
493    }
494
495    private void imageSizeChanged(Bitmap image) {
496        if (image == null || getImagePreset() == null)
497            return;
498        float w = image.getWidth();
499        float h = image.getHeight();
500        GeometryMetadata geo = getImagePreset().mGeoData;
501        RectF pb = geo.getPhotoBounds();
502        if (w == pb.width() && h == pb.height()) {
503            return;
504        }
505        RectF r = new RectF(0, 0, w, h);
506        getImagePreset().mGeoData.setPhotoBounds(r);
507        getImagePreset().mGeoData.setCropBounds(r);
508
509    }
510
511    public boolean updateGeometryFlags() {
512        return true;
513    }
514
515    public void updateImage() {
516        invalidate();
517        if (!updateGeometryFlags()) {
518            return;
519        }
520        Bitmap bitmap = mImageLoader.getOriginalBitmapLarge();
521        if (bitmap != null) {
522            imageSizeChanged(bitmap);
523        }
524    }
525
526    public void imageLoaded() {
527        updateImage();
528        invalidate();
529    }
530
531    public void saveImage(FilterShowActivity filterShowActivity, File file) {
532        mImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
533    }
534
535    public void saveToUri(Bitmap f, Uri u, String m, FilterShowActivity filterShowActivity) {
536        mImageLoader.saveToUri(f, u, m, filterShowActivity);
537    }
538
539    public void returnFilteredResult(FilterShowActivity filterShowActivity) {
540        mImageLoader.returnFilteredResult(getImagePreset(), filterShowActivity);
541    }
542
543    public boolean scaleInProgress() {
544        return mScaleGestureDetector.isInProgress();
545    }
546
547    protected boolean isOriginalDisabled() {
548        return mOriginalDisabled;
549    }
550
551    protected void setOriginalDisabled(boolean originalDisabled) {
552        mOriginalDisabled = originalDisabled;
553    }
554
555    @Override
556    public boolean onTouchEvent(MotionEvent event) {
557        super.onTouchEvent(event);
558        mGestureDetector.onTouchEvent(event);
559        boolean scaleInProgress = scaleInProgress();
560        mScaleGestureDetector.onTouchEvent(event);
561        if (!scaleInProgress() && scaleInProgress) {
562            // If we were scaling, the scale will stop but we will
563            // still issue an ACTION_UP. Let the subclasses know.
564            mFinishedScalingOperation = true;
565        }
566
567        int ex = (int) event.getX();
568        int ey = (int) event.getY();
569        if (event.getAction() == MotionEvent.ACTION_DOWN) {
570            mTouchDown.x = ex;
571            mTouchDown.y = ey;
572            mTouchShowOriginalDate = System.currentTimeMillis();
573            mShowOriginalDirection = 0;
574            MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
575        }
576
577        if (event.getAction() == MotionEvent.ACTION_MOVE) {
578            mTouch.x = ex;
579            mTouch.y = ey;
580
581            if (!ENABLE_COMPARISON || event.getPointerCount() == 2) {
582                float scaleFactor = MasterImage.getImage().getScaleFactor();
583                if (scaleFactor >= 1) {
584                    float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
585                    float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
586                    Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
587                    Point translation = MasterImage.getImage().getTranslation();
588                    translation.x = (int) (originalTranslation.x + translateX);
589                    translation.y = (int) (originalTranslation.y + translateY);
590                    MasterImage.getImage().setTranslation(translation);
591                }
592                mTouchShowOriginal = false;
593            } else if (!mOriginalDisabled && !mActivity.isShowingHistoryPanel()
594                    && (System.currentTimeMillis() - mTouchShowOriginalDate
595                            > mTouchShowOriginalDelayMin)
596                    && event.getPointerCount() == 1) {
597                mTouchShowOriginal = true;
598            }
599        }
600
601        if (event.getAction() == MotionEvent.ACTION_UP) {
602            mTouchShowOriginal = false;
603            mTouchDown.x = 0;
604            mTouchDown.y = 0;
605            mTouch.x = 0;
606            mTouch.y = 0;
607            if (MasterImage.getImage().getScaleFactor() <= 1) {
608                MasterImage.getImage().setScaleFactor(1);
609                MasterImage.getImage().resetTranslation();
610            }
611        }
612        invalidate();
613        return true;
614    }
615
616    // listview stuff
617    public void showOriginal(boolean show) {
618        invalidate();
619    }
620
621    @Override
622    public boolean onDoubleTap(MotionEvent arg0) {
623        mZoomIn = !mZoomIn;
624        float scale = 1.0f;
625        if (mZoomIn) {
626            scale = MasterImage.getImage().getMaxScaleFactor();
627        }
628        if (scale != MasterImage.getImage().getScaleFactor()) {
629            MasterImage.getImage().setScaleFactor(scale);
630            MasterImage.getImage().needsUpdateFullResPreview();
631            invalidate();
632        }
633        return true;
634    }
635
636    @Override
637    public boolean onDoubleTapEvent(MotionEvent arg0) {
638        // TODO Auto-generated method stub
639        return false;
640    }
641
642    @Override
643    public boolean onSingleTapConfirmed(MotionEvent arg0) {
644        // TODO Auto-generated method stub
645        return false;
646    }
647
648    @Override
649    public boolean onDown(MotionEvent arg0) {
650        // TODO Auto-generated method stub
651        return false;
652    }
653
654    @Override
655    public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
656        if (mActivity == null) {
657            return false;
658        }
659        if (endEvent.getPointerCount() == 2) {
660            return false;
661        }
662        if (ENABLE_HISTORY_SWAP && (!mActivity.isShowingHistoryPanel() && startEvent.getX() > endEvent.getX())
663                || (mActivity.isShowingHistoryPanel() && endEvent.getX() > startEvent.getX())) {
664            if (!mTouchShowOriginal
665                    || (mTouchShowOriginal &&
666                            (System.currentTimeMillis() - mTouchShowOriginalDate
667                            < mTouchShowOriginalDelayMax))) {
668                mActivity.toggleHistoryPanel();
669            }
670        }
671        return true;
672    }
673
674    @Override
675    public void onLongPress(MotionEvent arg0) {
676        // TODO Auto-generated method stub
677    }
678
679    @Override
680    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
681        // TODO Auto-generated method stub
682        return false;
683    }
684
685    @Override
686    public void onShowPress(MotionEvent arg0) {
687        // TODO Auto-generated method stub
688    }
689
690    @Override
691    public boolean onSingleTapUp(MotionEvent arg0) {
692        // TODO Auto-generated method stub
693        return false;
694    }
695
696    public boolean useUtilityPanel() {
697        return false;
698    }
699
700    public void openUtilityPanel(final LinearLayout accessoryViewList) {
701        // TODO Auto-generated method stub
702    }
703
704    @Override
705    public boolean onScale(ScaleGestureDetector detector) {
706        float scaleFactor = MasterImage.getImage().getScaleFactor();
707        scaleFactor = scaleFactor * detector.getScaleFactor();
708        if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
709            scaleFactor = MasterImage.getImage().getMaxScaleFactor();
710        }
711        if (scaleFactor < 0.5) {
712            scaleFactor = 0.5f;
713        }
714        MasterImage.getImage().setScaleFactor(scaleFactor);
715        return true;
716    }
717
718    @Override
719    public boolean onScaleBegin(ScaleGestureDetector detector) {
720        return true;
721    }
722
723    @Override
724    public void onScaleEnd(ScaleGestureDetector detector) {
725        if (MasterImage.getImage().getScaleFactor() < 1) {
726            MasterImage.getImage().setScaleFactor(1);
727            invalidate();
728        }
729    }
730
731    public boolean didFinishScalingOperation() {
732        if (mFinishedScalingOperation) {
733            mFinishedScalingOperation = false;
734            return true;
735        }
736        return false;
737    }
738
739}
740