OSDN Git Service

1ff4227a78ac86241b021fb38d8f76151c42a852
[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 import com.android.gallery3d.filtershow.tools.SaveImage;
44
45 import java.io.File;
46
47 public 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 }