1 /* 2 * Copyright (C) 2007 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 17 package com.android.camera; 18 19 import com.android.gallery.R; 20 21 import android.graphics.Canvas; 22 import android.graphics.Matrix; 23 import android.graphics.Paint; 24 import android.graphics.Path; 25 import android.graphics.Rect; 26 import android.graphics.RectF; 27 import android.graphics.Region; 28 import android.graphics.drawable.Drawable; 29 import android.view.View; 30 31 // This class is used by CropImage to display a highlighted cropping rectangle 32 // overlayed with the image. There are two coordinate spaces in use. One is 33 // image, another is screen. computeLayout() uses mMatrix to map from image 34 // space to screen space. 35 class HighlightView { 36 37 @SuppressWarnings("unused") 38 private static final String TAG = "HighlightView"; 39 View mContext; // The View displaying the image. 40 41 public static final int GROW_NONE = (1 << 0); 42 public static final int GROW_LEFT_EDGE = (1 << 1); 43 public static final int GROW_RIGHT_EDGE = (1 << 2); 44 public static final int GROW_TOP_EDGE = (1 << 3); 45 public static final int GROW_BOTTOM_EDGE = (1 << 4); 46 public static final int MOVE = (1 << 5); 47 48 public HighlightView(View ctx) { 49 mContext = ctx; 50 } 51 52 private void init() { 53 android.content.res.Resources resources = mContext.getResources(); 54 mResizeDrawableWidth = 55 resources.getDrawable(R.drawable.camera_crop_width); 56 mResizeDrawableHeight = 57 resources.getDrawable(R.drawable.camera_crop_height); 58 mResizeDrawableDiagonal = 59 resources.getDrawable(R.drawable.indicator_autocrop); 60 } 61 62 boolean mIsFocused; 63 boolean mHidden; 64 65 public boolean hasFocus() { 66 return mIsFocused; 67 } 68 69 public void setFocus(boolean f) { 70 mIsFocused = f; 71 } 72 73 public void setHidden(boolean hidden) { 74 mHidden = hidden; 75 } 76 77 protected void draw(Canvas canvas) { 78 if (mHidden) { 79 return; 80 } 81 canvas.save(); 82 Path path = new Path(); 83 if (!hasFocus()) { 84 mOutlinePaint.setColor(0xFF000000); 85 canvas.drawRect(mDrawRect, mOutlinePaint); 86 } else { 87 Rect viewDrawingRect = new Rect(); 88 mContext.getDrawingRect(viewDrawingRect); 89 if (mCircle) { 90 float width = mDrawRect.width(); 91 float height = mDrawRect.height(); 92 path.addCircle(mDrawRect.left + (width / 2), 93 mDrawRect.top + (height / 2), 94 width / 2, 95 Path.Direction.CW); 96 mOutlinePaint.setColor(0xFFEF04D6); 97 } else { 98 path.addRect(new RectF(mDrawRect), Path.Direction.CW); 99 mOutlinePaint.setColor(0xFFFF8A00); 100 } 101 canvas.clipPath(path, Region.Op.DIFFERENCE); 102 canvas.drawRect(viewDrawingRect, 103 hasFocus() ? mFocusPaint : mNoFocusPaint); 104 105 canvas.restore(); 106 canvas.drawPath(path, mOutlinePaint); 107 108 if (mMode == ModifyMode.Grow) { 109 if (mCircle) { 110 int width = mResizeDrawableDiagonal.getIntrinsicWidth(); 111 int height = mResizeDrawableDiagonal.getIntrinsicHeight(); 112 113 int d = (int) Math.round(Math.cos(/*45deg*/Math.PI / 4D) 114 * (mDrawRect.width() / 2D)); 115 int x = mDrawRect.left 116 + (mDrawRect.width() / 2) + d - width / 2; 117 int y = mDrawRect.top 118 + (mDrawRect.height() / 2) - d - height / 2; 119 mResizeDrawableDiagonal.setBounds(x, y, 120 x + mResizeDrawableDiagonal.getIntrinsicWidth(), 121 y + mResizeDrawableDiagonal.getIntrinsicHeight()); 122 mResizeDrawableDiagonal.draw(canvas); 123 } else { 124 int left = mDrawRect.left + 1; 125 int right = mDrawRect.right + 1; 126 int top = mDrawRect.top + 4; 127 int bottom = mDrawRect.bottom + 3; 128 129 int widthWidth = 130 mResizeDrawableWidth.getIntrinsicWidth() / 2; 131 int widthHeight = 132 mResizeDrawableWidth.getIntrinsicHeight() / 2; 133 int heightHeight = 134 mResizeDrawableHeight.getIntrinsicHeight() / 2; 135 int heightWidth = 136 mResizeDrawableHeight.getIntrinsicWidth() / 2; 137 138 int xMiddle = mDrawRect.left 139 + ((mDrawRect.right - mDrawRect.left) / 2); 140 int yMiddle = mDrawRect.top 141 + ((mDrawRect.bottom - mDrawRect.top) / 2); 142 143 mResizeDrawableWidth.setBounds(left - widthWidth, 144 yMiddle - widthHeight, 145 left + widthWidth, 146 yMiddle + widthHeight); 147 mResizeDrawableWidth.draw(canvas); 148 149 mResizeDrawableWidth.setBounds(right - widthWidth, 150 yMiddle - widthHeight, 151 right + widthWidth, 152 yMiddle + widthHeight); 153 mResizeDrawableWidth.draw(canvas); 154 155 mResizeDrawableHeight.setBounds(xMiddle - heightWidth, 156 top - heightHeight, 157 xMiddle + heightWidth, 158 top + heightHeight); 159 mResizeDrawableHeight.draw(canvas); 160 161 mResizeDrawableHeight.setBounds(xMiddle - heightWidth, 162 bottom - heightHeight, 163 xMiddle + heightWidth, 164 bottom + heightHeight); 165 mResizeDrawableHeight.draw(canvas); 166 } 167 } 168 } 169 } 170 171 public void setMode(ModifyMode mode) { 172 if (mode != mMode) { 173 mMode = mode; 174 mContext.invalidate(); 175 } 176 } 177 178 // Determines which edges are hit by touching at (x, y). 179 public int getHit(float x, float y) { 180 Rect r = computeLayout(); 181 final float hysteresis = 20F; 182 int retval = GROW_NONE; 183 184 if (mCircle) { 185 float distX = x - r.centerX(); 186 float distY = y - r.centerY(); 187 int distanceFromCenter = 188 (int) Math.sqrt(distX * distX + distY * distY); 189 int radius = mDrawRect.width() / 2; 190 int delta = distanceFromCenter - radius; 191 if (Math.abs(delta) <= hysteresis) { 192 if (Math.abs(distY) > Math.abs(distX)) { 193 if (distY < 0) { 194 retval = GROW_TOP_EDGE; 195 } else { 196 retval = GROW_BOTTOM_EDGE; 197 } 198 } else { 199 if (distX < 0) { 200 retval = GROW_LEFT_EDGE; 201 } else { 202 retval = GROW_RIGHT_EDGE; 203 } 204 } 205 } else if (distanceFromCenter < radius) { 206 retval = MOVE; 207 } else { 208 retval = GROW_NONE; 209 } 210 } else { 211 // verticalCheck makes sure the position is between the top and 212 // the bottom edge (with some tolerance). Similar for horizCheck. 213 boolean verticalCheck = (y >= r.top - hysteresis) 214 && (y < r.bottom + hysteresis); 215 boolean horizCheck = (x >= r.left - hysteresis) 216 && (x < r.right + hysteresis); 217 218 // Check whether the position is near some edge(s). 219 if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) { 220 retval |= GROW_LEFT_EDGE; 221 } 222 if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) { 223 retval |= GROW_RIGHT_EDGE; 224 } 225 if ((Math.abs(r.top - y) < hysteresis) && horizCheck) { 226 retval |= GROW_TOP_EDGE; 227 } 228 if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) { 229 retval |= GROW_BOTTOM_EDGE; 230 } 231 232 // Not near any edge but inside the rectangle: move. 233 if (retval == GROW_NONE && r.contains((int) x, (int) y)) { 234 retval = MOVE; 235 } 236 } 237 return retval; 238 } 239 240 // Handles motion (dx, dy) in screen space. 241 // The "edge" parameter specifies which edges the user is dragging. 242 void handleMotion(int edge, float dx, float dy) { 243 Rect r = computeLayout(); 244 if (edge == GROW_NONE) { 245 return; 246 } else if (edge == MOVE) { 247 // Convert to image space before sending to moveBy(). 248 moveBy(dx * (mCropRect.width() / r.width()), 249 dy * (mCropRect.height() / r.height())); 250 } else { 251 if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) { 252 dx = 0; 253 } 254 255 if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) { 256 dy = 0; 257 } 258 259 // Convert to image space before sending to growBy(). 260 float xDelta = dx * (mCropRect.width() / r.width()); 261 float yDelta = dy * (mCropRect.height() / r.height()); 262 growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta, 263 (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta); 264 } 265 } 266 267 // Grows the cropping rectange by (dx, dy) in image space. 268 void moveBy(float dx, float dy) { 269 Rect invalRect = new Rect(mDrawRect); 270 271 mCropRect.offset(dx, dy); 272 273 // Put the cropping rectangle inside image rectangle. 274 mCropRect.offset( 275 Math.max(0, mImageRect.left - mCropRect.left), 276 Math.max(0, mImageRect.top - mCropRect.top)); 277 278 mCropRect.offset( 279 Math.min(0, mImageRect.right - mCropRect.right), 280 Math.min(0, mImageRect.bottom - mCropRect.bottom)); 281 282 mDrawRect = computeLayout(); 283 invalRect.union(mDrawRect); 284 invalRect.inset(-10, -10); 285 mContext.invalidate(invalRect); 286 } 287 288 // Grows the cropping rectange by (dx, dy) in image space. 289 void growBy(float dx, float dy) { 290 if (mMaintainAspectRatio) { 291 if (dx != 0) { 292 dy = dx / mInitialAspectRatio; 293 } else if (dy != 0) { 294 dx = dy * mInitialAspectRatio; 295 } 296 } 297 298 // Don't let the cropping rectangle grow too fast. 299 // Grow at most half of the difference between the image rectangle and 300 // the cropping rectangle. 301 RectF r = new RectF(mCropRect); 302 if (dx > 0F && r.width() + 2 * dx > mImageRect.width()) { 303 float adjustment = (mImageRect.width() - r.width()) / 2F; 304 dx = adjustment; 305 if (mMaintainAspectRatio) { 306 dy = dx / mInitialAspectRatio; 307 } 308 } 309 if (dy > 0F && r.height() + 2 * dy > mImageRect.height()) { 310 float adjustment = (mImageRect.height() - r.height()) / 2F; 311 dy = adjustment; 312 if (mMaintainAspectRatio) { 313 dx = dy * mInitialAspectRatio; 314 } 315 } 316 317 r.inset(-dx, -dy); 318 319 // Don't let the cropping rectangle shrink too fast. 320 final float widthCap = 25F; 321 if (r.width() < widthCap) { 322 r.inset(-(widthCap - r.width()) / 2F, 0F); 323 } 324 float heightCap = mMaintainAspectRatio 325 ? (widthCap / mInitialAspectRatio) 326 : widthCap; 327 if (r.height() < heightCap) { 328 r.inset(0F, -(heightCap - r.height()) / 2F); 329 } 330 331 // Put the cropping rectangle inside the image rectangle. 332 if (r.left < mImageRect.left) { 333 r.offset(mImageRect.left - r.left, 0F); 334 } else if (r.right > mImageRect.right) { 335 r.offset(-(r.right - mImageRect.right), 0); 336 } 337 if (r.top < mImageRect.top) { 338 r.offset(0F, mImageRect.top - r.top); 339 } else if (r.bottom > mImageRect.bottom) { 340 r.offset(0F, -(r.bottom - mImageRect.bottom)); 341 } 342 343 mCropRect.set(r); 344 mDrawRect = computeLayout(); 345 mContext.invalidate(); 346 } 347 348 // Returns the cropping rectangle in image space. 349 public Rect getCropRect() { 350 return new Rect((int) mCropRect.left, (int) mCropRect.top, 351 (int) mCropRect.right, (int) mCropRect.bottom); 352 } 353 354 // Maps the cropping rectangle from image space to screen space. 355 private Rect computeLayout() { 356 RectF r = new RectF(mCropRect.left, mCropRect.top, 357 mCropRect.right, mCropRect.bottom); 358 mMatrix.mapRect(r); 359 return new Rect(Math.round(r.left), Math.round(r.top), 360 Math.round(r.right), Math.round(r.bottom)); 361 } 362 363 public void invalidate() { 364 mDrawRect = computeLayout(); 365 } 366 367 public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean circle, 368 boolean maintainAspectRatio) { 369 if (circle) { 370 maintainAspectRatio = true; 371 } 372 mMatrix = new Matrix(m); 373 374 mCropRect = cropRect; 375 mImageRect = new RectF(imageRect); 376 mMaintainAspectRatio = maintainAspectRatio; 377 mCircle = circle; 378 379 mInitialAspectRatio = mCropRect.width() / mCropRect.height(); 380 mDrawRect = computeLayout(); 381 382 mFocusPaint.setARGB(125, 50, 50, 50); 383 mNoFocusPaint.setARGB(125, 50, 50, 50); 384 mOutlinePaint.setStrokeWidth(3F); 385 mOutlinePaint.setStyle(Paint.Style.STROKE); 386 mOutlinePaint.setAntiAlias(true); 387 388 mMode = ModifyMode.None; 389 init(); 390 } 391 392 enum ModifyMode { None, Move, Grow } 393 394 private ModifyMode mMode = ModifyMode.None; 395 396 Rect mDrawRect; // in screen space 397 private RectF mImageRect; // in image space 398 RectF mCropRect; // in image space 399 Matrix mMatrix; 400 401 private boolean mMaintainAspectRatio = false; 402 private float mInitialAspectRatio; 403 private boolean mCircle = false; 404 405 private Drawable mResizeDrawableWidth; 406 private Drawable mResizeDrawableHeight; 407 private Drawable mResizeDrawableDiagonal; 408 409 private final Paint mFocusPaint = new Paint(); 410 private final Paint mNoFocusPaint = new Paint(); 411 private final Paint mOutlinePaint = new Paint(); 412 } 413