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