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.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;
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;
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 int mTextSize;
55 protected int mTextPadding;
57 protected ImageLoader mImageLoader = null;
59 protected int mBackgroundColor;
61 private GestureDetector mGestureDetector = null;
62 private ScaleGestureDetector mScaleGestureDetector = null;
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;
73 private Point mTouchDown = new Point();
74 private Point mTouch = new Point();
75 private boolean mFinishedScalingOperation = false;
77 private int mOriginalTextMargin;
78 private int mOriginalTextSize;
79 private String mOriginalText;
80 private boolean mZoomIn = false;
81 Point mOriginalTranslation = new Point();
83 float mStartFocusX, mStartFocusY;
84 private enum InteractionMode {
89 InteractionMode mInteractionMode = InteractionMode.NONE;
91 protected GeometryMetadata getGeometry() {
92 return new GeometryMetadata(getImagePreset().getGeometry());
95 private FilterShowActivity mActivity = null;
97 public FilterShowActivity getActivity() {
101 public boolean hasModifications() {
102 return MasterImage.getImage().hasModifications();
105 public void resetParameter() {
106 // TODO: implement reset
109 public void onNewValue(int parameter) {
113 public ImageShow(Context context, AttributeSet attrs, int defStyle) {
114 super(context, attrs, defStyle);
115 setupImageShow(context);
118 public ImageShow(Context context, AttributeSet attrs) {
119 super(context, attrs);
120 setupImageShow(context);
124 public ImageShow(Context context) {
126 setupImageShow(context);
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);
142 public void setupGestureDetector(Context context) {
143 mGestureDetector = new GestureDetector(context, this);
144 mScaleGestureDetector = new ScaleGestureDetector(context, this);
148 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
149 int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
150 int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
151 setMeasuredDimension(parentWidth, parentHeight);
154 public ImageFilter getCurrentFilter() {
155 return MasterImage.getImage().getCurrentFilter();
158 public Rect getImageBounds() {
159 Rect dst = new Rect();
160 getImagePreset().getGeometry().getPhotoBounds().roundOut(dst);
164 public Rect getImageCropBounds() {
165 return GeometryMath.roundNearest(getImagePreset().getGeometry().getPreviewCropBounds());
168 /* consider moving the following 2 methods into a subclass */
170 * This function calculates a Image to Screen Transformation matrix
172 * @param reflectRotation set true if you want the rotation encoded
173 * @return Image to Screen transformation matrix
175 protected Matrix getImageToScreenMatrix(boolean reflectRotation) {
176 GeometryMetadata geo = getImagePreset().getGeometry();
177 if (geo == null || mImageLoader == null
178 || MasterImage.getImage().getOriginalBounds() == null) {
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);
192 * This function calculates a to Screen Image Transformation matrix
194 * @param reflectRotation set true if you want the rotation encoded
195 * @return Screen to Image transformation matrix
197 protected Matrix getScreenToImageMatrix(boolean reflectRotation) {
198 Matrix m = getImageToScreenMatrix(reflectRotation);
199 Matrix invert = new Matrix();
204 public ImagePreset getImagePreset() {
205 return MasterImage.getImage().getPreset();
209 public void onDraw(Canvas canvas) {
210 MasterImage.getImage().setImageShowSize(getWidth(), getHeight());
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();
217 Matrix scalingMatrix = new Matrix();
218 scalingMatrix.postScale(scaleFactor, scaleFactor, cx, cy);
219 scalingMatrix.preTranslate(translation.x, translation.y);
221 RectF unscaledClipRect = new RectF(mImageBounds);
222 scalingMatrix.mapRect(unscaledClipRect, unscaledClipRect);
226 boolean enablePartialRendering = false;
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);
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);
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);
253 canvas.scale(scaleFactor, scaleFactor, cx, cy);
254 canvas.translate(translation.x, translation.y);
255 drawPartialImage(canvas, getGeometryOnlyImage());
261 public void resetImageCaches(ImageShow caller) {
262 if (mImageLoader == null) {
265 MasterImage.getImage().updatePresets(true);
268 public Bitmap getFiltersOnlyImage() {
269 return MasterImage.getImage().getFiltersOnlyImage();
272 public Bitmap getGeometryOnlyImage() {
273 return MasterImage.getImage().getGeometryOnlyImage();
276 public Bitmap getFilteredImage() {
277 return MasterImage.getImage().getFilteredImage();
280 public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) {
282 Rect s = new Rect(0, 0, image.getWidth(),
285 float scale = GeometryMath.scale(image.getWidth(), image.getHeight(), getWidth(),
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;
293 Rect d = new Rect((int) tx, (int) ty, (int) (w + tx),
298 canvas.drawBitmap(image, s, d, mPaint);
302 public void drawPartialImage(Canvas canvas, Bitmap image) {
303 boolean showsOriginal = MasterImage.getImage().showsOriginal();
304 if (!showsOriginal && !mTouchShowOriginal)
308 if (mShowOriginalDirection == 0) {
309 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) {
310 mShowOriginalDirection = UNVEIL_VERTICAL;
312 mShowOriginalDirection = UNVEIL_HORIZONTAL;
318 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
319 px = mImageBounds.width();
320 py = mTouch.y - mImageBounds.top;
322 px = mTouch.x - mImageBounds.left;
323 py = mImageBounds.height();
325 px = mImageBounds.width();
329 Rect d = new Rect(mImageBounds.left, mImageBounds.top,
330 mImageBounds.left + px, mImageBounds.top + py);
332 drawImage(canvas, image, false);
333 Paint paint = new Paint();
334 paint.setColor(Color.BLACK);
335 paint.setStrokeWidth(3);
337 if (mShowOriginalDirection == UNVEIL_VERTICAL) {
338 canvas.drawLine(mImageBounds.left, mTouch.y,
339 mImageBounds.right, mTouch.y, paint);
341 canvas.drawLine(mTouch.x, mImageBounds.top,
342 mTouch.x, mImageBounds.bottom, paint);
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);
363 public void bindAsImageLoadListener() {
364 MasterImage.getImage().addListener(this);
367 private void imageSizeChanged(Bitmap image) {
368 if (image == null || getImagePreset() == null)
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()) {
377 RectF r = new RectF(0, 0, w, h);
378 geo.setPhotoBounds(r);
379 geo.setCropBounds(r);
380 getImagePreset().setGeometry(geo);
383 public void updateImage() {
385 Bitmap bitmap = MasterImage.getImage().getOriginalBitmapLarge();
386 if (bitmap != null) {
387 imageSizeChanged(bitmap);
391 public void imageLoaded() {
396 public void saveImage(FilterShowActivity filterShowActivity, File file) {
397 ImageLoader.saveImage(getImagePreset(), filterShowActivity, file);
401 public boolean scaleInProgress() {
402 return mScaleGestureDetector.isInProgress();
406 public boolean onTouchEvent(MotionEvent event) {
407 super.onTouchEvent(event);
408 int action = event.getAction();
409 action = action & MotionEvent.ACTION_MASK;
411 mGestureDetector.onTouchEvent(event);
412 boolean scaleInProgress = scaleInProgress();
413 mScaleGestureDetector.onTouchEvent(event);
414 if (mInteractionMode == InteractionMode.SCALE) {
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;
423 int ex = (int) event.getX();
424 int ey = (int) event.getY();
425 if (action == MotionEvent.ACTION_DOWN) {
426 mInteractionMode = InteractionMode.MOVE;
429 mTouchShowOriginalDate = System.currentTimeMillis();
430 mShowOriginalDirection = 0;
431 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation());
434 if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) {
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;
457 if (action == MotionEvent.ACTION_UP) {
458 mInteractionMode = InteractionMode.NONE;
459 mTouchShowOriginal = false;
464 if (MasterImage.getImage().getScaleFactor() <= 1) {
465 MasterImage.getImage().setScaleFactor(1);
466 MasterImage.getImage().resetTranslation();
473 protected boolean enableComparison() {
478 public boolean onDoubleTap(MotionEvent arg0) {
482 scale = MasterImage.getImage().getMaxScaleFactor();
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);
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) *
504 if (Math.abs(translation.y) > maxTranslationY) {
505 translation.y = (int) (Math.signum(translation.y) *
513 public boolean onDoubleTapEvent(MotionEvent arg0) {
518 public boolean onSingleTapConfirmed(MotionEvent arg0) {
523 public boolean onDown(MotionEvent arg0) {
528 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) {
529 if (mActivity == null) {
532 if (endEvent.getPointerCount() == 2) {
539 public void onLongPress(MotionEvent arg0) {
543 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) {
548 public void onShowPress(MotionEvent arg0) {
552 public boolean onSingleTapUp(MotionEvent arg0) {
556 public boolean useUtilityPanel() {
560 public void openUtilityPanel(final LinearLayout accessoryViewList) {
564 public boolean onScale(ScaleGestureDetector detector) {
565 MasterImage img = MasterImage.getImage();
566 float scaleFactor = img.getScaleFactor();
568 scaleFactor = scaleFactor * detector.getScaleFactor();
569 if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) {
570 scaleFactor = MasterImage.getImage().getMaxScaleFactor();
572 if (scaleFactor < 0.5) {
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);
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;
604 public void onScaleEnd(ScaleGestureDetector detector) {
605 mInteractionMode = InteractionMode.NONE;
606 if (MasterImage.getImage().getScaleFactor() < 1) {
607 MasterImage.getImage().setScaleFactor(1);
612 public boolean didFinishScalingOperation() {
613 if (mFinishedScalingOperation) {
614 mFinishedScalingOperation = false;