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