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