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.graphics.Bitmap; 20import android.graphics.Matrix; 21import android.graphics.Rect; 22import android.graphics.RectF; 23 24import com.android.gallery3d.filtershow.cache.ImageLoader; 25import com.android.gallery3d.filtershow.filters.ImageFilterGeometry; 26 27public class GeometryMetadata { 28 // Applied in order: rotate, crop, scale. 29 // Do not scale saved image (presumably?). 30 private static final ImageFilterGeometry mImageFilter = new ImageFilterGeometry(); 31 private static final String LOGTAG = "GeometryMetadata"; 32 private float mScaleFactor = 1.0f; 33 private float mRotation = 0; 34 private float mStraightenRotation = 0; 35 private final RectF mCropBounds = new RectF(); 36 private final RectF mPhotoBounds = new RectF(); 37 private FLIP mFlip = FLIP.NONE; 38 39 private RectF mBounds = new RectF(); 40 41 public enum FLIP { 42 NONE, VERTICAL, HORIZONTAL, BOTH 43 } 44 45 public GeometryMetadata() { 46 } 47 48 public GeometryMetadata(GeometryMetadata g) { 49 set(g); 50 } 51 52 public boolean hasModifications() { 53 if (mScaleFactor != 1.0f) { 54 return true; 55 } 56 if (mRotation != 0) { 57 return true; 58 } 59 if (mStraightenRotation != 0) { 60 return true; 61 } 62 Rect cropBounds = GeometryMath.roundNearest(mCropBounds); 63 Rect photoBounds = GeometryMath.roundNearest(mPhotoBounds); 64 if (!cropBounds.equals(photoBounds)) { 65 return true; 66 } 67 if (!mFlip.equals(FLIP.NONE)) { 68 return true; 69 } 70 return false; 71 } 72 73 public Bitmap apply(Bitmap original, float scaleFactor, boolean highQuality) { 74 if (!hasModifications()) { 75 return original; 76 } 77 mImageFilter.setGeometryMetadata(this); 78 Bitmap m = mImageFilter.apply(original, scaleFactor, highQuality); 79 return m; 80 } 81 82 public void set(GeometryMetadata g) { 83 mScaleFactor = g.mScaleFactor; 84 mRotation = g.mRotation; 85 mStraightenRotation = g.mStraightenRotation; 86 mCropBounds.set(g.mCropBounds); 87 mPhotoBounds.set(g.mPhotoBounds); 88 mFlip = g.mFlip; 89 mBounds = g.mBounds; 90 } 91 92 public float getScaleFactor() { 93 return mScaleFactor; 94 } 95 96 public float getRotation() { 97 return mRotation; 98 } 99 100 public float getStraightenRotation() { 101 return mStraightenRotation; 102 } 103 104 public RectF getPreviewCropBounds() { 105 return new RectF(mCropBounds); 106 } 107 108 public RectF getCropBounds(Bitmap bitmap) { 109 float scale = 1.0f; 110 scale = GeometryMath.scale(mPhotoBounds.width(), mPhotoBounds.height(), bitmap.getWidth(), 111 bitmap.getHeight()); 112 return new RectF(mCropBounds.left * scale, mCropBounds.top * scale, 113 mCropBounds.right * scale, mCropBounds.bottom * scale); 114 } 115 116 public FLIP getFlipType() { 117 return mFlip; 118 } 119 120 public RectF getPhotoBounds() { 121 return new RectF(mPhotoBounds); 122 } 123 124 public void setScaleFactor(float scale) { 125 mScaleFactor = scale; 126 } 127 128 public void setFlipType(FLIP flip) { 129 mFlip = flip; 130 } 131 132 public void setRotation(float rotation) { 133 mRotation = rotation; 134 } 135 136 public void setStraightenRotation(float straighten) { 137 mStraightenRotation = straighten; 138 } 139 140 public void setCropBounds(RectF newCropBounds) { 141 mCropBounds.set(newCropBounds); 142 } 143 144 public void setPhotoBounds(RectF newPhotoBounds) { 145 mPhotoBounds.set(newPhotoBounds); 146 } 147 148 public boolean cropFitsInPhoto(RectF cropBounds) { 149 return mPhotoBounds.contains(cropBounds); 150 } 151 152 @Override 153 public boolean equals(Object o) { 154 if (this == o) 155 return true; 156 if (o == null || getClass() != o.getClass()) 157 return false; 158 159 GeometryMetadata d = (GeometryMetadata) o; 160 return (mScaleFactor == d.mScaleFactor && 161 mRotation == d.mRotation && 162 mStraightenRotation == d.mStraightenRotation && 163 mFlip == d.mFlip && 164 mCropBounds.equals(d.mCropBounds) && mPhotoBounds.equals(d.mPhotoBounds)); 165 } 166 167 @Override 168 public int hashCode() { 169 int result = 23; 170 result = 31 * result + Float.floatToIntBits(mRotation); 171 result = 31 * result + Float.floatToIntBits(mStraightenRotation); 172 result = 31 * result + Float.floatToIntBits(mScaleFactor); 173 result = 31 * result + mFlip.hashCode(); 174 result = 31 * result + mCropBounds.hashCode(); 175 result = 31 * result + mPhotoBounds.hashCode(); 176 return result; 177 } 178 179 @Override 180 public String toString() { 181 return getClass().getName() + "[" + "scale=" + mScaleFactor 182 + ",rotation=" + mRotation + ",flip=" + mFlip + ",straighten=" 183 + mStraightenRotation + ",cropRect=" + mCropBounds.toShortString() 184 + ",photoRect=" + mPhotoBounds.toShortString() + "]"; 185 } 186 187 // TODO: refactor away 188 protected static Matrix getHorizontalMatrix(float width) { 189 Matrix flipHorizontalMatrix = new Matrix(); 190 flipHorizontalMatrix.setScale(-1, 1); 191 flipHorizontalMatrix.postTranslate(width, 0); 192 return flipHorizontalMatrix; 193 } 194 195 protected static void concatHorizontalMatrix(Matrix m, float width) { 196 m.postScale(-1, 1); 197 m.postTranslate(width, 0); 198 } 199 200 // TODO: refactor away 201 protected static Matrix getVerticalMatrix(float height) { 202 Matrix flipVerticalMatrix = new Matrix(); 203 flipVerticalMatrix.setScale(1, -1); 204 flipVerticalMatrix.postTranslate(0, height); 205 return flipVerticalMatrix; 206 } 207 208 protected static void concatVerticalMatrix(Matrix m, float height) { 209 m.postScale(1, -1); 210 m.postTranslate(0, height); 211 } 212 213 // TODO: refactor away 214 public static Matrix getFlipMatrix(float width, float height, FLIP type) { 215 if (type == FLIP.HORIZONTAL) { 216 return getHorizontalMatrix(width); 217 } else if (type == FLIP.VERTICAL) { 218 return getVerticalMatrix(height); 219 } else if (type == FLIP.BOTH) { 220 Matrix flipper = getVerticalMatrix(height); 221 flipper.postConcat(getHorizontalMatrix(width)); 222 return flipper; 223 } else { 224 Matrix m = new Matrix(); 225 m.reset(); // identity 226 return m; 227 } 228 } 229 230 public static void concatMirrorMatrix(Matrix m, float width, float height, FLIP type) { 231 if (type == FLIP.HORIZONTAL) { 232 concatHorizontalMatrix(m, width); 233 } else if (type == FLIP.VERTICAL) { 234 concatVerticalMatrix(m, height); 235 } else if (type == FLIP.BOTH) { 236 concatVerticalMatrix(m, height); 237 concatHorizontalMatrix(m, width); 238 } 239 } 240 241 public Matrix getMatrixOriginalOrientation(int orientation, float originalWidth, 242 float originalHeight) { 243 Matrix imageRotation = new Matrix(); 244 switch (orientation) { 245 case ImageLoader.ORI_ROTATE_90: { 246 imageRotation.setRotate(90, originalWidth / 2f, originalHeight / 2f); 247 imageRotation.postTranslate(-(originalWidth - originalHeight) / 2f, 248 -(originalHeight - originalWidth) / 2f); 249 break; 250 } 251 case ImageLoader.ORI_ROTATE_180: { 252 imageRotation.setRotate(180, originalWidth / 2f, originalHeight / 2f); 253 break; 254 } 255 case ImageLoader.ORI_ROTATE_270: { 256 imageRotation.setRotate(270, originalWidth / 2f, originalHeight / 2f); 257 imageRotation.postTranslate(-(originalWidth - originalHeight) / 2f, 258 -(originalHeight - originalWidth) / 2f); 259 break; 260 } 261 case ImageLoader.ORI_FLIP_HOR: { 262 imageRotation.preScale(-1, 1); 263 break; 264 } 265 case ImageLoader.ORI_FLIP_VERT: { 266 imageRotation.preScale(1, -1); 267 break; 268 } 269 case ImageLoader.ORI_TRANSPOSE: { 270 imageRotation.setRotate(90, originalWidth / 2f, originalHeight / 2f); 271 imageRotation.postTranslate(-(originalWidth - originalHeight) / 2f, 272 -(originalHeight - originalWidth) / 2f); 273 imageRotation.preScale(1, -1); 274 break; 275 } 276 case ImageLoader.ORI_TRANSVERSE: { 277 imageRotation.setRotate(270, originalWidth / 2f, originalHeight / 2f); 278 imageRotation.postTranslate(-(originalWidth - originalHeight) / 2f, 279 -(originalHeight - originalWidth) / 2f); 280 imageRotation.preScale(1, -1); 281 break; 282 } 283 } 284 return imageRotation; 285 } 286 287 public Matrix getOriginalToScreen(boolean rotate, float originalWidth, float originalHeight, 288 float viewWidth, float viewHeight) { 289 RectF photoBounds = getPhotoBounds(); 290 RectF cropBounds = getPreviewCropBounds(); 291 float imageWidth = cropBounds.width(); 292 float imageHeight = cropBounds.height(); 293 294 int orientation = ImageLoader.getZoomOrientation(); 295 Matrix imageRotation = getMatrixOriginalOrientation(orientation, originalWidth, 296 originalHeight); 297 if (orientation == ImageLoader.ORI_ROTATE_90 || 298 orientation == ImageLoader.ORI_ROTATE_270 || 299 orientation == ImageLoader.ORI_TRANSPOSE || 300 orientation == ImageLoader.ORI_TRANSVERSE) { 301 float tmp = originalWidth; 302 originalWidth = originalHeight; 303 originalHeight = tmp; 304 } 305 306 float preScale = GeometryMath.scale(originalWidth, originalHeight, 307 photoBounds.width(), photoBounds.height()); 308 float scale = GeometryMath.scale(imageWidth, imageHeight, viewWidth, viewHeight); 309 // checks if local rotation is an odd multiple of 90. 310 if (((int) (getRotation() / 90)) % 2 != 0) { 311 scale = GeometryMath.scale(imageWidth, imageHeight, viewHeight, viewWidth); 312 } 313 // put in screen coordinates 314 RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale); 315 RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale); 316 float[] displayCenter = { 317 viewWidth / 2f, viewHeight / 2f 318 }; 319 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop, 320 getRotation(), getStraightenRotation(), getFlipType(), displayCenter); 321 float[] cropCenter = { 322 scaledCrop.centerX(), scaledCrop.centerY() 323 }; 324 m1.mapPoints(cropCenter); 325 GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter); 326 m1.preRotate(getStraightenRotation(), scaledPhoto.centerX(), scaledPhoto.centerY()); 327 m1.preScale(scale, scale); 328 m1.preScale(preScale, preScale); 329 m1.preConcat(imageRotation); 330 331 return m1; 332 } 333 334 // TODO: refactor away 335 public Matrix getFlipMatrix(float width, float height) { 336 FLIP type = getFlipType(); 337 return getFlipMatrix(width, height, type); 338 } 339 340 public boolean hasSwitchedWidthHeight() { 341 return (((int) (mRotation / 90)) % 2) != 0; 342 } 343 344 // TODO: refactor away 345 public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, 346 float rotation) { 347 float dx0 = width / 2; 348 float dy0 = height / 2; 349 Matrix m = getFlipMatrix(width, height); 350 m.postTranslate(-dx0, -dy0); 351 m.postRotate(rotation); 352 m.postScale(scaling, scaling); 353 m.postTranslate(dx, dy); 354 return m; 355 } 356 357 // TODO: refactor away 358 public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, 359 boolean onlyRotate) { 360 float rot = mRotation; 361 if (!onlyRotate) { 362 rot += mStraightenRotation; 363 } 364 return buildGeometryMatrix(width, height, scaling, dx, dy, rot); 365 } 366 367 // TODO: refactor away 368 public Matrix buildGeometryUIMatrix(float scaling, float dx, float dy) { 369 float w = mPhotoBounds.width(); 370 float h = mPhotoBounds.height(); 371 return buildGeometryMatrix(w, h, scaling, dx, dy, false); 372 } 373 374 public static Matrix buildPhotoMatrix(RectF photo, RectF crop, float rotation, 375 float straighten, FLIP type) { 376 Matrix m = new Matrix(); 377 m.setRotate(straighten, photo.centerX(), photo.centerY()); 378 concatMirrorMatrix(m, photo.right, photo.bottom, type); 379 m.postRotate(rotation, crop.centerX(), crop.centerY()); 380 381 return m; 382 } 383 384 public static Matrix buildCropMatrix(RectF crop, float rotation) { 385 Matrix m = new Matrix(); 386 m.setRotate(rotation, crop.centerX(), crop.centerY()); 387 return m; 388 } 389 390 public static void concatRecenterMatrix(Matrix m, float[] currentCenter, float[] newCenter) { 391 m.postTranslate(newCenter[0] - currentCenter[0], newCenter[1] - currentCenter[1]); 392 } 393 394 /** 395 * Builds a matrix to transform a bitmap of width bmWidth and height 396 * bmHeight so that the region of the bitmap being cropped to is oriented 397 * and centered at displayCenter. 398 * 399 * @param bmWidth 400 * @param bmHeight 401 * @param displayCenter 402 * @return 403 */ 404 public Matrix buildTotalXform(float bmWidth, float bmHeight, float[] displayCenter) { 405 RectF rp = getPhotoBounds(); 406 RectF rc = getPreviewCropBounds(); 407 408 float scale = GeometryMath.scale(rp.width(), rp.height(), bmWidth, bmHeight); 409 RectF scaledCrop = GeometryMath.scaleRect(rc, scale); 410 RectF scaledPhoto = GeometryMath.scaleRect(rp, scale); 411 412 Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop, 413 getRotation(), getStraightenRotation(), 414 getFlipType(), displayCenter); 415 float[] cropCenter = { 416 scaledCrop.centerX(), scaledCrop.centerY() 417 }; 418 m1.mapPoints(cropCenter); 419 420 GeometryMetadata.concatRecenterMatrix(m1, cropCenter, displayCenter); 421 m1.preRotate(getStraightenRotation(), scaledPhoto.centerX(), 422 scaledPhoto.centerY()); 423 return m1; 424 } 425 426 /** 427 * Builds a matrix that rotates photo rect about it's center by the 428 * straighten angle, mirrors it about the crop center, and rotates it about 429 * the crop center by the rotation angle, and re-centers the photo rect. 430 * 431 * @param photo 432 * @param crop 433 * @param rotation 434 * @param straighten 435 * @param type 436 * @param newCenter 437 * @return 438 */ 439 public static Matrix buildCenteredPhotoMatrix(RectF photo, RectF crop, float rotation, 440 float straighten, FLIP type, float[] newCenter) { 441 Matrix m = buildPhotoMatrix(photo, crop, rotation, straighten, type); 442 float[] center = { 443 photo.centerX(), photo.centerY() 444 }; 445 m.mapPoints(center); 446 concatRecenterMatrix(m, center, newCenter); 447 return m; 448 } 449 450 /** 451 * Builds a matrix that rotates a crop rect about it's center by rotation 452 * angle, then re-centers the crop rect. 453 * 454 * @param crop 455 * @param rotation 456 * @param newCenter 457 * @return 458 */ 459 public static Matrix buildCenteredCropMatrix(RectF crop, float rotation, float[] newCenter) { 460 Matrix m = buildCropMatrix(crop, rotation); 461 float[] center = { 462 crop.centerX(), crop.centerY() 463 }; 464 m.mapPoints(center); 465 concatRecenterMatrix(m, center, newCenter); 466 return m; 467 } 468 469 /** 470 * Builds a matrix that transforms the crop rect to its view coordinates 471 * inside the photo rect. 472 * 473 * @param photo 474 * @param crop 475 * @param rotation 476 * @param straighten 477 * @param type 478 * @param newCenter 479 * @return 480 */ 481 public static Matrix buildWanderingCropMatrix(RectF photo, RectF crop, float rotation, 482 float straighten, FLIP type, float[] newCenter) { 483 Matrix m = buildCenteredPhotoMatrix(photo, crop, rotation, straighten, type, newCenter); 484 m.preRotate(-straighten, photo.centerX(), photo.centerY()); 485 return m; 486 } 487} 488