• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.car.apps.common;
17 
18 import static android.graphics.Bitmap.Config.ARGB_8888;
19 
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Matrix;
25 import android.graphics.Paint;
26 import android.graphics.RectF;
27 import android.graphics.drawable.BitmapDrawable;
28 import android.graphics.drawable.Drawable;
29 import android.util.Log;
30 import android.util.Size;
31 
32 import androidx.annotation.ColorInt;
33 import androidx.annotation.NonNull;
34 import androidx.annotation.Nullable;
35 
36 public class BitmapUtils {
37     private static final String TAG = "BitmapUtils";
38 
39     /**
40      * Scales a bitmap while preserving the proportions such that both dimensions are the smallest
41      * values possible that are equal to or larger than the given dimensions.
42      *
43      * This function can be a few times as expensive as Bitmap.createScaledBitmap with
44      * filtering when downscaling, but it produces much nicer results.
45      *
46      * @param bm The bitmap to scale.
47      * @param width The desired width.
48      * @param height The desired height.
49      * @return The scaled bitmap, or the original bitmap if scaling was not necessary.
50      */
scaleBitmap(Bitmap bm, int width, int height)51     public static Bitmap scaleBitmap(Bitmap bm, int width, int height) {
52         if (bm == null || (bm.getHeight() == height && bm.getWidth() == width)) {
53             return bm;
54         }
55 
56         float heightScale = 1f;
57         if (bm.getHeight() > height) {
58             heightScale = (float) height / bm.getHeight();
59         }
60         float widthScale = 1f;
61         if (bm.getWidth() > width) {
62             widthScale = (float) width / bm.getWidth();
63         }
64         float scale = heightScale > widthScale ? heightScale : widthScale;
65         int scaleWidth = (int) Math.ceil(bm.getWidth() * scale);
66         int scaleHeight = (int) Math.ceil(bm.getHeight() * scale);
67 
68         Bitmap scaledBm = bm;
69         // If you try to scale an image down too much in one go, you can end up with discontinuous
70         // interpolation. Therefore, if necessary, we scale the image to twice the desired size
71         // and do a second scaling to the desired size, which smooths jaggedness from the first go.
72         if (scale < .5f) {
73             scaledBm = Bitmap.createScaledBitmap(scaledBm, scaleWidth * 2, scaleHeight * 2, true);
74         }
75 
76         if (scale != 1f) {
77             Bitmap newScaledBitmap = Bitmap
78                     .createScaledBitmap(scaledBm, scaleWidth, scaleHeight, true);
79             if (scaledBm != bm) {
80                 scaledBm.recycle();
81             }
82             scaledBm = newScaledBitmap;
83         }
84         return scaledBm;
85     }
86 
87     /**
88      * Crops the given bitmap to a centered rectangle of the given dimensions.
89      *
90      * @param bm the bitmap to crop.
91      * @param width the width to crop to.
92      * @param height the height to crop to.
93      * @return The cropped bitmap, or the original if no cropping was necessary.
94      */
cropBitmap(Bitmap bm, int width, int height)95     public static Bitmap cropBitmap(Bitmap bm, int width, int height) {
96         if (bm == null) {
97             return bm;
98         }
99         if (bm.getHeight() < height || bm.getWidth() < width) {
100             if (Log.isLoggable(TAG, Log.INFO)) {
101                 Log.i(TAG, String.format(
102                         "Can't crop bitmap to larger dimensions (%d, %d) -> (%d, %d).",
103                         bm.getWidth(), bm.getHeight(), width, height));
104             }
105             return bm;
106         }
107         int x = (bm.getWidth() - width) / 2;
108         int y =(bm.getHeight() - height) / 2;
109         return Bitmap.createBitmap(bm, x, y, width, height);
110     }
111 
112     /** Creates a copy of the given bitmap and applies the given color over the result */
113     @Nullable
createTintedBitmap(@ullable Bitmap image, @ColorInt int colorOverlay)114     public static Bitmap createTintedBitmap(@Nullable Bitmap image, @ColorInt int colorOverlay) {
115         if (image == null) return null;
116         Bitmap clone = Bitmap.createBitmap(image.getWidth(), image.getHeight(), ARGB_8888);
117         Canvas canvas = new Canvas(clone);
118         canvas.drawBitmap(image, 0f, 0f, new Paint());
119         canvas.drawColor(colorOverlay);
120         return clone;
121     }
122 
123     /** Returns a tinted drawable if flagging is enabled and the given drawable is a bitmap. */
124     @NonNull
maybeFlagDrawable(@onNull Context context, @NonNull Drawable drawable)125     public static Drawable maybeFlagDrawable(@NonNull Context context, @NonNull Drawable drawable) {
126         if (drawable instanceof BitmapDrawable) {
127             CommonFlags flags = CommonFlags.getInstance(context);
128             Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
129             if (flags.shouldFlagImproperImageRefs() && bitmap != null) {
130                 Resources res = context.getResources();
131                 int tint = context.getColor(R.color.improper_image_refs_tint_color);
132                 drawable = new BitmapDrawable(res, BitmapUtils.createTintedBitmap(bitmap, tint));
133             }
134         }
135         return drawable;
136     }
137 
138     /** Renders the drawable into a bitmap if needed. */
fromDrawable(Drawable drawable, @Nullable Size bitmapSize)139     public static Bitmap fromDrawable(Drawable drawable, @Nullable Size bitmapSize) {
140         if (drawable instanceof BitmapDrawable) {
141             BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
142             if (bitmapDrawable.getBitmap() != null) {
143                 return bitmapDrawable.getBitmap();
144             }
145         }
146 
147         Matrix matrix = new Matrix();
148         if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
149             bitmapSize = new Size(1, 1);
150             drawable.setBounds(0, 0, bitmapSize.getWidth(), bitmapSize.getHeight());
151         } else {
152             if (bitmapSize == null) {
153                 bitmapSize = new Size(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
154             }
155             drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
156             RectF srcR = new RectF(0f, 0f, drawable.getIntrinsicWidth(),
157                     drawable.getIntrinsicHeight());
158             RectF dstR = new RectF(0f, 0f, bitmapSize.getWidth(), bitmapSize.getHeight());
159             matrix.setRectToRect(srcR, dstR, Matrix.ScaleToFit.CENTER);
160         }
161 
162         Bitmap bitmap = Bitmap.createBitmap(bitmapSize.getWidth(), bitmapSize.getHeight(),
163                 Bitmap.Config.ARGB_8888);
164 
165         Canvas canvas = new Canvas(bitmap);
166         canvas.setMatrix(matrix);
167         drawable.draw(canvas);
168 
169         return bitmap;
170     }
171 }
172