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 androidx.core.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 convertToAlphaMask(Bitmap b)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 createShader(Bitmap b)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 getActivity()138 public FilterShowActivity getActivity() { 139 return mActivity; 140 } 141 hasModifications()142 public boolean hasModifications() { 143 return MasterImage.getImage().hasModifications(); 144 } 145 resetParameter()146 public void resetParameter() { 147 // TODO: implement reset 148 } 149 onNewValue(int parameter)150 public void onNewValue(int parameter) { 151 invalidate(); 152 } 153 ImageShow(Context context, AttributeSet attrs, int defStyle)154 public ImageShow(Context context, AttributeSet attrs, int defStyle) { 155 super(context, attrs, defStyle); 156 setupImageShow(context); 157 } 158 ImageShow(Context context, AttributeSet attrs)159 public ImageShow(Context context, AttributeSet attrs) { 160 super(context, attrs); 161 setupImageShow(context); 162 163 } 164 ImageShow(Context context)165 public ImageShow(Context context) { 166 super(context); 167 setupImageShow(context); 168 } 169 setupImageShow(Context context)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 attach()189 public void attach() { 190 MasterImage.getImage().addObserver(this); 191 bindAsImageLoadListener(); 192 MasterImage.getImage().resetGeometryImages(false); 193 } 194 detach()195 public void detach() { 196 MasterImage.getImage().removeObserver(this); 197 mMaskPaint.reset(); 198 } 199 setupGestureDetector(Context context)200 public void setupGestureDetector(Context context) { 201 mGestureDetector = new GestureDetector(context, this); 202 mScaleGestureDetector = new ScaleGestureDetector(context, this); 203 } 204 205 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 getCurrentFilter()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 */ getImageToScreenMatrix(boolean reflectRotation)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 */ getScreenToImageMatrix(boolean reflectRotation)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 getImagePreset()250 public ImagePreset getImagePreset() { 251 return MasterImage.getImage().getPreset(); 252 } 253 254 @Override onDraw(Canvas canvas)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 drawHighresImage(Canvas canvas, Bitmap fullHighres)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 resetImageCaches(ImageShow caller)336 public void resetImageCaches(ImageShow caller) { 337 MasterImage.getImage().invalidatePreview(); 338 } 339 getFiltersOnlyImage()340 public Bitmap getFiltersOnlyImage() { 341 return MasterImage.getImage().getFiltersOnlyImage(); 342 } 343 getGeometryOnlyImage()344 public Bitmap getGeometryOnlyImage() { 345 return MasterImage.getImage().getGeometryOnlyImage(); 346 } 347 getFilteredImage()348 public Bitmap getFilteredImage() { 349 return MasterImage.getImage().getFilteredImage(); 350 } 351 drawImageAndAnimate(Canvas canvas, Bitmap image)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 Shader maskShader = createShader(image); 419 maskShader.setLocalMatrix(mShaderMatrix); 420 mMaskPaint.setShader(maskShader); 421 422 drawShadow(canvas, mImageBounds); // as needed 423 canvas.drawBitmap(previousImage, m, mPaint); 424 canvas.clipRect(mImageBounds); 425 canvas.translate(x, y); 426 canvas.scale(maskScale, maskScale); 427 canvas.drawBitmap(sMask, 0, 0, mMaskPaint); 428 needsToDrawImage = false; 429 } 430 } else if (master.getCurrentLookAnimation() 431 == MasterImage.ROTATE_ANIMATION) { 432 Rect d1 = computeImageBounds(master.getPreviousImage().getHeight(), 433 master.getPreviousImage().getWidth()); 434 Rect d2 = computeImageBounds(master.getPreviousImage().getWidth(), 435 master.getPreviousImage().getHeight()); 436 float finalScale = d1.width() / (float) d2.height(); 437 finalScale = (1.0f * (1.0f - master.getAnimFraction())) 438 + (finalScale * master.getAnimFraction()); 439 canvas.rotate(master.getAnimRotationValue(), centerX, centerY); 440 canvas.scale(finalScale, finalScale, centerX, centerY); 441 } else if (master.getCurrentLookAnimation() 442 == MasterImage.MIRROR_ANIMATION) { 443 if (master.getCurrentFilterRepresentation() 444 instanceof FilterMirrorRepresentation) { 445 FilterMirrorRepresentation rep = 446 (FilterMirrorRepresentation) master.getCurrentFilterRepresentation(); 447 448 ImagePreset preset = master.getPreset(); 449 ArrayList<FilterRepresentation> geometry = 450 (ArrayList<FilterRepresentation>) preset.getGeometryFilters(); 451 GeometryMathUtils.GeometryHolder holder = null; 452 holder = GeometryMathUtils.unpackGeometry(geometry); 453 454 if (holder.rotation.value() == 90 || holder.rotation.value() == 270) { 455 if (rep.isHorizontal() && !rep.isVertical()) { 456 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY); 457 } else if (rep.isVertical() && !rep.isHorizontal()) { 458 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY); 459 } else if (rep.isHorizontal() && rep.isVertical()) { 460 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY); 461 } else { 462 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY); 463 } 464 } else { 465 if (rep.isHorizontal() && !rep.isVertical()) { 466 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY); 467 } else if (rep.isVertical() && !rep.isHorizontal()) { 468 canvas.scale(master.getAnimRotationValue(), 1, centerX, centerY); 469 } else if (rep.isHorizontal() && rep.isVertical()) { 470 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY); 471 } else { 472 canvas.scale(1, master.getAnimRotationValue(), centerX, centerY); 473 } 474 } 475 } 476 } 477 478 if (needsToDrawImage) { 479 drawShadow(canvas, previousBounds); // as needed 480 canvas.drawBitmap(previousImage, mp, mPaint); 481 } 482 483 canvas.restore(); 484 } else { 485 drawShadow(canvas, mImageBounds); // as needed 486 canvas.drawBitmap(image, m, mPaint); 487 } 488 489 canvas.restore(); 490 } 491 computeImageBounds(int imageWidth, int imageHeight)492 private Rect computeImageBounds(int imageWidth, int imageHeight) { 493 float scale = GeometryMathUtils.scale(imageWidth, imageHeight, 494 getWidth(), getHeight()); 495 496 float w = imageWidth * scale; 497 float h = imageHeight * scale; 498 float ty = (getHeight() - h) / 2.0f; 499 float tx = (getWidth() - w) / 2.0f; 500 return new Rect((int) tx + mShadowMargin, 501 (int) ty + mShadowMargin, 502 (int) (w + tx) - mShadowMargin, 503 (int) (h + ty) - mShadowMargin); 504 } 505 drawShadow(Canvas canvas, Rect d)506 private void drawShadow(Canvas canvas, Rect d) { 507 if (!mShadowDrawn) { 508 mShadowBounds.set(d.left - mShadowMargin, d.top - mShadowMargin, 509 d.right + mShadowMargin, d.bottom + mShadowMargin); 510 mShadow.setBounds(mShadowBounds); 511 mShadow.draw(canvas); 512 mShadowDrawn = true; 513 } 514 } 515 drawCompareImage(Canvas canvas, Bitmap image)516 public void drawCompareImage(Canvas canvas, Bitmap image) { 517 MasterImage master = MasterImage.getImage(); 518 boolean showsOriginal = master.showsOriginal(); 519 if (!showsOriginal && !mTouchShowOriginal) 520 return; 521 canvas.save(); 522 if (image != null) { 523 if (mShowOriginalDirection == 0) { 524 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) { 525 mShowOriginalDirection = UNVEIL_VERTICAL; 526 } else { 527 mShowOriginalDirection = UNVEIL_HORIZONTAL; 528 } 529 } 530 531 int px = 0; 532 int py = 0; 533 if (mShowOriginalDirection == UNVEIL_VERTICAL) { 534 px = mImageBounds.width(); 535 py = mTouch.y - mImageBounds.top; 536 } else { 537 px = mTouch.x - mImageBounds.left; 538 py = mImageBounds.height(); 539 if (showsOriginal) { 540 px = mImageBounds.width(); 541 } 542 } 543 544 Rect d = new Rect(mImageBounds.left, mImageBounds.top, 545 mImageBounds.left + px, mImageBounds.top + py); 546 if (mShowOriginalDirection == UNVEIL_HORIZONTAL) { 547 if (mTouchDown.x - mTouch.x > 0) { 548 d.set(mImageBounds.left + px, mImageBounds.top, 549 mImageBounds.right, mImageBounds.top + py); 550 } 551 } else { 552 if (mTouchDown.y - mTouch.y > 0) { 553 d.set(mImageBounds.left, mImageBounds.top + py, 554 mImageBounds.left + px, mImageBounds.bottom); 555 } 556 } 557 canvas.clipRect(d); 558 Matrix m = master.computeImageToScreen(image, 0, false); 559 canvas.drawBitmap(image, m, mPaint); 560 Paint paint = new Paint(); 561 paint.setColor(Color.BLACK); 562 paint.setStrokeWidth(3); 563 564 if (mShowOriginalDirection == UNVEIL_VERTICAL) { 565 canvas.drawLine(mImageBounds.left, mTouch.y, 566 mImageBounds.right, mTouch.y, paint); 567 } else { 568 canvas.drawLine(mTouch.x, mImageBounds.top, 569 mTouch.x, mImageBounds.bottom, paint); 570 } 571 572 Rect bounds = new Rect(); 573 paint.setAntiAlias(true); 574 paint.setTextSize(mOriginalTextSize); 575 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds); 576 paint.setColor(Color.BLACK); 577 paint.setStyle(Paint.Style.STROKE); 578 paint.setStrokeWidth(3); 579 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 580 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 581 paint.setStyle(Paint.Style.FILL); 582 paint.setStrokeWidth(1); 583 paint.setColor(Color.WHITE); 584 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 585 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 586 } 587 canvas.restore(); 588 } 589 bindAsImageLoadListener()590 public void bindAsImageLoadListener() { 591 MasterImage.getImage().addListener(this); 592 } 593 updateImage()594 public void updateImage() { 595 invalidate(); 596 } 597 imageLoaded()598 public void imageLoaded() { 599 updateImage(); 600 } 601 saveImage(FilterShowActivity filterShowActivity, File file)602 public void saveImage(FilterShowActivity filterShowActivity, File file) { 603 SaveImage.saveImage(getImagePreset(), filterShowActivity, file); 604 } 605 606 scaleInProgress()607 public boolean scaleInProgress() { 608 return mScaleGestureDetector.isInProgress(); 609 } 610 611 @Override onTouchEvent(MotionEvent event)612 public boolean onTouchEvent(MotionEvent event) { 613 super.onTouchEvent(event); 614 int action = event.getAction(); 615 action = action & MotionEvent.ACTION_MASK; 616 617 mGestureDetector.onTouchEvent(event); 618 boolean scaleInProgress = scaleInProgress(); 619 mScaleGestureDetector.onTouchEvent(event); 620 if (mInteractionMode == InteractionMode.SCALE) { 621 return true; 622 } 623 if (!scaleInProgress() && scaleInProgress) { 624 // If we were scaling, the scale will stop but we will 625 // still issue an ACTION_UP. Let the subclasses know. 626 mFinishedScalingOperation = true; 627 } 628 629 int ex = (int) event.getX(); 630 int ey = (int) event.getY(); 631 if (action == MotionEvent.ACTION_DOWN) { 632 mInteractionMode = InteractionMode.MOVE; 633 mTouchDown.x = ex; 634 mTouchDown.y = ey; 635 mTouchShowOriginalDate = System.currentTimeMillis(); 636 mShowOriginalDirection = 0; 637 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation()); 638 } 639 640 if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) { 641 mTouch.x = ex; 642 mTouch.y = ey; 643 644 float scaleFactor = MasterImage.getImage().getScaleFactor(); 645 if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) { 646 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor; 647 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor; 648 Point originalTranslation = MasterImage.getImage().getOriginalTranslation(); 649 Point translation = MasterImage.getImage().getTranslation(); 650 translation.x = (int) (originalTranslation.x + translateX); 651 translation.y = (int) (originalTranslation.y + translateY); 652 MasterImage.getImage().setTranslation(translation); 653 mTouchShowOriginal = false; 654 } else if (enableComparison() && !mOriginalDisabled 655 && (System.currentTimeMillis() - mTouchShowOriginalDate 656 > mTouchShowOriginalDelayMin) 657 && event.getPointerCount() == 1) { 658 mTouchShowOriginal = true; 659 } 660 } 661 662 if (action == MotionEvent.ACTION_UP 663 || action == MotionEvent.ACTION_CANCEL 664 || action == MotionEvent.ACTION_OUTSIDE) { 665 mInteractionMode = InteractionMode.NONE; 666 mTouchShowOriginal = false; 667 mTouchDown.x = 0; 668 mTouchDown.y = 0; 669 mTouch.x = 0; 670 mTouch.y = 0; 671 if (MasterImage.getImage().getScaleFactor() <= 1) { 672 MasterImage.getImage().setScaleFactor(1); 673 MasterImage.getImage().resetTranslation(); 674 } 675 } 676 677 float scaleFactor = MasterImage.getImage().getScaleFactor(); 678 Point translation = MasterImage.getImage().getTranslation(); 679 constrainTranslation(translation, scaleFactor); 680 MasterImage.getImage().setTranslation(translation); 681 682 invalidate(); 683 return true; 684 } 685 startAnimTranslation(int fromX, int toX, int fromY, int toY, int delay)686 private void startAnimTranslation(int fromX, int toX, 687 int fromY, int toY, int delay) { 688 if (fromX == toX && fromY == toY) { 689 return; 690 } 691 if (mAnimatorTranslateX != null) { 692 mAnimatorTranslateX.cancel(); 693 } 694 if (mAnimatorTranslateY != null) { 695 mAnimatorTranslateY.cancel(); 696 } 697 mAnimatorTranslateX = ValueAnimator.ofInt(fromX, toX); 698 mAnimatorTranslateY = ValueAnimator.ofInt(fromY, toY); 699 mAnimatorTranslateX.setDuration(delay); 700 mAnimatorTranslateY.setDuration(delay); 701 mAnimatorTranslateX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 702 @Override 703 public void onAnimationUpdate(ValueAnimator animation) { 704 Point translation = MasterImage.getImage().getTranslation(); 705 translation.x = (Integer) animation.getAnimatedValue(); 706 MasterImage.getImage().setTranslation(translation); 707 invalidate(); 708 } 709 }); 710 mAnimatorTranslateY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 711 @Override 712 public void onAnimationUpdate(ValueAnimator animation) { 713 Point translation = MasterImage.getImage().getTranslation(); 714 translation.y = (Integer) animation.getAnimatedValue(); 715 MasterImage.getImage().setTranslation(translation); 716 invalidate(); 717 } 718 }); 719 mAnimatorTranslateX.start(); 720 mAnimatorTranslateY.start(); 721 } 722 applyTranslationConstraints()723 private void applyTranslationConstraints() { 724 float scaleFactor = MasterImage.getImage().getScaleFactor(); 725 Point translation = MasterImage.getImage().getTranslation(); 726 int x = translation.x; 727 int y = translation.y; 728 constrainTranslation(translation, scaleFactor); 729 730 if (x != translation.x || y != translation.y) { 731 startAnimTranslation(x, translation.x, 732 y, translation.y, 733 mAnimationSnapDelay); 734 } 735 } 736 enableComparison()737 protected boolean enableComparison() { 738 return true; 739 } 740 741 @Override onDoubleTap(MotionEvent arg0)742 public boolean onDoubleTap(MotionEvent arg0) { 743 mZoomIn = !mZoomIn; 744 float scale = 1.0f; 745 final float x = arg0.getX(); 746 final float y = arg0.getY(); 747 if (mZoomIn) { 748 scale = MasterImage.getImage().getMaxScaleFactor(); 749 } 750 if (scale != MasterImage.getImage().getScaleFactor()) { 751 if (mAnimatorScale != null) { 752 mAnimatorScale.cancel(); 753 } 754 mAnimatorScale = ValueAnimator.ofFloat( 755 MasterImage.getImage().getScaleFactor(), 756 scale 757 ); 758 float translateX = (getWidth() / 2 - x); 759 float translateY = (getHeight() / 2 - y); 760 Point translation = MasterImage.getImage().getTranslation(); 761 int startTranslateX = translation.x; 762 int startTranslateY = translation.y; 763 if (scale != 1.0f) { 764 translation.x = (int) (mOriginalTranslation.x + translateX); 765 translation.y = (int) (mOriginalTranslation.y + translateY); 766 } else { 767 translation.x = 0; 768 translation.y = 0; 769 } 770 constrainTranslation(translation, scale); 771 772 startAnimTranslation(startTranslateX, translation.x, 773 startTranslateY, translation.y, 774 mAnimationZoomDelay); 775 mAnimatorScale.setDuration(mAnimationZoomDelay); 776 mAnimatorScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 777 @Override 778 public void onAnimationUpdate(ValueAnimator animation) { 779 MasterImage.getImage().setScaleFactor((Float) animation.getAnimatedValue()); 780 invalidate(); 781 } 782 }); 783 mAnimatorScale.addListener(new Animator.AnimatorListener() { 784 @Override 785 public void onAnimationStart(Animator animation) { 786 } 787 788 @Override 789 public void onAnimationEnd(Animator animation) { 790 applyTranslationConstraints(); 791 MasterImage.getImage().needsUpdatePartialPreview(); 792 invalidate(); 793 } 794 795 @Override 796 public void onAnimationCancel(Animator animation) { 797 798 } 799 800 @Override 801 public void onAnimationRepeat(Animator animation) { 802 803 } 804 }); 805 mAnimatorScale.start(); 806 } 807 return true; 808 } 809 constrainTranslation(Point translation, float scale)810 private void constrainTranslation(Point translation, float scale) { 811 int currentEdgeEffect = 0; 812 if (scale <= 1) { 813 mCurrentEdgeEffect = 0; 814 mEdgeEffect.finish(); 815 return; 816 } 817 818 Matrix originalToScreen = MasterImage.getImage().originalImageToScreen(); 819 Rect originalBounds = MasterImage.getImage().getOriginalBounds(); 820 RectF screenPos = new RectF(originalBounds); 821 originalToScreen.mapRect(screenPos); 822 823 boolean rightConstraint = screenPos.right < getWidth() - mShadowMargin; 824 boolean leftConstraint = screenPos.left > mShadowMargin; 825 boolean topConstraint = screenPos.top > mShadowMargin; 826 boolean bottomConstraint = screenPos.bottom < getHeight() - mShadowMargin; 827 828 if (screenPos.width() > getWidth()) { 829 if (rightConstraint && !leftConstraint) { 830 float tx = screenPos.right - translation.x * scale; 831 translation.x = (int) ((getWidth() - mShadowMargin - tx) / scale); 832 currentEdgeEffect = EDGE_RIGHT; 833 } else if (leftConstraint && !rightConstraint) { 834 float tx = screenPos.left - translation.x * scale; 835 translation.x = (int) ((mShadowMargin - tx) / scale); 836 currentEdgeEffect = EDGE_LEFT; 837 } 838 } else { 839 float tx = screenPos.right - translation.x * scale; 840 float dx = (getWidth() - 2 * mShadowMargin - screenPos.width()) / 2f; 841 translation.x = (int) ((getWidth() - mShadowMargin - tx - dx) / scale); 842 } 843 844 if (screenPos.height() > getHeight()) { 845 if (bottomConstraint && !topConstraint) { 846 float ty = screenPos.bottom - translation.y * scale; 847 translation.y = (int) ((getHeight() - mShadowMargin - ty) / scale); 848 currentEdgeEffect = EDGE_BOTTOM; 849 } else if (topConstraint && !bottomConstraint) { 850 float ty = screenPos.top - translation.y * scale; 851 translation.y = (int) ((mShadowMargin - ty) / scale); 852 currentEdgeEffect = EDGE_TOP; 853 } 854 } else { 855 float ty = screenPos.bottom - translation.y * scale; 856 float dy = (getHeight()- 2 * mShadowMargin - screenPos.height()) / 2f; 857 translation.y = (int) ((getHeight() - mShadowMargin - ty - dy) / scale); 858 } 859 860 if (mCurrentEdgeEffect != currentEdgeEffect) { 861 if (mCurrentEdgeEffect == 0 || currentEdgeEffect != 0) { 862 mCurrentEdgeEffect = currentEdgeEffect; 863 mEdgeEffect.finish(); 864 } 865 mEdgeEffect.setSize(getWidth(), mEdgeSize); 866 } 867 if (currentEdgeEffect != 0) { 868 mEdgeEffect.onPull(mEdgeSize); 869 } 870 } 871 872 @Override 873 public boolean onDoubleTapEvent(MotionEvent arg0) { 874 return false; 875 } 876 877 @Override 878 public boolean onSingleTapConfirmed(MotionEvent arg0) { 879 return false; 880 } 881 882 @Override 883 public boolean onDown(MotionEvent arg0) { 884 return false; 885 } 886 887 @Override 888 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) { 889 if (mActivity == null) { 890 return false; 891 } 892 if (endEvent.getPointerCount() == 2) { 893 return false; 894 } 895 return true; 896 } 897 898 @Override 899 public void onLongPress(MotionEvent arg0) { 900 } 901 902 @Override 903 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 904 return false; 905 } 906 907 @Override 908 public void onShowPress(MotionEvent arg0) { 909 } 910 911 @Override 912 public boolean onSingleTapUp(MotionEvent arg0) { 913 return false; 914 } 915 916 public boolean useUtilityPanel() { 917 return false; 918 } 919 920 public void openUtilityPanel(final LinearLayout accessoryViewList) { 921 } 922 923 @Override 924 public boolean onScale(ScaleGestureDetector detector) { 925 MasterImage img = MasterImage.getImage(); 926 float scaleFactor = img.getScaleFactor(); 927 928 scaleFactor = scaleFactor * detector.getScaleFactor(); 929 if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) { 930 scaleFactor = MasterImage.getImage().getMaxScaleFactor(); 931 } 932 if (scaleFactor < 1.0f) { 933 scaleFactor = 1.0f; 934 } 935 MasterImage.getImage().setScaleFactor(scaleFactor); 936 scaleFactor = img.getScaleFactor(); 937 float focusx = detector.getFocusX(); 938 float focusy = detector.getFocusY(); 939 float translateX = (focusx - mStartFocusX) / scaleFactor; 940 float translateY = (focusy - mStartFocusY) / scaleFactor; 941 Point translation = MasterImage.getImage().getTranslation(); 942 translation.x = (int) (mOriginalTranslation.x + translateX); 943 translation.y = (int) (mOriginalTranslation.y + translateY); 944 MasterImage.getImage().setTranslation(translation); 945 invalidate(); 946 return true; 947 } 948 949 @Override 950 public boolean onScaleBegin(ScaleGestureDetector detector) { 951 Point pos = MasterImage.getImage().getTranslation(); 952 mOriginalTranslation.x = pos.x; 953 mOriginalTranslation.y = pos.y; 954 mOriginalScale = MasterImage.getImage().getScaleFactor(); 955 mStartFocusX = detector.getFocusX(); 956 mStartFocusY = detector.getFocusY(); 957 mInteractionMode = InteractionMode.SCALE; 958 return true; 959 } 960 961 @Override 962 public void onScaleEnd(ScaleGestureDetector detector) { 963 mInteractionMode = InteractionMode.NONE; 964 if (MasterImage.getImage().getScaleFactor() < 1) { 965 MasterImage.getImage().setScaleFactor(1); 966 invalidate(); 967 } 968 } 969 970 public boolean didFinishScalingOperation() { 971 if (mFinishedScalingOperation) { 972 mFinishedScalingOperation = false; 973 return true; 974 } 975 return false; 976 } 977 978 } 979