2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.filtershow.imageshow;
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.Color;
23 import android.graphics.Matrix;
24 import android.graphics.Paint;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.graphics.RectF;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.util.AttributeSet;
31 import android.view.GestureDetector;
32 import android.view.GestureDetector.OnDoubleTapListener;
33 import android.view.GestureDetector.OnGestureListener;
34 import android.view.MotionEvent;
35 import android.view.ScaleGestureDetector;
36 import android.view.View;
37 import android.widget.LinearLayout;
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.presets.ImagePreset;
46 public class ImageShow extends View implements OnGestureListener,
47 ScaleGestureDetector.OnScaleGestureListener,
50 private static final String LOGTAG = "ImageShow";
51 private static final boolean ENABLE_ZOOMED_COMPARISON = false;
53 protected Paint mPaint = new Paint();
54 protected static int mTextSize = 24;
55 protected static int mTextPadding = 20;
57 protected ImageLoader mImageLoader = null;
58 private boolean mDirtyGeometry = false;
60 private Bitmap mBackgroundImage = null;
61 private final boolean USE_BACKGROUND_IMAGE = false;
62 private static int mBackgroundColor = Color.RED;
64 private GestureDetector mGestureDetector = null;
65 private ScaleGestureDetector mScaleGestureDetector = null;
67 protected Rect mImageBounds = new Rect();
68 private boolean mOriginalDisabled = false;
69 private boolean mTouchShowOriginal = false;
70 private long mTouchShowOriginalDate = 0;
71 private final long mTouchShowOriginalDelayMin = 200; // 200ms
72 private final long mTouchShowOriginalDelayMax = 300; // 300ms
73 private int mShowOriginalDirection = 0;
74 private static int UNVEIL_HORIZONTAL = 1;
75 private static int UNVEIL_VERTICAL = 2;
77 private Point mTouchDown = new Point();
78 private Point mTouch = new Point();
79 private boolean mFinishedScalingOperation = false;
81 private static int mOriginalTextMargin = 8;
82 private static int mOriginalTextSize = 26;
83 private static String mOriginalText = "Original";
84 private boolean mZoomIn = false;
85 Point mOriginalTranslation = new Point();
87 float mStartFocusX, mStartFocusY;
88 private enum InteractionMode {
93 private String mToast = null;
94 private boolean mShowToast = false;
95 private boolean mImportantToast = false;
96 InteractionMode mInteractionMode = InteractionMode.NONE;
98 protected GeometryMetadata getGeometry() {
99 return new GeometryMetadata(getImagePreset().mGeoData);
102 private FilterShowActivity mActivity = null;
104 public static void setDefaultBackgroundColor(int value) {
105 mBackgroundColor = value;
108 public FilterShowActivity getActivity() {
112 public int getDefaultBackgroundColor() {
113 return mBackgroundColor;
116 public static void setTextSize(int value) {
120 public static void setTextPadding(int value) {
121 mTextPadding = value;
124 public static void setOriginalTextMargin(int value) {
125 mOriginalTextMargin = value;
128 public static void setOriginalTextSize(int value) {
129 mOriginalTextSize = value;
132 public static void setOriginalText(String text) {
133 mOriginalText = text;
136 private final Handler mHandler = new Handler();
138 public void select() {
141 public void unselect() {
144 public boolean hasModifications() {
145 if (getImagePreset() == null) {
148 return getImagePreset().hasModifications();
151 public void resetParameter() {
152 // TODO: implement reset
155 public void onNewValue(int parameter) {
157 mActivity.enableSave(hasModifications());
160 public Point getTouchPoint() {
164 public ImageShow(Context context, AttributeSet attrs) {
165 super(context, attrs);
167 setupGestureDetector(context);
168 mActivity = (FilterShowActivity) context;
169 MasterImage.getImage().addObserver(this);
172 public ImageShow(Context context) {
175 setupGestureDetector(context);
176 mActivity = (FilterShowActivity) context;
177 MasterImage.getImage().addObserver(this);
180 public void setupGestureDetector(Context context) {
181 mGestureDetector = new GestureDetector(context, this);
182 mScaleGestureDetector = new ScaleGestureDetector(context, this);
186 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
187 int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
188 int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
189 setMeasuredDimension(parentWidth, parentHeight);
192 public ImageFilter getCurrentFilter() {
193 return MasterImage.getImage().getCurrentFilter();
196 public void showToast(String text) {
197 showToast(text, false);
200 public void showToast(String text, boolean important) {
203 mImportantToast = important;
206 mHandler.postDelayed(new Runnable() {
215 public Rect getImageBounds() {
216 Rect dst = new Rect();
217 getImagePreset().mGeoData.getPhotoBounds().roundOut(dst);
221 public Rect getImageCropBounds() {
222 return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds());
225 /* consider moving the following 2 methods into a subclass */
227 * This function calculates a Image to Screen Transformation matrix
229 * @param reflectRotation set true if you want the rotation encoded
230 * @return Image to Screen transformation matrix
232 protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
233 GeometryMetadata geo = getImagePreset().mGeoData;
234 if (geo == null || mImageLoader == null
235 || mImageLoader.getOriginalBounds() == null) {
238 Matrix m = geo.getOriginalToScreen(reflectRotation,
239 mImageLoader.getOriginalBounds().width(),
240 mImageLoader.getOriginalBounds().height(), getWidth(), getHeight());
241 Point translate = MasterImage.getImage().getTranslation();
242 float scaleFactor = MasterImage.getImage().getScaleFactor();
243 m.postTranslate(translate.x, translate.y);
244 m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f);
249 * This function calculates a to Screen Image Transformation matrix
251 * @param reflectRotation set true if you want the rotation encoded
252 * @return Screen to Image transformation matrix
254 protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
255 Matrix m = getImageToScreenMatrix(reflectRotation);
256 Matrix invert = new Matrix();
261 public Rect getDisplayedImageBounds() {
265 public ImagePreset getImagePreset() {
266 return MasterImage.getImage().getPreset();
269 public void drawToast(Canvas canvas) {
270 if (mShowToast && mToast != null) {
271 Paint paint = new Paint();
272 paint.setTextSize(128);
273 float textWidth = paint.measureText(mToast);
274 int toastX = (int) ((getWidth() - textWidth) / 2.0f);
275 int toastY = (int) (getHeight() / 3.0f);
277 paint.setARGB(255, 0, 0, 0);
278 canvas.drawText(mToast, toastX - 2, toastY - 2, paint);
279 canvas.drawText(mToast, toastX - 2, toastY, paint);
280 canvas.drawText(mToast, toastX, toastY - 2, paint);
281 canvas.drawText(mToast, toastX + 2, toastY + 2, paint);
282 canvas.drawText(mToast, toastX + 2, toastY, paint);
283 canvas.drawText(mToast, toastX, toastY + 2, paint);
284 if (mImportantToast) {
285 paint.setARGB(255, 200, 0, 0);
287 paint.setARGB(255, 255, 255, 255);
289 canvas.drawText(mToast, toastX, toastY, paint);
294 public void onDraw(Canvas canvas) {
295 MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
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();
302 Matrix scalingMatrix = new Matrix();
303 scalingMatrix.postScale(scaleFactor, scaleFactor, cx, cy);
304 scalingMatrix.preTranslate(translation.x, translation.y);
306 RectF unscaledClipRect = new RectF(mImageBounds);
307 scalingMatrix.mapRect(unscaledClipRect, unscaledClipRect);
311 boolean enablePartialRendering = false;
313 // For now, partial rendering is disabled for all filters,
314 // so no need to clip.
315 if (enablePartialRendering && !unscaledClipRect.isEmpty()) {
316 canvas.clipRect(unscaledClipRect);
320 // TODO: center scale on gesture
321 canvas.scale(scaleFactor, scaleFactor, cx, cy);
322 canvas.translate(translation.x, translation.y);
323 drawBackground(canvas);
324 drawImage(canvas, getFilteredImage(), true);
325 Bitmap highresPreview = MasterImage.getImage().getHighresImage();
326 if (highresPreview != null) {
327 drawImage(canvas, highresPreview, false);
331 if (showTitle() && getImagePreset() != null) {
332 mPaint.setARGB(200, 0, 0, 0);
333 mPaint.setTextSize(mTextSize);
335 Rect textRect = new Rect(0, 0, getWidth(), mTextSize + mTextPadding);
336 canvas.drawRect(textRect, mPaint);
337 mPaint.setARGB(255, 200, 200, 200);
338 canvas.drawText(getImagePreset().name(), mTextPadding,
339 1.5f * mTextPadding, mPaint);
342 Bitmap partialPreview = MasterImage.getImage().getPartialImage();
343 if (partialPreview != null) {
344 Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight());
345 Rect dest = new Rect(0, 0, getWidth(), getHeight());
346 canvas.drawBitmap(partialPreview, src, dest, mPaint);
350 canvas.scale(scaleFactor, scaleFactor, cx, cy);
351 canvas.translate(translation.x, translation.y);
352 drawPartialImage(canvas, getGeometryOnlyImage());
360 public void resetImageCaches(ImageShow caller) {
361 if (mImageLoader == null) {
364 MasterImage.getImage().updatePresets(true);
367 public Bitmap getFiltersOnlyImage() {
368 return MasterImage.getImage().getFiltersOnlyImage();
371 public Bitmap getGeometryOnlyImage() {
372 return MasterImage.getImage().getGeometryOnlyImage();
375 public Bitmap getFilteredImage() {
376 return MasterImage.getImage().getFilteredImage();
379 public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) {
381 Rect s = new Rect(0, 0, image.getWidth(),
384 float scale = GeometryMath.scale(image.getWidth(), image.getHeight(), getWidth(),
387 float w = image.getWidth() * scale;
388 float h = image.getHeight() * scale;
389 float ty = (getHeight() - h) / 2.0f;
390 float tx = (getWidth() - w) / 2.0f;
392 Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
397 canvas.drawBitmap(image, s, d, mPaint);
401 public void drawPartialImage(Canvas canvas, Bitmap image) {
402 boolean showsOriginal = MasterImage.getImage().showsOriginal();
403 if (!showsOriginal && !mTouchShowOriginal)
407 if (mShowOriginalDirection == 0) {
408 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
409 mShowOriginalDirection = UNVEIL_VERTICAL;
411 mShowOriginalDirection = UNVEIL_HORIZONTAL;
417 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
418 px = mImageBounds.width();
419 py = (int) (mTouch.y - mImageBounds.top);
421 px = (int) (mTouch.x - mImageBounds.left);
422 py = mImageBounds.height();
424 px = mImageBounds.width();
428 Rect d = new Rect(mImageBounds.left, mImageBounds.top,
429 mImageBounds.left + px, mImageBounds.top + py);
431 drawImage(canvas, image, false);
432 Paint paint = new Paint();
433 paint.setColor(Color.BLACK);
434 paint.setStrokeWidth(3);
436 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
437 canvas.drawLine(mImageBounds.left, mTouch.y,
438 mImageBounds.right, mTouch.y, paint);
440 canvas.drawLine(mTouch.x, mImageBounds.top,
441 mTouch.x, mImageBounds.bottom, paint);
444 Rect bounds = new Rect();
445 paint.setAntiAlias(true);
446 paint.setTextSize(mOriginalTextSize);
447 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds);
448 paint.setColor(Color.BLACK);
449 paint.setStyle(Paint.Style.STROKE);
450 paint.setStrokeWidth(3);
451 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
452 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
453 paint.setStyle(Paint.Style.FILL);
454 paint.setStrokeWidth(1);
455 paint.setColor(Color.WHITE);
456 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin,
457 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint);
462 public void drawBackground(Canvas canvas) {
463 if (USE_BACKGROUND_IMAGE) {
464 if (mBackgroundImage == null) {
465 mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources());
467 if (mBackgroundImage != null) {
468 Rect s = new Rect(0, 0, mBackgroundImage.getWidth(),
469 mBackgroundImage.getHeight());
470 Rect d = new Rect(0, 0, getWidth(), getHeight());
471 canvas.drawBitmap(mBackgroundImage, s, d, mPaint);
474 canvas.drawARGB(0, 0, 0, 0);
478 public boolean showTitle() {
482 public void setImageLoader(ImageLoader loader) {
483 mImageLoader = loader;
484 if (mImageLoader != null) {
485 mImageLoader.addListener(this);
486 MasterImage.getImage().setImageLoader(mImageLoader);
490 private void setDirtyGeometryFlag() {
491 mDirtyGeometry = true;
494 protected void clearDirtyGeometryFlag() {
495 mDirtyGeometry = false;
498 protected boolean getDirtyGeometryFlag() {
499 return mDirtyGeometry;
502 private void imageSizeChanged(Bitmap image) {
503 if (image == null || getImagePreset() == null)
505 float w = image.getWidth();
506 float h = image.getHeight();
507 GeometryMetadata geo = getImagePreset().mGeoData;
508 RectF pb = geo.getPhotoBounds();
509 if (w == pb.width() && h == pb.height()) {
512 RectF r = new RectF(0, 0, w, h);
513 getImagePreset().mGeoData.setPhotoBounds(r);
514 getImagePreset().mGeoData.setCropBounds(r);
518 public boolean updateGeometryFlags() {
522 public void updateImage() {
524 if (!updateGeometryFlags()) {
527 Bitmap bitmap = mImageLoader.getOriginalBitmapLarge();
528 if (bitmap != null) {
529 imageSizeChanged(bitmap);
533 public void imageLoaded() {
538 public void saveImage(FilterShowActivity filterShowActivity, File file) {
539 mImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
543 public boolean scaleInProgress() {
544 return mScaleGestureDetector.isInProgress();
547 protected boolean isOriginalDisabled() {
548 return mOriginalDisabled;
551 protected void setOriginalDisabled(boolean originalDisabled) {
552 mOriginalDisabled = originalDisabled;
556 public boolean onTouchEvent(MotionEvent event) {
557 super.onTouchEvent(event);
558 int action = event.getAction();
559 action = action & MotionEvent.ACTION_MASK;
561 mGestureDetector.onTouchEvent(event);
562 boolean scaleInProgress = scaleInProgress();
563 mScaleGestureDetector.onTouchEvent(event);
564 if (mInteractionMode == InteractionMode.SCALE) {
567 if (!scaleInProgress() && scaleInProgress) {
568 // If we were scaling, the scale will stop but we will
569 // still issue an ACTION_UP. Let the subclasses know.
570 mFinishedScalingOperation = true;
573 int ex = (int) event.getX();
574 int ey = (int) event.getY();
575 if (action == MotionEvent.ACTION_DOWN) {
576 mInteractionMode = InteractionMode.MOVE;
579 mTouchShowOriginalDate = System.currentTimeMillis();
580 mShowOriginalDirection = 0;
581 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
584 if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
588 float scaleFactor = MasterImage.getImage().getScaleFactor();
589 if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) {
590 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor;
591 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor;
592 Point originalTranslation = MasterImage.getImage().getOriginalTranslation();
593 Point translation = MasterImage.getImage().getTranslation();
594 translation.x = (int) (originalTranslation.x + translateX);
595 translation.y = (int) (originalTranslation.y + translateY);
596 constrainTranslation(translation, scaleFactor);
597 MasterImage.getImage().setTranslation(translation);
598 mTouchShowOriginal = false;
599 } else if (enableComparison() && !mOriginalDisabled
600 && (System.currentTimeMillis() - mTouchShowOriginalDate
601 > mTouchShowOriginalDelayMin)
602 && event.getPointerCount() == 1) {
603 mTouchShowOriginal = true;
607 if (action == MotionEvent.ACTION_UP) {
608 mInteractionMode = InteractionMode.NONE;
609 mTouchShowOriginal = false;
614 if (MasterImage.getImage().getScaleFactor() <= 1) {
615 MasterImage.getImage().setScaleFactor(1);
616 MasterImage.getImage().resetTranslation();
623 protected boolean enableComparison() {
628 public void showOriginal(boolean show) {
633 public boolean onDoubleTap(MotionEvent arg0) {
637 scale = MasterImage.getImage().getMaxScaleFactor();
639 if (scale != MasterImage.getImage().getScaleFactor()) {
640 MasterImage.getImage().setScaleFactor(scale);
641 float translateX = (getWidth() / 2 - arg0.getX());
642 float translateY = (getHeight() / 2 - arg0.getY());
643 Point translation = MasterImage.getImage().getTranslation();
644 translation.x = (int) (mOriginalTranslation.x + translateX);
645 translation.y = (int) (mOriginalTranslation.y + translateY);
646 constrainTranslation(translation, scale);
647 MasterImage.getImage().setTranslation(translation);
653 private void constrainTranslation(Point translation, float scale) {
654 float maxTranslationX = getWidth() / scale;
655 float maxTranslationY = getHeight() / scale;
656 if (Math.abs(translation.x) > maxTranslationX) {
657 translation.x = (int) (Math.signum(translation.x) *
659 if (Math.abs(translation.y) > maxTranslationY) {
660 translation.y = (int) (Math.signum(translation.y) *
668 public boolean onDoubleTapEvent(MotionEvent arg0) {
669 // TODO Auto-generated method stub
674 public boolean onSingleTapConfirmed(MotionEvent arg0) {
675 // TODO Auto-generated method stub
680 public boolean onDown(MotionEvent arg0) {
681 // TODO Auto-generated method stub
686 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
687 if (mActivity == null) {
690 if (endEvent.getPointerCount() == 2) {
697 public void onLongPress(MotionEvent arg0) {
698 // TODO Auto-generated method stub
702 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
703 // TODO Auto-generated method stub
708 public void onShowPress(MotionEvent arg0) {
709 // TODO Auto-generated method stub
713 public boolean onSingleTapUp(MotionEvent arg0) {
714 // TODO Auto-generated method stub
718 public boolean useUtilityPanel() {
722 public void openUtilityPanel(final LinearLayout accessoryViewList) {
723 // TODO Auto-generated method stub
727 public boolean onScale(ScaleGestureDetector detector) {
728 MasterImage img = MasterImage.getImage();
729 float scaleFactor = img.getScaleFactor();
730 Point pos = img.getTranslation();
732 scaleFactor = scaleFactor * detector.getScaleFactor();
733 if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
734 scaleFactor = MasterImage.getImage().getMaxScaleFactor();
736 if (scaleFactor < 0.5) {
739 MasterImage.getImage().setScaleFactor(scaleFactor);
740 scaleFactor = img.getScaleFactor();
741 pos = img.getTranslation();
742 float focusx = detector.getFocusX();
743 float focusy = detector.getFocusY();
744 float translateX = (focusx - mStartFocusX) / scaleFactor;
745 float translateY = (focusy - mStartFocusY) / scaleFactor;
746 Point translation = MasterImage.getImage().getTranslation();
747 translation.x = (int) (mOriginalTranslation.x + translateX);
748 translation.y = (int) (mOriginalTranslation.y + translateY);
749 constrainTranslation(translation, scaleFactor);
750 MasterImage.getImage().setTranslation(translation);
757 public boolean onScaleBegin(ScaleGestureDetector detector) {
758 Point pos = MasterImage.getImage().getTranslation();
759 mOriginalTranslation.x = pos.x;
760 mOriginalTranslation.y = pos.y;
761 mOriginalScale = MasterImage.getImage().getScaleFactor();
762 mStartFocusX = detector.getFocusX();
763 mStartFocusY = detector.getFocusY();
764 mInteractionMode = InteractionMode.SCALE;
769 public void onScaleEnd(ScaleGestureDetector detector) {
770 mInteractionMode = InteractionMode.NONE;
771 if (MasterImage.getImage().getScaleFactor() < 1) {
772 MasterImage.getImage().setScaleFactor(1);
777 public boolean didFinishScalingOperation() {
778 if (mFinishedScalingOperation) {
779 mFinishedScalingOperation = false;