ImageShow.java revision 0c1b4c6422a4d2d9b81cc0946d1c9675440a94e2
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 17package com.android.gallery3d.filtershow.imageshow; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.graphics.Bitmap; 22import android.graphics.Canvas; 23import android.graphics.Color; 24import android.graphics.Matrix; 25import android.graphics.Paint; 26import android.graphics.Point; 27import android.graphics.Rect; 28import android.graphics.RectF; 29import android.util.AttributeSet; 30import android.view.GestureDetector; 31import android.view.GestureDetector.OnDoubleTapListener; 32import android.view.GestureDetector.OnGestureListener; 33import android.view.MotionEvent; 34import android.view.ScaleGestureDetector; 35import android.view.View; 36import android.widget.LinearLayout; 37 38import com.android.gallery3d.R; 39import com.android.gallery3d.filtershow.FilterShowActivity; 40import com.android.gallery3d.filtershow.cache.ImageLoader; 41import com.android.gallery3d.filtershow.filters.ImageFilter; 42import com.android.gallery3d.filtershow.pipeline.ImagePreset; 43 44import java.io.File; 45 46public class ImageShow extends View implements OnGestureListener, 47 ScaleGestureDetector.OnScaleGestureListener, 48 OnDoubleTapListener { 49 50 private static final String LOGTAG = "ImageShow"; 51 private static final boolean ENABLE_ZOOMED_COMPARISON = false; 52 53 protected Paint mPaint = new Paint(); 54 protected int mTextSize; 55 protected int mTextPadding; 56 57 protected ImageLoader mImageLoader = null; 58 59 protected int mBackgroundColor; 60 61 private GestureDetector mGestureDetector = null; 62 private ScaleGestureDetector mScaleGestureDetector = null; 63 64 protected Rect mImageBounds = new Rect(); 65 private boolean mOriginalDisabled = false; 66 private boolean mTouchShowOriginal = false; 67 private long mTouchShowOriginalDate = 0; 68 private final long mTouchShowOriginalDelayMin = 200; // 200ms 69 private int mShowOriginalDirection = 0; 70 private static int UNVEIL_HORIZONTAL = 1; 71 private static int UNVEIL_VERTICAL = 2; 72 73 private Point mTouchDown = new Point(); 74 private Point mTouch = new Point(); 75 private boolean mFinishedScalingOperation = false; 76 77 private int mOriginalTextMargin; 78 private int mOriginalTextSize; 79 private String mOriginalText; 80 private boolean mZoomIn = false; 81 Point mOriginalTranslation = new Point(); 82 float mOriginalScale; 83 float mStartFocusX, mStartFocusY; 84 private enum InteractionMode { 85 NONE, 86 SCALE, 87 MOVE 88 } 89 InteractionMode mInteractionMode = InteractionMode.NONE; 90 91 protected GeometryMetadata getGeometry() { 92 return new GeometryMetadata(getImagePreset().getGeometry()); 93 } 94 95 private FilterShowActivity mActivity = null; 96 97 public FilterShowActivity getActivity() { 98 return mActivity; 99 } 100 101 public boolean hasModifications() { 102 return MasterImage.getImage().hasModifications(); 103 } 104 105 public void resetParameter() { 106 // TODO: implement reset 107 } 108 109 public void onNewValue(int parameter) { 110 invalidate(); 111 } 112 113 public ImageShow(Context context, AttributeSet attrs, int defStyle) { 114 super(context, attrs, defStyle); 115 setupImageShow(context); 116 } 117 118 public ImageShow(Context context, AttributeSet attrs) { 119 super(context, attrs); 120 setupImageShow(context); 121 122 } 123 124 public ImageShow(Context context) { 125 super(context); 126 setupImageShow(context); 127 } 128 129 private void setupImageShow(Context context) { 130 Resources res = context.getResources(); 131 mTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_text_size); 132 mTextPadding = res.getDimensionPixelSize(R.dimen.photoeditor_text_padding); 133 mOriginalTextMargin = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_margin); 134 mOriginalTextSize = res.getDimensionPixelSize(R.dimen.photoeditor_original_text_size); 135 mBackgroundColor = res.getColor(R.color.background_screen); 136 mOriginalText = res.getString(R.string.original_picture_text); 137 setupGestureDetector(context); 138 mActivity = (FilterShowActivity) context; 139 MasterImage.getImage().addObserver(this); 140 } 141 142 public void setupGestureDetector(Context context) { 143 mGestureDetector = new GestureDetector(context, this); 144 mScaleGestureDetector = new ScaleGestureDetector(context, this); 145 } 146 147 @Override 148 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 149 int parentWidth = MeasureSpec.getSize(widthMeasureSpec); 150 int parentHeight = MeasureSpec.getSize(heightMeasureSpec); 151 setMeasuredDimension(parentWidth, parentHeight); 152 } 153 154 public ImageFilter getCurrentFilter() { 155 return MasterImage.getImage().getCurrentFilter(); 156 } 157 158 public Rect getImageBounds() { 159 Rect dst = new Rect(); 160 getImagePreset().getGeometry().getPhotoBounds().roundOut(dst); 161 return dst; 162 } 163 164 public Rect getImageCropBounds() { 165 return GeometryMath.roundNearest(getImagePreset().getGeometry().getPreviewCropBounds()); 166 } 167 168 /* consider moving the following 2 methods into a subclass */ 169 /** 170 * This function calculates a Image to Screen Transformation matrix 171 * 172 * @param reflectRotation set true if you want the rotation encoded 173 * @return Image to Screen transformation matrix 174 */ 175 protected Matrix getImageToScreenMatrix(boolean reflectRotation) { 176 GeometryMetadata geo = getImagePreset().getGeometry(); 177 if (geo == null || mImageLoader == null 178 || MasterImage.getImage().getOriginalBounds() == null) { 179 return new Matrix(); 180 } 181 Matrix m = geo.getOriginalToScreen(reflectRotation, 182 MasterImage.getImage().getOriginalBounds().width(), 183 MasterImage.getImage().getOriginalBounds().height(), getWidth(), getHeight()); 184 Point translate = MasterImage.getImage().getTranslation(); 185 float scaleFactor = MasterImage.getImage().getScaleFactor(); 186 m.postTranslate(translate.x, translate.y); 187 m.postScale(scaleFactor, scaleFactor, getWidth() / 2.0f, getHeight() / 2.0f); 188 return m; 189 } 190 191 /** 192 * This function calculates a to Screen Image Transformation matrix 193 * 194 * @param reflectRotation set true if you want the rotation encoded 195 * @return Screen to Image transformation matrix 196 */ 197 protected Matrix getScreenToImageMatrix(boolean reflectRotation) { 198 Matrix m = getImageToScreenMatrix(reflectRotation); 199 Matrix invert = new Matrix(); 200 m.invert(invert); 201 return invert; 202 } 203 204 public ImagePreset getImagePreset() { 205 return MasterImage.getImage().getPreset(); 206 } 207 208 @Override 209 public void onDraw(Canvas canvas) { 210 MasterImage.getImage().setImageShowSize(getWidth(), getHeight()); 211 212 float cx = canvas.getWidth()/2.0f; 213 float cy = canvas.getHeight()/2.0f; 214 float scaleFactor = MasterImage.getImage().getScaleFactor(); 215 Point translation = MasterImage.getImage().getTranslation(); 216 217 Matrix scalingMatrix = new Matrix(); 218 scalingMatrix.postScale(scaleFactor, scaleFactor, cx, cy); 219 scalingMatrix.preTranslate(translation.x, translation.y); 220 221 RectF unscaledClipRect = new RectF(mImageBounds); 222 scalingMatrix.mapRect(unscaledClipRect, unscaledClipRect); 223 224 canvas.save(); 225 226 boolean enablePartialRendering = false; 227 228 // For now, partial rendering is disabled for all filters, 229 // so no need to clip. 230 if (enablePartialRendering && !unscaledClipRect.isEmpty()) { 231 canvas.clipRect(unscaledClipRect); 232 } 233 234 canvas.save(); 235 // TODO: center scale on gesture 236 canvas.scale(scaleFactor, scaleFactor, cx, cy); 237 canvas.translate(translation.x, translation.y); 238 drawImage(canvas, getFilteredImage(), true); 239 Bitmap highresPreview = MasterImage.getImage().getHighresImage(); 240 if (highresPreview != null) { 241 drawImage(canvas, highresPreview, false); 242 } 243 canvas.restore(); 244 245 Bitmap partialPreview = MasterImage.getImage().getPartialImage(); 246 if (partialPreview != null) { 247 Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight()); 248 Rect dest = new Rect(0, 0, getWidth(), getHeight()); 249 canvas.drawBitmap(partialPreview, src, dest, mPaint); 250 } 251 252 canvas.save(); 253 canvas.scale(scaleFactor, scaleFactor, cx, cy); 254 canvas.translate(translation.x, translation.y); 255 drawPartialImage(canvas, getGeometryOnlyImage()); 256 canvas.restore(); 257 258 canvas.restore(); 259 } 260 261 public void resetImageCaches(ImageShow caller) { 262 if (mImageLoader == null) { 263 return; 264 } 265 MasterImage.getImage().updatePresets(true); 266 } 267 268 public Bitmap getFiltersOnlyImage() { 269 return MasterImage.getImage().getFiltersOnlyImage(); 270 } 271 272 public Bitmap getGeometryOnlyImage() { 273 return MasterImage.getImage().getGeometryOnlyImage(); 274 } 275 276 public Bitmap getFilteredImage() { 277 return MasterImage.getImage().getFilteredImage(); 278 } 279 280 public void drawImage(Canvas canvas, Bitmap image, boolean updateBounds) { 281 if (image != null) { 282 Rect s = new Rect(0, 0, image.getWidth(), 283 image.getHeight()); 284 285 float scale = GeometryMath.scale(image.getWidth(), image.getHeight(), getWidth(), 286 getHeight()); 287 288 float w = image.getWidth() * scale; 289 float h = image.getHeight() * scale; 290 float ty = (getHeight() - h) / 2.0f; 291 float tx = (getWidth() - w) / 2.0f; 292 293 Rect d = new Rect((int) tx, (int) ty, (int) (w + tx), 294 (int) (h + ty)); 295 if (updateBounds) { 296 mImageBounds = d; 297 } 298 canvas.drawBitmap(image, s, d, mPaint); 299 } 300 } 301 302 public void drawPartialImage(Canvas canvas, Bitmap image) { 303 boolean showsOriginal = MasterImage.getImage().showsOriginal(); 304 if (!showsOriginal && !mTouchShowOriginal) 305 return; 306 canvas.save(); 307 if (image != null) { 308 if (mShowOriginalDirection == 0) { 309 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) { 310 mShowOriginalDirection = UNVEIL_VERTICAL; 311 } else { 312 mShowOriginalDirection = UNVEIL_HORIZONTAL; 313 } 314 } 315 316 int px = 0; 317 int py = 0; 318 if (mShowOriginalDirection == UNVEIL_VERTICAL) { 319 px = mImageBounds.width(); 320 py = mTouch.y - mImageBounds.top; 321 } else { 322 px = mTouch.x - mImageBounds.left; 323 py = mImageBounds.height(); 324 if (showsOriginal) { 325 px = mImageBounds.width(); 326 } 327 } 328 329 Rect d = new Rect(mImageBounds.left, mImageBounds.top, 330 mImageBounds.left + px, mImageBounds.top + py); 331 canvas.clipRect(d); 332 drawImage(canvas, image, false); 333 Paint paint = new Paint(); 334 paint.setColor(Color.BLACK); 335 paint.setStrokeWidth(3); 336 337 if (mShowOriginalDirection == UNVEIL_VERTICAL) { 338 canvas.drawLine(mImageBounds.left, mTouch.y, 339 mImageBounds.right, mTouch.y, paint); 340 } else { 341 canvas.drawLine(mTouch.x, mImageBounds.top, 342 mTouch.x, mImageBounds.bottom, paint); 343 } 344 345 Rect bounds = new Rect(); 346 paint.setAntiAlias(true); 347 paint.setTextSize(mOriginalTextSize); 348 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds); 349 paint.setColor(Color.BLACK); 350 paint.setStyle(Paint.Style.STROKE); 351 paint.setStrokeWidth(3); 352 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 353 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 354 paint.setStyle(Paint.Style.FILL); 355 paint.setStrokeWidth(1); 356 paint.setColor(Color.WHITE); 357 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 358 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 359 } 360 canvas.restore(); 361 } 362 363 public void bindAsImageLoadListener() { 364 MasterImage.getImage().addListener(this); 365 } 366 367 private void imageSizeChanged(Bitmap image) { 368 if (image == null || getImagePreset() == null) 369 return; 370 float w = image.getWidth(); 371 float h = image.getHeight(); 372 GeometryMetadata geo = getImagePreset().getGeometry(); 373 RectF pb = geo.getPhotoBounds(); 374 if (w == pb.width() && h == pb.height()) { 375 return; 376 } 377 RectF r = new RectF(0, 0, w, h); 378 geo.setPhotoBounds(r); 379 geo.setCropBounds(r); 380 getImagePreset().setGeometry(geo); 381 } 382 383 public void updateImage() { 384 invalidate(); 385 Bitmap bitmap = MasterImage.getImage().getOriginalBitmapLarge(); 386 if (bitmap != null) { 387 imageSizeChanged(bitmap); 388 } 389 } 390 391 public void imageLoaded() { 392 updateImage(); 393 invalidate(); 394 } 395 396 public void saveImage(FilterShowActivity filterShowActivity, File file) { 397 ImageLoader.saveImage(getImagePreset(), filterShowActivity, file); 398 } 399 400 401 public boolean scaleInProgress() { 402 return mScaleGestureDetector.isInProgress(); 403 } 404 405 @Override 406 public boolean onTouchEvent(MotionEvent event) { 407 super.onTouchEvent(event); 408 int action = event.getAction(); 409 action = action & MotionEvent.ACTION_MASK; 410 411 mGestureDetector.onTouchEvent(event); 412 boolean scaleInProgress = scaleInProgress(); 413 mScaleGestureDetector.onTouchEvent(event); 414 if (mInteractionMode == InteractionMode.SCALE) { 415 return true; 416 } 417 if (!scaleInProgress() && scaleInProgress) { 418 // If we were scaling, the scale will stop but we will 419 // still issue an ACTION_UP. Let the subclasses know. 420 mFinishedScalingOperation = true; 421 } 422 423 int ex = (int) event.getX(); 424 int ey = (int) event.getY(); 425 if (action == MotionEvent.ACTION_DOWN) { 426 mInteractionMode = InteractionMode.MOVE; 427 mTouchDown.x = ex; 428 mTouchDown.y = ey; 429 mTouchShowOriginalDate = System.currentTimeMillis(); 430 mShowOriginalDirection = 0; 431 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation()); 432 } 433 434 if (action == MotionEvent.ACTION_MOVE && mInteractionMode == InteractionMode.MOVE) { 435 mTouch.x = ex; 436 mTouch.y = ey; 437 438 float scaleFactor = MasterImage.getImage().getScaleFactor(); 439 if (scaleFactor > 1 && (!ENABLE_ZOOMED_COMPARISON || event.getPointerCount() == 2)) { 440 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor; 441 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor; 442 Point originalTranslation = MasterImage.getImage().getOriginalTranslation(); 443 Point translation = MasterImage.getImage().getTranslation(); 444 translation.x = (int) (originalTranslation.x + translateX); 445 translation.y = (int) (originalTranslation.y + translateY); 446 constrainTranslation(translation, scaleFactor); 447 MasterImage.getImage().setTranslation(translation); 448 mTouchShowOriginal = false; 449 } else if (enableComparison() && !mOriginalDisabled 450 && (System.currentTimeMillis() - mTouchShowOriginalDate 451 > mTouchShowOriginalDelayMin) 452 && event.getPointerCount() == 1) { 453 mTouchShowOriginal = true; 454 } 455 } 456 457 if (action == MotionEvent.ACTION_UP) { 458 mInteractionMode = InteractionMode.NONE; 459 mTouchShowOriginal = false; 460 mTouchDown.x = 0; 461 mTouchDown.y = 0; 462 mTouch.x = 0; 463 mTouch.y = 0; 464 if (MasterImage.getImage().getScaleFactor() <= 1) { 465 MasterImage.getImage().setScaleFactor(1); 466 MasterImage.getImage().resetTranslation(); 467 } 468 } 469 invalidate(); 470 return true; 471 } 472 473 protected boolean enableComparison() { 474 return true; 475 } 476 477 @Override 478 public boolean onDoubleTap(MotionEvent arg0) { 479 mZoomIn = !mZoomIn; 480 float scale = 1.0f; 481 if (mZoomIn) { 482 scale = MasterImage.getImage().getMaxScaleFactor(); 483 } 484 if (scale != MasterImage.getImage().getScaleFactor()) { 485 MasterImage.getImage().setScaleFactor(scale); 486 float translateX = (getWidth() / 2 - arg0.getX()); 487 float translateY = (getHeight() / 2 - arg0.getY()); 488 Point translation = MasterImage.getImage().getTranslation(); 489 translation.x = (int) (mOriginalTranslation.x + translateX); 490 translation.y = (int) (mOriginalTranslation.y + translateY); 491 constrainTranslation(translation, scale); 492 MasterImage.getImage().setTranslation(translation); 493 invalidate(); 494 } 495 return true; 496 } 497 498 private void constrainTranslation(Point translation, float scale) { 499 float maxTranslationX = getWidth() / scale; 500 float maxTranslationY = getHeight() / scale; 501 if (Math.abs(translation.x) > maxTranslationX) { 502 translation.x = (int) (Math.signum(translation.x) * 503 maxTranslationX); 504 if (Math.abs(translation.y) > maxTranslationY) { 505 translation.y = (int) (Math.signum(translation.y) * 506 maxTranslationY); 507 } 508 509 } 510 } 511 512 @Override 513 public boolean onDoubleTapEvent(MotionEvent arg0) { 514 return false; 515 } 516 517 @Override 518 public boolean onSingleTapConfirmed(MotionEvent arg0) { 519 return false; 520 } 521 522 @Override 523 public boolean onDown(MotionEvent arg0) { 524 return false; 525 } 526 527 @Override 528 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) { 529 if (mActivity == null) { 530 return false; 531 } 532 if (endEvent.getPointerCount() == 2) { 533 return false; 534 } 535 return true; 536 } 537 538 @Override 539 public void onLongPress(MotionEvent arg0) { 540 } 541 542 @Override 543 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 544 return false; 545 } 546 547 @Override 548 public void onShowPress(MotionEvent arg0) { 549 } 550 551 @Override 552 public boolean onSingleTapUp(MotionEvent arg0) { 553 return false; 554 } 555 556 public boolean useUtilityPanel() { 557 return false; 558 } 559 560 public void openUtilityPanel(final LinearLayout accessoryViewList) { 561 } 562 563 @Override 564 public boolean onScale(ScaleGestureDetector detector) { 565 MasterImage img = MasterImage.getImage(); 566 float scaleFactor = img.getScaleFactor(); 567 568 scaleFactor = scaleFactor * detector.getScaleFactor(); 569 if (scaleFactor > MasterImage.getImage().getMaxScaleFactor()) { 570 scaleFactor = MasterImage.getImage().getMaxScaleFactor(); 571 } 572 if (scaleFactor < 0.5) { 573 scaleFactor = 0.5f; 574 } 575 MasterImage.getImage().setScaleFactor(scaleFactor); 576 scaleFactor = img.getScaleFactor(); 577 float focusx = detector.getFocusX(); 578 float focusy = detector.getFocusY(); 579 float translateX = (focusx - mStartFocusX) / scaleFactor; 580 float translateY = (focusy - mStartFocusY) / scaleFactor; 581 Point translation = MasterImage.getImage().getTranslation(); 582 translation.x = (int) (mOriginalTranslation.x + translateX); 583 translation.y = (int) (mOriginalTranslation.y + translateY); 584 constrainTranslation(translation, scaleFactor); 585 MasterImage.getImage().setTranslation(translation); 586 587 invalidate(); 588 return true; 589 } 590 591 @Override 592 public boolean onScaleBegin(ScaleGestureDetector detector) { 593 Point pos = MasterImage.getImage().getTranslation(); 594 mOriginalTranslation.x = pos.x; 595 mOriginalTranslation.y = pos.y; 596 mOriginalScale = MasterImage.getImage().getScaleFactor(); 597 mStartFocusX = detector.getFocusX(); 598 mStartFocusY = detector.getFocusY(); 599 mInteractionMode = InteractionMode.SCALE; 600 return true; 601 } 602 603 @Override 604 public void onScaleEnd(ScaleGestureDetector detector) { 605 mInteractionMode = InteractionMode.NONE; 606 if (MasterImage.getImage().getScaleFactor() < 1) { 607 MasterImage.getImage().setScaleFactor(1); 608 invalidate(); 609 } 610 } 611 612 public boolean didFinishScalingOperation() { 613 if (mFinishedScalingOperation) { 614 mFinishedScalingOperation = false; 615 return true; 616 } 617 return false; 618 } 619 620} 621