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