Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 67f87795 authored by Aurélien Pomini's avatar Aurélien Pomini
Browse files

Improve the wallpaper crop & rescale logic

The current crop/rescale logic is... weird. See the associated bug for
moe details.

Instead, let's do the following:
   - if all crops for all orientations have more height in px than their
     associated screen, downsample the image
   - if the "total crop" width or height is more than twice the screen
     largest dimension or the max openGL texture size, downsample
     (note: "twice" is to let room for parallax, or to let have a too
     high res image (to some extent) as long as the crops are low res.
     This is more a security check and should not be used in practice.)

Also, move from using ImageDecoder.decodeBitmap() with sampleSize then
Bitmap.createScaledBitmap, to using ImageDecoder.decodeBitmap() with
targetSize, which should reduce the amount of IO.

Test: manual
Test: atest WallpaperManagerTest, WallpaperCropperTest
Bug: 332695334
Flag: aconfig com.android.window.flags.multi_crop
Change-Id: I68f1b48d136c4942880fc55e79cbbf7a16763b66
parent 7dca5acb
Loading
Loading
Loading
Loading
+66 −34
Original line number Diff line number Diff line
@@ -493,6 +493,7 @@ public class WallpaperCropper {
                }
            }
            final Rect cropHint;
            final SparseArray<Rect> defaultCrops;

            // A wallpaper with cropHints = Map.of(ORIENTATION_UNKNOWN, rect) is treated like
            // a wallpaper with cropHints = null and  cropHint = rect.
