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