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.asset;
17 
18 import android.graphics.Bitmap;
19 import android.graphics.Point;
20 import android.graphics.Rect;
21 
22 import androidx.annotation.WorkerThread;
23 
24 import java.io.ByteArrayInputStream;
25 import java.io.ByteArrayOutputStream;
26 import java.io.InputStream;
27 
28 /**
29  * Collection of static utility methods for decoding and processing Bitmaps.
30  */
31 public class BitmapUtils {
32     private static final float DEFAULT_CENTER_ALIGNMENT = 0.5f;
33 
34     // Suppress default constructor for noninstantiability.
BitmapUtils()35     private BitmapUtils() {
36         throw new AssertionError();
37     }
38 
39     /**
40      * Calculates the highest subsampling factor to scale the source image to the target view without
41      * losing visible quality. Final result is based on powers of 2 because it should be set as
42      * BitmapOptions#inSampleSize.
43      *
44      * @param srcWidth     Width of source image.
45      * @param srcHeight    Height of source image.
46      * @param targetWidth  Width of target view.
47      * @param targetHeight Height of target view.
48      * @return Highest subsampling factor as a power of 2.
49      */
calculateInSampleSize( int srcWidth, int srcHeight, int targetWidth, int targetHeight)50     public static int calculateInSampleSize(
51             int srcWidth, int srcHeight, int targetWidth, int targetHeight) {
52         int shift = 0;
53         int halfHeight = srcHeight / 2;
54         int halfWidth = srcWidth / 2;
55 
56         // Calculate the largest inSampleSize value that is a power of 2 and keeps both the result
57         // bitmap's height and width at least as large as the target height and width.
58         while (((halfHeight >> shift) >= targetHeight) && ((halfWidth >> shift) >= targetWidth)) {
59             shift++;
60         }
61 
62         return 1 << shift;
63     }
64 
65     /**
66      * Generates a hash code for the given bitmap. Computation starts with a nonzero prime number,
67      * then for the integer values of height, width, and a selection of pixel colors, multiplies the
68      * result by 31 and adds said integer value. Multiply by 31 because it is prime and conveniently 1
69      * less than 32 which is 2 ^ 5, allowing the VM to replace multiplication by a bit shift and
70      * subtraction for performance.
71      * <p>
72      * This method should be called off the UI thread.
73      */
generateHashCode(Bitmap bitmap)74     public static long generateHashCode(Bitmap bitmap) {
75         long result = 17;
76 
77         int width = bitmap.getWidth();
78         int height = bitmap.getHeight();
79 
80         result = 31 * result + width;
81         result = 31 * result + height;
82 
83         // Traverse pixels exponentially so that hash code generation scales well with large images.
84         for (int x = 0; x < width; x = x * 2 + 1) {
85             for (int y = 0; y < height; y = y * 2 + 1) {
86                 result = 31 * result + bitmap.getPixel(x, y);
87             }
88         }
89 
90         return result;
91     }
92 
93     /**
94      * Calculates horizontal alignment of the rect within the supplied dimensions.
95      *
96      * @return A float value between 0 and 1 specifying horizontal alignment; 0 for left-aligned, 0.5
97      * for horizontal center-aligned, and 1 for right-aligned.
98      */
calculateHorizontalAlignment(Point dimensions, Rect rect)99     public static float calculateHorizontalAlignment(Point dimensions, Rect rect) {
100         int paddingLeft = rect.left;
101         int paddingRight = dimensions.x - rect.right;
102         int totalHorizontalPadding = paddingLeft + paddingRight;
103         // Zero horizontal padding means that there is no room to crop horizontally so we just fall
104         // back to a default center-alignment value.
105         return (totalHorizontalPadding == 0)
106                 ? DEFAULT_CENTER_ALIGNMENT
107                 : paddingLeft / ((float) paddingLeft + paddingRight);
108     }
109 
110     /**
111      * Calculates vertical alignment of the rect within the supplied dimensions.
112      *
113      * @return A float value between 0 and 1 specifying vertical alignment; 0 for top-aligned, 0.5 for
114      * vertical center-aligned, and 1 for bottom-aligned.
115      */
calculateVerticalAlignment(Point dimensions, Rect rect)116     public static float calculateVerticalAlignment(Point dimensions, Rect rect) {
117         int paddingTop = rect.top;
118         int paddingBottom = dimensions.y - rect.bottom;
119         int totalVerticalPadding = paddingTop + paddingBottom;
120         // Zero vertical padding means that there is no room to crop vertically so we just fall back to
121         // a default center-alignment value.
122         return (totalVerticalPadding == 0)
123                 ? DEFAULT_CENTER_ALIGNMENT
124                 : paddingTop / ((float) paddingTop + paddingBottom);
125     }
126 
127     /**
128      * Converts the bitmap into an input stream with 100% quality.
129      *
130      * Should not be called from the main thread.
131      */
132     @WorkerThread
bitmapToInputStream(Bitmap bitmap)133     public static InputStream bitmapToInputStream(Bitmap bitmap) {
134         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
135         if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)) {
136             return new ByteArrayInputStream(outputStream.toByteArray());
137         } else {
138             return null;
139         }
140     }
141 }
142