@@ -504,7 +505,7 @@ public class WallpaperCropper {
            if (multiCrop() && wallpaper.mCropHints.size() > 0) {
                // Some suggested crops per screen orientation were provided,
                // use them to compute the default crops for this device
                SparseArray<Rect> defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize);
                defaultCrops = getDefaultCrops(wallpaper.mCropHints, bitmapSize);
                // Adapt the provided crops to match the actual crops for the default display
                SparseArray<Rect> updatedCropHints = new SparseArray<>();
                for (int i = 0; i < wallpaper.mCropHints.size(); i++) {
@@ -535,7 +536,7 @@ public class WallpaperCropper {
                    wallpaper.cropHint.set(bitmapRect);
                }
                Point cropSize = new Point(wallpaper.cropHint.width(), wallpaper.cropHint.height());
                SparseArray<Rect> defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
                defaultCrops = getDefaultCrops(new SparseArray<>(), cropSize);
                cropHint = getTotalCrop(defaultCrops);
                cropHint.offset(wallpaper.cropHint.left, wallpaper.cropHint.top);
                wallpaper.cropHint.set(cropHint);
@@ -544,6 +545,7 @@ public class WallpaperCropper {
                }
            } else {
                cropHint = new Rect(wallpaper.cropHint);
                defaultCrops = null;
            }

            if (DEBUG) {
@@ -584,6 +586,33 @@ public class WallpaperCropper {
                    || cropHint.height() > GLHelper.getMaxTextureSize()
                    || cropHint.width() > GLHelper.getMaxTextureSize();

            float sampleSize = Float.MAX_VALUE;
            if (multiCrop()) {
                // If all crops for all orientations have more width and height in pixel
                // than the display for this orientation, downsample the image
                for (int i = 0; i < defaultCrops.size(); i++) {
                    int orientation = defaultCrops.keyAt(i);
                    Rect crop = defaultCrops.valueAt(i);
                    Point displayForThisOrientation = mWallpaperDisplayHelper
                            .getDefaultDisplaySizes().get(orientation);
                    if (displayForThisOrientation == null) continue;
                    float sampleSizeForThisOrientation = Math.max(1f, Math.min(
                            crop.width() / displayForThisOrientation.x,
                            crop.height() / displayForThisOrientation.y));
                    sampleSize = Math.min(sampleSize, sampleSizeForThisOrientation);
                }
                // If the total crop has more width or height than either the max texture size
                // or twice the largest display dimension, downsample the image
                int maxCropSize = Math.min(
                        2 * mWallpaperDisplayHelper.getDefaultDisplayLargestDimension(),
                        GLHelper.getMaxTextureSize());
                float minimumSampleSize = Math.max(1f, Math.max(
                        (float) cropHint.height() / maxCropSize,
                        (float) cropHint.width()) / maxCropSize);
                sampleSize = Math.max(sampleSize, minimumSampleSize);
                needScale = sampleSize > 1f;
            }

            //make sure screen aspect ratio is preserved if width is scaled under screen size
            if (needScale && !multiCrop()) {
                final float scaleByHeight = (float) wpData.mHeight / (float) cropHint.height();
@@ -598,7 +627,8 @@ public class WallpaperCropper {

            if (DEBUG_CROP) {
                Slog.v(TAG, "crop: w=" + cropHint.width() + " h=" + cropHint.height());
                Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight);
                if (multiCrop()) Slog.v(TAG, "defaultCrops: " + defaultCrops);
                if (!multiCrop()) Slog.v(TAG, "dims: w=" + wpData.mWidth + " h=" + wpData.mHeight);
                Slog.v(TAG, "meas: w=" + options.outWidth + " h=" + options.outHeight);
                Slog.v(TAG, "crop?=" + needCrop + " scale?=" + needScale);
            }
@@ -641,28 +671,17 @@ public class WallpaperCropper {
                    options.inJustDecodeBounds = false;

                    final Rect estimateCrop = new Rect(cropHint);
                    estimateCrop.scale(1f / options.inSampleSize);
                    if (!multiCrop()) estimateCrop.scale(1f / options.inSampleSize);
                    else estimateCrop.scale(1f / sampleSize);
                    float hRatio = (float) wpData.mHeight / estimateCrop.height();
                    if (multiCrop()) {
                        // make sure the crop height is at most the display largest dimension
                        hRatio = (float) mWallpaperDisplayHelper.getDefaultDisplayLargestDimension()
                                / estimateCrop.height();
                        hRatio = Math.min(hRatio, 1f);
                    }
                    final int destHeight = (int) (estimateCrop.height() * hRatio);
                    final int destWidth = (int) (estimateCrop.width() * hRatio);

                    // We estimated an invalid crop, try to adjust the cropHint to get a valid one.
                    if (destWidth > GLHelper.getMaxTextureSize()) {
                    if (!multiCrop() && destWidth > GLHelper.getMaxTextureSize()) {
                        if (DEBUG) {
                            Slog.w(TAG, "Invalid crop dimensions, trying to adjust.");
                        }
                        if (multiCrop()) {
                            // clear custom crop guidelines, fallback to system default
                            wallpaper.mCropHints.clear();
                            generateCropInternal(wallpaper);
                            return;
                        }

                        int newHeight = (int) (wpData.mHeight / hRatio);
                        int newWidth = (int) (wpData.mWidth / hRatio);
@@ -679,16 +698,27 @@ public class WallpaperCropper {
                    // We've got the safe cropHint; now we want to scale it properly to
                    // the desired rectangle.
                    // That's a height-biased operation: make it fit the hinted height.
                    final int safeHeight = (int) (estimateCrop.height() * hRatio + 0.5f);
                    final int safeWidth = (int) (estimateCrop.width() * hRatio + 0.5f);
                    final int safeHeight = !multiCrop()
                            ? (int) (estimateCrop.height() * hRatio + 0.5f)
                            : (int) (cropHint.height() / sampleSize + 0.5f);
                    final int safeWidth = !multiCrop()
                            ? (int) (estimateCrop.width() * hRatio + 0.5f)
                            : (int) (cropHint.width() / sampleSize + 0.5f);

                    if (DEBUG_CROP) {
                        Slog.v(TAG, "Decode parameters:");
                        Slog.v(TAG, "  cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
                        if (!multiCrop()) {
                            Slog.v(TAG,
                                    "  cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
                            Slog.v(TAG, "  down sampling=" + options.inSampleSize
                                    + ", hRatio=" + hRatio);
                            Slog.v(TAG, "  dest=" + destWidth + "x" + destHeight);
                        Slog.v(TAG, "  safe=" + safeWidth + "x" + safeHeight);
                        }
                        if (multiCrop()) {
                            Slog.v(TAG, "  cropHint=" + cropHint);
                            Slog.v(TAG, "  sampleSize=" + sampleSize);
                        }
                        Slog.v(TAG, "  targetSize=" + safeWidth + "x" + safeHeight);
                        Slog.v(TAG, "  maxTextureSize=" + GLHelper.getMaxTextureSize());
                    }

@@ -703,24 +733,28 @@ public class WallpaperCropper {

                    final ImageDecoder.Source srcData =
                            ImageDecoder.createSource(wallpaper.getWallpaperFile());
                    final int sampleSize = scale;
                    final int finalScale = scale;
                    final int rescaledBitmapWidth = (int) (0.5f + bitmapSize.x / sampleSize);
                    final int rescaledBitmapHeight = (int) (0.5f + bitmapSize.y / sampleSize);
                    Bitmap cropped = ImageDecoder.decodeBitmap(srcData, (decoder, info, src) -> {
                        decoder.setTargetSampleSize(sampleSize);
                        if (!multiCrop()) decoder.setTargetSampleSize(finalScale);
                        if (multiCrop()) {
                            decoder.setTargetSize(rescaledBitmapWidth, rescaledBitmapHeight);
                        }
                        decoder.setCrop(estimateCrop);
                    });

                    record.delete();

                    if (cropped == null) {
                    if (!multiCrop() && cropped == null) {
                        Slog.e(TAG, "Could not decode new wallpaper");
                    } else {
                        // We are safe to create final crop with safe dimensions now.
                        final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
                                safeWidth, safeHeight, true);
                        final Bitmap finalCrop = multiCrop() ? cropped
                                : Bitmap.createScaledBitmap(cropped, safeWidth, safeHeight, true);

                        if (multiCrop()) {
                            wallpaper.mSampleSize =
                                    ((float) cropHint.height()) / finalCrop.getHeight();
                            wallpaper.mSampleSize = sampleSize;
                        }

                        if (DEBUG) {
@@ -739,9 +773,7 @@ public class WallpaperCropper {
                        success = true;
                    }
                } catch (Exception e) {
                    if (DEBUG) {
                    Slog.e(TAG, "Error decoding crop", e);
                    }
                } finally {
                    IoUtils.closeQuietly(bos);
                    IoUtils.closeQuietly(f);