ImageShow.java revision a3a4c954c6917375a852d3f3c64d0c76693b5677
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.graphics.*; 21import android.net.Uri; 22import android.os.Handler; 23import android.util.AttributeSet; 24import android.util.Log; 25import android.view.*; 26import android.view.GestureDetector.OnDoubleTapListener; 27import android.view.GestureDetector.OnGestureListener; 28import android.widget.LinearLayout; 29 30import com.android.gallery3d.filtershow.FilterShowActivity; 31import com.android.gallery3d.filtershow.PanelController; 32import com.android.gallery3d.filtershow.cache.ImageLoader; 33import com.android.gallery3d.filtershow.cache.RenderingRequestCaller; 34import com.android.gallery3d.filtershow.filters.ImageFilter; 35import com.android.gallery3d.filtershow.presets.ImagePreset; 36 37import java.io.File; 38 39public class ImageShow extends View implements OnGestureListener, 40 ScaleGestureDetector.OnScaleGestureListener, 41 OnDoubleTapListener { 42 43 private static final String LOGTAG = "ImageShow"; 44 45 protected Paint mPaint = new Paint(); 46 protected static int mTextSize = 24; 47 protected static int mTextPadding = 20; 48 49 protected ImageLoader mImageLoader = null; 50 private boolean mDirtyGeometry = false; 51 52 private Bitmap mBackgroundImage = null; 53 private final boolean USE_BACKGROUND_IMAGE = false; 54 private static int mBackgroundColor = Color.RED; 55 56 private GestureDetector mGestureDetector = null; 57 private ScaleGestureDetector mScaleGestureDetector = null; 58 59 protected Rect mImageBounds = new Rect(); 60 private boolean mOriginalDisabled = false; 61 private boolean mTouchShowOriginal = false; 62 private long mTouchShowOriginalDate = 0; 63 private final long mTouchShowOriginalDelayMin = 200; // 200ms 64 private final long mTouchShowOriginalDelayMax = 300; // 300ms 65 private int mShowOriginalDirection = 0; 66 private static int UNVEIL_HORIZONTAL = 1; 67 private static int UNVEIL_VERTICAL = 2; 68 69 private Point mTouchDown = new Point(); 70 private Point mTouch = new Point(); 71 private boolean mFinishedScalingOperation = false; 72 73 private static int mOriginalTextMargin = 8; 74 private static int mOriginalTextSize = 26; 75 private static String mOriginalText = "Original"; 76 77 protected GeometryMetadata getGeometry() { 78 return new GeometryMetadata(getImagePreset().mGeoData); 79 } 80 81 private String mToast = null; 82 private boolean mShowToast = false; 83 private boolean mImportantToast = false; 84 85 private PanelController mController = null; 86 87 private FilterShowActivity mActivity = null; 88 89 public static void setDefaultBackgroundColor(int value) { 90 mBackgroundColor = value; 91 } 92 93 public FilterShowActivity getActivity() { 94 return mActivity; 95 } 96 97 public int getDefaultBackgroundColor() { 98 return mBackgroundColor; 99 } 100 101 public static void setTextSize(int value) { 102 mTextSize = value; 103 } 104 105 public static void setTextPadding(int value) { 106 mTextPadding = value; 107 } 108 109 public static void setOriginalTextMargin(int value) { 110 mOriginalTextMargin = value; 111 } 112 113 public static void setOriginalTextSize(int value) { 114 mOriginalTextSize = value; 115 } 116 117 public static void setOriginalText(String text) { 118 mOriginalText = text; 119 } 120 121 private final Handler mHandler = new Handler(); 122 123 public void select() { 124 } 125 126 public void unselect() { 127 } 128 129 public boolean hasModifications() { 130 if (getImagePreset() == null) { 131 return false; 132 } 133 return getImagePreset().hasModifications(); 134 } 135 136 public void resetParameter() { 137 // TODO: implement reset 138 } 139 140 public void setPanelController(PanelController controller) { 141 mController = controller; 142 } 143 144 public PanelController getPanelController() { 145 return mController; 146 } 147 148 public void onNewValue(int parameter) { 149 if (getImagePreset() != null) { 150 getImagePreset().fillImageStateAdapter(MasterImage.getImage().getState()); 151 } 152 if (getPanelController() != null) { 153 getPanelController().onNewValue(parameter); 154 } 155 invalidate(); 156 mActivity.enableSave(hasModifications()); 157 } 158 159 public Point getTouchPoint() { 160 return mTouch; 161 } 162 163 public ImageShow(Context context, AttributeSet attrs) { 164 super(context, attrs); 165 166 setupGestureDetector(context); 167 mActivity = (FilterShowActivity) context; 168 MasterImage.getImage().addObserver(this); 169 } 170 171 public ImageShow(Context context) { 172 super(context); 173 174 setupGestureDetector(context); 175 mActivity = (FilterShowActivity) context; 176 MasterImage.getImage().addObserver(this); 177 } 178 179 public void setupGestureDetector(Context context) { 180 mGestureDetector = new GestureDetector(context, this); 181 mScaleGestureDetector = new ScaleGestureDetector(context, this); 182 } 183 184 @Override 185 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 186 int parentWidth = MeasureSpec.getSize(widthMeasureSpec); 187 int parentHeight = MeasureSpec.getSize(heightMeasureSpec); 188 setMeasuredDimension(parentWidth, parentHeight); 189 } 190 191 public ImageFilter getCurrentFilter() { 192 return MasterImage.getImage().getCurrentFilter(); 193 } 194 195 public void showToast(String text) { 196 showToast(text, false); 197 } 198 199 public void showToast(String text, boolean important) { 200 mToast = text; 201 mShowToast = true; 202 mImportantToast = important; 203 invalidate(); 204 205 mHandler.postDelayed(new Runnable() { 206 @Override 207 public void run() { 208 mShowToast = false; 209 invalidate(); 210 } 211 }, 400); 212 } 213 214 public Rect getImageBounds() { 215 Rect dst = new Rect(); 216 getImagePreset().mGeoData.getPhotoBounds().roundOut(dst); 217 return dst; 218 } 219 220 public Rect getImageCropBounds() { 221 return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds()); 222 } 223 224 /* consider moving the following 2 methods into a subclass */ 225 /** 226 * This function calculates a Image to Screen Transformation matrix 227 * 228 * @param reflectRotation set true if you want the rotation encoded 229 * @return Image to Screen transformation matrix 230 */ 231 protected Matrix getImageToScreenMatrix(boolean reflectRotation) { 232 GeometryMetadata geo = getImagePreset().mGeoData; 233 if (geo == null || mImageLoader == null 234 || mImageLoader.getOriginalBounds() == null) { 235 return new Matrix(); 236 } 237 Matrix m = geo.getOriginalToScreen(reflectRotation, 238 mImageLoader.getOriginalBounds().width(), 239 mImageLoader.getOriginalBounds().height(), getWidth(), getHeight()); 240 Point translate = MasterImage.getImage().getTranslation(); 241 float scaleFactor = MasterImage.getImage().getScaleFactor(); 242 m.postTranslate(translate.x, translate.y); 243 m.postScale(scaleFactor, scaleFactor, getWidth()/2.0f, getHeight()/2.0f); 244 return m; 245 } 246 247 /** 248 * This function calculates a to Screen Image Transformation matrix 249 * 250 * @param reflectRotation set true if you want the rotation encoded 251 * @return Screen to Image transformation matrix 252 */ 253 protected Matrix getScreenToImageMatrix(boolean reflectRotation) { 254 Matrix m = getImageToScreenMatrix(reflectRotation); 255 Matrix invert = new Matrix(); 256 m.invert(invert); 257 return invert; 258 } 259 260 public Rect getDisplayedImageBounds() { 261 return mImageBounds; 262 } 263 264 public ImagePreset getImagePreset() { 265 return MasterImage.getImage().getPreset(); 266 } 267 268 public void drawToast(Canvas canvas) { 269 if (mShowToast && mToast != null) { 270 Paint paint = new Paint(); 271 paint.setTextSize(128); 272 float textWidth = paint.measureText(mToast); 273 int toastX = (int) ((getWidth() - textWidth) / 2.0f); 274 int toastY = (int) (getHeight() / 3.0f); 275 276 paint.setARGB(255, 0, 0, 0); 277 canvas.drawText(mToast, toastX - 2, toastY - 2, paint); 278 canvas.drawText(mToast, toastX - 2, toastY, paint); 279 canvas.drawText(mToast, toastX, toastY - 2, paint); 280 canvas.drawText(mToast, toastX + 2, toastY + 2, paint); 281 canvas.drawText(mToast, toastX + 2, toastY, paint); 282 canvas.drawText(mToast, toastX, toastY + 2, paint); 283 if (mImportantToast) { 284 paint.setARGB(255, 200, 0, 0); 285 } else { 286 paint.setARGB(255, 255, 255, 255); 287 } 288 canvas.drawText(mToast, toastX, toastY, paint); 289 } 290 } 291 292 @Override 293 public void onDraw(Canvas canvas) { 294 MasterImage.getImage().setImageShowSize(getWidth(), getHeight()); 295 canvas.save(); 296 // TODO: center scale on gesture 297 float cx = canvas.getWidth()/2.0f; 298 float cy = canvas.getHeight()/2.0f; 299 float scaleFactor = MasterImage.getImage().getScaleFactor(); 300 Point translation = MasterImage.getImage().getTranslation(); 301 canvas.scale(scaleFactor, scaleFactor, cx, cy); 302 canvas.translate(translation.x, translation.y); 303 drawBackground(canvas); 304 drawImage(canvas, getFilteredImage()); 305 canvas.restore(); 306 307 if (showTitle() && getImagePreset() != null) { 308 mPaint.setARGB(200, 0, 0, 0); 309 mPaint.setTextSize(mTextSize); 310 311 Rect textRect = new Rect(0, 0, getWidth(), mTextSize + mTextPadding); 312 canvas.drawRect(textRect, mPaint); 313 mPaint.setARGB(255, 200, 200, 200); 314 canvas.drawText(getImagePreset().name(), mTextPadding, 315 1.5f * mTextPadding, mPaint); 316 } 317 318 Bitmap partialPreview = MasterImage.getImage().getPartialImage(); 319 if (partialPreview != null) { 320 Rect src = new Rect(0, 0, partialPreview.getWidth(), partialPreview.getHeight()); 321 Rect dest = new Rect(0, 0, getWidth(), getHeight()); 322 canvas.drawBitmap(partialPreview, src, dest, mPaint); 323 } 324 325 canvas.save(); 326 canvas.scale(scaleFactor, scaleFactor, cx, cy); 327 canvas.translate(translation.x, translation.y); 328 drawPartialImage(canvas, getGeometryOnlyImage()); 329 canvas.restore(); 330 331 drawToast(canvas); 332 } 333 334 public void resetImageCaches(ImageShow caller) { 335 if (mImageLoader == null) { 336 return; 337 } 338 MasterImage.getImage().updatePresets(true); 339 } 340 341 public Bitmap getFiltersOnlyImage() { 342 return MasterImage.getImage().getFiltersOnlyImage(); 343 } 344 345 public Bitmap getGeometryOnlyImage() { 346 return MasterImage.getImage().getGeometryOnlyImage(); 347 } 348 349 public Bitmap getFilteredImage() { 350 return MasterImage.getImage().getFilteredImage(); 351 } 352 353 public void drawImage(Canvas canvas, Bitmap image) { 354 if (image != null) { 355 Rect s = new Rect(0, 0, image.getWidth(), 356 image.getHeight()); 357 358 float scale = GeometryMath.scale(image.getWidth(), image.getHeight(), getWidth(), 359 getHeight()); 360 361 float w = image.getWidth() * scale; 362 float h = image.getHeight() * scale; 363 float ty = (getHeight() - h) / 2.0f; 364 float tx = (getWidth() - w) / 2.0f; 365 366 Rect d = new Rect((int) tx, (int) ty, (int) (w + tx), 367 (int) (h + ty)); 368 mImageBounds = d; 369 canvas.drawBitmap(image, s, d, mPaint); 370 } 371 } 372 373 public void drawPartialImage(Canvas canvas, Bitmap image) { 374 if (!mTouchShowOriginal) 375 return; 376 canvas.save(); 377 if (image != null) { 378 if (mShowOriginalDirection == 0) { 379 if (Math.abs(mTouch.y - mTouchDown.y) > Math.abs(mTouch.x - mTouchDown.x)) { 380 mShowOriginalDirection = UNVEIL_VERTICAL; 381 } else { 382 mShowOriginalDirection = UNVEIL_HORIZONTAL; 383 } 384 } 385 386 int px = 0; 387 int py = 0; 388 if (mShowOriginalDirection == UNVEIL_VERTICAL) { 389 px = mImageBounds.width(); 390 py = (int) (mTouch.y - mImageBounds.top); 391 } else { 392 px = (int) (mTouch.x - mImageBounds.left); 393 py = mImageBounds.height(); 394 } 395 396 Rect d = new Rect(mImageBounds.left, mImageBounds.top, 397 mImageBounds.left + px, mImageBounds.top + py); 398 canvas.clipRect(d); 399 drawImage(canvas, image); 400 Paint paint = new Paint(); 401 paint.setColor(Color.BLACK); 402 paint.setStrokeWidth(3); 403 404 if (mShowOriginalDirection == UNVEIL_VERTICAL) { 405 canvas.drawLine(mImageBounds.left, mTouch.y, 406 mImageBounds.right, mTouch.y, paint); 407 } else { 408 canvas.drawLine(mTouch.x, mImageBounds.top, 409 mTouch.x, mImageBounds.bottom, paint); 410 } 411 412 Rect bounds = new Rect(); 413 paint.setAntiAlias(true); 414 paint.setTextSize(mOriginalTextSize); 415 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds); 416 paint.setColor(Color.BLACK); 417 paint.setStyle(Paint.Style.STROKE); 418 paint.setStrokeWidth(3); 419 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 420 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 421 paint.setStyle(Paint.Style.FILL); 422 paint.setStrokeWidth(1); 423 paint.setColor(Color.WHITE); 424 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 425 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 426 } 427 canvas.restore(); 428 } 429 430 public void drawBackground(Canvas canvas) { 431 if (USE_BACKGROUND_IMAGE) { 432 if (mBackgroundImage == null) { 433 mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources()); 434 } 435 if (mBackgroundImage != null) { 436 Rect s = new Rect(0, 0, mBackgroundImage.getWidth(), 437 mBackgroundImage.getHeight()); 438 Rect d = new Rect(0, 0, getWidth(), getHeight()); 439 canvas.drawBitmap(mBackgroundImage, s, d, mPaint); 440 } 441 } else { 442 canvas.drawColor(mBackgroundColor); 443 } 444 } 445 446 public boolean showTitle() { 447 return false; 448 } 449 450 public void setImageLoader(ImageLoader loader) { 451 mImageLoader = loader; 452 if (mImageLoader != null) { 453 mImageLoader.addListener(this); 454 MasterImage.getImage().setImageLoader(mImageLoader); 455 } 456 } 457 458 private void setDirtyGeometryFlag() { 459 mDirtyGeometry = true; 460 } 461 462 protected void clearDirtyGeometryFlag() { 463 mDirtyGeometry = false; 464 } 465 466 protected boolean getDirtyGeometryFlag() { 467 return mDirtyGeometry; 468 } 469 470 private void imageSizeChanged(Bitmap image) { 471 if (image == null || getImagePreset() == null) 472 return; 473 float w = image.getWidth(); 474 float h = image.getHeight(); 475 GeometryMetadata geo = getImagePreset().mGeoData; 476 RectF pb = geo.getPhotoBounds(); 477 if (w == pb.width() && h == pb.height()) { 478 return; 479 } 480 RectF r = new RectF(0, 0, w, h); 481 getImagePreset().mGeoData.setPhotoBounds(r); 482 getImagePreset().mGeoData.setCropBounds(r); 483 484 } 485 486 public boolean updateGeometryFlags() { 487 return true; 488 } 489 490 public void updateImage() { 491 invalidate(); 492 if (!updateGeometryFlags()) { 493 return; 494 } 495 Bitmap bitmap = mImageLoader.getOriginalBitmapLarge(); 496 if (bitmap != null) { 497 imageSizeChanged(bitmap); 498 } 499 } 500 501 public void imageLoaded() { 502 updateImage(); 503 invalidate(); 504 } 505 506 public void saveImage(FilterShowActivity filterShowActivity, File file) { 507 mImageLoader.saveImage(getImagePreset(), filterShowActivity, file); 508 } 509 510 public void saveToUri(Bitmap f, Uri u, String m, FilterShowActivity filterShowActivity) { 511 mImageLoader.saveToUri(f, u, m, filterShowActivity); 512 } 513 514 public void returnFilteredResult(FilterShowActivity filterShowActivity) { 515 mImageLoader.returnFilteredResult(getImagePreset(), filterShowActivity); 516 } 517 518 public boolean scaleInProgress() { 519 return mScaleGestureDetector.isInProgress(); 520 } 521 522 protected boolean isOriginalDisabled() { 523 return mOriginalDisabled; 524 } 525 526 protected void setOriginalDisabled(boolean originalDisabled) { 527 mOriginalDisabled = originalDisabled; 528 } 529 530 @Override 531 public boolean onTouchEvent(MotionEvent event) { 532 super.onTouchEvent(event); 533 mGestureDetector.onTouchEvent(event); 534 boolean scaleInProgress = scaleInProgress(); 535 mScaleGestureDetector.onTouchEvent(event); 536 if (!scaleInProgress() && scaleInProgress) { 537 // If we were scaling, the scale will stop but we will 538 // still issue an ACTION_UP. Let the subclasses know. 539 mFinishedScalingOperation = true; 540 } 541 542 int ex = (int) event.getX(); 543 int ey = (int) event.getY(); 544 if (event.getAction() == MotionEvent.ACTION_DOWN) { 545 mTouchDown.x = ex; 546 mTouchDown.y = ey; 547 mTouchShowOriginalDate = System.currentTimeMillis(); 548 mShowOriginalDirection = 0; 549 MasterImage.getImage().setOriginalTranslation(MasterImage.getImage().getTranslation()); 550 } 551 552 if (event.getAction() == MotionEvent.ACTION_MOVE) { 553 mTouch.x = ex; 554 mTouch.y = ey; 555 556 if (event.getPointerCount() == 2) { 557 float scaleFactor = MasterImage.getImage().getScaleFactor(); 558 if (scaleFactor >= 1) { 559 float translateX = (mTouch.x - mTouchDown.x) / scaleFactor; 560 float translateY = (mTouch.y - mTouchDown.y) / scaleFactor; 561 Point originalTranslation = MasterImage.getImage().getOriginalTranslation(); 562 Point translation = MasterImage.getImage().getTranslation(); 563 translation.x = (int) (originalTranslation.x + translateX); 564 translation.y = (int) (originalTranslation.y + translateY); 565 MasterImage.getImage().setTranslation(translation); 566 } 567 } else if (!mOriginalDisabled && !mActivity.isShowingHistoryPanel() 568 && (System.currentTimeMillis() - mTouchShowOriginalDate 569 > mTouchShowOriginalDelayMin) 570 && event.getPointerCount() == 1) { 571 mTouchShowOriginal = true; 572 } 573 } 574 575 if (event.getAction() == MotionEvent.ACTION_UP) { 576 mTouchShowOriginal = false; 577 mTouchDown.x = 0; 578 mTouchDown.y = 0; 579 mTouch.x = 0; 580 mTouch.y = 0; 581 if (MasterImage.getImage().getScaleFactor() <= 1) { 582 MasterImage.getImage().setScaleFactor(1); 583 MasterImage.getImage().resetTranslation(); 584 } 585 } 586 invalidate(); 587 return true; 588 } 589 590 // listview stuff 591 public void showOriginal(boolean show) { 592 invalidate(); 593 } 594 595 @Override 596 public boolean onDoubleTap(MotionEvent arg0) { 597 // TODO Auto-generated method stub 598 return false; 599 } 600 601 @Override 602 public boolean onDoubleTapEvent(MotionEvent arg0) { 603 // TODO Auto-generated method stub 604 return false; 605 } 606 607 @Override 608 public boolean onSingleTapConfirmed(MotionEvent arg0) { 609 // TODO Auto-generated method stub 610 return false; 611 } 612 613 @Override 614 public boolean onDown(MotionEvent arg0) { 615 // TODO Auto-generated method stub 616 return false; 617 } 618 619 @Override 620 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) { 621 if (mActivity == null) { 622 return false; 623 } 624 if ((!mActivity.isShowingHistoryPanel() && startEvent.getX() > endEvent.getX()) 625 || (mActivity.isShowingHistoryPanel() && endEvent.getX() > startEvent.getX())) { 626 if (!mTouchShowOriginal 627 || (mTouchShowOriginal && 628 (System.currentTimeMillis() - mTouchShowOriginalDate 629 < mTouchShowOriginalDelayMax))) { 630 // TODO fix gesture. 631 // mActivity.toggleHistoryPanel(); 632 } 633 } 634 return true; 635 } 636 637 @Override 638 public void onLongPress(MotionEvent arg0) { 639 // TODO Auto-generated method stub 640 } 641 642 @Override 643 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 644 // TODO Auto-generated method stub 645 return false; 646 } 647 648 @Override 649 public void onShowPress(MotionEvent arg0) { 650 // TODO Auto-generated method stub 651 } 652 653 @Override 654 public boolean onSingleTapUp(MotionEvent arg0) { 655 // TODO Auto-generated method stub 656 return false; 657 } 658 659 public boolean useUtilityPanel() { 660 return true; 661 } 662 663 public void openUtilityPanel(final LinearLayout accessoryViewList) { 664 // TODO Auto-generated method stub 665 } 666 667 @Override 668 public boolean onScale(ScaleGestureDetector detector) { 669 float scaleFactor = MasterImage.getImage().getScaleFactor(); 670 scaleFactor = scaleFactor * detector.getScaleFactor(); 671 if (scaleFactor > 2) { 672 scaleFactor = 2; 673 } 674 if (scaleFactor < 0.5) { 675 scaleFactor = 0.5f; 676 } 677 MasterImage.getImage().setScaleFactor(scaleFactor); 678 return true; 679 } 680 681 @Override 682 public boolean onScaleBegin(ScaleGestureDetector detector) { 683 return true; 684 } 685 686 @Override 687 public void onScaleEnd(ScaleGestureDetector detector) { 688 if (MasterImage.getImage().getScaleFactor() < 1) { 689 MasterImage.getImage().setScaleFactor(1); 690 invalidate(); 691 } 692 } 693 694 public boolean didFinishScalingOperation() { 695 if (mFinishedScalingOperation) { 696 mFinishedScalingOperation = false; 697 return true; 698 } 699 return false; 700 } 701 702} 703