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