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.animation.Animator; 20 import android.animation.ValueAnimator; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.graphics.Bitmap; 24 import android.graphics.BitmapFactory; 25 import android.graphics.BitmapShader; 26 import android.graphics.Canvas; 27 import android.graphics.Color; 28 import android.graphics.Matrix; 29 import android.graphics.Paint; 30 import android.graphics.Point; 31 import android.graphics.Rect; 32 import android.graphics.RectF; 33 import android.graphics.Shader; 34 import android.graphics.drawable.NinePatchDrawable; 35 import android.support.v4.widget.EdgeEffectCompat; 36 import android.util.AttributeSet; 37 import android.util.Log; 38 import android.view.GestureDetector; 39 import android.view.GestureDetector.OnDoubleTapListener; 40 import android.view.GestureDetector.OnGestureListener; 41 import android.view.MotionEvent; 42 import android.view.ScaleGestureDetector; 43 import android.view.View; 44 import android.widget.LinearLayout; 45 46 import com.android.gallery3d.R; 47 import com.android.gallery3d.filtershow.FilterShowActivity; 48 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation; 49 import com.android.gallery3d.filtershow.filters.FilterRepresentation; 50 import com.android.gallery3d.filtershow.filters.ImageFilter; 51 import com.android.gallery3d.filtershow.pipeline.ImagePreset; 52 import com.android.gallery3d.filtershow.tools.SaveImage; 53 54 import java.io.File; 55 import java.util.ArrayList; 56 57 public class ImageShow extends View implements OnGestureListener, 58 ScaleGestureDetector.OnScaleGestureListener, 59 OnDoubleTapListener { 60 61 private static final String LOGTAG = "ImageShow"; 62 private static final boolean ENABLE_ZOOMED_COMPARISON = false; 63 64 protected Paint mPaint = new Paint(); 65 protected int mTextSize; 66 protected int mTextPadding; 67 68 protected int mBackgroundColor; 69 70 private GestureDetector mGestureDetector = null; 71 private ScaleGestureDetector mScaleGestureDetector = null; 72 73 protected Rect mImageBounds = new Rect(); 74 private boolean mOriginalDisabled = false; 75 private boolean mTouchShowOriginal = false; 76 private long mTouchShowOriginalDate = 0; 77 private final long mTouchShowOriginalDelayMin = 200; // 200ms 78 private int mShowOriginalDirection = 0; 79 private static int UNVEIL_HORIZONTAL = 1; 80 private static int UNVEIL_VERTICAL = 2; 81 82 private NinePatchDrawable mShadow = null; 83 private Rect mShadowBounds = new Rect(); 84 private int mShadowMargin = 15; // not scaled, fixed in the asset 85 private boolean mShadowDrawn = false; 86 87 private Point mTouchDown = new Point(); 88 private Point mTouch = new Point(); 89 private boolean mFinishedScalingOperation = false; 90 91 private int mOriginalTextMargin; 92 private int mOriginalTextSize; 93 private String mOriginalText; 94 private boolean mZoomIn = false; 95 Point mOriginalTranslation = new Point(); 96 float mOriginalScale; 97 float mStartFocusX, mStartFocusY; 98 99 private EdgeEffectCompat mEdgeEffect = null; 100 private static final int EDGE_LEFT = 1; 101 private static final int EDGE_TOP = 2; 102 private static final int EDGE_RIGHT = 3; 103 private static final int EDGE_BOTTOM = 4; 104 private int mCurrentEdgeEffect = 0; 105 private int mEdgeSize = 100; 106 107 private static final int mAnimationSnapDelay = 200; 108 private static final int mAnimationZoomDelay = 400; 109 private ValueAnimator mAnimatorScale = null; 110 private ValueAnimator mAnimatorTranslateX = null; 111 private ValueAnimator mAnimatorTranslateY = null; 112 113 private enum InteractionMode { 114 NONE, 115 SCALE, 116 MOVE 117 } 118 InteractionMode mInteractionMode = InteractionMode.NONE; 119 120 private static Bitmap sMask; 121 private Paint mMaskPaint = new Paint(); 122 private Matrix mShaderMatrix = new Matrix(); 123 private boolean mDidStartAnimation = false; 124 125 private static Bitmap convertToAlphaMask(Bitmap b) { 126 Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8); 127 Canvas c = new Canvas(a); 128 c.drawBitmap(b, 0.0f, 0.0f, null); 129 return a; 130 } 131 132 private static Shader createShader(Bitmap b) { 133 return new BitmapShader(b, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 134 } 135 136 private FilterShowActivity mActivity = null; 137 138 public FilterShowActivity getActivity() { 139 return mActivity; 140 } 141 142 public boolean hasModifications() { 143 return MasterImage.getImage().hasModifications(); 144 } 145 146 public void resetParameter() { 147 // TODO: implement reset 148 } 149 150 public void onNewValue(int parameter) { 151 invalidate(); 152 } 153 154 public ImageShow(Context context, AttributeSet attrs, int defStyle) { 155 super(context, attrs, defStyle); 156 setupImageShow(context); 157 } 158 159 public ImageShow(Context context, AttributeSet attrs) { 160 super(context, attrs); 161 setupImageShow(context); 162 163 } 164 165 public ImageShow(Context context) { 166 super(context); 167 setupImageShow(context); 168 } 169 170 private void setupImageShow(Context context) { 171 Resources res = context.getResources(); 172 mTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_text_size); 173 mTextPadding = res.getDimensionPixelSize(R.dimen.photoeditor_text_padding); 174 mOriginalTextMargin = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_margin); 175 mOriginalTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_size); 176 mBackgroundColor = res.getColor(R.color.background_screen); 177 mOriginalText = res.getString(R.string.original_picture_text); 178 mShadow = (NinePatchDrawable) res.getDrawable(R.drawable.geometry_shadow); 179 setupGestureDetector(context); 180 mActivity = (FilterShowActivity) context; 181 if (sMask == null) { 182 Bitmap mask = BitmapFactory.decodeResource(res, R.drawable.spot_mask); 183 sMask = convertToAlphaMask(mask); 184 } 185 mEdgeEffect = new EdgeEffectCompat(context); 186 mEdgeSize = res.getDimensionPixelSize(R.dimen.edge_glow_size); 187 } 188 189 public void attach() { 190 MasterImage.getImage().addObserver(this); 191 bindAsImageLoadListener(); 192 MasterImage.getImage().resetGeometryImages(false); 193 } 194 195 public void detach() { 196 MasterImage.getImage().removeObserver(this); 197 mMaskPaint.reset(); 198 } 199 200 public void setupGestureDetector(Context context) { 201 mGestureDetector = new GestureDetector(context, this); 202 mScaleGestureDetector = new ScaleGestureDetector(context, this); 203 } 204 205 @Override 206 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 207 int parentWidth = MeasureSpec.getSize(widthMeasureSpec); 208 int parentHeight = MeasureSpec.getSize(heightMeasureSpec); 209 setMeasuredDimension(parentWidth, parentHeight); 210 } 211 212 public ImageFilter getCurrentFilter() { 213 return MasterImage.getImage().getCurrentFilter(); 214 } 215 216 /* consider moving the following 2 methods into a subclass */ 217 /** 218 * This function calculates a Image to Screen Transformation matrix 219 * 220 * @param reflectRotation set true if you want the rotation encoded 221 * @return Image to Screen transformation matrix 222 */ 223 protected Matrix getImageToScreenMatrix(boolean reflectRotation) { 224 MasterImage master = MasterImage.getImage(); 225 if (master.getOriginalBounds() == null) { 226 return new Matrix(); 227 } 228 Matrix m = GeometryMathUtils.getImageToScreenMatrix(master.getPreset().getGeometryFilters(), 229 reflectRotation, master.getOriginalBounds(), getWidth(), getHeight()); 230 Point translate = master.getTranslation(); 231 float scaleFactor = master.getScaleFactor(); 232 m.postTranslate(translate.x, translate.y); 233 m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f); 234 return m; 235 } 236 237 /** 238 * This function calculates a to Screen Image Transformation matrix 239 * 240 * @param reflectRotation set true if you want the rotation encoded 241 * @return Screen to Image transformation matrix 242 */ 243 protected Matrix getScreenToImageMatrix(boolean reflectRotation) { 244 Matrix m = getImageToScreenMatrix(reflectRotation); 245 Matrix invert = new Matrix(); 246 m.invert(invert); 247 return invert; 248 } 249 250 public ImagePreset getImagePreset() { 251 return MasterImage.getImage().getPreset(); 252 } 253 254 @Override 255 public void onDraw(Canvas canvas) { 256 mPaint.reset(); 257 mPaint.setAntiAlias(true); 258 mPaint.setFilterBitmap(true); 259 MasterImage.getImage().setImageShowSize( 260 getWidth() - 2*mShadowMargin, 261 getHeight() - 2*mShadowMargin); 262 263 MasterImage img = MasterImage.getImage(); 264 // Hide the loading indicator as needed 265 if (mActivity.isLoadingVisible() && getFilteredImage() != null) { 266 if ((img.getLoadedPreset() == null) 267 || (img.getLoadedPreset() != null 268 && img.getLoadedPreset().equals(img.getCurrentPreset()))) { 269 mActivity.stopLoadingIndicator(); 270 } else if (img.getLoadedPreset() != null) { 271 return; 272 } 273 mActivity.stopLoadingIndicator(); 274 } 275 276 canvas.save(); 277 278 mShadowDrawn = false; 279 280 Bitmap highresPreview = MasterImage.getImage().getHighresImage(); 281 Bitmap fullHighres = MasterImage.getImage().getPartialImage(); 282 283 boolean isDoingNewLookAnimation = MasterImage.getImage().onGoingNewLookAnimation(); 284 285 if (highresPreview == null || isDoingNewLookAnimation) { 286 drawImageAndAnimate(canvas, getFilteredImage()); 287 } else { 288 drawImageAndAnimate(canvas, highresPreview); 289 } 290 291 drawHighresImage(canvas, fullHighres); 292 drawCompareImage(canvas, getGeometryOnlyImage()); 293 294 canvas.restore(); 295 296 if (!mEdgeEffect.isFinished()) { 297 canvas.save(); 298 float dx = (getHeight() - getWidth()) / 2f; 299 if (getWidth() > getHeight()) { 300 dx = - (getWidth() - getHeight()) / 2f; 301 } 302 if (mCurrentEdgeEffect == EDGE_BOTTOM) { 303 canvas.rotate(180, getWidth()/2, getHeight()/2); 304 } else if (mCurrentEdgeEffect == EDGE_RIGHT) { 305 canvas.rotate(90, getWidth()/2, getHeight()/2); 306 canvas.translate(0, dx); 307 } else if (mCurrentEdgeEffect == EDGE_LEFT) { 308 canvas.rotate(270, getWidth()/2, getHeight()/2); 309 canvas.translate(0, dx); 310 } 311 if (mCurrentEdgeEffect != 0) { 312 mEdgeEffect.draw(canvas); 313 } 314 canvas.restore(); 315 invalidate(); 316 } else { 317 mCurrentEdgeEffect = 0; 318 } 319 } 320 321 private void drawHighresImage(Canvas canvas, Bitmap fullHighres) { 322 Matrix originalToScreen = MasterImage.getImage().originalImageToScreen(); 323 if (fullHighres != null && originalToScreen != null) { 324 Matrix screenToOriginal = new Matrix(); 325 originalToScreen.invert(screenToOriginal); 326 Rect rBounds = new Rect(); 327 rBounds.set(MasterImage.getImage().getPartialBounds()); 328 if (fullHighres != null) { 329 originalToScreen.preTranslate(rBounds.left, rBounds.top); 330 canvas.clipRect(mImageBounds); 331 canvas.drawBitmap(fullHighres, originalToScreen, mPaint); 332 } 333 } 334 } 335 336 public void resetImageCaches(ImageShow caller) { 337 MasterImage.getImage().invalidatePreview(); 338 } 339 340 public Bitmap getFiltersOnlyImage() { 341 return MasterImage.getImage().getFiltersOnlyImage(); 342 } 343 344 public Bitmap getGeometryOnlyImage() { 345 return MasterImage.getImage().getGeometryOnlyImage(); 346 } 347 348 public Bitmap getFilteredImage() { 349 return MasterImage.getImage().getFilteredImage(); 350 } 351 352 public void drawImageAndAnimate(Canvas canvas, 353 Bitmap image) { 354 if (image == null) { 355 return; 356 } 357 MasterImage master = MasterImage.getImage(); 358 Matrix m = master.computeImageToScreen(image, 0, false); 359 if (m == null) { 360 return; 361 } 362 363 canvas.save(); 364 365 RectF d = new RectF(0, 0, image.getWidth(), image.getHeight()); 366 m.mapRect(d); 367 d.roundOut(mImageBounds); 368 369 boolean showAnimatedImage = master.onGoingNewLookAnimation(); 370 if (!showAnimatedImage && mDidStartAnimation) { 371 // animation ended, but do we have the correct image to show? 372 if (master.getPreset().equals(master.getCurrentPreset())) { 373 // we do, let's stop showing the animated image 374 mDidStartAnimation = false; 375 MasterImage.getImage().resetAnimBitmap(); 376 } else { 377 showAnimatedImage = true; 378 } 379 } else if (showAnimatedImage) { 380 mDidStartAnimation = true; 381 } 382 383 if (showAnimatedImage) { 384 canvas.save(); 385 386 // Animation uses the image before the change 387 Bitmap previousImage = master.getPreviousImage(); 388 Matrix mp = master.computeImageToScreen(previousImage, 0, false); 389 RectF dp = new RectF(0, 0, previousImage.getWidth(), previousImage.getHeight()); 390 mp.mapRect(dp); 391 Rect previousBounds = new Rect(); 392 dp.roundOut(previousBounds); 393 float centerX = dp.centerX(); 394 float centerY = dp.centerY(); 395 boolean needsToDrawImage = true; 396 397 if (master.getCurrentLookAnimation() 398 == MasterImage.CIRCLE_ANIMATION) { 399 float maskScale = MasterImage.getImage().getMaskScale(); 400 if (maskScale >= 0.0f) { 401 float maskW = sMask.getWidth() / 2.0f; 402 float maskH = sMask.getHeight() / 2.0f; 403 Point point = mActivity.hintTouchPoint(this); 404 float maxMaskScale = 2 * Math.max(getWidth(), getHeight()) 405 / Math.min(maskW, maskH); 406 maskScale = maskScale * maxMaskScale; 407 float x = point.x - maskW * maskScale; 408 float y = point.y - maskH * maskScale; 409 410 // Prepare the shader 411 mShaderMatrix.reset(); 412 mShaderMatrix.setScale(1.0f / maskScale, 1.0f / maskScale); 413 mShaderMatrix.preTranslate(-x + mImageBounds.left, -y + mImageBounds.top); 414 float scaleImageX = mImageBounds.width() / (float) image.getWidth(); 415 float scaleImageY = mImageBounds.height() / (float) image.getHeight(); 416 mShaderMatrix.preScale(scaleImageX, scaleImageY); 417 mMaskPaint.reset(); 418 mMaskPaint.setShader(createShader(image)); 419 mMaskPaint.getShader().setLocalMatrix(mShaderMatrix); 420 421 drawShadow(canvas, mImageBounds); // as needed 422 canvas.drawBitmap(previousImage, m, mPaint); 423 canvas.clipRect(mImageBounds); 424 canvas.translate(x, y); 425 canvas.scale(maskScale, maskScale); 426 canvas.drawBitmap(sMask, 0, 0, mMaskPaint); 427 needsToDrawImage = false; 428 } 429 } else if (master.getCurrentLookAnimation() 430 == MasterImage.ROTATE_ANIMATION) { 431 Rect d1 = computeImageBounds(master.getPreviousImage().getHeight(), 432 master.getPreviousImage().getWidth()); 433 Rect d2 = computeImageBounds(master.getPreviousImage().getWidth(), 434 master.getPreviousImage().getHeight()); 435 float finalScale = d1.width() / (float) d2.height(); 436 finalScale = (1.0f * (1.0f - master.getAnimFraction())) 437 + (finalScale * master.getAnimFraction()); 438 canvas.rotate(master.getAnimRotationValue(), centerX, centerY); 439 canvas.scale(finalScale, finalScale, centerX, centerY); 440 } else if (master.getCurrentLookAnimation() 441 == MasterImage.MIRROR_ANIMATION) { 442 if (master.getCurrentFilterRepresentation() 443 instanceof FilterMirrorRepresentation) { 444 FilterMirrorRepresentation rep = 445 (FilterMirrorRepresentation) master.getCurrentFilterRepresentation(); 446 447 ImagePreset preset = master.getPreset(); 448 ArrayList<FilterRepresentation> geometry = 449 (ArrayList<FilterRepresentation>) preset.getGeometryFilters(); 450 GeometryMathUtils.GeometryHolder holder = null; 451 holder = GeometryMathUtils.unpackGeometry(geometry); 452 453 if (holder.rotation.value() == 90 || holder.rotation.value() == 270) { 454 if (rep.isHorizontal() && !rep.isVertical()) { 455 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY); 456 } else if (rep.isVertical() && !rep.isHorizontal()) { 457 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY); 458 } else if (rep.isHorizontal() && rep.isVertical()) { 459 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY); 460 } else { 461 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY); 462 } 463 } else { 464 if (rep.isHorizontal() && !rep.isVertical()) { 465 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY); 466 } else if (rep.isVertical() && !rep.isHorizontal()) { 467 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY); 468 } else if (rep.isHorizontal() && rep.isVertical()) { 469 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY); 470 } else { 471 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY); 472 } 473 } 474 } 475 } 476 477 if (needsToDrawImage) { 478 drawShadow(canvas, previousBounds); // as needed 479 canvas.drawBitmap(previousImage, mp, mPaint); 480 } 481 482 canvas.restore(); 483 } else { 484 drawShadow(canvas, mImageBounds); // as needed 485 canvas.drawBitmap(image, m, mPaint); 486 } 487 488 canvas.restore(); 489 } 490 491 private Rect computeImageBounds(int imageWidth, int imageHeight) { 492 float scale = GeometryMathUtils.scale(imageWidth, imageHeight, 493 getWidth(), getHeight()); 494 495 float w = imageWidth * scale; 496 float h = imageHeight * scale; 497 float ty = (getHeight() - h) / 2.0f; 498 float tx = (getWidth() - w) / 2.0f; 499 return new Rect((int) tx + mShadowMargin, 500 (int) ty + mShadowMargin, 501 (int) (w + tx) - mShadowMargin, 502 (int) (h + ty) - mShadowMargin); 503 } 504 505 private void drawShadow(Canvas canvas, Rect d) { 506 if (!mShadowDrawn) { 507 mShadowBounds.set(d.left - mShadowMargin, d.top - mShadowMargin, 508 d.right + mShadowMargin, d.bottom + mShadowMargin); 509 mShadow.setBounds(mShadowBounds); 510 mShadow.draw(canvas); 511 mShadowDrawn = true; 512 } 513 } 514 515 public void drawCompareImage(Canvas canvas, Bitmap image) { 516 MasterImage master = MasterImage.getImage(); 517 boolean showsOriginal = master.showsOriginal(); 518 if (!showsOriginal && !mTouchShowOriginal) 519 return; 520 canvas.save(); 521 if (image != null) { 522 if (mShowOriginalDirection == 0) { 523 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) { 524 mShowOriginalDirection = UNVEIL_VERTICAL; 525 } else { 526 mShowOriginalDirection = UNVEIL_HORIZONTAL; 527 } 528 } 529 530 int px = 0; 531 int py = 0; 532 if (mShowOriginalDirection == UNVEIL_VERTICAL) { 533 px = mImageBounds.width(); 534 py = mTouch.y - mImageBounds.top; 535 } else { 536 px = mTouch.x - mImageBounds.left; 537 py = mImageBounds.height(); 538 if (showsOriginal) { 539 px = mImageBounds.width(); 540 } 541 } 542 543 Rect d = new Rect(mImageBounds.left, mImageBounds.top, 544 mImageBounds.left + px, mImageBounds.top + py); 545 if (mShowOriginalDirection == UNVEIL_HORIZONTAL) { 546 if (mTouchDown.x - mTouch.x > 0) { 547 d.set(mImageBounds.left + px, mImageBounds.top, 548 mImageBounds.right, mImageBounds.top + py); 549 } 550 } else { 551 if (mTouchDown.y - mTouch.y > 0) { 552 d.set(mImageBounds.left, mImageBounds.top + py, 553 mImageBounds.left + px, mImageBounds.bottom); 554 } 555 } 556 canvas.clipRect(d); 557 Matrix m = master.computeImageToScreen(image, 0, false); 558 canvas.drawBitmap(image, m, mPaint); 559 Paint paint = new Paint(); 560 paint.setColor(Color.BLACK); 561 paint.setStrokeWidth(3); 562 563 if (mShowOriginalDirection == UNVEIL_VERTICAL) { 564 canvas.drawLine(mImageBounds.left, mTouch.y, 565 mImageBounds.right, mTouch.y, paint); 566 } else { 567 canvas.drawLine(mTouch.x, mImageBounds.top, 568 mTouch.x, mImageBounds.bottom, paint); 569 } 570 571 Rect bounds = new Rect(); 572 paint.setAntiAlias(true); 573 paint.setTextSize(mOriginalTextSize); 574 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds); 575 paint.setColor(Color.BLACK); 576 paint.setStyle(Paint.Style.STROKE); 577 paint.setStrokeWidth(3); 578 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 579 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 580 paint.setStyle(Paint.Style.FILL); 581 paint.setStrokeWidth(1); 582 paint.setColor(Color.WHITE); 583 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 584 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 585 } 586 canvas.restore(); 587 } 588 589 public void bindAsImageLoadListener() { 590 MasterImage.getImage().addListener(this); 591 } 592 593 public void updateImage() { 594 invalidate(); 595 } 596 597 public void imageLoaded() { 598 updateImage(); 599 } 600 601 public void saveImage(FilterShowActivity filterShowActivity, File file) { 602 SaveImage.saveImage(getImagePreset(), filterShowActivity, file); 603 } 604 605 606 public boolean scaleInProgress() { 607 return mScaleGestureDetector.isInProgress(); 608 } 609 610 @Override 611 public boolean onTouchEvent(MotionEvent event) { 612 super.onTouchEvent(event); 613 int action = event.getAction(); 614 action = action & MotionEvent.ACTION_MASK; 615 616 mGestureDetector.onTouchEvent(event); 617 boolean scaleInProgress = scaleInProgress(); 618 mScaleGestureDetector.onTouchEvent(event); 619 if (mInteractionMode == InteractionMode.SCALE) { 620 return true; 621 } 622 if (!scaleInProgress() && scaleInProgress) { 623 // If we were scaling, the scale will stop but we will 624 // still issue an ACTION_UP. Let the subclasses know. 625 mFinishedScalingOperation = true; 626 } 627 628 int ex = (int) event.getX(); 629 int ey = (int) event.getY(); 630 if (action == MotionEvent.ACTION_DOWN) { 631 mInteractionMode = InteractionMode.MOVE; 632 mTouchDown.x = ex; 633 mTouchDown.y = ey; 634 mTouchShowOriginalDate = System.currentTimeMillis(); 635 mShowOriginalDirection = 0; 636 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation()); 637 } 638 639 if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) { 640 mTouch.x = ex; 641 mTouch.y = ey; 642 643 float scaleFactor = MasterImage.getImage().getScaleFactor(); 644 if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) { 645 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor; 646 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor; 647 Point originalTranslation = MasterImage.getImage().getOriginalTranslation(); 648 Point translation = MasterImage.getImage().getTranslation(); 649 translation.x = (int) (originalTranslation.x + translateX); 650 translation.y = (int) (originalTranslation.y + translateY); 651 MasterImage.getImage().setTranslation(translation); 652 mTouchShowOriginal = false; 653 } else if (enableComparison() && !mOriginalDisabled 654 && (System.currentTimeMillis() - mTouchShowOriginalDate 655 > mTouchShowOriginalDelayMin) 656 && event.getPointerCount() == 1) { 657 mTouchShowOriginal = true; 658 } 659 } 660 661 if (action == MotionEvent.ACTION_UP 662 || action == MotionEvent.ACTION_CANCEL 663 || action == MotionEvent.ACTION_OUTSIDE) { 664 mInteractionMode = InteractionMode.NONE; 665 mTouchShowOriginal = false; 666 mTouchDown.x = 0; 667 mTouchDown.y = 0; 668 mTouch.x = 0; 669 mTouch.y = 0; 670 if (MasterImage.getImage().getScaleFactor() <= 1) { 671 MasterImage.getImage().setScaleFactor(1); 672 MasterImage.getImage().resetTranslation(); 673 } 674 } 675 676 float scaleFactor = MasterImage.getImage().getScaleFactor(); 677 Point translation = MasterImage.getImage().getTranslation(); 678 constrainTranslation(translation, scaleFactor); 679 MasterImage.getImage().setTranslation(translation); 680 681 invalidate(); 682 return true; 683 } 684 685 private void startAnimTranslation(int fromX, int toX, 686 int fromY, int toY, int delay) { 687 if (fromX == toX && fromY == toY) { 688 return; 689 } 690 if (mAnimatorTranslateX != null) { 691 mAnimatorTranslateX.cancel(); 692 } 693 if (mAnimatorTranslateY != null) { 694 mAnimatorTranslateY.cancel(); 695 } 696 mAnimatorTranslateX = ValueAnimator.ofInt(fromX, toX); 697 mAnimatorTranslateY = ValueAnimator.ofInt(fromY, toY); 698 mAnimatorTranslateX.setDuration(delay); 699 mAnimatorTranslateY.setDuration(delay); 700 mAnimatorTranslateX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 701 @Override 702 public void onAnimationUpdate(ValueAnimator animation) { 703 Point translation = MasterImage.getImage().getTranslation(); 704 translation.x = (Integer) animation.getAnimatedValue(); 705 MasterImage.getImage().setTranslation(translation); 706 invalidate(); 707 } 708 }); 709 mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 710 @Override 711 public void onAnimationUpdate(ValueAnimator animation) { 712 Point translation = MasterImage.getImage().getTranslation(); 713 translation.y = (Integer) animation.getAnimatedValue(); 714 MasterImage.getImage().setTranslation(translation); 715 invalidate(); 716 } 717 }); 718 mAnimatorTranslateX.start(); 719 mAnimatorTranslateY.start(); 720 } 721 722 private void applyTranslationConstraints() { 723 float scaleFactor = MasterImage.getImage().getScaleFactor(); 724 Point translation = MasterImage.getImage().getTranslation(); 725 int x = translation.x; 726 int y = translation.y; 727 constrainTranslation(translation, scaleFactor); 728 729 if (x != translation.x || y != translation.y) { 730 startAnimTranslation(x, translation.x, 731 y, translation.y, 732 mAnimationSnapDelay); 733 } 734 } 735 736 protected boolean enableComparison() { 737 return true; 738 } 739 740 @Override 741 public boolean onDoubleTap(MotionEvent arg0) { 742 mZoomIn = !mZoomIn; 743 float scale = 1.0f; 744 final float x = arg0.getX(); 745 final float y = arg0.getY(); 746 if (mZoomIn) { 747 scale = MasterImage.getImage().getMaxScaleFactor(); 748 } 749 if (scale != MasterImage.getImage().getScaleFactor()) { 750 if (mAnimatorScale != null) { 751 mAnimatorScale.cancel(); 752 } 753 mAnimatorScale = ValueAnimator.ofFloat( 754 MasterImage.getImage().getScaleFactor(), 755 scale 756 ); 757 float translateX = (getWidth() / 2 - x); 758 float translateY = (getHeight() / 2 - y); 759 Point translation = MasterImage.getImage().getTranslation(); 760 int startTranslateX = translation.x; 761 int startTranslateY = translation.y; 762 if (scale != 1.0f) { 763 translation.x = (int) (mOriginalTranslation.x + translateX); 764 translation.y = (int) (mOriginalTranslation.y + translateY); 765 } else { 766 translation.x = 0; 767 translation.y = 0; 768 } 769 constrainTranslation(translation, scale); 770 771 startAnimTranslation(startTranslateX, translation.x, 772 startTranslateY, translation.y, 773 mAnimationZoomDelay); 774 mAnimatorScale.setDuration(mAnimationZoomDelay); 775 mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 776 @Override 777 public void onAnimationUpdate(ValueAnimator animation) { 778 MasterImage.getImage().setScaleFactor((Float) animation.getAnimatedValue()); 779 invalidate(); 780 } 781 }); 782 mAnimatorScale.addListener(new Animator.AnimatorListener() { 783 @Override 784 public void onAnimationStart(Animator animation) { 785 } 786 787 @Override 788 public void onAnimationEnd(Animator animation) { 789 applyTranslationConstraints(); 790 MasterImage.getImage().needsUpdatePartialPreview(); 791 invalidate(); 792 } 793 794 @Override 795 public void onAnimationCancel(Animator animation) { 796 797 } 798 799 @Override 800 public void onAnimationRepeat(Animator animation) { 801 802 } 803 }); 804 mAnimatorScale.start(); 805 } 806 return true; 807 } 808 809 private void constrainTranslation(Point translation, float scale) { 810 int currentEdgeEffect = 0; 811 if (scale <= 1) { 812 mCurrentEdgeEffect = 0; 813 mEdgeEffect.finish(); 814 return; 815 } 816 817 Matrix originalToScreen = MasterImage.getImage().originalImageToScreen(); 818 Rect originalBounds = MasterImage.getImage().getOriginalBounds(); 819 RectF screenPos = new RectF(originalBounds); 820 originalToScreen.mapRect(screenPos); 821 822 boolean rightConstraint = screenPos.right < getWidth() - mShadowMargin; 823 boolean leftConstraint = screenPos.left > mShadowMargin; 824 boolean topConstraint = screenPos.top > mShadowMargin; 825 boolean bottomConstraint = screenPos.bottom < getHeight() - mShadowMargin; 826 827 if (screenPos.width() > getWidth()) { 828 if (rightConstraint && !leftConstraint) { 829 float tx = screenPos.right - translation.x * scale; 830 translation.x = (int) ((getWidth() - mShadowMargin - tx) / scale); 831 currentEdgeEffect = EDGE_RIGHT; 832 } else if (leftConstraint && !rightConstraint) { 833 float tx = screenPos.left - translation.x * scale; 834 translation.x = (int) ((mShadowMargin - tx) / scale); 835 currentEdgeEffect = EDGE_LEFT; 836 } 837 } else { 838 float tx = screenPos.right - translation.x * scale; 839 float dx = (getWidth() - 2 * mShadowMargin - screenPos.width()) / 2f; 840 translation.x = (int) ((getWidth() - mShadowMargin - tx - dx) / scale); 841 } 842 843 if (screenPos.height() > getHeight()) { 844 if (bottomConstraint && !topConstraint) { 845 float ty = screenPos.bottom - translation.y * scale; 846 translation.y = (int) ((getHeight() - mShadowMargin - ty) / scale); 847 currentEdgeEffect = EDGE_BOTTOM; 848 } else if (topConstraint && !bottomConstraint) { 849 float ty = screenPos.top - translation.y * scale; 850 translation.y = (int) ((mShadowMargin - ty) / scale); 851 currentEdgeEffect = EDGE_TOP; 852 } 853 } else { 854 float ty = screenPos.bottom - translation.y * scale; 855 float dy = (getHeight()- 2 * mShadowMargin - screenPos.height()) / 2f; 856 translation.y = (int) ((getHeight() - mShadowMargin - ty - dy) / scale); 857 } 858 859 if (mCurrentEdgeEffect != currentEdgeEffect) { 860 if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) { 861 mCurrentEdgeEffect = currentEdgeEffect; 862 mEdgeEffect.finish(); 863 } 864 mEdgeEffect.setSize(getWidth(), mEdgeSize); 865 } 866 if (currentEdgeEffect != 0) { 867 mEdgeEffect.onPull(mEdgeSize); 868 } 869 } 870 871 @Override 872 public boolean onDoubleTapEvent(MotionEvent arg0) { 873 return false; 874 } 875 876 @Override 877 public boolean onSingleTapConfirmed(MotionEvent arg0) { 878 return false; 879 } 880 881 @Override 882 public boolean onDown(MotionEvent arg0) { 883 return false; 884 } 885 886 @Override 887 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) { 888 if (mActivity == null) { 889 return false; 890 } 891 if (endEvent.getPointerCount() == 2) { 892 return false; 893 } 894 return true; 895 } 896 897 @Override 898 public void onLongPress(MotionEvent arg0) { 899 } 900 901 @Override 902 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 903 return false; 904 } 905 906 @Override 907 public void onShowPress(MotionEvent arg0) { 908 } 909 910 @Override 911 public boolean onSingleTapUp(MotionEvent arg0) { 912 return false; 913 } 914 915 public boolean useUtilityPanel() { 916 return false; 917 } 918 919 public void openUtilityPanel(final LinearLayout accessoryViewList) { 920 } 921 922 @Override 923 public boolean onScale(ScaleGestureDetector detector) { 924 MasterImage img = MasterImage.getImage(); 925 float scaleFactor = img.getScaleFactor(); 926 927 scaleFactor = scaleFactor * detector.getScaleFactor(); 928 if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) { 929 scaleFactor = MasterImage.getImage().getMaxScaleFactor(); 930 } 931 if (scaleFactor < 1.0f) { 932 scaleFactor = 1.0f; 933 } 934 MasterImage.getImage().setScaleFactor(scaleFactor); 935 scaleFactor = img.getScaleFactor(); 936 float focusx = detector.getFocusX(); 937 float focusy = detector.getFocusY(); 938 float translateX = (focusx - mStartFocusX) / scaleFactor; 939 float translateY = (focusy - mStartFocusY) / scaleFactor; 940 Point translation = MasterImage.getImage().getTranslation(); 941 translation.x = (int) (mOriginalTranslation.x + translateX); 942 translation.y = (int) (mOriginalTranslation.y + translateY); 943 MasterImage.getImage().setTranslation(translation); 944 invalidate(); 945 return true; 946 } 947 948 @Override 949 public boolean onScaleBegin(ScaleGestureDetector detector) { 950 Point pos = MasterImage.getImage().getTranslation(); 951 mOriginalTranslation.x = pos.x; 952 mOriginalTranslation.y = pos.y; 953 mOriginalScale = MasterImage.getImage().getScaleFactor(); 954 mStartFocusX = detector.getFocusX(); 955 mStartFocusY = detector.getFocusY(); 956 mInteractionMode = InteractionMode.SCALE; 957 return true; 958 } 959 960 @Override 961 public void onScaleEnd(ScaleGestureDetector detector) { 962 mInteractionMode = InteractionMode.NONE; 963 if (MasterImage.getImage().getScaleFactor() < 1) { 964 MasterImage.getImage().setScaleFactor(1); 965 invalidate(); 966 } 967 } 968 969 public boolean didFinishScalingOperation() { 970 if (mFinishedScalingOperation) { 971 mFinishedScalingOperation = false; 972 return true; 973 } 974 return false; 975 } 976 977 } 978