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