ImageShow.java revision 4d276f338bbdc53f8a3b4806265bc26c7fe0ea7c
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.Bitmap; 21import android.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.Paint; 24import android.graphics.Rect; 25import android.graphics.RectF; 26import android.os.Handler; 27import android.util.AttributeSet; 28import android.view.GestureDetector; 29import android.view.GestureDetector.OnDoubleTapListener; 30import android.view.GestureDetector.OnGestureListener; 31import android.view.MotionEvent; 32import android.view.View; 33import android.widget.ArrayAdapter; 34import android.widget.SeekBar; 35import android.widget.SeekBar.OnSeekBarChangeListener; 36 37import com.android.gallery3d.R; 38import com.android.gallery3d.filtershow.FilterShowActivity; 39import com.android.gallery3d.filtershow.HistoryAdapter; 40import com.android.gallery3d.filtershow.ImageStateAdapter; 41import com.android.gallery3d.filtershow.PanelController; 42import com.android.gallery3d.filtershow.cache.ImageLoader; 43import com.android.gallery3d.filtershow.filters.ImageFilter; 44import com.android.gallery3d.filtershow.presets.ImagePreset; 45import com.android.gallery3d.filtershow.ui.SliderController; 46import com.android.gallery3d.filtershow.ui.SliderListener; 47 48import java.io.File; 49 50public class ImageShow extends View implements OnGestureListener, 51 OnDoubleTapListener, 52 SliderListener, 53 OnSeekBarChangeListener { 54 55 private static final String LOGTAG = "ImageShow"; 56 57 protected Paint mPaint = new Paint(); 58 protected static int mTextSize = 24; 59 protected static int mTextPadding = 20; 60 61 protected ImagePreset mImagePreset = null; 62 protected ImageLoader mImageLoader = null; 63 private ImageFilter mCurrentFilter = null; 64 private boolean mDirtyGeometry = true; 65 66 private Bitmap mBackgroundImage = null; 67 private final boolean USE_BACKGROUND_IMAGE = false; 68 private static int mBackgroundColor = Color.RED; 69 70 // TODO: remove protected here, it should be private 71 protected Bitmap mForegroundImage = null; 72 protected Bitmap mFilteredImage = null; 73 74 private final boolean USE_SLIDER_GESTURE = false; // set to true to have 75 // slider gesture 76 protected SliderController mSliderController = new SliderController(); 77 78 private GestureDetector mGestureDetector = null; 79 80 private HistoryAdapter mHistoryAdapter = null; 81 private ImageStateAdapter mImageStateAdapter = null; 82 83 private Rect mImageBounds = null; 84 85 private boolean mTouchShowOriginal = false; 86 private long mTouchShowOriginalDate = 0; 87 private final long mTouchShowOriginalDelayMin = 200; // 200ms 88 private final long mTouchShowOriginalDelayMax = 300; // 300ms 89 private int mTouchDownX = 0; 90 private int mTouchDownY = 0; 91 protected float mTouchX = 0; 92 protected float mTouchY = 0; 93 94 private static int mOriginalTextMargin = 8; 95 private static int mOriginalTextSize = 26; 96 private static String mOriginalText = "Original"; 97 98 protected GeometryMetadata getGeometry() { 99 return new GeometryMetadata(getImagePreset().mGeoData); 100 } 101 102 public void setGeometry(GeometryMetadata d) { 103 getImagePreset().mGeoData.set(d); 104 } 105 106 private boolean mShowControls = false; 107 private boolean mShowOriginal = false; 108 private String mToast = null; 109 private boolean mShowToast = false; 110 private boolean mImportantToast = false; 111 112 private SeekBar mSeekBar = null; 113 private PanelController mController = null; 114 115 private FilterShowActivity mActivity = null; 116 117 public static void setDefaultBackgroundColor(int value) { 118 mBackgroundColor = value; 119 } 120 121 public static void setTextSize(int value) { 122 mTextSize = value; 123 } 124 125 public static void setTextPadding(int value) { 126 mTextPadding = value; 127 } 128 129 public static void setOriginalTextMargin(int value) { 130 mOriginalTextMargin = value; 131 } 132 133 public static void setOriginalTextSize(int value) { 134 mOriginalTextSize = value; 135 } 136 137 public static void setOriginalText(String text) { 138 mOriginalText = text; 139 } 140 141 private final Handler mHandler = new Handler(); 142 143 public void select() { 144 if (getCurrentFilter() != null) { 145 int parameter = getCurrentFilter().getParameter(); 146 int maxp = getCurrentFilter().getMaxParameter(); 147 int minp = getCurrentFilter().getMinParameter(); 148 updateSeekBar(parameter, minp, maxp); 149 } 150 if (mSeekBar != null) { 151 mSeekBar.setOnSeekBarChangeListener(this); 152 } 153 } 154 155 private int parameterToUI(int parameter, int minp, int maxp, int uimax) { 156 return (uimax * (parameter - minp)) / (maxp - minp); 157 } 158 159 private int uiToParameter(int ui, int minp, int maxp, int uimax) { 160 return ((maxp - minp) * ui) / uimax + minp; 161 } 162 163 public void updateSeekBar(int parameter, int minp, int maxp) { 164 if (mSeekBar == null) { 165 return; 166 } 167 int seekMax = mSeekBar.getMax(); 168 int progress = parameterToUI(parameter, minp, maxp, seekMax); 169 mSeekBar.setProgress(progress); 170 if (getPanelController() != null) { 171 getPanelController().onNewValue(parameter); 172 } 173 } 174 175 public void unselect() { 176 177 } 178 179 public boolean hasModifications() { 180 if (getImagePreset() == null) { 181 return false; 182 } 183 return getImagePreset().hasModifications(); 184 } 185 186 public void resetParameter() { 187 ImageFilter currentFilter = getCurrentFilter(); 188 if (currentFilter != null) { 189 onNewValue(currentFilter.getDefaultParameter()); 190 } 191 if (USE_SLIDER_GESTURE) { 192 mSliderController.reset(); 193 } 194 } 195 196 public void setPanelController(PanelController controller) { 197 mController = controller; 198 } 199 200 public PanelController getPanelController() { 201 return mController; 202 } 203 204 @Override 205 public void onNewValue(int parameter) { 206 int maxp = 100; 207 int minp = -100; 208 if (getCurrentFilter() != null) { 209 getCurrentFilter().setParameter(parameter); 210 maxp = getCurrentFilter().getMaxParameter(); 211 minp = getCurrentFilter().getMinParameter(); 212 } 213 if (getImagePreset() != null) { 214 mImageLoader.resetImageForPreset(getImagePreset(), this); 215 getImagePreset().fillImageStateAdapter(mImageStateAdapter); 216 } 217 if (getPanelController() != null) { 218 getPanelController().onNewValue(parameter); 219 } 220 updateSeekBar(parameter, minp, maxp); 221 invalidate(); 222 } 223 224 @Override 225 public void onTouchDown(float x, float y) { 226 mTouchX = x; 227 mTouchY = y; 228 invalidate(); 229 } 230 231 @Override 232 public void onTouchUp() { 233 } 234 235 public ImageShow(Context context, AttributeSet attrs) { 236 super(context, attrs); 237 if (USE_SLIDER_GESTURE) { 238 mSliderController.setListener(this); 239 } 240 mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row, 241 R.id.rowTextView); 242 mImageStateAdapter = new ImageStateAdapter(context, 243 R.layout.filtershow_imagestate_row); 244 setupGestureDetector(context); 245 mActivity = (FilterShowActivity) context; 246 } 247 248 public ImageShow(Context context) { 249 super(context); 250 if (USE_SLIDER_GESTURE) { 251 mSliderController.setListener(this); 252 } 253 mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row, 254 R.id.rowTextView); 255 setupGestureDetector(context); 256 mActivity = (FilterShowActivity) context; 257 } 258 259 public void setupGestureDetector(Context context) { 260 mGestureDetector = new GestureDetector(context, this); 261 } 262 263 @Override 264 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 265 int parentWidth = MeasureSpec.getSize(widthMeasureSpec); 266 int parentHeight = MeasureSpec.getSize(heightMeasureSpec); 267 setMeasuredDimension(parentWidth, parentHeight); 268 if (USE_SLIDER_GESTURE) { 269 mSliderController.setWidth(parentWidth); 270 mSliderController.setHeight(parentHeight); 271 } 272 } 273 274 public void setSeekBar(SeekBar seekBar) { 275 mSeekBar = seekBar; 276 } 277 278 public void setCurrentFilter(ImageFilter filter) { 279 mCurrentFilter = filter; 280 } 281 282 public ImageFilter getCurrentFilter() { 283 return mCurrentFilter; 284 } 285 286 public void setAdapter(HistoryAdapter adapter) { 287 mHistoryAdapter = adapter; 288 } 289 290 public void showToast(String text) { 291 showToast(text, false); 292 } 293 294 public void showToast(String text, boolean important) { 295 mToast = text; 296 mShowToast = true; 297 mImportantToast = important; 298 invalidate(); 299 300 mHandler.postDelayed(new Runnable() { 301 @Override 302 public void run() { 303 mShowToast = false; 304 invalidate(); 305 } 306 }, 400); 307 } 308 309 public Rect getImageBounds() { 310 Rect dst = new Rect(); 311 getImagePreset().mGeoData.getPhotoBounds().roundOut(dst); 312 return dst; 313 } 314 315 public Rect getDisplayedImageBounds() { 316 return mImageBounds; 317 } 318 319 public ImagePreset getImagePreset() { 320 return mImagePreset; 321 } 322 323 protected Bitmap getOriginalFrontBitmap() { 324 if (mImageLoader != null) { 325 return mImageLoader.getOriginalBitmapLarge(); 326 } 327 return null; 328 } 329 330 public void drawToast(Canvas canvas) { 331 if (mShowToast && mToast != null) { 332 Paint paint = new Paint(); 333 paint.setTextSize(128); 334 float textWidth = paint.measureText(mToast); 335 int toastX = (int) ((getWidth() - textWidth) / 2.0f); 336 int toastY = (int) (getHeight() / 3.0f); 337 338 paint.setARGB(255, 0, 0, 0); 339 canvas.drawText(mToast, toastX - 2, toastY - 2, paint); 340 canvas.drawText(mToast, toastX - 2, toastY, paint); 341 canvas.drawText(mToast, toastX, toastY - 2, paint); 342 canvas.drawText(mToast, toastX + 2, toastY + 2, paint); 343 canvas.drawText(mToast, toastX + 2, toastY, paint); 344 canvas.drawText(mToast, toastX, toastY + 2, paint); 345 if (mImportantToast) { 346 paint.setARGB(255, 200, 0, 0); 347 } else { 348 paint.setARGB(255, 255, 255, 255); 349 } 350 canvas.drawText(mToast, toastX, toastY, paint); 351 } 352 } 353 354 @Override 355 public void onDraw(Canvas canvas) { 356 drawBackground(canvas); 357 getFilteredImage(); 358 drawImage(canvas, mFilteredImage); 359 drawPartialImage(canvas, mForegroundImage); 360 361 if (showTitle() && getImagePreset() != null) { 362 mPaint.setARGB(200, 0, 0, 0); 363 mPaint.setTextSize(mTextSize); 364 365 Rect textRect = new Rect(0, 0, getWidth(), mTextSize + mTextPadding); 366 canvas.drawRect(textRect, mPaint); 367 mPaint.setARGB(255, 200, 200, 200); 368 canvas.drawText(getImagePreset().name(), mTextPadding, 369 1.5f * mTextPadding, mPaint); 370 } 371 372 if (showControls()) { 373 if (USE_SLIDER_GESTURE) { 374 mSliderController.onDraw(canvas); 375 } 376 } 377 378 drawToast(canvas); 379 } 380 381 public void getFilteredImage() { 382 Bitmap filteredImage = null; 383 if (mImageLoader != null) { 384 filteredImage = mImageLoader.getImageForPreset(this, 385 getImagePreset(), showHires()); 386 } 387 388 if (filteredImage == null) { 389 // if no image for the current preset, use the previous one 390 filteredImage = mFilteredImage; 391 } else { 392 mFilteredImage = filteredImage; 393 } 394 395 if (mShowOriginal || mFilteredImage == null) { 396 mFilteredImage = mForegroundImage; 397 } 398 } 399 400 public void drawImage(Canvas canvas, Bitmap image) { 401 if (image != null) { 402 Rect s = new Rect(0, 0, image.getWidth(), 403 image.getHeight()); 404 float ratio = image.getWidth() 405 / (float) image.getHeight(); 406 float w = getWidth(); 407 float h = w / ratio; 408 float ty = (getHeight() - h) / 2.0f; 409 float tx = 0; 410 if (ratio < 1.0f || (getHeight() < w)) { 411 h = getHeight(); 412 w = h * ratio; 413 tx = (getWidth() - w) / 2.0f; 414 ty = 0; 415 } 416 Rect d = new Rect((int) tx, (int) ty, (int) (w + tx), 417 (int) (h + ty)); 418 mImageBounds = d; 419 canvas.drawBitmap(image, s, d, mPaint); 420 } 421 } 422 423 public void drawPartialImage(Canvas canvas, Bitmap image) { 424 if (!mTouchShowOriginal) 425 return; 426 canvas.save(); 427 if (image != null) { 428 boolean goingDown = false; 429 if ((mTouchY - mTouchDownY) > (mTouchX - mTouchDownX)) { 430 goingDown = true; 431 } 432 int px = (int) (mTouchX - mImageBounds.left); 433 int py = mImageBounds.height(); 434 if (goingDown) { 435 px = mImageBounds.width(); 436 py = (int) (mTouchY - mImageBounds.top); 437 } 438 Rect d = new Rect(mImageBounds.left, mImageBounds.top, 439 mImageBounds.left + px, mImageBounds.top + py); 440 canvas.clipRect(d); 441 drawImage(canvas, image); 442 Paint paint = new Paint(); 443 paint.setColor(Color.BLACK); 444 if (goingDown) { 445 canvas.drawLine(mImageBounds.left, mTouchY - 1, 446 mImageBounds.right, mTouchY - 1, paint); 447 } else { 448 canvas.drawLine(mTouchX - 1, mImageBounds.top, 449 mTouchX - 1, mImageBounds.bottom, paint); 450 } 451 Rect bounds = new Rect(); 452 paint.setTextSize(mOriginalTextSize); 453 paint.getTextBounds(mOriginalText, 0, mOriginalText.length(), bounds); 454 paint.setColor(Color.BLACK); 455 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin + 1, 456 mImageBounds.top + bounds.height() + mOriginalTextMargin + 1, paint); 457 paint.setColor(Color.WHITE); 458 canvas.drawText(mOriginalText, mImageBounds.left + mOriginalTextMargin, 459 mImageBounds.top + bounds.height() + mOriginalTextMargin, paint); 460 } 461 canvas.restore(); 462 } 463 464 public void drawBackground(Canvas canvas) { 465 if (USE_BACKGROUND_IMAGE) { 466 if (mBackgroundImage == null) { 467 mBackgroundImage = mImageLoader.getBackgroundBitmap(getResources()); 468 } 469 if (mBackgroundImage != null) { 470 Rect s = new Rect(0, 0, mBackgroundImage.getWidth(), 471 mBackgroundImage.getHeight()); 472 Rect d = new Rect(0, 0, getWidth(), getHeight()); 473 canvas.drawBitmap(mBackgroundImage, s, d, mPaint); 474 } 475 } else { 476 canvas.drawColor(mBackgroundColor); 477 } 478 } 479 480 public ImageShow setShowControls(boolean value) { 481 mShowControls = value; 482 if (mShowControls) { 483 if (mSeekBar != null) { 484 mSeekBar.setVisibility(View.VISIBLE); 485 } 486 } else { 487 if (mSeekBar != null) { 488 mSeekBar.setVisibility(View.INVISIBLE); 489 } 490 } 491 return this; 492 } 493 494 public boolean showControls() { 495 return mShowControls; 496 } 497 498 public boolean showHires() { 499 return true; 500 } 501 502 public boolean showTitle() { 503 return false; 504 } 505 506 public void setImagePreset(ImagePreset preset) { 507 setImagePreset(preset, true); 508 } 509 510 public void setImagePreset(ImagePreset preset, boolean addToHistory) { 511 mImagePreset = preset; 512 if (getImagePreset() != null) { 513 getImagePreset().setImageLoader(mImageLoader); 514 if (addToHistory) { 515 mHistoryAdapter.addHistoryItem(getImagePreset()); 516 } 517 getImagePreset().setEndpoint(this); 518 updateImage(); 519 } 520 mImagePreset.fillImageStateAdapter(mImageStateAdapter); 521 invalidate(); 522 } 523 524 public void setImageLoader(ImageLoader loader) { 525 mImageLoader = loader; 526 if (mImageLoader != null) { 527 mImageLoader.addListener(this); 528 if (mImagePreset != null) { 529 mImagePreset.setImageLoader(mImageLoader); 530 } 531 } 532 } 533 534 private void setDirtyGeometryFlag() { 535 mDirtyGeometry = true; 536 } 537 538 protected void clearDirtyGeometryFlag() { 539 mDirtyGeometry = false; 540 } 541 542 protected boolean getDirtyGeometryFlag() { 543 return mDirtyGeometry; 544 } 545 546 private void imageSizeChanged(Bitmap image) { 547 if (image == null || getImagePreset() == null) 548 return; 549 float w = image.getWidth(); 550 float h = image.getHeight(); 551 GeometryMetadata geo = getImagePreset().mGeoData; 552 RectF pb = geo.getPhotoBounds(); 553 if (w == pb.width() && h == pb.height()) { 554 return; 555 } 556 RectF r = new RectF(0, 0, w, h); 557 getImagePreset().mGeoData.setPhotoBounds(r); 558 getImagePreset().mGeoData.setCropBounds(r); 559 setDirtyGeometryFlag(); 560 } 561 562 public void updateImage() { 563 mForegroundImage = getOriginalFrontBitmap(); 564 imageSizeChanged(mForegroundImage); // TODO: should change to filtered 565 setDirtyGeometryFlag(); 566 } 567 568 public void updateFilteredImage(Bitmap bitmap) { 569 mFilteredImage = bitmap; 570 } 571 572 public void saveImage(FilterShowActivity filterShowActivity, File file) { 573 mImageLoader.saveImage(getImagePreset(), filterShowActivity, file); 574 } 575 576 @Override 577 public boolean onTouchEvent(MotionEvent event) { 578 super.onTouchEvent(event); 579 if (USE_SLIDER_GESTURE) { 580 mSliderController.onTouchEvent(event); 581 } 582 if (mGestureDetector != null) { 583 mGestureDetector.onTouchEvent(event); 584 } 585 int ex = (int) event.getX(); 586 int ey = (int) event.getY(); 587 if (event.getAction() == MotionEvent.ACTION_DOWN) { 588 mTouchDownX = ex; 589 mTouchDownY = ey; 590 mTouchShowOriginalDate = System.currentTimeMillis(); 591 } 592 if (event.getAction() == MotionEvent.ACTION_MOVE) { 593 mTouchX = ex; 594 mTouchY = ey; 595 if (!mActivity.isShowingHistoryPanel() 596 && (System.currentTimeMillis() - mTouchShowOriginalDate 597 > mTouchShowOriginalDelayMin)) { 598 mTouchShowOriginal = true; 599 } 600 } 601 if (event.getAction() == MotionEvent.ACTION_UP) { 602 mTouchShowOriginal = false; 603 mTouchDownX = 0; 604 mTouchDownY = 0; 605 mTouchX = 0; 606 mTouchY = 0; 607 } 608 invalidate(); 609 return true; 610 } 611 612 // listview stuff 613 614 public HistoryAdapter getHistory() { 615 return mHistoryAdapter; 616 } 617 618 public ArrayAdapter getImageStateAdapter() { 619 return mImageStateAdapter; 620 } 621 622 public void onItemClick(int position) { 623 setImagePreset(new ImagePreset(mHistoryAdapter.getItem(position)), false); 624 // we need a copy from the history 625 mHistoryAdapter.setCurrentPreset(position); 626 } 627 628 public void showOriginal(boolean show) { 629 mShowOriginal = show; 630 invalidate(); 631 } 632 633 public float getImageRotation() { 634 return getImagePreset().mGeoData.getRotation(); 635 } 636 637 public float getImageRotationZoomFactor() { 638 return getImagePreset().mGeoData.getScaleFactor(); 639 } 640 641 public void setImageRotation(float r) { 642 getImagePreset().mGeoData.setRotation(r); 643 } 644 645 public void setImageRotationZoomFactor(float f) { 646 getImagePreset().mGeoData.setScaleFactor(f); 647 } 648 649 public void setImageRotation(float imageRotation, 650 float imageRotationZoomFactor) { 651 float r = getImageRotation(); 652 if (imageRotation != r) { 653 invalidate(); 654 } 655 setImageRotation(imageRotation); 656 setImageRotationZoomFactor(imageRotationZoomFactor); 657 } 658 659 @Override 660 public void onProgressChanged(SeekBar arg0, int progress, boolean arg2) { 661 int parameter = progress; 662 if (getCurrentFilter() != null) { 663 int maxp = getCurrentFilter().getMaxParameter(); 664 int minp = getCurrentFilter().getMinParameter(); 665 parameter = uiToParameter(progress, minp, maxp, arg0.getMax()); 666 } 667 668 onNewValue(parameter); 669 } 670 671 @Override 672 public void onStartTrackingTouch(SeekBar arg0) { 673 // TODO Auto-generated method stub 674 675 } 676 677 @Override 678 public void onStopTrackingTouch(SeekBar arg0) { 679 // TODO Auto-generated method stub 680 681 } 682 683 @Override 684 public boolean onDoubleTap(MotionEvent arg0) { 685 // TODO Auto-generated method stub 686 return false; 687 } 688 689 @Override 690 public boolean onDoubleTapEvent(MotionEvent arg0) { 691 // TODO Auto-generated method stub 692 return false; 693 } 694 695 @Override 696 public boolean onSingleTapConfirmed(MotionEvent arg0) { 697 // TODO Auto-generated method stub 698 return false; 699 } 700 701 @Override 702 public boolean onDown(MotionEvent arg0) { 703 // TODO Auto-generated method stub 704 return false; 705 } 706 707 @Override 708 public boolean onFling(MotionEvent startEvent, MotionEvent endEvent, float arg2, float arg3) { 709 if ((!mActivity.isShowingHistoryPanel() && startEvent.getX() > endEvent.getX()) 710 || (mActivity.isShowingHistoryPanel() && endEvent.getX() > startEvent.getX())) { 711 if (!mTouchShowOriginal 712 || (mTouchShowOriginal && 713 (System.currentTimeMillis() - mTouchShowOriginalDate 714 < mTouchShowOriginalDelayMax))) { 715 mActivity.toggleHistoryPanel(); 716 } 717 } 718 return true; 719 } 720 721 @Override 722 public void onLongPress(MotionEvent arg0) { 723 // TODO Auto-generated method stub 724 725 } 726 727 @Override 728 public boolean onScroll(MotionEvent arg0, MotionEvent arg1, float arg2, float arg3) { 729 // TODO Auto-generated method stub 730 return false; 731 } 732 733 @Override 734 public void onShowPress(MotionEvent arg0) { 735 // TODO Auto-generated method stub 736 737 } 738 739 @Override 740 public boolean onSingleTapUp(MotionEvent arg0) { 741 // TODO Auto-generated method stub 742 return false; 743 } 744} 745