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