| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.wallpaper.util; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.graphics.Rect; |
| import android.os.Build.VERSION; |
| import android.os.Build.VERSION_CODES; |
| import android.view.Display; |
| |
| import com.android.wallpaper.config.BaseFlags; |
| import com.android.wallpaper.module.InjectorProvider; |
| |
| /** |
| * Static utility methods for wallpaper cropping operations. |
| */ |
| public final class WallpaperCropUtils { |
| |
| private static final float WALLPAPER_SCREENS_SPAN = 2f; |
| private static final float ASPECT_RATIO_LANDSCAPE = 16 / 10f; |
| private static final float ASPECT_RATIO_PORTRAIT = 10 / 16f; |
| private static final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.2f; |
| private static final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.5f; |
| |
| // Suppress default constructor for noninstantiability. |
| private WallpaperCropUtils() { |
| throw new AssertionError(); |
| } |
| |
| /** |
| * Calculates parallax travel (i.e., extra width) for a screen with the given resolution. |
| */ |
| public static float wallpaperTravelToScreenWidthRatio(int width, int height) { |
| float aspectRatio = width / (float) height; |
| |
| // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.2 * screen width |
| // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.5 * screen width |
| // We will use these two data points to extrapolate how much the wallpaper parallax effect |
| // to span (i.e., travel) at any aspect ratio. We use the following two linear formulas, |
| // where |
| // the coefficient on x is the aspect ratio (width/height): |
| // (16/10)x + y = 1.2 |
| // (10/16)x + y = 1.5 |
| // We solve for x and y and end up with a final formula: |
| float x = (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE |
| - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) |
| / (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); |
| float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; |
| return x * aspectRatio + y; |
| } |
| |
| /** |
| * Calculates ideal crop surface size for a device such that there is room for parallax in both |
| * landscape and portrait screen orientations. |
| */ |
| public static Point getDefaultCropSurfaceSize(Resources resources, Display display) { |
| Point minDims = new Point(); |
| Point maxDims = new Point(); |
| display.getCurrentSizeRange(minDims, maxDims); |
| |
| int maxDim = Math.max(maxDims.x, maxDims.y); |
| int minDim = Math.max(minDims.x, minDims.y); |
| |
| if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { |
| Point realSize = new Point(); |
| display.getRealSize(realSize); |
| maxDim = Math.max(realSize.x, realSize.y); |
| minDim = Math.min(realSize.x, realSize.y); |
| } |
| |
| return calculateCropSurfaceSize(resources, maxDim, minDim, display.getWidth(), |
| display.getHeight()); |
| } |
| |
| /** |
| * Calculates ideal crop surface size for a surface of dimensions maxDim x minDim such that |
| * there is room for parallax in both* landscape and portrait screen orientations. |
| */ |
| public static Point calculateCropSurfaceSize(Resources resources, int maxDim, int minDim, |
| int width, int height) { |
| final int defaultWidth, defaultHeight; |
| if (resources.getConfiguration().smallestScreenWidthDp >= 720) { |
| defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); |
| } else { |
| defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim); |
| } |
| defaultHeight = width < height ? maxDim : minDim; |
| |
| return new Point(defaultWidth, defaultHeight); |
| } |
| |
| /** |
| * Calculates the relative position between an outer and inner rectangle when the outer one is |
| * center-cropped to the inner one. |
| * |
| * @param outer Size of outer rectangle as a Point (x,y). |
| * @param inner Size of inner rectangle as a Point (x,y). |
| * @param alignStart Whether the inner rectangle should be aligned to the start of the layout |
| * with the outer one and ignore horizontal centering. |
| * @param isRtl Whether the layout direction is RTL (or false for LTR). |
| * @return Position relative to the top left corner of the outer rectangle, where the size of |
| * each rectangle is represented by Points, in coordinates (x,y) relative to the outer rectangle |
| * where the top left corner is (0,0) |
| * @throws IllegalArgumentException if inner rectangle is not contained within outer rectangle |
| * which would return a position with at least one negative |
| * coordinate. |
| */ |
| public static Point calculateCenterPosition(Point outer, Point inner, boolean alignStart, |
| boolean isRtl) { |
| if (inner.x > outer.x || inner.y > outer.y) { |
| throw new IllegalArgumentException("Inner rectangle " + inner + " should be contained" |
| + " completely within the outer rectangle " + outer + "."); |
| } |
| |
| Point relativePosition = new Point(); |
| |
| if (alignStart) { |
| relativePosition.x = isRtl ? outer.x - inner.x : 0; |
| } else { |
| relativePosition.x = Math.round((outer.x - inner.x) / 2f); |
| } |
| relativePosition.y = Math.round((outer.y - inner.y) / 2f); |
| |
| return relativePosition; |
| } |
| |
| /** |
| * Calculates the minimum zoom such that the maximum surface area of the outer rectangle is |
| * visible within the inner rectangle. |
| * |
| * @param outer Size of outer rectangle as a Point (x,y). |
| * @param inner Size of inner rectangle as a Point (x,y). |
| */ |
| public static float calculateMinZoom(Point outer, Point inner) { |
| float minZoom; |
| if (inner.x / (float) inner.y > outer.x / (float) outer.y) { |
| minZoom = inner.x / (float) outer.x; |
| } else { |
| minZoom = inner.y / (float) outer.y; |
| } |
| return minZoom; |
| } |
| |
| |
| /** |
| * Calculates the center position of a wallpaper of the given size, based on a "crop surface" |
| * (with extra width to account for parallax) superimposed on the screen. Trying to show as |
| * much of the wallpaper as possible on the crop surface and align screen to crop surface such |
| * that the centered wallpaper matches what would be seen by the user in the left-most home |
| * screen. |
| * |
| * @param wallpaperSize full size of the wallpaper |
| * @param visibleWallpaperRect visible area available for the wallpaper |
| * @return a point corresponding to the position of wallpaper that should be in the center |
| * of the screen. |
| */ |
| public static PointF calculateDefaultCenter(Context context, Point wallpaperSize, |
| Rect visibleWallpaperRect) { |
| |
| WallpaperCropUtils.adjustCurrentWallpaperCropRect(context, wallpaperSize, |
| visibleWallpaperRect); |
| |
| return new PointF(visibleWallpaperRect.centerX(), |
| visibleWallpaperRect.centerY()); |
| } |
| |
| /** |
| * Calculates the rectangle to crop a wallpaper of the given size, and considering the given |
| * scrollX and scrollY offsets |
| * @param wallpaperZoom zoom applied to the raw wallpaper image |
| * @param wallpaperSize full ("raw") wallpaper size |
| * @param defaultCropSurfaceSize @see #getDefaultCropSurfaceSize(Resources, Display) |
| * @param targetHostSize target size where the wallpaper will be rendered (eg, the display size) |
| * @param scrollX x-axis offset the cropping area from the wallpaper's 0,0 position |
| * @param scrollY y-axis offset the cropping area from the wallpaper's 0,0 position |
| * @return a Rect representing the area of the wallpaper to crop. |
| */ |
| public static Rect calculateCropRect(Context context, float wallpaperZoom, Point wallpaperSize, |
| Point defaultCropSurfaceSize, Point targetHostSize, int scrollX, int scrollY, |
| boolean cropExtraWidth) { |
| BaseFlags flags = InjectorProvider.getInjector().getFlags(); |
| boolean isMultiCropEnabled = |
| flags.isMultiCropPreviewUiEnabled() && flags.isMultiCropEnabled(); |
| // Calculate Rect of wallpaper in physical pixel terms (i.e., scaled to current zoom). |
| int scaledWallpaperWidth = Math.round(wallpaperSize.x * wallpaperZoom); |
| int scaledWallpaperHeight = Math.round(wallpaperSize.y * wallpaperZoom); |
| Rect scaledWallpaperRect = new Rect(); |
| scaledWallpaperRect.set(0, 0, scaledWallpaperWidth, scaledWallpaperHeight); |
| |
| // Crop rect should start off as the visible screen and then include extra width and height |
| // if available within wallpaper at the current zoom. |
| Rect cropRect = new Rect(scrollX, scrollY, scrollX + targetHostSize.x, |
| scrollY + targetHostSize.y); |
| |
| int extraWidth = defaultCropSurfaceSize.x - targetHostSize.x; |
| int extraHeightTopAndBottom = (int) ((defaultCropSurfaceSize.y - targetHostSize.y) / 2f); |
| |
| if (cropExtraWidth) { |
| // Try to increase size of screenRect to include extra width depending on the layout |
| // direction. |
| if (RtlUtils.isRtl(context)) { |
| cropRect.left = Math.max(cropRect.left - extraWidth, scaledWallpaperRect.left); |
| } else { |
| cropRect.right = Math.min(cropRect.right + extraWidth, scaledWallpaperRect.right); |
| } |
| } |
| |
| // Try to increase the size of the cropRect to to include extra height. |
| int availableExtraHeightTop = cropRect.top - Math.max( |
| scaledWallpaperRect.top, |
| cropRect.top - extraHeightTopAndBottom); |
| int availableExtraHeightBottom = Math.min( |
| scaledWallpaperRect.bottom, |
| cropRect.bottom + extraHeightTopAndBottom) - cropRect.bottom; |
| if (isMultiCropEnabled) { |
| // availableExtraHeightBottom shouldn't be negative, but it could happen if wallpaper |
| // zoom < 1, in that case it should be zero. Safe guard availableExtraHeightTop as well. |
| // Looks like a bug, but we always have > 1 zoom for existing preview, so flag guard |
| // this block. |
| availableExtraHeightTop = Math.max(availableExtraHeightTop, 0); |
| availableExtraHeightBottom = Math.max(availableExtraHeightBottom, 0); |
| } |
| |
| int availableExtraHeightTopAndBottom = |
| Math.min(availableExtraHeightTop, availableExtraHeightBottom); |
| cropRect.top -= availableExtraHeightTopAndBottom; |
| cropRect.bottom += availableExtraHeightTopAndBottom; |
| |
| return cropRect; |
| } |
| |
| /** |
| * Calculates the center area of the outer rectangle which is visible in the inner rectangle |
| * after applying the minimum zoom. |
| * |
| * @param outer the size of outer rectangle as a Point (x,y). |
| * @param inner the size of inner rectangle as a Point (x,y). |
| */ |
| public static Rect calculateVisibleRect(Point outer, Point inner) { |
| PointF visibleRectCenter = new PointF(outer.x / 2f, outer.y / 2f); |
| if (inner.x / (float) inner.y > outer.x / (float) outer.y) { |
| float minZoom = inner.x / (float) outer.x; |
| float visibleRectHeight = inner.y / minZoom; |
| return new Rect(0, (int) (visibleRectCenter.y - visibleRectHeight / 2), |
| outer.x, (int) (visibleRectCenter.y + visibleRectHeight / 2)); |
| } else { |
| float minZoom = inner.y / (float) outer.y; |
| float visibleRectWidth = inner.x / minZoom; |
| return new Rect((int) (visibleRectCenter.x - visibleRectWidth / 2), |
| 0, (int) (visibleRectCenter.x + visibleRectWidth / 2), outer.y); |
| } |
| } |
| |
| public static void adjustCurrentWallpaperCropRect(Context context, Point assetDimensions, |
| Rect cropRect) { |
| adjustCropRect(context, cropRect, true /* zoomIn */); |
| } |
| |
| /** Adjust the crop rect by zooming in with systemWallpaperMaxScale. */ |
| public static void adjustCropRect(Context context, Rect cropRect, boolean zoomIn) { |
| float centerX = cropRect.centerX(); |
| float centerY = cropRect.centerY(); |
| float width = cropRect.width(); |
| float height = cropRect.height(); |
| float systemWallpaperMaxScale = getSystemWallpaperMaximumScale(context); |
| float scale = zoomIn ? systemWallpaperMaxScale : 1.0f / systemWallpaperMaxScale; |
| |
| // Adjust the rect according to the system wallpaper's maximum scale. |
| int left = (int) (centerX - (width / 2) / scale); |
| int top = (int) (centerY - (height / 2) / scale); |
| int right = (int) (centerX + (width / 2) / scale); |
| int bottom = (int) (centerY + (height / 2) / scale); |
| cropRect.set(left, top, right, bottom); |
| } |
| |
| /** Adjust the given Point, representing a size by systemWallpaperMaxScale. */ |
| public static void scaleSize(Context context, Point size) { |
| float systemWallpaperMaxScale = getSystemWallpaperMaximumScale(context); |
| size.set((int) (size.x * systemWallpaperMaxScale), |
| (int) (size.y * systemWallpaperMaxScale)); |
| } |
| /** |
| * Calculates {@link Rect} of the wallpaper which we want to crop to in physical pixel terms |
| * (i.e., scaled to current zoom) when the wallpaper is laid on a fullscreen view. |
| */ |
| public static Rect calculateCropRect(Context context, Display display, Point rawWallpaperSize, |
| Rect visibleRawWallpaperRect, float wallpaperZoom) { |
| Point screenSize = ScreenSizeCalculator.getInstance().getScreenSize(display); |
| Point defaultCropSize = WallpaperCropUtils.getDefaultCropSurfaceSize( |
| context.getResources(), display); |
| return calculateCropRect(context, screenSize, defaultCropSize, rawWallpaperSize, |
| visibleRawWallpaperRect, wallpaperZoom); |
| } |
| |
| /** |
| * Calculates {@link Rect} of the wallpaper which we want to crop to in physical pixel terms |
| * (i.e., scaled to current zoom). |
| * |
| * @param hostViewSize the size of the view hosting the wallpaper as a Point (x,y). |
| * @param cropSize the default size of the crop as a Point (x,y). |
| * @param rawWallpaperSize the size of the raw wallpaper as a Point (x,y). |
| * @param visibleRawWallpaperRect the area of the raw wallpaper which is expected to see. |
| * @param wallpaperZoom the factor which is used to scale the raw wallpaper. |
| * @param cropExtraWidth true to crop extra wallpaper width for panel sliding. |
| */ |
| public static Rect calculateCropRect(Context context, Point hostViewSize, Point cropSize, |
| Point rawWallpaperSize, Rect visibleRawWallpaperRect, float wallpaperZoom, |
| boolean cropExtraWidth) { |
| int scrollX = (int) (visibleRawWallpaperRect.left * wallpaperZoom); |
| int scrollY = (int) (visibleRawWallpaperRect.top * wallpaperZoom); |
| |
| return calculateCropRect(context, wallpaperZoom, rawWallpaperSize, cropSize, hostViewSize, |
| scrollX, scrollY, cropExtraWidth); |
| } |
| |
| /** |
| * Calculates {@link Rect} of the wallpaper which we want to crop to in physical pixel terms |
| * (i.e., scaled to current zoom). |
| * |
| * @param hostViewSize the size of the view hosting the wallpaper as a Point (x,y). |
| * @param cropSize the default size of the crop as a Point (x,y). |
| * @param rawWallpaperSize the size of the raw wallpaper as a Point (x,y). |
| * @param visibleRawWallpaperRect the area of the raw wallpaper which is expected to see. |
| * @param wallpaperZoom the factor which is used to scale the raw wallpaper. |
| */ |
| public static Rect calculateCropRect(Context context, Point hostViewSize, Point cropSize, |
| Point rawWallpaperSize, Rect visibleRawWallpaperRect, float wallpaperZoom) { |
| return calculateCropRect(context, hostViewSize, cropSize, rawWallpaperSize, |
| visibleRawWallpaperRect, wallpaperZoom, /* cropExtraWidth= */ true); |
| } |
| |
| /** |
| * Resize the wallpaper size so it's new size fits in a outWidth by outHeight rectangle. |
| * |
| * @param wallpaperSize Rectangle with the current wallpaper size. It will be resized. |
| * @param outWidth the width of the rectangle in which the wallpaperSize needs to fit. |
| * @param outHeight the height of the rectangle in which the wallpaperSize needs to fit. |
| */ |
| public static void fitToSize(Rect wallpaperSize, int outWidth, int outHeight) { |
| if (wallpaperSize.isEmpty()) { |
| return; |
| } |
| float maxSizeOut = Math.max(outWidth, outHeight); |
| float maxSizeIn = Math.max(wallpaperSize.width(), wallpaperSize.height()); |
| float scale = maxSizeOut / maxSizeIn; |
| |
| // Scale the wallpaper size |
| if (scale != 1.0f) { |
| wallpaperSize.left = (int) (wallpaperSize.left * scale + 0.5f); |
| wallpaperSize.top = (int) (wallpaperSize.top * scale + 0.5f); |
| wallpaperSize.right = (int) (wallpaperSize.right * scale + 0.5f); |
| wallpaperSize.bottom = (int) (wallpaperSize.bottom * scale + 0.5f); |
| } |
| } |
| |
| /** |
| * Get the system wallpaper's maximum scale value. |
| */ |
| public static float getSystemWallpaperMaximumScale(Context context) { |
| return context.getResources() |
| .getFloat(Resources.getSystem().getIdentifier( |
| /* name= */ "config_wallpaperMaxScale", |
| /* defType= */ "dimen", |
| /* defPackage= */ "android")); |
| } |
| |
| /** |
| * Gets the scale of screen size and crop rect real size |
| * |
| * @param wallpaperScale The scale of crop rect and real size rect |
| * @param cropRect The area wallpaper cropped |
| * @param screenWidth The width of screen size |
| * @param screenHeight The height of screen size |
| */ |
| public static float getScaleOfScreenResolution(float wallpaperScale, Rect cropRect, |
| int screenWidth, int screenHeight) { |
| int rectRealWidth = Math.round((float) cropRect.width() / wallpaperScale); |
| int rectRealHeight = Math.round((float) cropRect.height() / wallpaperScale); |
| int cropWidth = cropRect.width(); |
| int cropHeight = cropRect.height(); |
| // Not scale with screen resolution because cropRect is bigger than screen size. |
| if (cropWidth >= screenWidth || cropHeight >= screenHeight) { |
| return 1; |
| } |
| |
| int newWidth = screenWidth; |
| int newHeight = screenHeight; |
| // Screen size is bigger than crop real size so we only need enlarge to real size |
| if (newWidth > rectRealWidth || newHeight > rectRealHeight) { |
| newWidth = rectRealWidth; |
| newHeight = rectRealWidth; |
| } |
| float screenScale = Math.min((float) newWidth / cropWidth, (float) newHeight / cropHeight); |
| |
| // screenScale < 1 means our real crop size is smaller than crop size it should be. |
| // So we do nothing in this case, otherwise it'll cause wallpaper smaller than we expected. |
| if (screenScale < 1) { |
| return 1; |
| } |
| return screenScale; |
| } |
| } |