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 17 package com.android.camera.crop; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Matrix; 21 import android.graphics.RectF; 22 23 import java.util.Arrays; 24 25 public class CropMath { 26 27 /** 28 * Gets a float array of the 2D coordinates representing a rectangles 29 * corners. 30 * The order of the corners in the float array is: 31 * 0------->1 32 * ^ | 33 * | v 34 * 3<-------2 35 * 36 * @param r the rectangle to get the corners of 37 * @return the float array of corners (8 floats) 38 */ 39 40 public static float[] getCornersFromRect(RectF r) { 41 float[] corners = { 42 r.left, r.top, 43 r.right, r.top, 44 r.right, r.bottom, 45 r.left, r.bottom 46 }; 47 return corners; 48 } 49 50 /** 51 * Returns true iff point (x, y) is within or on the rectangle's bounds. 52 * RectF's "contains" function treats points on the bottom and right bound 53 * as not being contained. 54 * 55 * @param r the rectangle 56 * @param x the x value of the point 57 * @param y the y value of the point 58 * @return 59 */ 60 public static boolean inclusiveContains(RectF r, float x, float y) { 61 return !(x > r.right || x < r.left || y > r.bottom || y < r.top); 62 } 63 64 /** 65 * Takes an array of 2D coordinates representing corners and returns the 66 * smallest rectangle containing those coordinates. 67 * 68 * @param array array of 2D coordinates 69 * @return smallest rectangle containing coordinates 70 */ 71 public static RectF trapToRect(float[] array) { 72 RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, 73 Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); 74 for (int i = 1; i < array.length; i += 2) { 75 float x = array[i - 1]; 76 float y = array[i]; 77 r.left = (x < r.left) ? x : r.left; 78 r.top = (y < r.top) ? y : r.top; 79 r.right = (x > r.right) ? x : r.right; 80 r.bottom = (y > r.bottom) ? y : r.bottom; 81 } 82 r.sort(); 83 return r; 84 } 85 86 /** 87 * If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the 88 * image bound rectangle, clamps it to the edge of the rectangle. 89 * 90 * @param imageBound the rectangle to clamp edge points to. 91 * @param array an array of points to clamp to the rectangle, gets set to 92 * the clamped values. 93 */ 94 public static void getEdgePoints(RectF imageBound, float[] array) { 95 if (array.length < 2) 96 return; 97 for (int x = 0; x < array.length; x += 2) { 98 array[x] = GeometryMathUtils.clamp(array[x], imageBound.left, imageBound.right); 99 array[x + 1] = GeometryMathUtils.clamp(array[x + 1], imageBound.top, imageBound.bottom); 100 } 101 } 102 103 /** 104 * Takes a point and the corners of a rectangle and returns the two corners 105 * representing the side of the rectangle closest to the point. 106 * 107 * @param point the point which is being checked 108 * @param corners the corners of the rectangle 109 * @return two corners representing the side of the rectangle 110 */ 111 public static float[] closestSide(float[] point, float[] corners) { 112 int len = corners.length; 113 float oldMag = Float.POSITIVE_INFINITY; 114 float[] bestLine = null; 115 for (int i = 0; i < len; i += 2) { 116 float[] line = { 117 corners[i], corners[(i + 1) % len], 118 corners[(i + 2) % len], corners[(i + 3) % len] 119 }; 120 float mag = GeometryMathUtils.vectorLength( 121 GeometryMathUtils.shortestVectorFromPointToLine(point, line)); 122 if (mag < oldMag) { 123 oldMag = mag; 124 bestLine = line; 125 } 126 } 127 return bestLine; 128 } 129 130 /** 131 * Checks if a given point is within a rotated rectangle. 132 * 133 * @param point 2D point to check 134 * @param bound rectangle to rotate 135 * @param rot angle of rotation about rectangle center 136 * @return true if point is within rotated rectangle 137 */ 138 public static boolean pointInRotatedRect(float[] point, RectF bound, float rot) { 139 Matrix m = new Matrix(); 140 float[] p = Arrays.copyOf(point, 2); 141 m.setRotate(rot, bound.centerX(), bound.centerY()); 142 Matrix m0 = new Matrix(); 143 if (!m.invert(m0)) 144 return false; 145 m0.mapPoints(p); 146 return inclusiveContains(bound, p[0], p[1]); 147 } 148 149 /** 150 * Checks if a given point is within a rotated rectangle. 151 * 152 * @param point 2D point to check 153 * @param rotatedRect corners of a rotated rectangle 154 * @param center center of the rotated rectangle 155 * @return true if point is within rotated rectangle 156 */ 157 public static boolean pointInRotatedRect(float[] point, float[] rotatedRect, float[] center) { 158 RectF unrotated = new RectF(); 159 float angle = getUnrotated(rotatedRect, center, unrotated); 160 return pointInRotatedRect(point, unrotated, angle); 161 } 162 163 /** 164 * Resizes rectangle to have a certain aspect ratio (center remains 165 * stationary). 166 * 167 * @param r rectangle to resize 168 * @param w new width aspect 169 * @param h new height aspect 170 */ 171 public static void fixAspectRatio(RectF r, float w, float h) { 172 float scale = Math.min(r.width() / w, r.height() / h); 173 float centX = r.centerX(); 174 float centY = r.centerY(); 175 float hw = scale * w / 2; 176 float hh = scale * h / 2; 177 r.set(centX - hw, centY - hh, centX + hw, centY + hh); 178 } 179 180 /** 181 * Resizes rectangle to have a certain aspect ratio (center remains 182 * stationary) while constraining it to remain within the original rect. 183 * 184 * @param r rectangle to resize 185 * @param w new width aspect 186 * @param h new height aspect 187 */ 188 public static void fixAspectRatioContained(RectF r, float w, float h) { 189 float origW = r.width(); 190 float origH = r.height(); 191 float origA = origW / origH; 192 float a = w / h; 193 float finalW = origW; 194 float finalH = origH; 195 if (origA < a) { 196 finalH = origW / a; 197 r.top = r.centerY() - finalH / 2; 198 r.bottom = r.top + finalH; 199 } else { 200 finalW = origH * a; 201 r.left = r.centerX() - finalW / 2; 202 r.right = r.left + finalW; 203 } 204 } 205 206 /** 207 * Stretches/Scales/Translates photoBounds to match displayBounds, and 208 * and returns an equivalent stretched/scaled/translated cropBounds or null 209 * if the mapping is invalid. 210 * @param cropBounds cropBounds to transform 211 * @param photoBounds original bounds containing crop bounds 212 * @param displayBounds final bounds for crop 213 * @return the stretched/scaled/translated crop bounds that fit within displayBounds 214 */ 215 public static RectF getScaledCropBounds(RectF cropBounds, RectF photoBounds, 216 RectF displayBounds) { 217 Matrix m = new Matrix(); 218 m.setRectToRect(photoBounds, displayBounds, Matrix.ScaleToFit.FILL); 219 RectF trueCrop = new RectF(cropBounds); 220 if (!m.mapRect(trueCrop)) { 221 return null; 222 } 223 return trueCrop; 224 } 225 226 /** 227 * Returns the size of a bitmap in bytes. 228 * @param bmap bitmap whose size to check 229 * @return bitmap size in bytes 230 */ 231 public static int getBitmapSize(Bitmap bmap) { 232 return bmap.getRowBytes() * bmap.getHeight(); 233 } 234 235 /** 236 * Constrains rotation to be in [0, 90, 180, 270] rounding down. 237 * @param rotation any rotation value, in degrees 238 * @return integer rotation in [0, 90, 180, 270] 239 */ 240 public static int constrainedRotation(float rotation) { 241 int r = (int) ((rotation % 360) / 90); 242 r = (r < 0) ? (r + 4) : r; 243 return r * 90; 244 } 245 246 private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { 247 float dy = rotatedRect[1] - rotatedRect[3]; 248 float dx = rotatedRect[0] - rotatedRect[2]; 249 float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI); 250 Matrix m = new Matrix(); 251 m.setRotate(-angle, center[0], center[1]); 252 float[] unrotatedRect = new float[rotatedRect.length]; 253 m.mapPoints(unrotatedRect, rotatedRect); 254 unrotated.set(trapToRect(unrotatedRect)); 255 return angle; 256 } 257 258 } 259