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.gallery3d.filtershow.imageshow; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Canvas; 21 import android.graphics.Matrix; 22 import android.graphics.Paint; 23 import android.graphics.Rect; 24 import android.graphics.RectF; 25 import android.util.Log; 26 27 import com.android.gallery3d.filtershow.cache.BitmapCache; 28 import com.android.gallery3d.filtershow.cache.ImageLoader; 29 import com.android.gallery3d.filtershow.filters.FilterCropRepresentation; 30 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation; 31 import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror; 32 import com.android.gallery3d.filtershow.filters.FilterRepresentation; 33 import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation; 34 import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation.Rotation; 35 import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation; 36 import com.android.gallery3d.filtershow.pipeline.ImagePreset; 37 38 import java.util.Collection; 39 import java.util.Iterator; 40 41 public final class GeometryMathUtils { 42 private static final String TAG = "GeometryMathUtils"; 43 public static final float SHOW_SCALE = .9f; 44 GeometryMathUtils()45 private GeometryMathUtils() {}; 46 47 // Holder class for Geometry data. 48 public static final class GeometryHolder { 49 public Rotation rotation = FilterRotateRepresentation.getNil(); 50 public float straighten = FilterStraightenRepresentation.getNil(); 51 public RectF crop = FilterCropRepresentation.getNil(); 52 public Mirror mirror = FilterMirrorRepresentation.getNil(); 53 set(GeometryHolder h)54 public void set(GeometryHolder h) { 55 rotation = h.rotation; 56 straighten = h.straighten; 57 crop.set(h.crop); 58 mirror = h.mirror; 59 } 60 wipe()61 public void wipe() { 62 rotation = FilterRotateRepresentation.getNil(); 63 straighten = FilterStraightenRepresentation.getNil(); 64 crop = FilterCropRepresentation.getNil(); 65 mirror = FilterMirrorRepresentation.getNil(); 66 } 67 isNil()68 public boolean isNil() { 69 return rotation == FilterRotateRepresentation.getNil() && 70 straighten == FilterStraightenRepresentation.getNil() && 71 crop.equals(FilterCropRepresentation.getNil()) && 72 mirror == FilterMirrorRepresentation.getNil(); 73 } 74 75 @Override equals(Object o)76 public boolean equals(Object o) { 77 if (this == o) { 78 return true; 79 } 80 if (!(o instanceof GeometryHolder)) { 81 return false; 82 } 83 GeometryHolder h = (GeometryHolder) o; 84 return rotation == h.rotation && straighten == h.straighten && 85 ((crop == null && h.crop == null) || (crop != null && crop.equals(h.crop))) && 86 mirror == h.mirror; 87 } 88 89 @Override toString()90 public String toString() { 91 return getClass().getSimpleName() + "[" + "rotation:" + rotation.value() 92 + ",straighten:" + straighten + ",crop:" + crop.toString() 93 + ",mirror:" + mirror.value() + "]"; 94 } 95 } 96 97 // Math operations for 2d vectors clamp(float i, float low, float high)98 public static float clamp(float i, float low, float high) { 99 return Math.max(Math.min(i, high), low); 100 } 101 lineIntersect(float[] line1, float[] line2)102 public static float[] lineIntersect(float[] line1, float[] line2) { 103 float a0 = line1[0]; 104 float a1 = line1[1]; 105 float b0 = line1[2]; 106 float b1 = line1[3]; 107 float c0 = line2[0]; 108 float c1 = line2[1]; 109 float d0 = line2[2]; 110 float d1 = line2[3]; 111 float t0 = a0 - b0; 112 float t1 = a1 - b1; 113 float t2 = b0 - d0; 114 float t3 = d1 - b1; 115 float t4 = c0 - d0; 116 float t5 = c1 - d1; 117 118 float denom = t1 * t4 - t0 * t5; 119 if (denom == 0) 120 return null; 121 float u = (t3 * t4 + t5 * t2) / denom; 122 float[] intersect = { 123 b0 + u * t0, b1 + u * t1 124 }; 125 return intersect; 126 } 127 shortestVectorFromPointToLine(float[] point, float[] line)128 public static float[] shortestVectorFromPointToLine(float[] point, float[] line) { 129 float x1 = line[0]; 130 float x2 = line[2]; 131 float y1 = line[1]; 132 float y2 = line[3]; 133 float xdelt = x2 - x1; 134 float ydelt = y2 - y1; 135 if (xdelt == 0 && ydelt == 0) 136 return null; 137 float u = ((point[0] - x1) * xdelt + (point[1] - y1) * ydelt) 138 / (xdelt * xdelt + ydelt * ydelt); 139 float[] ret = { 140 (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1)) 141 }; 142 float[] vec = { 143 ret[0] - point[0], ret[1] - point[1] 144 }; 145 return vec; 146 } 147 148 // A . B dotProduct(float[] a, float[] b)149 public static float dotProduct(float[] a, float[] b) { 150 return a[0] * b[0] + a[1] * b[1]; 151 } 152 normalize(float[] a)153 public static float[] normalize(float[] a) { 154 float length = (float) Math.hypot(a[0], a[1]); 155 float[] b = { 156 a[0] / length, a[1] / length 157 }; 158 return b; 159 } 160 161 // A onto B scalarProjection(float[] a, float[] b)162 public static float scalarProjection(float[] a, float[] b) { 163 float length = (float) Math.hypot(b[0], b[1]); 164 return dotProduct(a, b) / length; 165 } 166 getVectorFromPoints(float[] point1, float[] point2)167 public static float[] getVectorFromPoints(float[] point1, float[] point2) { 168 float[] p = { 169 point2[0] - point1[0], point2[1] - point1[1] 170 }; 171 return p; 172 } 173 getUnitVectorFromPoints(float[] point1, float[] point2)174 public static float[] getUnitVectorFromPoints(float[] point1, float[] point2) { 175 float[] p = { 176 point2[0] - point1[0], point2[1] - point1[1] 177 }; 178 float length = (float) Math.hypot(p[0], p[1]); 179 p[0] = p[0] / length; 180 p[1] = p[1] / length; 181 return p; 182 } 183 scaleRect(RectF r, float scale)184 public static void scaleRect(RectF r, float scale) { 185 r.set(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale); 186 } 187 188 // A - B vectorSubtract(float[] a, float[] b)189 public static float[] vectorSubtract(float[] a, float[] b) { 190 int len = a.length; 191 if (len != b.length) 192 return null; 193 float[] ret = new float[len]; 194 for (int i = 0; i < len; i++) { 195 ret[i] = a[i] - b[i]; 196 } 197 return ret; 198 } 199 vectorLength(float[] a)200 public static float vectorLength(float[] a) { 201 return (float) Math.hypot(a[0], a[1]); 202 } 203 scale(float oldWidth, float oldHeight, float newWidth, float newHeight)204 public static float scale(float oldWidth, float oldHeight, float newWidth, float newHeight) { 205 if (oldHeight == 0 || oldWidth == 0 || (oldWidth == newWidth && oldHeight == newHeight)) { 206 return 1; 207 } 208 return Math.min(newWidth / oldWidth, newHeight / oldHeight); 209 } 210 roundNearest(RectF r)211 public static Rect roundNearest(RectF r) { 212 Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), 213 Math.round(r.bottom)); 214 return q; 215 } 216 concatMirrorMatrix(Matrix m, GeometryHolder holder)217 private static void concatMirrorMatrix(Matrix m, GeometryHolder holder) { 218 Mirror type = holder.mirror; 219 if (type == Mirror.HORIZONTAL) { 220 if (holder.rotation.value() == 90 221 || holder.rotation.value() == 270) { 222 type = Mirror.VERTICAL; 223 } 224 } else if (type == Mirror.VERTICAL) { 225 if (holder.rotation.value() == 90 226 || holder.rotation.value() == 270) { 227 type = Mirror.HORIZONTAL; 228 } 229 } 230 if (type == Mirror.HORIZONTAL) { 231 m.postScale(-1, 1); 232 } else if (type == Mirror.VERTICAL) { 233 m.postScale(1, -1); 234 } else if (type == Mirror.BOTH) { 235 m.postScale(1, -1); 236 m.postScale(-1, 1); 237 } 238 } 239 getRotationForOrientation(int orientation)240 private static int getRotationForOrientation(int orientation) { 241 switch (orientation) { 242 case ImageLoader.ORI_ROTATE_90: 243 return 90; 244 case ImageLoader.ORI_ROTATE_180: 245 return 180; 246 case ImageLoader.ORI_ROTATE_270: 247 return 270; 248 default: 249 return 0; 250 } 251 } 252 unpackGeometry(Collection<FilterRepresentation> geometry)253 public static GeometryHolder unpackGeometry(Collection<FilterRepresentation> geometry) { 254 GeometryHolder holder = new GeometryHolder(); 255 unpackGeometry(holder, geometry); 256 return holder; 257 } 258 unpackGeometry(GeometryHolder out, Collection<FilterRepresentation> geometry)259 public static void unpackGeometry(GeometryHolder out, 260 Collection<FilterRepresentation> geometry) { 261 out.wipe(); 262 // Get geometry data from filters 263 for (FilterRepresentation r : geometry) { 264 if (r.isNil()) { 265 continue; 266 } 267 if (r.getSerializationName() == FilterRotateRepresentation.SERIALIZATION_NAME) { 268 out.rotation = ((FilterRotateRepresentation) r).getRotation(); 269 } else if (r.getSerializationName() == 270 FilterStraightenRepresentation.SERIALIZATION_NAME) { 271 out.straighten = ((FilterStraightenRepresentation) r).getStraighten(); 272 } else if (r.getSerializationName() == FilterCropRepresentation.SERIALIZATION_NAME) { 273 ((FilterCropRepresentation) r).getCrop(out.crop); 274 } else if (r.getSerializationName() == FilterMirrorRepresentation.SERIALIZATION_NAME) { 275 out.mirror = ((FilterMirrorRepresentation) r).getMirror(); 276 } 277 } 278 } 279 replaceInstances(Collection<FilterRepresentation> geometry, FilterRepresentation rep)280 public static void replaceInstances(Collection<FilterRepresentation> geometry, 281 FilterRepresentation rep) { 282 Iterator<FilterRepresentation> iter = geometry.iterator(); 283 while (iter.hasNext()) { 284 FilterRepresentation r = iter.next(); 285 if (ImagePreset.sameSerializationName(rep, r)) { 286 iter.remove(); 287 } 288 } 289 if (!rep.isNil()) { 290 geometry.add(rep); 291 } 292 } 293 initializeHolder(GeometryHolder outHolder, FilterRepresentation currentLocal)294 public static void initializeHolder(GeometryHolder outHolder, 295 FilterRepresentation currentLocal) { 296 Collection<FilterRepresentation> geometry = PrimaryImage.getImage().getPreset() 297 .getGeometryFilters(); 298 replaceInstances(geometry, currentLocal); 299 unpackGeometry(outHolder, geometry); 300 } 301 finalGeometryRect(int width, int height, Collection<FilterRepresentation> geometry)302 public static Rect finalGeometryRect(int width, int height, 303 Collection<FilterRepresentation> geometry) { 304 GeometryHolder holder = unpackGeometry(geometry); 305 RectF crop = getTrueCropRect(holder, width, height); 306 Rect frame = new Rect(); 307 crop.roundOut(frame); 308 return frame; 309 } 310 applyFullGeometryMatrix(Bitmap image, GeometryHolder holder)311 private static Bitmap applyFullGeometryMatrix(Bitmap image, GeometryHolder holder) { 312 int width = image.getWidth(); 313 int height = image.getHeight(); 314 RectF crop = getTrueCropRect(holder, width, height); 315 Rect frame = new Rect(); 316 crop.roundOut(frame); 317 Matrix m = getCropSelectionToScreenMatrix(null, holder, width, height, frame.width(), 318 frame.height()); 319 BitmapCache bitmapCache = PrimaryImage.getImage().getBitmapCache(); 320 Bitmap temp = bitmapCache.getBitmap(frame.width(), 321 frame.height(), BitmapCache.UTIL_GEOMETRY); 322 Canvas canvas = new Canvas(temp); 323 Paint paint = new Paint(); 324 paint.setAntiAlias(true); 325 paint.setFilterBitmap(true); 326 paint.setDither(true); 327 canvas.drawBitmap(image, m, paint); 328 return temp; 329 } 330 getImageToScreenMatrix(Collection<FilterRepresentation> geometry, boolean reflectRotation, Rect bmapDimens, float viewWidth, float viewHeight)331 public static Matrix getImageToScreenMatrix(Collection<FilterRepresentation> geometry, 332 boolean reflectRotation, Rect bmapDimens, float viewWidth, float viewHeight) { 333 GeometryHolder h = unpackGeometry(geometry); 334 return GeometryMathUtils.getOriginalToScreen(h, reflectRotation, bmapDimens.width(), 335 bmapDimens.height(), viewWidth, viewHeight); 336 } 337 getPartialToScreenMatrix(Collection<FilterRepresentation> geometry, Rect originalBounds, float w, float h, float pw, float ph)338 public static Matrix getPartialToScreenMatrix(Collection<FilterRepresentation> geometry, 339 Rect originalBounds, float w, float h, 340 float pw, float ph) { 341 GeometryHolder holder = unpackGeometry(geometry); 342 RectF rCrop = new RectF(0, 0, originalBounds.width(), originalBounds.height()); 343 float angle = holder.straighten; 344 int rotation = holder.rotation.value(); 345 346 ImageStraighten.getUntranslatedStraightenCropBounds(rCrop, angle); 347 float dx = (w - pw) / 2f; 348 float dy = (h - ph) / 2f; 349 Matrix compensation = new Matrix(); 350 compensation.postTranslate(dx, dy); 351 float cScale = originalBounds.width() / rCrop.width(); 352 if (rCrop.width() < rCrop.height()) { 353 cScale = originalBounds.height() / rCrop.height(); 354 } 355 float scale = w / pw; 356 if (w < h) { 357 scale = h / ph; 358 } 359 scale = scale * cScale; 360 float cx = w / 2f; 361 float cy = h / 2f; 362 363 compensation.postScale(scale, scale, cx, cy); 364 compensation.postRotate(angle, cx, cy); 365 compensation.postRotate(rotation, cx, cy); 366 compensation.postTranslate(-cx, -cy); 367 concatMirrorMatrix(compensation, holder); 368 compensation.postTranslate(cx, cy); 369 return compensation; 370 } 371 getOriginalToScreen(GeometryHolder holder, boolean rotate, float originalWidth, float originalHeight, float viewWidth, float viewHeight)372 public static Matrix getOriginalToScreen(GeometryHolder holder, boolean rotate, 373 float originalWidth, 374 float originalHeight, float viewWidth, float viewHeight) { 375 int orientation = PrimaryImage.getImage().getZoomOrientation(); 376 int rotation = getRotationForOrientation(orientation); 377 Rotation prev = holder.rotation; 378 rotation = (rotation + prev.value()) % 360; 379 holder.rotation = Rotation.fromValue(rotation); 380 Matrix m = getCropSelectionToScreenMatrix(null, holder, (int) originalWidth, 381 (int) originalHeight, (int) viewWidth, (int) viewHeight); 382 holder.rotation = prev; 383 return m; 384 } 385 applyGeometryRepresentations(Collection<FilterRepresentation> res, Bitmap image)386 public static Bitmap applyGeometryRepresentations(Collection<FilterRepresentation> res, 387 Bitmap image) { 388 GeometryHolder holder = unpackGeometry(res); 389 Bitmap bmap = image; 390 // If there are geometry changes, apply them to the image 391 if (!holder.isNil()) { 392 bmap = applyFullGeometryMatrix(bmap, holder); 393 if (bmap != image) { 394 BitmapCache cache = PrimaryImage.getImage().getBitmapCache(); 395 cache.cache(image); 396 } 397 } 398 return bmap; 399 } 400 drawTransformedCropped(GeometryHolder holder, Canvas canvas, Bitmap photo, int viewWidth, int viewHeight)401 public static RectF drawTransformedCropped(GeometryHolder holder, Canvas canvas, 402 Bitmap photo, int viewWidth, int viewHeight) { 403 if (photo == null) { 404 return null; 405 } 406 RectF crop = new RectF(); 407 Matrix m = getCropSelectionToScreenMatrix(crop, holder, photo.getWidth(), photo.getHeight(), 408 viewWidth, viewHeight); 409 canvas.save(); 410 canvas.clipRect(crop); 411 Paint p = new Paint(); 412 p.setAntiAlias(true); 413 canvas.drawBitmap(photo, m, p); 414 canvas.restore(); 415 return crop; 416 } 417 needsDimensionSwap(Rotation rotation)418 public static boolean needsDimensionSwap(Rotation rotation) { 419 switch (rotation) { 420 case NINETY: 421 case TWO_SEVENTY: 422 return true; 423 default: 424 return false; 425 } 426 } 427 428 // Gives matrix for rotated, straightened, mirrored bitmap centered at 0,0. getFullGeometryMatrix(GeometryHolder holder, int bitmapWidth, int bitmapHeight)429 private static Matrix getFullGeometryMatrix(GeometryHolder holder, int bitmapWidth, 430 int bitmapHeight) { 431 float centerX = bitmapWidth / 2f; 432 float centerY = bitmapHeight / 2f; 433 Matrix m = new Matrix(); 434 m.setTranslate(-centerX, -centerY); 435 m.postRotate(holder.straighten + holder.rotation.value()); 436 concatMirrorMatrix(m, holder); 437 return m; 438 } 439 getFullGeometryToScreenMatrix(GeometryHolder holder, int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight)440 public static Matrix getFullGeometryToScreenMatrix(GeometryHolder holder, int bitmapWidth, 441 int bitmapHeight, int viewWidth, int viewHeight) { 442 int bh = bitmapHeight; 443 int bw = bitmapWidth; 444 if (GeometryMathUtils.needsDimensionSwap(holder.rotation)) { 445 bh = bitmapWidth; 446 bw = bitmapHeight; 447 } 448 float scale = GeometryMathUtils.scale(bw, bh, viewWidth, viewHeight); 449 scale *= SHOW_SCALE; 450 float s = Math.min(viewWidth / (float) bitmapWidth, viewHeight / (float) bitmapHeight); 451 Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight); 452 m.postScale(scale, scale); 453 m.postTranslate(viewWidth / 2f, viewHeight / 2f); 454 return m; 455 } 456 getTrueCropRect(GeometryHolder holder, int bitmapWidth, int bitmapHeight)457 public static RectF getTrueCropRect(GeometryHolder holder, int bitmapWidth, int bitmapHeight) { 458 RectF r = new RectF(holder.crop); 459 FilterCropRepresentation.findScaledCrop(r, bitmapWidth, bitmapHeight); 460 float s = holder.straighten; 461 holder.straighten = 0; 462 Matrix m1 = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight); 463 holder.straighten = s; 464 m1.mapRect(r); 465 return r; 466 } 467 getCropSelectionToScreenMatrix(RectF outCrop, GeometryHolder holder, int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight)468 public static Matrix getCropSelectionToScreenMatrix(RectF outCrop, GeometryHolder holder, 469 int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight) { 470 Matrix m = getFullGeometryMatrix(holder, bitmapWidth, bitmapHeight); 471 RectF crop = getTrueCropRect(holder, bitmapWidth, bitmapHeight); 472 float scale = GeometryMathUtils.scale(crop.width(), crop.height(), viewWidth, viewHeight); 473 m.postScale(scale, scale); 474 GeometryMathUtils.scaleRect(crop, scale); 475 m.postTranslate(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY()); 476 if (outCrop != null) { 477 crop.offset(viewWidth / 2f - crop.centerX(), viewHeight / 2f - crop.centerY()); 478 outCrop.set(crop); 479 } 480 return m; 481 } 482 getCropSelectionToScreenMatrix(RectF outCrop, Collection<FilterRepresentation> res, int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight)483 public static Matrix getCropSelectionToScreenMatrix(RectF outCrop, 484 Collection<FilterRepresentation> res, int bitmapWidth, int bitmapHeight, int viewWidth, 485 int viewHeight) { 486 GeometryHolder holder = unpackGeometry(res); 487 return getCropSelectionToScreenMatrix(outCrop, holder, bitmapWidth, bitmapHeight, 488 viewWidth, viewHeight); 489 } 490 } 491