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