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