Home | History | Annotate | Download | only in imageshow
      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