• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Context;
19 import android.content.res.Resources;
20 import android.graphics.Point;
21 import android.graphics.PointF;
22 import android.graphics.Rect;
23 import android.os.Build.VERSION;
24 import android.os.Build.VERSION_CODES;
25 import android.view.Display;
26 import android.view.View;
27 
28 import com.android.systemui.shared.system.WallpaperManagerCompat;
29 
30 /**
31  * Static utility methods for wallpaper cropping operations.
32  */
33 public final class WallpaperCropUtils {
34 
35     private static final float WALLPAPER_SCREENS_SPAN = 2f;
36     private static final float ASPECT_RATIO_LANDSCAPE = 16 / 10f;
37     private static final float ASPECT_RATIO_PORTRAIT = 10 / 16f;
38     private static final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.2f;
39     private static final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.5f;
40 
41     // Suppress default constructor for noninstantiability.
WallpaperCropUtils()42     private WallpaperCropUtils() {
43         throw new AssertionError();
44     }
45 
46     /**
47      * Calculates parallax travel (i.e., extra width) for a screen with the given resolution.
48      */
wallpaperTravelToScreenWidthRatio(int width, int height)49     public static float wallpaperTravelToScreenWidthRatio(int width, int height) {
50         float aspectRatio = width / (float) height;
51 
52         // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.2 * screen width
53         // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.5 * screen width
54         // We will use these two data points to extrapolate how much the wallpaper parallax effect
55         // to span (i.e., travel) at any aspect ratio. We use the following two linear formulas,
56         // where
57         // the coefficient on x is the aspect ratio (width/height):
58         //   (16/10)x + y = 1.2
59         //   (10/16)x + y = 1.5
60         // We solve for x and y and end up with a final formula:
61         float x = (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE
62                 - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT)
63                 / (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
64         float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
65         return x * aspectRatio + y;
66     }
67 
68     /**
69      * Calculates ideal crop surface size for a device such that there is room for parallax in both
70      * landscape and portrait screen orientations.
71      */
getDefaultCropSurfaceSize(Resources resources, Display display)72     public static Point getDefaultCropSurfaceSize(Resources resources, Display display) {
73         Point minDims = new Point();
74         Point maxDims = new Point();
75         display.getCurrentSizeRange(minDims, maxDims);
76 
77         int maxDim = Math.max(maxDims.x, maxDims.y);
78         int minDim = Math.max(minDims.x, minDims.y);
79 
80         if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
81             Point realSize = new Point();
82             display.getRealSize(realSize);
83             maxDim = Math.max(realSize.x, realSize.y);
84             minDim = Math.min(realSize.x, realSize.y);
85         }
86 
87         return calculateCropSurfaceSize(resources, maxDim, minDim);
88     }
89 
90     /**
91      * Calculates ideal crop surface size for a surface of dimensions maxDim x minDim such that
92      * there is room for parallax in both* landscape and portrait screen orientations.
93      */
calculateCropSurfaceSize(Resources resources, int maxDim, int minDim)94     public static Point calculateCropSurfaceSize(Resources resources, int maxDim, int minDim) {
95         final int defaultWidth, defaultHeight;
96         if (resources.getConfiguration().smallestScreenWidthDp >= 720) {
97             defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
98             defaultHeight = maxDim;
99         } else {
100             defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim);
101             defaultHeight = maxDim;
102         }
103 
104         return new Point(defaultWidth, defaultHeight);
105     }
106 
107     /**
108      * Calculates the relative position between an outer and inner rectangle when the outer one is
109      * center-cropped to the inner one.
110      *
111      * @param outer      Size of outer rectangle as a Point (x,y).
112      * @param inner      Size of inner rectangle as a Point (x,y).
113      * @param alignStart Whether the inner rectangle should be aligned to the start of the layout
114      *                   with the outer one and ignore horizontal centering.
115      * @param isRtl      Whether the layout direction is RTL (or false for LTR).
116      * @return Position relative to the top left corner of the outer rectangle, where the size of
117      * each rectangle is represented by Points, in coordinates (x,y) relative to the outer rectangle
118      * where the top left corner is (0,0)
119      * @throws IllegalArgumentException if inner rectangle is not contained within outer rectangle
120      *                                  which would return a position with at least one negative
121      *                                  coordinate.
122      */
calculateCenterPosition(Point outer, Point inner, boolean alignStart, boolean isRtl)123     public static Point calculateCenterPosition(Point outer, Point inner, boolean alignStart,
124             boolean isRtl) {
125         if (inner.x > outer.x || inner.y > outer.y) {
126             throw new IllegalArgumentException("Inner rectangle " + inner + " should be contained"
127                     + " completely within the outer rectangle " + outer + ".");
128         }
129 
130         Point relativePosition = new Point();
131 
132         if (alignStart) {
133             relativePosition.x = isRtl ? outer.x - inner.x : 0;
134         } else {
135             relativePosition.x = Math.round((outer.x - inner.x) / 2f);
136         }
137         relativePosition.y = Math.round((outer.y - inner.y) / 2f);
138 
139         return relativePosition;
140     }
141 
142     /**
143      * Calculates the minimum zoom such that the maximum surface area of the outer rectangle is
144      * visible within the inner rectangle.
145      *
146      * @param outer Size of outer rectangle as a Point (x,y).
147      * @param inner Size of inner rectangle as a Point (x,y).
148      */
calculateMinZoom(Point outer, Point inner)149     public static float calculateMinZoom(Point outer, Point inner) {
150         float minZoom;
151         if (inner.x / (float) inner.y > outer.x / (float) outer.y) {
152             minZoom = inner.x / (float) outer.x;
153         } else {
154             minZoom = inner.y / (float) outer.y;
155         }
156         return minZoom;
157     }
158 
159 
160     /**
161      * Calculates the center position of a wallpaper of the given size, based on a "crop surface"
162      * (with extra width to account for parallax) superimposed on the screen. Trying to show as
163      * much of the wallpaper as possible on the crop surface and align screen to crop surface such
164      * that the centered wallpaper matches what would be seen by the user in the left-most home
165      * screen.
166      *
167      * @param wallpaperSize full size of the wallpaper
168      * @param visibleWallpaperRect visible area available for the wallpaper
169      * @return a point corresponding to the position of wallpaper that should be in the center
170      *      of the screen.
171      */
calculateDefaultCenter(Context context, Point wallpaperSize, Rect visibleWallpaperRect)172     public static PointF calculateDefaultCenter(Context context, Point wallpaperSize,
173             Rect visibleWallpaperRect) {
174 
175         WallpaperCropUtils.adjustCurrentWallpaperCropRect(context, wallpaperSize,
176                 visibleWallpaperRect);
177 
178         return new PointF(visibleWallpaperRect.centerX(),
179                 visibleWallpaperRect.centerY());
180     }
181 
182     /**
183      * Calculates the rectangle to crop a wallpaper of the given size, and considering the given
184      * scrollX and scrollY offsets
185      * @param wallpaperZoom zoom applied to the raw wallpaper image
186      * @param wallpaperSize full ("raw") wallpaper size
187      * @param defaultCropSurfaceSize @see #getDefaultCropSurfaceSize(Resources, Display)
188      * @param targetHostSize target size where the wallpaper will be rendered (eg, the display size)
189      * @param scrollX x-axis offset the cropping area from the wallpaper's 0,0 position
190      * @param scrollY y-axis offset the cropping area from the wallpaper's 0,0 position
191      * @return a Rect representing the area of the wallpaper to crop.
192      */
calculateCropRect(Context context, float wallpaperZoom, Point wallpaperSize, Point defaultCropSurfaceSize, Point targetHostSize, int scrollX, int scrollY)193     public static Rect calculateCropRect(Context context, float wallpaperZoom, Point wallpaperSize,
194             Point defaultCropSurfaceSize, Point targetHostSize, int scrollX, int scrollY) {
195         // Calculate Rect of wallpaper in physical pixel terms (i.e., scaled to current zoom).
196         int scaledWallpaperWidth = Math.round(wallpaperSize.x * wallpaperZoom);
197         int scaledWallpaperHeight = Math.round(wallpaperSize.y * wallpaperZoom);
198         Rect rect = new Rect();
199         rect.set(0, 0, scaledWallpaperWidth, scaledWallpaperHeight);
200 
201         // Crop rect should start off as the visible screen and then include extra width and height
202         // if available within wallpaper at the current zoom.
203         Rect cropRect = new Rect(scrollX, scrollY, scrollX + targetHostSize.x,
204                 scrollY + targetHostSize.y);
205 
206         int extraWidth = defaultCropSurfaceSize.x - targetHostSize.x;
207         int extraHeightTopAndBottom = (int) ((defaultCropSurfaceSize.y - targetHostSize.y) / 2f);
208 
209         // Try to increase size of screenRect to include extra width depending on the layout
210         // direction.
211         if (isRtl(context)) {
212             cropRect.left = Math.max(cropRect.left - extraWidth, rect.left);
213         } else {
214             cropRect.right = Math.min(cropRect.right + extraWidth, rect.right);
215         }
216 
217         // Try to increase the size of the cropRect to to include extra height.
218         int availableExtraHeightTop = cropRect.top - Math.max(
219                 rect.top,
220                 cropRect.top - extraHeightTopAndBottom);
221         int availableExtraHeightBottom = Math.min(
222                 rect.bottom,
223                 cropRect.bottom + extraHeightTopAndBottom) - cropRect.bottom;
224 
225         int availableExtraHeightTopAndBottom =
226                 Math.min(availableExtraHeightTop, availableExtraHeightBottom);
227         cropRect.top -= availableExtraHeightTopAndBottom;
228         cropRect.bottom += availableExtraHeightTopAndBottom;
229 
230         return cropRect;
231     }
232 
233     /**
234      * Calculates the center area of the outer rectangle which is visible in the inner rectangle
235      * after applying the minimum zoom.
236      *
237      * @param outer the size of outer rectangle as a Point (x,y).
238      * @param inner the size of inner rectangle as a Point (x,y).
239      */
calculateVisibleRect(Point outer, Point inner)240     public static Rect calculateVisibleRect(Point outer, Point inner) {
241         PointF visibleRectCenter = new PointF(outer.x / 2f, outer.y / 2f);
242         if (inner.x / (float) inner.y > outer.x / (float) outer.y) {
243             float minZoom = inner.x / (float) outer.x;
244             float visibleRectHeight = inner.y / minZoom;
245             return new Rect(0, (int) (visibleRectCenter.y - visibleRectHeight / 2),
246                     outer.x, (int) (visibleRectCenter.y + visibleRectHeight / 2));
247         } else {
248             float minZoom = inner.y / (float) outer.y;
249             float visibleRectWidth = inner.x / minZoom;
250             return new Rect((int) (visibleRectCenter.x - visibleRectWidth / 2),
251                     0, (int) (visibleRectCenter.x + visibleRectWidth / 2), outer.y);
252         }
253     }
254 
adjustCurrentWallpaperCropRect(Context context, Point assetDimensions, Rect cropRect)255     public static void adjustCurrentWallpaperCropRect(Context context, Point assetDimensions,
256             Rect cropRect) {
257         adjustCropRect(context, cropRect, true /* zoomIn */);
258     }
259 
260     /** Adjust the crop rect by zooming in with systemWallpaperMaxScale. */
adjustCropRect(Context context, Rect cropRect, boolean zoomIn)261     public static void adjustCropRect(Context context, Rect cropRect, boolean zoomIn) {
262         float centerX = cropRect.centerX();
263         float centerY = cropRect.centerY();
264         float width = cropRect.width();
265         float height = cropRect.height();
266         float systemWallpaperMaxScale = getSystemWallpaperMaximumScale(context);
267         float scale = zoomIn ? systemWallpaperMaxScale : 1.0f / systemWallpaperMaxScale;
268 
269         // Adjust the rect according to the system wallpaper's maximum scale.
270         int left = (int) (centerX - (width / 2) / scale);
271         int top = (int) (centerY - (height / 2) / scale);
272         int right = (int) (centerX + (width / 2) / scale);
273         int bottom = (int) (centerY + (height / 2) / scale);
274         cropRect.set(left, top, right, bottom);
275     }
276 
277     /** Adjust the given Point, representing a size by  systemWallpaperMaxScale. */
scaleSize(Context context, Point size)278     public static void scaleSize(Context context, Point size) {
279         float systemWallpaperMaxScale = getSystemWallpaperMaximumScale(context);
280         size.set((int) (size.x * systemWallpaperMaxScale),
281                 (int) (size.y * systemWallpaperMaxScale));
282     }
283     /**
284      * Calculates {@link Rect} of the wallpaper which we want to crop to in physical pixel terms
285      * (i.e., scaled to current zoom) when the wallpaper is laid on a fullscreen view.
286      */
calculateCropRect(Context context, Display display, Point rawWallpaperSize, Rect visibleRawWallpaperRect, float wallpaperZoom)287     public static Rect calculateCropRect(Context context, Display display, Point rawWallpaperSize,
288             Rect visibleRawWallpaperRect, float wallpaperZoom) {
289         Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display);
290         Point defaultCropSize = WallpaperCropUtils.getDefaultCropSurfaceSize(
291                 context.getResources(), display);
292         return calculateCropRect(context, screenSize, defaultCropSize, rawWallpaperSize,
293                 visibleRawWallpaperRect, wallpaperZoom);
294     }
295 
296     /**
297      * Calculates {@link Rect} of the wallpaper which we want to crop to in physical pixel terms
298      * (i.e., scaled to current zoom).
299      *
300      * @param hostViewSize            the size of the view hosting the wallpaper as a Point (x,y).
301      * @param cropSize                the default size of the crop as a Point (x,y).
302      * @param rawWallpaperSize        the size of the raw wallpaper as a Point (x,y).
303      * @param visibleRawWallpaperRect the area of the raw wallpaper which is expected to see.
304      * @param wallpaperZoom           the factor which is used to scale the raw wallpaper.
305      */
calculateCropRect(Context context, Point hostViewSize, Point cropSize, Point rawWallpaperSize, Rect visibleRawWallpaperRect, float wallpaperZoom)306     public static Rect calculateCropRect(Context context, Point hostViewSize, Point cropSize,
307             Point rawWallpaperSize, Rect visibleRawWallpaperRect, float wallpaperZoom) {
308         int scrollX = (int) (visibleRawWallpaperRect.left * wallpaperZoom);
309         int scrollY = (int) (visibleRawWallpaperRect.top * wallpaperZoom);
310 
311         return calculateCropRect(context, wallpaperZoom, rawWallpaperSize, cropSize, hostViewSize,
312                 scrollX, scrollY);
313     }
314 
315     /**
316      * Resize the wallpaper size so it's new size fits in a outWidth by outHeight rectangle.
317      *
318      * @param wallpaperSize Rectangle with the current wallpaper size. It will be resized.
319      * @param outWidth      the width of the rectangle in which the wallpaperSize needs to fit.
320      * @param outHeight     the height of the rectangle in which the wallpaperSize needs to fit.
321      */
fitToSize(Rect wallpaperSize, int outWidth, int outHeight)322     public static void fitToSize(Rect wallpaperSize, int outWidth, int outHeight) {
323         if (wallpaperSize.isEmpty()) {
324             return;
325         }
326         float maxSizeOut = Math.max(outWidth, outHeight);
327         float maxSizeIn = Math.max(wallpaperSize.width(), wallpaperSize.height());
328         float scale = maxSizeOut / maxSizeIn;
329 
330         // Scale the wallpaper size
331         if (scale != 1.0f) {
332             wallpaperSize.left = (int) (wallpaperSize.left * scale + 0.5f);
333             wallpaperSize.top = (int) (wallpaperSize.top * scale + 0.5f);
334             wallpaperSize.right = (int) (wallpaperSize.right * scale + 0.5f);
335             wallpaperSize.bottom = (int) (wallpaperSize.bottom * scale + 0.5f);
336         }
337     }
338 
339     /**
340      * Get the system wallpaper's maximum scale value.
341      */
getSystemWallpaperMaximumScale(Context context)342     public static float getSystemWallpaperMaximumScale(Context context) {
343         return WallpaperManagerCompat.getWallpaperZoomOutMaxScale(context);
344     }
345 
346     /**
347      * Returns whether layout direction is RTL (or false for LTR). Since native RTL layout support
348      * was added in API 17, returns false for versions lower than 17.
349      */
isRtl(Context context)350     public static boolean isRtl(Context context) {
351         return context.getResources().getConfiguration().getLayoutDirection()
352                 == View.LAYOUT_DIRECTION_RTL;
353     }
354 }
355