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