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