OSDN Git Service

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