1 /*
2  * Copyright (C) 2017 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.wallpaper.util;
17 
18 import android.content.res.Resources;
19 import android.graphics.Point;
20 import android.os.Build.VERSION;
21 import android.os.Build.VERSION_CODES;
22 import android.view.Display;
23 
24 /**
25  * Static utility methods for wallpaper cropping operations.
26  */
27 public final class WallpaperCropUtils {
28 
29     private static final float WALLPAPER_SCREENS_SPAN = 2f;
30     private static final float ASPECT_RATIO_LANDSCAPE = 16 / 10f;
31     private static final float ASPECT_RATIO_PORTRAIT = 10 / 16f;
32     private static final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.2f;
33     private static final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.5f;
34 
35     // Suppress default constructor for noninstantiability.
WallpaperCropUtils()36     private WallpaperCropUtils() {
37         throw new AssertionError();
38     }
39 
40     /**
41      * Calculates parallax travel (i.e., extra width) for a screen with the given resolution.
42      */
wallpaperTravelToScreenWidthRatio(int width, int height)43     public static float wallpaperTravelToScreenWidthRatio(int width, int height) {
44         float aspectRatio = width / (float) height;
45 
46         // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.2 * screen width
47         // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.5 * screen width
48         // We will use these two data points to extrapolate how much the wallpaper parallax effect
49         // to span (i.e., travel) at any aspect ratio. We use the following two linear formulas, where
50         // the coefficient on x is the aspect ratio (width/height):
51         //   (16/10)x + y = 1.2
52         //   (10/16)x + y = 1.5
53         // We solve for x and y and end up with a final formula:
54         float x = (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT)
55                 / (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
56         float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
57         return x * aspectRatio + y;
58     }
59 
60     /**
61      * Calculates ideal crop surface size for a device such that there is room for parallax in both
62      * landscape and portrait screen orientations.
63      */
getDefaultCropSurfaceSize(Resources resources, Display display)64     public static Point getDefaultCropSurfaceSize(Resources resources, Display display) {
65         Point minDims = new Point();
66         Point maxDims = new Point();
67         display.getCurrentSizeRange(minDims, maxDims);
68 
69         int maxDim = Math.max(maxDims.x, maxDims.y);
70         int minDim = Math.max(minDims.x, minDims.y);
71 
72         if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
73             Point realSize = new Point();
74             display.getRealSize(realSize);
75             maxDim = Math.max(realSize.x, realSize.y);
76             minDim = Math.min(realSize.x, realSize.y);
77         }
78 
79         final int defaultWidth, defaultHeight;
80         if (resources.getConfiguration().smallestScreenWidthDp >= 720) {
81             defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
82             defaultHeight = maxDim;
83         } else {
84             defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
85             defaultHeight = maxDim;
86         }
87 
88         return new Point(defaultWidth, defaultHeight);
89     }
90 
91     /**
92      * Calculates the relative position between an outer and inner rectangle when the outer one is
93      * center-cropped to the inner one.
94      *
95      * @param outer      Size of outer rectangle as a Point (x,y).
96      * @param inner      Size of inner rectangle as a Point (x,y).
97      * @param alignStart Whether the inner rectangle should be aligned to the start of the layout with
98      *                   the outer one and ignore horizontal centering.
99      * @param isRtl      Whether the layout direction is RTL (or false for LTR).
100      * @return Position relative to the top left corner of the outer rectangle, where the size of each
101      * rectangle is represented by Points, in coordinates (x,y) relative to the outer rectangle
102      * where the top left corner is (0,0)
103      * @throws IllegalArgumentException if inner rectangle is not contained within outer rectangle
104      *                                  which would return a position with at least one negative coordinate.
105      */
calculateCenterPosition(Point outer, Point inner, boolean alignStart, boolean isRtl)106     public static Point calculateCenterPosition(Point outer, Point inner, boolean alignStart,
107                                                 boolean isRtl) {
108         if (inner.x > outer.x || inner.y > outer.y) {
109             throw new IllegalArgumentException("Inner rectangle " + inner + " should be contained"
110                     + " completely within the outer rectangle " + outer + ".");
111         }
112 
113         Point relativePosition = new Point();
114 
115         if (alignStart) {
116             relativePosition.x = isRtl ? outer.x - inner.x : 0;
117         } else {
118             relativePosition.x = Math.round((outer.x - inner.x) / 2f);
119         }
120         relativePosition.y = Math.round((outer.y - inner.y) / 2f);
121 
122         return relativePosition;
123     }
124 
125     /**
126      * Calculates the minimum zoom such that the maximum surface area of the outer rectangle is
127      * visible within the inner rectangle.
128      *
129      * @param outer Size of outer rectangle as a Point (x,y).
130      * @param inner Size of inner rectangle as a Point (x,y).
131      */
calculateMinZoom(Point outer, Point inner)132     public static float calculateMinZoom(Point outer, Point inner) {
133         float minZoom;
134         if (inner.x / (float) inner.y > outer.x / (float) outer.y) {
135             minZoom = inner.x / (float) outer.x;
136         } else {
137             minZoom = inner.y / (float) outer.y;
138         }
139         return minZoom;
140     }
141 }
142