ImageShow.java revision db68fbd8857ca83b5a54402d1b07c75d36921641
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.content.res.Resources;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.Matrix;
25import android.graphics.Paint;
26import android.graphics.Point;
27import android.graphics.Rect;
28import android.graphics.RectF;
29import android.util.AttributeSet;
30import android.view.GestureDetector;
31import android.view.GestureDetector.OnDoubleTapListener;
32import android.view.GestureDetector.OnGestureListener;
33import android.view.MotionEvent;
34import android.view.ScaleGestureDetector;
35import android.view.View;
36import android.widget.LinearLayout;
37
38import com.android.gallery3d.R;
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.pipeline.ImagePreset;
43import com.android.gallery3d.filtershow.tools.SaveImage;
44
45import java.io.File;
46
47public class ImageShow extends View implements OnGestureListener,
48        ScaleGestureDetector.OnScaleGestureListener,
49        OnDoubleTapListener {
50
51    private static final String LOGTAG = "ImageShow";
52    private static final boolean ENABLE_ZOOMED_COMPARISON = false;
53
54    protected Paint mPaint = new Paint();
55    protected int mTextSize;
56    protected int mTextPadding;
57
58    protected int mBackgroundColor;
59
60    private GestureDetector mGestureDetector = null;
61    private ScaleGestureDetector mScaleGestureDetector = null;
62
63    protected Rect mImageBounds = new Rect();
64    private boolean mOriginalDisabled = false;
65    private boolean mTouchShowOriginal = false;
66    private long mTouchShowOriginalDate = 0;
67    private final long mTouchShowOriginalDelayMin = 200; // 200ms
68    private int mShowOriginalDirection = 0;
69    private static int UNVEIL_HORIZONTAL = 1;
70    private static int UNVEIL_VERTICAL = 2;
71
72    private Point mTouchDown = new Point();
73    private Point mTouch = new Point();
74    private boolean mFinishedScalingOperation = false;
75
76    private int mOriginalTextMargin;
77    private int mOriginalTextSize;
78    private String mOriginalText;
79    private boolean mZoomIn = false;
80    Point mOriginalTranslation = new Point();
81    float mOriginalScale;
82    float mStartFocusX, mStartFocusY;
83    private enum InteractionMode {
84        NONE,
85        SCALE,
86        MOVE
87    }
88    InteractionMode mInteractionMode = InteractionMode.NONE;
89
90    protected GeometryMetadata getGeometry() {
91        return new GeometryMetadata(getImagePreset().getGeometry());
92    }
93
94    private FilterShowActivity mActivity = null;
95
96    public FilterShowActivity getActivity() {
97        return mActivity;
98    }
99
100    public boolean hasModifications() {
101        return MasterImage.getImage().hasModifications();
102    }
103
104    public void resetParameter() {
105        // TODO: implement reset
106    }
107
108    public void onNewValue(int parameter) {
109        invalidate();
110    }
111
112    public ImageShow(Context context, AttributeSet attrs, int defStyle) {
113        super(context, attrs, defStyle);
114        setupImageShow(context);
115    }
116
117    public ImageShow(Context context, AttributeSet attrs) {
118        super(context, attrs);
119        setupImageShow(context);
120
121    }
122
123    public ImageShow(Context context) {
124        super(context);
125        setupImageShow(context);
126    }
127
128    private void setupImageShow(Context context) {
129        Resources res = context.getResources();
130        mTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_text_size);
131        mTextPadding = res.getDimensionPixelSize(R.dimen.photoeditor_text_padding);
132        mOriginalTextMargin = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_margin);
133        mOriginalTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_size);
134        mBackgroundColor = res.getColor(R.color.background_screen);
135        mOriginalText = res.getString(R.string.original_picture_text);
136        setupGestureDetector(context);
137        mActivity = (FilterShowActivity) context;
138        MasterImage.getImage().addObserver(this);
139    }
140
141    public void setupGestureDetector(Context context) {
142        mGestureDetector = new GestureDetector(context, this);
143        mScaleGestureDetector = new ScaleGestureDetector(context, this);
144    }
145
146    @Override
147    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
148        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
149        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
150        setMeasuredDimension(parentWidth, parentHeight);
151    }
152
153    public ImageFilter getCurrentFilter() {
154        return MasterImage.getImage().getCurrentFilter();
155    }
156
157    public Rect getImageBounds() {
158        Rect dst = new Rect();
159        getImagePreset().getGeometry().getPhotoBounds().roundOut(dst);
160        return dst;
161    }
162
163    public Rect getImageCropBounds() {
164        return GeometryMath.roundNearest(getImagePreset().getGeometry().getPreviewCropBounds());
165    }
166
167    /* consider moving the following 2 methods into a subclass */
168    /**
169     * This function calculates a Image to Screen Transformation matrix
170     *
171     * @param reflectRotation set true if you want the rotation encoded
172     * @return Image to Screen transformation matrix
173     */
174    protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
175        GeometryMetadata geo = getImagePreset().getGeometry();
176        if (geo == null
177                || MasterImage.getImage().getOriginalBounds() == null) {
178            return new Matrix();
179        }
180        Matrix m = geo.getOriginalToScreen(reflectRotation,
181                MasterImage.getImage().getOriginalBounds().width(),
182                MasterImage.getImage().getOriginalBounds().height(), getWidth(), getHeight());
183        Point translate = MasterImage.getImage().getTranslation();
184        float scaleFactor = MasterImage.getImage().getScaleFactor();
185        m.postTranslate(translate.x, translate.y);
186        m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
187        return m;
188    }
189
190    /**
191     * This function calculates a to Screen Image Transformation matrix
192     *
193     * @param reflectRotation set true if you want the rotation encoded
194     * @return Screen to Image transformation matrix
195     */
196    protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
197        Matrix m = getImageToScreenMatrix(reflectRotation);
198        Matrix invert = new Matrix();
199        m.invert(invert);
200        return invert;
201    }
202
203    public ImagePreset getImagePreset() {
204        return MasterImage.getImage().getPreset();
205    }
206
207    @Override
208    public void onDraw(Canvas canvas) {
209        MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
210
211        float cx = canvas.getWidth()/2.0f;
212        float cy = canvas.getHeight()/2.0f;
213        float scaleFactor = MasterImage.getImage().getScaleFactor();
214        Point translation = MasterImage.getImage().getTranslation();
215
216        Matrix scalingMatrix = new Matrix();
217        scalingMatrix.postScale(scaleFactor, scaleFactor, cx, cy);
218        scalingMatrix.preTranslate(translation.x, translation.y);
219
220        RectF unscaledClipRect = new RectF(mImageBounds);
221        scalingMatrix.mapRect(unscaledClipRect, unscaledClipRect);
222
223        canvas.save();
224
225        boolean enablePartialRendering = false;
226
227        // For now, partial rendering is disabled for all filters,
228        // so no need to clip.
229        if (enablePartialRendering && !unscaledClipRect.isEmpty()) {
230            canvas.clipRect(unscaledClipRect);
231        }
232
233        canvas.save();
234        // TODO: center scale on gesture
235        canvas.scale(scaleFactor, scaleFactor, cx, cy);
236        canvas.translate(translation.x, translation.y);
237        drawImage(canvas, getFilteredImage(), true);
238        Bitmap highresPreview = MasterImage.getImage().getHighresImage();
239        if (highresPreview != null) {
240            drawImage(canvas, highresPreview, false);
241        }
242        canvas.restore();
243
244        Bitmap partialPreview = MasterImage.getImage().getPartialImage();
245        if (partialPreview != null) {
246            Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight());
247            Rect dest = new Rect(0, 0, getWidth(), getHeight());
248            canvas.drawBitmap(partialPreview, src, dest, mPaint);
249        }
250
251        canvas.save();
252        canvas.scale(scaleFactor, scaleFactor, cx, cy);
253        canvas.translate(translation.x, translation.y);
254        drawPartialImage(canvas, getGeometryOnlyImage());
255        canvas.restore();
256
257        canvas.restore();
258    }
259
260    public void resetImageCaches(ImageShow caller) {
261        MasterImage.getImage().updatePresets(true);
262    }
263
264    public Bitmap getFiltersOnlyImage() {
265        return MasterImage.getImage().getFiltersOnlyImage();
266    }
267
268    public Bitmap getGeometryOnlyImage() {
269        return MasterImage.getImage().getGeometryOnlyImage();
270    }
271
272    public Bitmap getFilteredImage() {
273        return MasterImage.getImage().getFilteredImage();
274    }
275
276    public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) {
277        if (image != null) {
278            Rect s = new Rect(0, 0, image.getWidth(),
279                    image.getHeight());
280
281            float scale = GeometryMath.scale(image.getWidth(), image.getHeight(), getWidth(),
282                    getHeight());
283
284            float w = image.getWidth() * scale;
285            float h = image.getHeight() * scale;
286            float ty = (getHeight() - h) / 2.0f;
287            float tx = (getWidth() - w) / 2.0f;
288
289            Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
290                    (int) (h + ty));
291            if (updateBounds) {
292                mImageBounds = d;
293            }
294            canvas.drawBitmap(image, s, d, mPaint);
295        }
296    }
297
298    public void drawPartialImage(Canvas canvas, Bitmap image) {
299        boolean showsOriginal = MasterImage.getImage().showsOriginal();
300        if (!showsOriginal && !mTouchShowOriginal)
301            return;
302        canvas.save();
303        if (image != null) {
304            if (mShowOriginalDirection == 0) {
305                if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
306                    mShowOriginalDirection = UNVEIL_VERTICAL;
307                } else {
308                    mShowOriginalDirection = UNVEIL_HORIZONTAL;
309                }
310            }
311
312            int px = 0;
313            int py = 0;
314            if (mShowOriginalDirection == UNVEIL_VERTICAL) {
315                px = mImageBounds.width();
316                py = mTouch.y - mImageBounds.top;
317            } else {
318                px = mTouch.x - mImageBounds.left;
319                py = mImageBounds.height();
320                if (showsOriginal) {
321                    px = mImageBounds.width();
322                }
323            }
324
325            Rect d = new Rect(mImageBounds.left, mImageBounds.top,
326                    mImageBounds.left + px, mImageBounds.top + py);
327            canvas.clipRect(d);
328            drawImage(canvas, image, false);
329            Paint paint = new Paint();
330            paint.setColor(Color.BLACK);
331            paint.setStrokeWidth(3);
332
333            if (mShowOriginalDirection == UNVEIL_VERTICAL) {
334                canvas.drawLine(mImageBounds.left, mTouch.y,
335                        mImageBounds.right, mTouch.y, paint);
336            } else {
337                canvas.drawLine(mTouch.x, mImageBounds.top,
338                        mTouch.x, mImageBounds.bottom, paint);
339            }
340
341            Rect bounds = new Rect();
342            paint.setAntiAlias(true);
343            paint.setTextSize(mOriginalTextSize);
344            paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
345            paint.setColor(Color.BLACK);
346            paint.setStyle(Paint.Style.STROKE);
347            paint.setStrokeWidth(3);
348            canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
349                    mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
350            paint.setStyle(Paint.Style.FILL);
351            paint.setStrokeWidth(1);
352            paint.setColor(Color.WHITE);
353            canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
354                    mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
355        }
356        canvas.restore();
357    }
358
359    public void bindAsImageLoadListener() {
360        MasterImage.getImage().addListener(this);
361    }
362
363    private void imageSizeChanged(Bitmap image) {
364        if (image == null || getImagePreset() == null)
365            return;
366        float w = image.getWidth();
367        float h = image.getHeight();
368        GeometryMetadata geo = getImagePreset().getGeometry();
369        RectF pb = geo.getPhotoBounds();
370        if (w == pb.width() && h == pb.height()) {
371            return;
372        }
373        RectF r = new RectF(0, 0, w, h);
374        geo.setPhotoBounds(r);
375        geo.setCropBounds(r);
376        getImagePreset().setGeometry(geo);
377    }
378
379    public void updateImage() {
380        invalidate();
381        Bitmap bitmap = MasterImage.getImage().getOriginalBitmapLarge();
382        if (bitmap != null) {
383            imageSizeChanged(bitmap);
384        }
385    }
386
387    public void imageLoaded() {
388        updateImage();
389        invalidate();
390    }
391
392    public void saveImage(FilterShowActivity filterShowActivity, File file) {
393        SaveImage.saveImage(getImagePreset(), filterShowActivity, file);
394    }
395
396
397    public boolean scaleInProgress() {
398        return mScaleGestureDetector.isInProgress();
399    }
400
401    @Override
402    public boolean onTouchEvent(MotionEvent event) {
403        super.onTouchEvent(event);
404        int action = event.getAction();
405        action = action & MotionEvent.ACTION_MASK;
406
407        mGestureDetector.onTouchEvent(event);
408        boolean scaleInProgress = scaleInProgress();
409        mScaleGestureDetector.onTouchEvent(event);
410        if (mInteractionMode == InteractionMode.SCALE) {
411            return true;
412        }
413        if (!scaleInProgress() && scaleInProgress) {
414            // If we were scaling, the scale will stop but we will
415            // still issue an ACTION_UP. Let the subclasses know.
416            mFinishedScalingOperation = true;
417        }
418
419        int ex = (int) event.getX();
420        int ey = (int) event.getY();
421        if (action == MotionEvent.ACTION_DOWN) {
422            mInteractionMode = InteractionMode.MOVE;
423            mTouchDown.x = ex;
424            mTouchDown.y = ey;
425            mTouchShowOriginalDate = System.currentTimeMillis();
426            mShowOriginalDirection = 0;
427            MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
428        }
429
430        if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
431            mTouch.x = ex;
432            mTouch.y = ey;
433
434            float scaleFactor = MasterImage.getImage().getScaleFactor();
435            if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
436                float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
437                float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
438                Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
439                Point translation = MasterImage.getImage().getTranslation();
440                translation.x = (int) (originalTranslation.x + translateX);
441                translation.y = (int) (originalTranslation.y + translateY);
442                constrainTranslation(translation, scaleFactor);
443                MasterImage.getImage().setTranslation(translation);
444                mTouchShowOriginal = false;
445            } else if (enableComparison() && !mOriginalDisabled
446                    && (System.currentTimeMillis() - mTouchShowOriginalDate
447                            > mTouchShowOriginalDelayMin)
448                    && event.getPointerCount() == 1) {
449                mTouchShowOriginal = true;
450            }
451        }
452
453        if (action == MotionEvent.ACTION_UP) {
454            mInteractionMode = InteractionMode.NONE;
455            mTouchShowOriginal = false;
456            mTouchDown.x = 0;
457            mTouchDown.y = 0;
458            mTouch.x = 0;
459            mTouch.y = 0;
460            if (MasterImage.getImage().getScaleFactor() <= 1) {
461                MasterImage.getImage().setScaleFactor(1);
462                MasterImage.getImage().resetTranslation();
463            }
464        }
465        invalidate();
466        return true;
467    }
468
469    protected boolean enableComparison() {
470        return true;
471    }
472
473    @Override
474    public boolean onDoubleTap(MotionEvent arg0) {
475        mZoomIn = !mZoomIn;
476        float scale = 1.0f;
477        if (mZoomIn) {
478            scale = MasterImage.getImage().getMaxScaleFactor();
479        }
480        if (scale != MasterImage.getImage().getScaleFactor()) {
481            MasterImage.getImage().setScaleFactor(scale);
482            float translateX = (getWidth() / 2 - arg0.getX());
483            float translateY = (getHeight() / 2 - arg0.getY());
484            Point translation = MasterImage.getImage().getTranslation();
485            translation.x = (int) (mOriginalTranslation.x + translateX);
486            translation.y = (int) (mOriginalTranslation.y + translateY);
487            constrainTranslation(translation, scale);
488            MasterImage.getImage().setTranslation(translation);
489            invalidate();
490        }
491        return true;
492    }
493
494    private void constrainTranslation(Point translation, float scale) {
495        float maxTranslationX = getWidth() / scale;
496        float maxTranslationY = getHeight() / scale;
497        if (Math.abs(translation.x) > maxTranslationX) {
498            translation.x = (int) (Math.signum(translation.x) *
499                    maxTranslationX);
500            if (Math.abs(translation.y) > maxTranslationY) {
501                translation.y = (int) (Math.signum(translation.y) *
502                        maxTranslationY);
503            }
504
505        }
506    }
507
508    @Override
509    public boolean onDoubleTapEvent(MotionEvent arg0) {
510        return false;
511    }
512
513    @Override
514    public boolean onSingleTapConfirmed(MotionEvent arg0) {
515        return false;
516    }
517
518    @Override
519    public boolean onDown(MotionEvent arg0) {
520        return false;
521    }
522
523    @Override
524    public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
525        if (mActivity == null) {
526            return false;
527        }
528        if (endEvent.getPointerCount() == 2) {
529            return false;
530        }
531        return true;
532    }
533
534    @Override
535    public void onLongPress(MotionEvent arg0) {
536    }
537
538    @Override
539    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
540        return false;
541    }
542
543    @Override
544    public void onShowPress(MotionEvent arg0) {
545    }
546
547    @Override
548    public boolean onSingleTapUp(MotionEvent arg0) {
549        return false;
550    }
551
552    public boolean useUtilityPanel() {
553        return false;
554    }
555
556    public void openUtilityPanel(final LinearLayout accessoryViewList) {
557    }
558
559    @Override
560    public boolean onScale(ScaleGestureDetector detector) {
561        MasterImage img = MasterImage.getImage();
562        float scaleFactor = img.getScaleFactor();
563
564        scaleFactor = scaleFactor * detector.getScaleFactor();
565        if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
566            scaleFactor = MasterImage.getImage().getMaxScaleFactor();
567        }
568        if (scaleFactor < 0.5) {
569            scaleFactor = 0.5f;
570        }
571        MasterImage.getImage().setScaleFactor(scaleFactor);
572        scaleFactor = img.getScaleFactor();
573        float focusx = detector.getFocusX();
574        float focusy = detector.getFocusY();
575        float translateX = (focusx - mStartFocusX) / scaleFactor;
576        float translateY = (focusy - mStartFocusY) / scaleFactor;
577        Point translation = MasterImage.getImage().getTranslation();
578        translation.x = (int) (mOriginalTranslation.x + translateX);
579        translation.y = (int) (mOriginalTranslation.y + translateY);
580        constrainTranslation(translation, scaleFactor);
581        MasterImage.getImage().setTranslation(translation);
582
583        invalidate();
584        return true;
585    }
586
587    @Override
588    public boolean onScaleBegin(ScaleGestureDetector detector) {
589        Point pos = MasterImage.getImage().getTranslation();
590        mOriginalTranslation.x = pos.x;
591        mOriginalTranslation.y = pos.y;
592        mOriginalScale = MasterImage.getImage().getScaleFactor();
593        mStartFocusX = detector.getFocusX();
594        mStartFocusY = detector.getFocusY();
595        mInteractionMode = InteractionMode.SCALE;
596        return true;
597    }
598
599    @Override
600    public void onScaleEnd(ScaleGestureDetector detector) {
601        mInteractionMode = InteractionMode.NONE;
602        if (MasterImage.getImage().getScaleFactor() < 1) {
603            MasterImage.getImage().setScaleFactor(1);
604            invalidate();
605        }
606    }
607
608    public boolean didFinishScalingOperation() {
609        if (mFinishedScalingOperation) {
610            mFinishedScalingOperation = false;
611            return true;
612        }
613        return false;
614    }
615
616}
617