Home | History | Annotate | Download | only in crop
      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 package com.android.gallery3d.filtershow.crop;
     17 
     18 import android.graphics.Matrix;
     19 import android.graphics.Rect;
     20 import android.graphics.RectF;
     21 
     22 import com.android.gallery3d.filtershow.imageshow.GeometryMathUtils;
     23 
     24 import java.util.Arrays;
     25 
     26 /**
     27  * Maintains invariant that inner rectangle is constrained to be within the
     28  * outer, rotated rectangle.
     29  */
     30 public class BoundedRect {
     31     private float rot;
     32     private RectF outer;
     33     private RectF inner;
     34     private float[] innerRotated;
     35 
     36     public BoundedRect(float rotation, Rect outerRect, Rect innerRect) {
     37         rot = rotation;
     38         outer = new RectF(outerRect);
     39         inner = new RectF(innerRect);
     40         innerRotated = CropMath.getCornersFromRect(inner);
     41         rotateInner();
     42         if (!isConstrained())
     43             reconstrain();
     44     }
     45 
     46     public BoundedRect(float rotation, RectF outerRect, RectF innerRect) {
     47         rot = rotation;
     48         outer = new RectF(outerRect);
     49         inner = new RectF(innerRect);
     50         innerRotated = CropMath.getCornersFromRect(inner);
     51         rotateInner();
     52         if (!isConstrained())
     53             reconstrain();
     54     }
     55 
     56     public void resetTo(float rotation, RectF outerRect, RectF innerRect) {
     57         rot = rotation;
     58         outer.set(outerRect);
     59         inner.set(innerRect);
     60         innerRotated = CropMath.getCornersFromRect(inner);
     61         rotateInner();
     62         if (!isConstrained())
     63             reconstrain();
     64     }
     65 
     66     /**
     67      * Sets inner, and re-constrains it to fit within the rotated bounding rect.
     68      */
     69     public void setInner(RectF newInner) {
     70         if (inner.equals(newInner))
     71             return;
     72         inner = newInner;
     73         innerRotated = CropMath.getCornersFromRect(inner);
     74         rotateInner();
     75         if (!isConstrained())
     76             reconstrain();
     77     }
     78 
     79     /**
     80      * Sets rotation, and re-constrains inner to fit within the rotated bounding rect.
     81      */
     82     public void setRotation(float rotation) {
     83         if (rotation == rot)
     84             return;
     85         rot = rotation;
     86         innerRotated = CropMath.getCornersFromRect(inner);
     87         rotateInner();
     88         if (!isConstrained())
     89             reconstrain();
     90     }
     91 
     92     public void setToInner(RectF r) {
     93         r.set(inner);
     94     }
     95 
     96     public void setToOuter(RectF r) {
     97         r.set(outer);
     98     }
     99 
    100     public RectF getInner() {
    101         return new RectF(inner);
    102     }
    103 
    104     public RectF getOuter() {
    105         return new RectF(outer);
    106     }
    107 
    108     /**
    109      * Tries to move the inner rectangle by (dx, dy).  If this would cause it to leave
    110      * the bounding rectangle, snaps the inner rectangle to the edge of the bounding
    111      * rectangle.
    112      */
    113     public void moveInner(float dx, float dy) {
    114         Matrix m0 = getInverseRotMatrix();
    115 
    116         RectF translatedInner = new RectF(inner);
    117         translatedInner.offset(dx, dy);
    118 
    119         float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner);
    120         float[] outerCorners = CropMath.getCornersFromRect(outer);
    121 
    122         m0.mapPoints(translatedInnerCorners);
    123         float[] correction = {
    124                 0, 0
    125         };
    126 
    127         // find correction vectors for corners that have moved out of bounds
    128         for (int i = 0; i < translatedInnerCorners.length; i += 2) {
    129             float correctedInnerX = translatedInnerCorners[i] + correction[0];
    130             float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
    131             if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
    132                 float[] badCorner = {
    133                         correctedInnerX, correctedInnerY
    134                 };
    135                 float[] nearestSide = CropMath.closestSide(badCorner, outerCorners);
    136                 float[] correctionVec =
    137                         GeometryMathUtils.shortestVectorFromPointToLine(badCorner, nearestSide);
    138                 correction[0] += correctionVec[0];
    139                 correction[1] += correctionVec[1];
    140             }
    141         }
    142 
    143         for (int i = 0; i < translatedInnerCorners.length; i += 2) {
    144             float correctedInnerX = translatedInnerCorners[i] + correction[0];
    145             float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
    146             if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
    147                 float[] correctionVec = {
    148                         correctedInnerX, correctedInnerY
    149                 };
    150                 CropMath.getEdgePoints(outer, correctionVec);
    151                 correctionVec[0] -= correctedInnerX;
    152                 correctionVec[1] -= correctedInnerY;
    153                 correction[0] += correctionVec[0];
    154                 correction[1] += correctionVec[1];
    155             }
    156         }
    157 
    158         // Set correction
    159         for (int i = 0; i < translatedInnerCorners.length; i += 2) {
    160             float correctedInnerX = translatedInnerCorners[i] + correction[0];
    161             float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
    162             // update translated corners with correction vectors
    163             translatedInnerCorners[i] = correctedInnerX;
    164             translatedInnerCorners[i + 1] = correctedInnerY;
    165         }
    166 
    167         innerRotated = translatedInnerCorners;
    168         // reconstrain to update inner
    169         reconstrain();
    170     }
    171 
    172     /**
    173      * Attempts to resize the inner rectangle.  If this would cause it to leave
    174      * the bounding rect, clips the inner rectangle to fit.
    175      */
    176     public void resizeInner(RectF newInner) {
    177         Matrix m = getRotMatrix();
    178         Matrix m0 = getInverseRotMatrix();
    179 
    180         float[] outerCorners = CropMath.getCornersFromRect(outer);
    181         m.mapPoints(outerCorners);
    182         float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
    183         float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
    184         RectF ret = new RectF(newInner);
    185 
    186         for (int i = 0; i < newInnerCorners.length; i += 2) {
    187             float[] c = {
    188                     newInnerCorners[i], newInnerCorners[i + 1]
    189             };
    190             float[] c0 = Arrays.copyOf(c, 2);
    191             m0.mapPoints(c0);
    192             if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
    193                 float[] outerSide = CropMath.closestSide(c, outerCorners);
    194                 float[] pathOfCorner = {
    195                         newInnerCorners[i], newInnerCorners[i + 1],
    196                         oldInnerCorners[i], oldInnerCorners[i + 1]
    197                 };
    198                 float[] p = GeometryMathUtils.lineIntersect(pathOfCorner, outerSide);
    199                 if (p == null) {
    200                     // lines are parallel or not well defined, so don't resize
    201                     p = new float[2];
    202                     p[0] = oldInnerCorners[i];
    203                     p[1] = oldInnerCorners[i + 1];
    204                 }
    205                 // relies on corners being in same order as method
    206                 // getCornersFromRect
    207                 switch (i) {
    208                     case 0:
    209                     case 1:
    210                         ret.left = (p[0] > ret.left) ? p[0] : ret.left;
    211                         ret.top = (p[1] > ret.top) ? p[1] : ret.top;
    212                         break;
    213                     case 2:
    214                     case 3:
    215                         ret.right = (p[0] < ret.right) ? p[0] : ret.right;
    216                         ret.top = (p[1] > ret.top) ? p[1] : ret.top;
    217                         break;
    218                     case 4:
    219                     case 5:
    220                         ret.right = (p[0] < ret.right) ? p[0] : ret.right;
    221                         ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
    222                         break;
    223                     case 6:
    224                     case 7:
    225                         ret.left = (p[0] > ret.left) ? p[0] : ret.left;
    226                         ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
    227                         break;
    228                     default:
    229                         break;
    230                 }
    231             }
    232         }
    233         float[] retCorners = CropMath.getCornersFromRect(ret);
    234         m0.mapPoints(retCorners);
    235         innerRotated = retCorners;
    236         // reconstrain to update inner
    237         reconstrain();
    238     }
    239 
    240     /**
    241      * Attempts to resize the inner rectangle.  If this would cause it to leave
    242      * the bounding rect, clips the inner rectangle to fit while maintaining
    243      * aspect ratio.
    244      */
    245     public void fixedAspectResizeInner(RectF newInner) {
    246         Matrix m = getRotMatrix();
    247         Matrix m0 = getInverseRotMatrix();
    248 
    249         float aspectW = inner.width();
    250         float aspectH = inner.height();
    251         float aspRatio = aspectW / aspectH;
    252         float[] corners = CropMath.getCornersFromRect(outer);
    253 
    254         m.mapPoints(corners);
    255         float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
    256         float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
    257 
    258         // find fixed corner
    259         int fixed = -1;
    260         if (inner.top == newInner.top) {
    261             if (inner.left == newInner.left)
    262                 fixed = 0; // top left
    263             else if (inner.right == newInner.right)
    264                 fixed = 2; // top right
    265         } else if (inner.bottom == newInner.bottom) {
    266             if (inner.right == newInner.right)
    267                 fixed = 4; // bottom right
    268             else if (inner.left == newInner.left)
    269                 fixed = 6; // bottom left
    270         }
    271         // no fixed corner, return without update
    272         if (fixed == -1)
    273             return;
    274         float widthSoFar = newInner.width();
    275         int moved = -1;
    276         for (int i = 0; i < newInnerCorners.length; i += 2) {
    277             float[] c = {
    278                     newInnerCorners[i], newInnerCorners[i + 1]
    279             };
    280             float[] c0 = Arrays.copyOf(c, 2);
    281             m0.mapPoints(c0);
    282             if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
    283                 moved = i;
    284                 if (moved == fixed)
    285                     continue;
    286                 float[] l2 = CropMath.closestSide(c, corners);
    287                 float[] l1 = {
    288                         newInnerCorners[i], newInnerCorners[i + 1],
    289                         oldInnerCorners[i], oldInnerCorners[i + 1]
    290                 };
    291                 float[] p = GeometryMathUtils.lineIntersect(l1, l2);
    292                 if (p == null) {
    293                     // lines are parallel or not well defined, so set to old
    294                     // corner
    295                     p = new float[2];
    296                     p[0] = oldInnerCorners[i];
    297                     p[1] = oldInnerCorners[i + 1];
    298                 }
    299                 // relies on corners being in same order as method
    300                 // getCornersFromRect
    301                 float fixed_x = oldInnerCorners[fixed];
    302                 float fixed_y = oldInnerCorners[fixed + 1];
    303                 float newWidth = Math.abs(fixed_x - p[0]);
    304                 float newHeight = Math.abs(fixed_y - p[1]);
    305                 newWidth = Math.max(newWidth, aspRatio * newHeight);
    306                 if (newWidth < widthSoFar)
    307                     widthSoFar = newWidth;
    308             }
    309         }
    310 
    311         float heightSoFar = widthSoFar / aspRatio;
    312         RectF ret = new RectF(inner);
    313         if (fixed == 0) {
    314             ret.right = ret.left + widthSoFar;
    315             ret.bottom = ret.top + heightSoFar;
    316         } else if (fixed == 2) {
    317             ret.left = ret.right - widthSoFar;
    318             ret.bottom = ret.top + heightSoFar;
    319         } else if (fixed == 4) {
    320             ret.left = ret.right - widthSoFar;
    321             ret.top = ret.bottom - heightSoFar;
    322         } else if (fixed == 6) {
    323             ret.right = ret.left + widthSoFar;
    324             ret.top = ret.bottom - heightSoFar;
    325         }
    326         float[] retCorners = CropMath.getCornersFromRect(ret);
    327         m0.mapPoints(retCorners);
    328         innerRotated = retCorners;
    329         // reconstrain to update inner
    330         reconstrain();
    331     }
    332 
    333     // internal methods
    334 
    335     private boolean isConstrained() {
    336         for (int i = 0; i < 8; i += 2) {
    337             if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1]))
    338                 return false;
    339         }
    340         return true;
    341     }
    342 
    343     private void reconstrain() {
    344         // innerRotated has been changed to have incorrect values
    345         CropMath.getEdgePoints(outer, innerRotated);
    346         Matrix m = getRotMatrix();
    347         float[] unrotated = Arrays.copyOf(innerRotated, 8);
    348         m.mapPoints(unrotated);
    349         inner = CropMath.trapToRect(unrotated);
    350     }
    351 
    352     private void rotateInner() {
    353         Matrix m = getInverseRotMatrix();
    354         m.mapPoints(innerRotated);
    355     }
    356 
    357     private Matrix getRotMatrix() {
    358         Matrix m = new Matrix();
    359         m.setRotate(rot, outer.centerX(), outer.centerY());
    360         return m;
    361     }
    362 
    363     private Matrix getInverseRotMatrix() {
    364         Matrix m = new Matrix();
    365         m.setRotate(-rot, outer.centerX(), outer.centerY());
    366         return m;
    367     }
    368 }
    369