1 /*
2  * Copyright (C) 2015 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.camera.util;
18 
19 import android.graphics.Rect;
20 
21 import com.google.common.base.Objects;
22 
23 import java.math.BigInteger;
24 
25 import javax.annotation.ParametersAreNonnullByDefault;
26 
27 /**
28  * Contains precise (integer) logic for handling aspect ratios as rational
29  * numbers.
30  */
31 @ParametersAreNonnullByDefault
32 public final class AspectRatio {
33     private static final AspectRatio ASPECT_RATIO_4x3 = AspectRatio.of(4, 3);
34     private static final AspectRatio ASPECT_RATIO_16x9 = AspectRatio.of(16, 9);
35 
36     private final int mWidth;
37     private final int mHeight;
38 
39     /**
40      * @param width The width of the aspect ratio, after simplification.
41      * @param height The height of the aspect ratio, after simplification.
42      */
AspectRatio(int width, int height)43     private AspectRatio(int width, int height) {
44         mWidth = width;
45         mHeight = height;
46     }
47 
of(int width, int height)48     public static AspectRatio of(int width, int height) {
49         int gcd = BigInteger.valueOf(width).gcd(BigInteger.valueOf(height)).intValue();
50         int simplifiedWidth = width / gcd;
51         int simplifiedHeight = height / gcd;
52         return new AspectRatio(simplifiedWidth, simplifiedHeight);
53     }
54 
of(Size size)55     public static AspectRatio of(Size size) {
56         return of(size.width(), size.height());
57     }
58 
of4x3()59     public static AspectRatio of4x3() {
60         return ASPECT_RATIO_4x3;
61     }
62 
of16x9()63     public static AspectRatio of16x9() {
64         return ASPECT_RATIO_16x9;
65     }
66 
getHeight()67     public int getHeight() {
68         return mHeight;
69     }
70 
getWidth()71     public int getWidth() {
72         return mWidth;
73     }
74 
toFloat()75     public float toFloat() {
76         return (float) mWidth / (float) mHeight;
77     }
78 
79     @Override
equals(Object o)80     public boolean equals(Object o) {
81         if (this == o)
82             return true;
83         if (!(o instanceof AspectRatio))
84             return false;
85 
86         AspectRatio that = (AspectRatio) o;
87 
88         if (mHeight != that.mHeight)
89             return false;
90         if (mWidth != that.mWidth)
91             return false;
92 
93         return true;
94     }
95 
96     @Override
hashCode()97     public int hashCode() {
98         return Objects.hashCode(mWidth, mHeight);
99     }
100 
101     @Override
toString()102     public String toString() {
103         return String.format("AspectRatio[%d:%d]", getWidth(), getHeight());
104     }
105 
106     /**
107      * @return The transpose of this aspect ratio.
108      */
transpose()109     public AspectRatio transpose() {
110         return of(mHeight, mWidth);
111     }
112 
113     /**
114      * @return The landscape version of this aspect ratio.
115      */
asLandscape()116     public AspectRatio asLandscape() {
117         if (isLandscape()) {
118             return this;
119         } else {
120             return transpose();
121         }
122     }
123 
124     /**
125      * @return The portrait version of this aspect ratio.
126      */
asPortrait()127     public AspectRatio asPortrait() {
128         if (isPortrait()) {
129             return this;
130         } else {
131             return transpose();
132         }
133     }
134 
135     /**
136      * @return The version of this aspect ratio in the same orientation
137      *         (portrait vs. landscape) of the other.
138      */
withOrientationOf(AspectRatio other)139     public AspectRatio withOrientationOf(AspectRatio other) {
140         if (other.isPortrait()) {
141             return asPortrait();
142         } else {
143             return asLandscape();
144         }
145     }
146 
147     /**
148      * @return True if this aspect ratio is wider than the other.
149      */
isWiderThan(AspectRatio other)150     public boolean isWiderThan(AspectRatio other) {
151         // this.mWidth other.mWidth
152         // ----------- > ------------
153         // this.mHeight other.mHeight
154         return this.mWidth * other.mHeight > other.mWidth * this.mHeight;
155     }
156 
157     /**
158      * @return True if this aspect ratio is taller than the other.
159      */
isTallerThan(AspectRatio other)160     public boolean isTallerThan(AspectRatio other) {
161         // this.mWidth other.mWidth
162         // ----------- < ------------
163         // this.mHeight other.mHeight
164         return this.mWidth * other.mHeight < other.mWidth * this.mHeight;
165     }
166 
167     /**
168      * @return The largest centered region of area with this aspect ratio. For
169      *         non-integer values, the returned rectangle coordinates are the
170      *         *floor* of the result.
171      */
getLargestCenterCrop(Size area)172     public Rect getLargestCenterCrop(Size area) {
173         AspectRatio original = of(area);
174 
175         if (this.isWiderThan(original)) {
176             // Crop off the top and bottom...
177             int cropHeight = area.width() * mHeight / mWidth;
178             int cropTop = (area.height() - cropHeight) / 2;
179             int cropBottom = cropTop + cropHeight;
180             int cropLeft = 0;
181             int cropRight = area.width();
182             return new Rect(cropLeft, cropTop, cropRight, cropBottom);
183         } else {
184             // Crop off the left and right...
185             int cropWidth = area.height() * mWidth / mHeight;
186             int cropLeft = (area.width() - cropWidth) / 2;
187             int cropRight = cropLeft + cropWidth;
188             int cropTop = 0;
189             int cropBottom = area.height();
190             return new Rect(cropLeft, cropTop, cropRight, cropBottom);
191         }
192     }
193 
194     /**
195      * @return True if this aspect ratio is in landscape orientation. Square
196      *         aspect ratios are both portrait *and* landscape.
197      */
isLandscape()198     private boolean isLandscape() {
199         return mWidth >= mHeight;
200     }
201 
202     /**
203      * @return True if this aspect ratio is in portrait orientation. Square
204      *         aspect ratios are both portrait *and* landscape.
205      */
isPortrait()206     private boolean isPortrait() {
207         return mWidth <= mHeight;
208     }
209 
210 }
211