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