blob: bd96ee58d9f8f84429fb1d078f3873c6d4a00323 [file] [log] [blame]
Rakesh Iyer2c939402016-10-19 23:26:02 -07001/*
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 */
16package com.android.car.apps.common;
17
Arnaud Berry38f99762019-07-08 15:32:51 -070018import static android.graphics.Bitmap.Config.ARGB_8888;
19
Arnaud Berry02b2a4d2019-08-19 11:20:26 -070020import android.content.Context;
21import android.content.res.Resources;
Rakesh Iyer2c939402016-10-19 23:26:02 -070022import android.graphics.Bitmap;
Arnaud Berry38f99762019-07-08 15:32:51 -070023import android.graphics.Canvas;
24import android.graphics.Matrix;
25import android.graphics.Paint;
26import android.graphics.RectF;
27import android.graphics.drawable.BitmapDrawable;
28import android.graphics.drawable.Drawable;
Rakesh Iyer2c939402016-10-19 23:26:02 -070029import android.util.Log;
Arnaud Berry38f99762019-07-08 15:32:51 -070030import android.util.Size;
Rakesh Iyer2c939402016-10-19 23:26:02 -070031
Arnaud Berry8fa94492020-03-10 13:25:14 -070032import androidx.annotation.ColorInt;
33import androidx.annotation.NonNull;
34import androidx.annotation.Nullable;
35
Rakesh Iyer2c939402016-10-19 23:26:02 -070036public 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 */
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 */
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 }
Arnaud Berry38f99762019-07-08 15:32:51 -0700111
112 /** Creates a copy of the given bitmap and applies the given color over the result */
113 @Nullable
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
Arnaud Berry02b2a4d2019-08-19 11:20:26 -0700123 /** Returns a tinted drawable if flagging is enabled and the given drawable is a bitmap. */
124 @NonNull
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
Arnaud Berry38f99762019-07-08 15:32:51 -0700138 /** Renders the drawable into a bitmap if needed. */
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 }
Rakesh Iyer2c939402016-10-19 23:26:02 -0700171}