1 /* 2 * Copyright (C) 2010 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 17 package com.android.gallery3d.common; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Bitmap.CompressFormat; 21 import android.graphics.BitmapFactory; 22 import android.graphics.Canvas; 23 import android.graphics.Matrix; 24 import android.graphics.Paint; 25 import android.os.Build; 26 import android.util.Log; 27 28 import java.io.ByteArrayOutputStream; 29 import java.lang.reflect.InvocationTargetException; 30 import java.lang.reflect.Method; 31 32 public class BitmapUtils { 33 private static final String TAG = "BitmapUtils"; 34 public static final int UNCONSTRAINED = -1; 35 private static final int COMPRESS_JPEG_QUALITY = 90; 36 37 private BitmapUtils(){} 38 39 /* 40 * Compute the sample size as a function of minSideLength 41 * and maxNumOfPixels. 42 * minSideLength is used to specify that minimal width or height of a 43 * bitmap. 44 * maxNumOfPixels is used to specify the maximal size in pixels that is 45 * tolerable in terms of memory usage. 46 * 47 * The function returns a sample size based on the constraints. 48 * Both size and minSideLength can be passed in as UNCONSTRAINED, 49 * which indicates no care of the corresponding constraint. 50 * The functions prefers returning a sample size that 51 * generates a smaller bitmap, unless minSideLength = UNCONSTRAINED. 52 * 53 * Also, the function rounds up the sample size to a power of 2 or multiple 54 * of 8 because BitmapFactory only honors sample size this way. 55 * For example, BitmapFactory downsamples an image by 2 even though the 56 * request is 3. So we round up the sample size to avoid OOM. 57 */ 58 public static int computeSampleSize(int width, int height, 59 int minSideLength, int maxNumOfPixels) { 60 int initialSize = computeInitialSampleSize( 61 width, height, minSideLength, maxNumOfPixels); 62 63 return initialSize <= 8 64 ? Utils.nextPowerOf2(initialSize) 65 : (initialSize + 7) / 8 * 8; 66 } 67 68 private static int computeInitialSampleSize(int w, int h, 69 int minSideLength, int maxNumOfPixels) { 70 if (maxNumOfPixels == UNCONSTRAINED 71 && minSideLength == UNCONSTRAINED) return 1; 72 73 int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 : 74 (int) Math.ceil(Math.sqrt((double) (w * h) / maxNumOfPixels)); 75 76 if (minSideLength == UNCONSTRAINED) { 77 return lowerBound; 78 } else { 79 int sampleSize = Math.min(w / minSideLength, h / minSideLength); 80 return Math.max(sampleSize, lowerBound); 81 } 82 } 83 84 // This computes a sample size which makes the longer side at least 85 // minSideLength long. If that's not possible, return 1. 86 public static int computeSampleSizeLarger(int w, int h, 87 int minSideLength) { 88 int initialSize = Math.max(w / minSideLength, h / minSideLength); 89 if (initialSize <= 1) return 1; 90 91 return initialSize <= 8 92 ? Utils.prevPowerOf2(initialSize) 93 : initialSize / 8 * 8; 94 } 95 96 // Fin the min x that 1 / x <= scale 97 public static int computeSampleSizeLarger(float scale) { 98 int initialSize = (int) Math.floor(1f / scale); 99 if (initialSize <= 1) return 1; 100 101 return initialSize <= 8 102 ? Utils.prevPowerOf2(initialSize) 103 : initialSize / 8 * 8; 104 } 105 106 // Find the max x that 1 / x >= scale. 107 public static int computeSampleSize(float scale) { 108 Utils.assertTrue(scale > 0); 109 int initialSize = Math.max(1, (int) Math.ceil(1 / scale)); 110 return initialSize <= 8 111 ? Utils.nextPowerOf2(initialSize) 112 : (initialSize + 7) / 8 * 8; 113 } 114 115 public static Bitmap resizeDownToPixels( 116 Bitmap bitmap, int targetPixels, boolean recycle) { 117 int width = bitmap.getWidth(); 118 int height = bitmap.getHeight(); 119 float scale = (float) Math.sqrt( 120 (double) targetPixels / (width * height)); 121 if (scale >= 1.0f) return bitmap; 122 return resizeBitmapByScale(bitmap, scale, recycle); 123 } 124 125 public static Bitmap resizeBitmapByScale( 126 Bitmap bitmap, float scale, boolean recycle) { 127 int width = Math.round(bitmap.getWidth() * scale); 128 int height = Math.round(bitmap.getHeight() * scale); 129 if (width == bitmap.getWidth() 130 && height == bitmap.getHeight()) return bitmap; 131 Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap)); 132 Canvas canvas = new Canvas(target); 133 canvas.scale(scale, scale); 134 Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG); 135 canvas.drawBitmap(bitmap, 0, 0, paint); 136 if (recycle) bitmap.recycle(); 137 return target; 138 } 139 140 private static Bitmap.Config getConfig(Bitmap bitmap) { 141 Bitmap.Config config = bitmap.getConfig(); 142 if (config == null) { 143 config = Bitmap.Config.ARGB_8888; 144 } 145 return config; 146 } 147 148 public static Bitmap resizeDownBySideLength( 149 Bitmap bitmap, int maxLength, boolean recycle) { 150 int srcWidth = bitmap.getWidth(); 151 int srcHeight = bitmap.getHeight(); 152 float scale = Math.min( 153 (float) maxLength / srcWidth, (float) maxLength / srcHeight); 154 if (scale >= 1.0f) return bitmap; 155 return resizeBitmapByScale(bitmap, scale, recycle); 156 } 157 158 // Resize the bitmap if each side is >= targetSize * 2 159 public static Bitmap resizeDownIfTooBig( 160 Bitmap bitmap, int targetSize, boolean recycle) { 161 int srcWidth = bitmap.getWidth(); 162 int srcHeight = bitmap.getHeight(); 163 float scale = Math.max( 164 (float) targetSize / srcWidth, (float) targetSize / srcHeight); 165 if (scale > 0.5f) return bitmap; 166 return resizeBitmapByScale(bitmap, scale, recycle); 167 } 168 169 // Crops a square from the center of the original image. 170 public static Bitmap cropCenter(Bitmap bitmap, boolean recycle) { 171 int width = bitmap.getWidth(); 172 int height = bitmap.getHeight(); 173 if (width == height) return bitmap; 174 int size = Math.min(width, height); 175 176 Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap)); 177 Canvas canvas = new Canvas(target); 178 canvas.translate((size - width) / 2, (size - height) / 2); 179 Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG); 180 canvas.drawBitmap(bitmap, 0, 0, paint); 181 if (recycle) bitmap.recycle(); 182 return target; 183 } 184 185 public static Bitmap resizeDownAndCropCenter(Bitmap bitmap, int size, 186 boolean recycle) { 187 int w = bitmap.getWidth(); 188 int h = bitmap.getHeight(); 189 int minSide = Math.min(w, h); 190 if (w == h && minSide <= size) return bitmap; 191 size = Math.min(size, minSide); 192 193 float scale = Math.max((float) size / bitmap.getWidth(), 194 (float) size / bitmap.getHeight()); 195 Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap)); 196 int width = Math.round(scale * bitmap.getWidth()); 197 int height = Math.round(scale * bitmap.getHeight()); 198 Canvas canvas = new Canvas(target); 199 canvas.translate((size - width) / 2f, (size - height) / 2f); 200 canvas.scale(scale, scale); 201 Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG); 202 canvas.drawBitmap(bitmap, 0, 0, paint); 203 if (recycle) bitmap.recycle(); 204 return target; 205 } 206 207 public static void recycleSilently(Bitmap bitmap) { 208 if (bitmap == null) return; 209 try { 210 bitmap.recycle(); 211 } catch (Throwable t) { 212 Log.w(TAG, "unable recycle bitmap", t); 213 } 214 } 215 216 public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) { 217 if (rotation == 0) return source; 218 int w = source.getWidth(); 219 int h = source.getHeight(); 220 Matrix m = new Matrix(); 221 m.postRotate(rotation); 222 Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true); 223 if (recycle) source.recycle(); 224 return bitmap; 225 } 226 227 public static Bitmap createVideoThumbnail(String filePath) { 228 // MediaMetadataRetriever is available on API Level 8 229 // but is hidden until API Level 10 230 Class<?> clazz = null; 231 Object instance = null; 232 try { 233 clazz = Class.forName("android.media.MediaMetadataRetriever"); 234 instance = clazz.newInstance(); 235 236 Method method = clazz.getMethod("setDataSource", String.class); 237 method.invoke(instance, filePath); 238 239 // The method name changes between API Level 9 and 10. 240 if (Build.VERSION.SDK_INT <= 9) { 241 return (Bitmap) clazz.getMethod("captureFrame").invoke(instance); 242 } else { 243 byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance); 244 if (data != null) { 245 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); 246 if (bitmap != null) return bitmap; 247 } 248 return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance); 249 } 250 } catch (IllegalArgumentException ex) { 251 // Assume this is a corrupt video file 252 } catch (RuntimeException ex) { 253 // Assume this is a corrupt video file. 254 } catch (InstantiationException e) { 255 Log.e(TAG, "createVideoThumbnail", e); 256 } catch (InvocationTargetException e) { 257 Log.e(TAG, "createVideoThumbnail", e); 258 } catch (ClassNotFoundException e) { 259 Log.e(TAG, "createVideoThumbnail", e); 260 } catch (NoSuchMethodException e) { 261 Log.e(TAG, "createVideoThumbnail", e); 262 } catch (IllegalAccessException e) { 263 Log.e(TAG, "createVideoThumbnail", e); 264 } finally { 265 try { 266 if (instance != null) { 267 clazz.getMethod("release").invoke(instance); 268 } 269 } catch (Exception ignored) { 270 } 271 } 272 return null; 273 } 274 275 public static byte[] compressBitmap(Bitmap bitmap) { 276 ByteArrayOutputStream os = new ByteArrayOutputStream(); 277 bitmap.compress(Bitmap.CompressFormat.JPEG, 278 COMPRESS_JPEG_QUALITY, os); 279 return os.toByteArray(); 280 } 281 282 public static boolean isSupportedByRegionDecoder(String mimeType) { 283 if (mimeType == null) return false; 284 mimeType = mimeType.toLowerCase(); 285 return mimeType.startsWith("image/") && 286 (!mimeType.equals("image/gif") && !mimeType.endsWith("bmp")); 287 } 288 289 public static boolean isRotationSupported(String mimeType) { 290 if (mimeType == null) return false; 291 mimeType = mimeType.toLowerCase(); 292 return mimeType.equals("image/jpeg"); 293 } 294 295 public static byte[] compressToBytes(Bitmap bitmap, int quality) { 296 ByteArrayOutputStream baos = new ByteArrayOutputStream(65536); 297 bitmap.compress(CompressFormat.JPEG, quality, baos); 298 return baos.toByteArray(); 299 } 300 } 301