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