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
17package com.android.camera.util;
18
19import android.graphics.Rect;
20
21import com.google.common.base.Objects;
22
23import java.math.BigInteger;
24
25import javax.annotation.ParametersAreNonnullByDefault;
26
27/**
28 * Contains precise (integer) logic for handling aspect ratios as rational
29 * numbers.
30 */
31@ParametersAreNonnullByDefault
32public 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     */
43    private AspectRatio(int width, int height) {
44        mWidth = width;
45        mHeight = height;
46    }
47
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
55    public static AspectRatio of(Size size) {
56        return of(size.width(), size.height());
57    }
58
59    public static AspectRatio of4x3() {
60        return ASPECT_RATIO_4x3;
61    }
62
63    public static AspectRatio of16x9() {
64        return ASPECT_RATIO_16x9;
65    }
66
67    public int getHeight() {
68        return mHeight;
69    }
70
71    public int getWidth() {
72        return mWidth;
73    }
74
75    public float toFloat() {
76        return (float) mWidth / (float) mHeight;
77    }
78
79    @Override
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
97    public int hashCode() {
98        return Objects.hashCode(mWidth, mHeight);
99    }
100
101    @Override
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     */
109    public AspectRatio transpose() {
110        return of(mHeight, mWidth);
111    }
112
113    /**
114     * @return The landscape version of this aspect ratio.
115     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
206    private boolean isPortrait() {
207        return mWidth <= mHeight;
208    }
209
210}
211