ImageShow.java revision 4d276f338bbdc53f8a3b4806265bc26c7fe0ea7c
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.Paint;
24import android.graphics.Rect;
25import android.graphics.RectF;
26import android.os.Handler;
27import android.util.AttributeSet;
28import android.view.GestureDetector;
29import android.view.GestureDetector.OnDoubleTapListener;
30import android.view.GestureDetector.OnGestureListener;
31import android.view.MotionEvent;
32import android.view.View;
33import android.widget.ArrayAdapter;
34import android.widget.SeekBar;
35import android.widget.SeekBar.OnSeekBarChangeListener;
36
37import com.android.gallery3d.R;
38import com.android.gallery3d.filtershow.FilterShowActivity;
39import com.android.gallery3d.filtershow.HistoryAdapter;
40import com.android.gallery3d.filtershow.ImageStateAdapter;
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;
45import com.android.gallery3d.filtershow.ui.SliderController;
46import com.android.gallery3d.filtershow.ui.SliderListener;
47
48import java.io.File;
49
50public class ImageShow extends View implements OnGestureListener,
51        OnDoubleTapListener,
52        SliderListener,
53        OnSeekBarChangeListener {
54
55    private static final String LOGTAG = "ImageShow";
56
57    protected Paint mPaint = new Paint();
58    protected static int mTextSize = 24;
59    protected static int mTextPadding = 20;
60
61    protected ImagePreset mImagePreset = null;
62    protected ImageLoader mImageLoader = null;
63    private ImageFilter mCurrentFilter = null;
64    private boolean mDirtyGeometry = true;
65
66    private Bitmap mBackgroundImage = null;
67    private final boolean USE_BACKGROUND_IMAGE = false;
68    private static int mBackgroundColor = Color.RED;
69
70    // TODO: remove protected here, it should be private
71    protected Bitmap mForegroundImage = null;
72    protected Bitmap mFilteredImage = null;
73
74    private final boolean USE_SLIDER_GESTURE = false; // set to true to have
75                                                      // slider gesture
76    protected SliderController mSliderController = new SliderController();
77
78    private GestureDetector mGestureDetector = null;
79
80    private HistoryAdapter mHistoryAdapter = null;
81    private ImageStateAdapter mImageStateAdapter = null;
82
83    private Rect mImageBounds = null;
84
85    private boolean mTouchShowOriginal = false;
86    private long mTouchShowOriginalDate = 0;
87    private final long mTouchShowOriginalDelayMin = 200; // 200ms
88    private final long mTouchShowOriginalDelayMax = 300; // 300ms
89    private int mTouchDownX = 0;
90    private int mTouchDownY = 0;
91    protected float mTouchX = 0;
92    protected float mTouchY = 0;
93
94    private static int mOriginalTextMargin = 8;
95    private static int mOriginalTextSize = 26;
96    private static String mOriginalText = "Original";
97
98    protected GeometryMetadata getGeometry() {
99        return new GeometryMetadata(getImagePreset().mGeoData);
100    }
101
102    public void setGeometry(GeometryMetadata d) {
103        getImagePreset().mGeoData.set(d);
104    }
105
106    private boolean mShowControls = false;
107    private boolean mShowOriginal = false;
108    private String mToast = null;
109    private boolean mShowToast = false;
110    private boolean mImportantToast = false;
111
112    private SeekBar mSeekBar = null;
113    private PanelController mController = null;
114
115    private FilterShowActivity mActivity = null;
116
117    public static void setDefaultBackgroundColor(int value) {
118        mBackgroundColor = value;
119    }
120
121    public static void setTextSize(int value) {
122        mTextSize = value;
123    }
124
125    public static void setTextPadding(int value) {
126        mTextPadding = value;
127    }
128
129    public static void setOriginalTextMargin(int value) {
130        mOriginalTextMargin = value;
131    }
132
133    public static void setOriginalTextSize(int value) {
134        mOriginalTextSize = value;
135    }
136
137    public static void setOriginalText(String text) {
138        mOriginalText = text;
139    }
140
141    private final Handler mHandler = new Handler();
142
143    public void select() {
144        if (getCurrentFilter() != null) {
145            int parameter = getCurrentFilter().getParameter();
146            int maxp = getCurrentFilter().getMaxParameter();
147            int minp = getCurrentFilter().getMinParameter();
148            updateSeekBar(parameter, minp, maxp);
149        }
150        if (mSeekBar != null) {
151            mSeekBar.setOnSeekBarChangeListener(this);
152        }
153    }
154
155    private int parameterToUI(int parameter, int minp, int maxp, int uimax) {
156        return (uimax * (parameter - minp)) / (maxp - minp);
157    }
158
159    private int uiToParameter(int ui, int minp, int maxp, int uimax) {
160        return ((maxp - minp) * ui) / uimax + minp;
161    }
162
163    public void updateSeekBar(int parameter, int minp, int maxp) {
164        if (mSeekBar == null) {
165            return;
166        }
167        int seekMax = mSeekBar.getMax();
168        int progress = parameterToUI(parameter, minp, maxp, seekMax);
169        mSeekBar.setProgress(progress);
170        if (getPanelController() != null) {
171            getPanelController().onNewValue(parameter);
172        }
173    }
174
175    public void unselect() {
176
177    }
178
179    public boolean hasModifications() {
180        if (getImagePreset() == null) {
181            return false;
182        }
183        return getImagePreset().hasModifications();
184    }
185
186    public void resetParameter() {
187        ImageFilter currentFilter = getCurrentFilter();
188        if (currentFilter != null) {
189            onNewValue(currentFilter.getDefaultParameter());
190        }
191        if (USE_SLIDER_GESTURE) {
192            mSliderController.reset();
193        }
194    }
195
196    public void setPanelController(PanelController controller) {
197        mController = controller;
198    }
199
200    public PanelController getPanelController() {
201        return mController;
202    }
203
204    @Override
205    public void onNewValue(int parameter) {
206        int maxp = 100;
207        int minp = -100;
208        if (getCurrentFilter() != null) {
209            getCurrentFilter().setParameter(parameter);
210            maxp = getCurrentFilter().getMaxParameter();
211            minp = getCurrentFilter().getMinParameter();
212        }
213        if (getImagePreset() != null) {
214            mImageLoader.resetImageForPreset(getImagePreset(), this);
215            getImagePreset().fillImageStateAdapter(mImageStateAdapter);
216        }
217        if (getPanelController() != null) {
218            getPanelController().onNewValue(parameter);
219        }
220        updateSeekBar(parameter, minp, maxp);
221        invalidate();
222    }
223
224    @Override
225    public void onTouchDown(float x, float y) {
226        mTouchX = x;
227        mTouchY = y;
228        invalidate();
229    }
230
231    @Override
232    public void onTouchUp() {
233    }
234
235    public ImageShow(Context context, AttributeSet attrs) {
236        super(context, attrs);
237        if (USE_SLIDER_GESTURE) {
238            mSliderController.setListener(this);
239        }
240        mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row,
241                R.id.rowTextView);
242        mImageStateAdapter = new ImageStateAdapter(context,
243                R.layout.filtershow_imagestate_row);
244        setupGestureDetector(context);
245        mActivity = (FilterShowActivity) context;
246    }
247
248    public ImageShow(Context context) {
249        super(context);
250        if (USE_SLIDER_GESTURE) {
251            mSliderController.setListener(this);
252        }
253        mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row,
254                R.id.rowTextView);
255        setupGestureDetector(context);
256        mActivity = (FilterShowActivity) context;
257    }
258
259    public void setupGestureDetector(Context context) {
260        mGestureDetector = new GestureDetector(context, this);
261    }
262
263    @Override
264    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
265        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
266        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
267        setMeasuredDimension(parentWidth, parentHeight);
268        if (USE_SLIDER_GESTURE) {
269            mSliderController.setWidth(parentWidth);
270            mSliderController.setHeight(parentHeight);
271        }
272    }
273
274    public void setSeekBar(SeekBar seekBar) {
275        mSeekBar = seekBar;
276    }
277
278    public void setCurrentFilter(ImageFilter filter) {
279        mCurrentFilter = filter;
280    }
281
282    public ImageFilter getCurrentFilter() {
283        return mCurrentFilter;
284    }
285
286    public void setAdapter(HistoryAdapter adapter) {
287        mHistoryAdapter = adapter;
288    }
289
290    public void showToast(String text) {
291        showToast(text, false);
292    }
293
294    public void showToast(String text, boolean important) {
295        mToast = text;
296        mShowToast = true;
297        mImportantToast = important;
298        invalidate();
299
300        mHandler.postDelayed(new Runnable() {
301            @Override
302            public void run() {
303                mShowToast = false;
304                invalidate();
305            }
306        }, 400);
307    }
308
309    public Rect getImageBounds() {
310        Rect dst = new Rect();
311        getImagePreset().mGeoData.getPhotoBounds().roundOut(dst);
312        return dst;
313    }
314
315    public Rect getDisplayedImageBounds() {
316        return mImageBounds;
317    }
318
319    public ImagePreset getImagePreset() {
320        return mImagePreset;
321    }
322
323    protected Bitmap getOriginalFrontBitmap() {
324        if (mImageLoader != null) {
325            return mImageLoader.getOriginalBitmapLarge();
326        }
327        return null;
328    }
329
330    public void drawToast(Canvas canvas) {
331        if (mShowToast && mToast != null) {
332            Paint paint = new Paint();
333            paint.setTextSize(128);
334            float textWidth = paint.measureText(mToast);
335            int toastX = (int) ((getWidth() - textWidth) / 2.0f);
336            int toastY = (int) (getHeight() / 3.0f);
337
338            paint.setARGB(255, 0, 0, 0);
339            canvas.drawText(mToast, toastX - 2, toastY - 2, paint);
340            canvas.drawText(mToast, toastX - 2, toastY, paint);
341            canvas.drawText(mToast, toastX, toastY - 2, paint);
342            canvas.drawText(mToast, toastX + 2, toastY + 2, paint);
343            canvas.drawText(mToast, toastX + 2, toastY, paint);
344            canvas.drawText(mToast, toastX, toastY + 2, paint);
345            if (mImportantToast) {
346                paint.setARGB(255, 200, 0, 0);
347            } else {
348                paint.setARGB(255, 255, 255, 255);
349            }
350            canvas.drawText(mToast, toastX, toastY, paint);
351        }
352    }
353
354    @Override
355    public void onDraw(Canvas canvas) {
356        drawBackground(canvas);
357        getFilteredImage();
358        drawImage(canvas, mFilteredImage);
359        drawPartialImage(canvas, mForegroundImage);
360
361        if (showTitle() && getImagePreset() != null) {
362            mPaint.setARGB(200, 0, 0, 0);
363            mPaint.setTextSize(mTextSize);
364
365            Rect textRect = new Rect(0, 0, getWidth(), mTextSize + mTextPadding);
366            canvas.drawRect(textRect, mPaint);
367            mPaint.setARGB(255, 200, 200, 200);
368            canvas.drawText(getImagePreset().name(), mTextPadding,
369                    1.5f * mTextPadding, mPaint);
370        }
371
372        if (showControls()) {
373            if (USE_SLIDER_GESTURE) {
374                mSliderController.onDraw(canvas);
375            }
376        }
377
378        drawToast(canvas);
379    }
380
381    public void getFilteredImage() {
382        Bitmap filteredImage = null;
383        if (mImageLoader != null) {
384            filteredImage = mImageLoader.getImageForPreset(this,
385                    getImagePreset(), showHires());
386        }
387
388        if (filteredImage == null) {
389            // if no image for the current preset, use the previous one
390            filteredImage = mFilteredImage;
391        } else {
392            mFilteredImage = filteredImage;
393        }
394
395        if (mShowOriginal || mFilteredImage == null) {
396            mFilteredImage = mForegroundImage;
397        }
398    }
399
400    public void drawImage(Canvas canvas, Bitmap image) {
401        if (image != null) {
402            Rect s = new Rect(0, 0, image.getWidth(),
403                    image.getHeight());
404            float ratio = image.getWidth()
405                    / (float) image.getHeight();
406            float w = getWidth();
407            float h = w / ratio;
408            float ty = (getHeight() - h) / 2.0f;
409            float tx = 0;
410            if (ratio < 1.0f || (getHeight() < w)) {
411                h = getHeight();
412                w = h * ratio;
413                tx = (getWidth() - w) / 2.0f;
414                ty = 0;
415            }
416            Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
417                    (int) (h + ty));
418            mImageBounds = d;
419            canvas.drawBitmap(image, s, d, mPaint);
420        }
421    }
422
423    public void drawPartialImage(Canvas canvas, Bitmap image) {
424        if (!mTouchShowOriginal)
425            return;
426        canvas.save();
427        if (image != null) {
428            boolean goingDown = false;
429            if ((mTouchY - mTouchDownY) > (mTouchX - mTouchDownX)) {
430                goingDown = true;
431            }
432            int px = (int) (mTouchX - mImageBounds.left);
433            int py = mImageBounds.height();
434            if (goingDown) {
435                px = mImageBounds.width();
436                py = (int) (mTouchY - mImageBounds.top);
437            }
438            Rect d = new Rect(mImageBounds.left, mImageBounds.top,
439                    mImageBounds.left + px, mImageBounds.top + py);
440            canvas.clipRect(d);
441            drawImage(canvas, image);
442            Paint paint = new Paint();
443            paint.setColor(Color.BLACK);
444            if (goingDown) {
445                canvas.drawLine(mImageBounds.left, mTouchY - 1,
446                        mImageBounds.right, mTouchY - 1, paint);
447            } else {
448                canvas.drawLine(mTouchX - 1, mImageBounds.top,
449                        mTouchX - 1, mImageBounds.bottom, paint);
450            }
451            Rect bounds = new Rect();
452            paint.setTextSize(mOriginalTextSize);
453            paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
454            paint.setColor(Color.BLACK);
455            canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin + 1,
456                    mImageBounds.top + bounds.height() + mOriginalTextMargin + 1, paint);
457            paint.setColor(Color.WHITE);
458            canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
459                    mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
460        }
461        canvas.restore();
462    }
463
464    public void drawBackground(Canvas canvas) {
465        if (USE_BACKGROUND_IMAGE) {
466            if (mBackgroundImage == null) {
467                mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources());
468            }
469            if (mBackgroundImage != null) {
470                Rect s = new Rect(0, 0, mBackgroundImage.getWidth(),
471                        mBackgroundImage.getHeight());
472                Rect d = new Rect(0, 0, getWidth(), getHeight());
473                canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
474            }
475        } else {
476            canvas.drawColor(mBackgroundColor);
477        }
478    }
479
480    public ImageShow setShowControls(boolean value) {
481        mShowControls = value;
482        if (mShowControls) {
483            if (mSeekBar != null) {
484                mSeekBar.setVisibility(View.VISIBLE);
485            }
486        } else {
487            if (mSeekBar != null) {
488                mSeekBar.setVisibility(View.INVISIBLE);
489            }
490        }
491        return this;
492    }
493
494    public boolean showControls() {
495        return mShowControls;
496    }
497
498    public boolean showHires() {
499        return true;
500    }
501
502    public boolean showTitle() {
503        return false;
504    }
505
506    public void setImagePreset(ImagePreset preset) {
507        setImagePreset(preset, true);
508    }
509
510    public void setImagePreset(ImagePreset preset, boolean addToHistory) {
511        mImagePreset = preset;
512        if (getImagePreset() != null) {
513            getImagePreset().setImageLoader(mImageLoader);
514            if (addToHistory) {
515                mHistoryAdapter.addHistoryItem(getImagePreset());
516            }
517            getImagePreset().setEndpoint(this);
518            updateImage();
519        }
520        mImagePreset.fillImageStateAdapter(mImageStateAdapter);
521        invalidate();
522    }
523
524    public void setImageLoader(ImageLoader loader) {
525        mImageLoader = loader;
526        if (mImageLoader != null) {
527            mImageLoader.addListener(this);
528            if (mImagePreset != null) {
529                mImagePreset.setImageLoader(mImageLoader);
530            }
531        }
532    }
533
534    private void setDirtyGeometryFlag() {
535        mDirtyGeometry = true;
536    }
537
538    protected void clearDirtyGeometryFlag() {
539        mDirtyGeometry = false;
540    }
541
542    protected boolean getDirtyGeometryFlag() {
543        return mDirtyGeometry;
544    }
545
546    private void imageSizeChanged(Bitmap image) {
547        if (image == null || getImagePreset() == null)
548            return;
549        float w = image.getWidth();
550        float h = image.getHeight();
551        GeometryMetadata geo = getImagePreset().mGeoData;
552        RectF pb = geo.getPhotoBounds();
553        if (w == pb.width() && h == pb.height()) {
554            return;
555        }
556        RectF r = new RectF(0, 0, w, h);
557        getImagePreset().mGeoData.setPhotoBounds(r);
558        getImagePreset().mGeoData.setCropBounds(r);
559        setDirtyGeometryFlag();
560    }
561
562    public void updateImage() {
563        mForegroundImage = getOriginalFrontBitmap();
564        imageSizeChanged(mForegroundImage); // TODO: should change to filtered
565        setDirtyGeometryFlag();
566    }
567
568    public void updateFilteredImage(Bitmap bitmap) {
569        mFilteredImage = bitmap;
570    }
571
572    public void saveImage(FilterShowActivity filterShowActivity, File file) {
573        mImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
574    }
575
576    @Override
577    public boolean onTouchEvent(MotionEvent event) {
578        super.onTouchEvent(event);
579        if (USE_SLIDER_GESTURE) {
580            mSliderController.onTouchEvent(event);
581        }
582        if (mGestureDetector != null) {
583            mGestureDetector.onTouchEvent(event);
584        }
585        int ex = (int) event.getX();
586        int ey = (int) event.getY();
587        if (event.getAction() == MotionEvent.ACTION_DOWN) {
588            mTouchDownX = ex;
589            mTouchDownY = ey;
590            mTouchShowOriginalDate = System.currentTimeMillis();
591        }
592        if (event.getAction() == MotionEvent.ACTION_MOVE) {
593            mTouchX = ex;
594            mTouchY = ey;
595            if (!mActivity.isShowingHistoryPanel()
596                    && (System.currentTimeMillis() - mTouchShowOriginalDate
597                          > mTouchShowOriginalDelayMin)) {
598                mTouchShowOriginal = true;
599            }
600        }
601        if (event.getAction() == MotionEvent.ACTION_UP) {
602            mTouchShowOriginal = false;
603            mTouchDownX = 0;
604            mTouchDownY = 0;
605            mTouchX = 0;
606            mTouchY = 0;
607        }
608        invalidate();
609        return true;
610    }
611
612    // listview stuff
613
614    public HistoryAdapter getHistory() {
615        return mHistoryAdapter;
616    }
617
618    public ArrayAdapter getImageStateAdapter() {
619        return mImageStateAdapter;
620    }
621
622    public void onItemClick(int position) {
623        setImagePreset(new ImagePreset(mHistoryAdapter.getItem(position)), false);
624        // we need a copy from the history
625        mHistoryAdapter.setCurrentPreset(position);
626    }
627
628    public void showOriginal(boolean show) {
629        mShowOriginal = show;
630        invalidate();
631    }
632
633    public float getImageRotation() {
634        return getImagePreset().mGeoData.getRotation();
635    }
636
637    public float getImageRotationZoomFactor() {
638        return getImagePreset().mGeoData.getScaleFactor();
639    }
640
641    public void setImageRotation(float r) {
642        getImagePreset().mGeoData.setRotation(r);
643    }
644
645    public void setImageRotationZoomFactor(float f) {
646        getImagePreset().mGeoData.setScaleFactor(f);
647    }
648
649    public void setImageRotation(float imageRotation,
650            float imageRotationZoomFactor) {
651        float r = getImageRotation();
652        if (imageRotation != r) {
653            invalidate();
654        }
655        setImageRotation(imageRotation);
656        setImageRotationZoomFactor(imageRotationZoomFactor);
657    }
658
659    @Override
660    public void onProgressChanged(SeekBar arg0, int progress, boolean arg2) {
661        int parameter = progress;
662        if (getCurrentFilter() != null) {
663            int maxp = getCurrentFilter().getMaxParameter();
664            int minp = getCurrentFilter().getMinParameter();
665            parameter = uiToParameter(progress, minp, maxp, arg0.getMax());
666        }
667
668        onNewValue(parameter);
669    }
670
671    @Override
672    public void onStartTrackingTouch(SeekBar arg0) {
673        // TODO Auto-generated method stub
674
675    }
676
677    @Override
678    public void onStopTrackingTouch(SeekBar arg0) {
679        // TODO Auto-generated method stub
680
681    }
682
683    @Override
684    public boolean onDoubleTap(MotionEvent arg0) {
685        // TODO Auto-generated method stub
686        return false;
687    }
688
689    @Override
690    public boolean onDoubleTapEvent(MotionEvent arg0) {
691        // TODO Auto-generated method stub
692        return false;
693    }
694
695    @Override
696    public boolean onSingleTapConfirmed(MotionEvent arg0) {
697        // TODO Auto-generated method stub
698        return false;
699    }
700
701    @Override
702    public boolean onDown(MotionEvent arg0) {
703        // TODO Auto-generated method stub
704        return false;
705    }
706
707    @Override
708    public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
709        if ((!mActivity.isShowingHistoryPanel() && startEvent.getX() > endEvent.getX())
710                || (mActivity.isShowingHistoryPanel() && endEvent.getX() > startEvent.getX())) {
711            if (!mTouchShowOriginal
712                    || (mTouchShowOriginal &&
713                          (System.currentTimeMillis() - mTouchShowOriginalDate
714                                  < mTouchShowOriginalDelayMax))) {
715                mActivity.toggleHistoryPanel();
716            }
717        }
718        return true;
719    }
720
721    @Override
722    public void onLongPress(MotionEvent arg0) {
723        // TODO Auto-generated method stub
724
725    }
726
727    @Override
728    public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
729        // TODO Auto-generated method stub
730        return false;
731    }
732
733    @Override
734    public void onShowPress(MotionEvent arg0) {
735        // TODO Auto-generated method stub
736
737    }
738
739    @Override
740    public boolean onSingleTapUp(MotionEvent arg0) {
741        // TODO Auto-generated method stub
742        return false;
743    }
744}
745