DecodeUtils.java revision a625b6562d3bfc86465778b336c96fb42064be21
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 17package com.android.gallery3d.data; 18 19import android.annotation.TargetApi; 20import android.graphics.Bitmap; 21import android.graphics.Bitmap.Config; 22import android.graphics.BitmapFactory; 23import android.graphics.BitmapFactory.Options; 24import android.graphics.BitmapRegionDecoder; 25import android.os.Build; 26import android.util.FloatMath; 27 28import com.android.gallery3d.common.ApiHelper; 29import com.android.gallery3d.common.BitmapUtils; 30import com.android.gallery3d.common.Utils; 31import com.android.gallery3d.ui.Log; 32import com.android.gallery3d.util.ThreadPool.CancelListener; 33import com.android.gallery3d.util.ThreadPool.JobContext; 34 35import java.io.FileDescriptor; 36import java.io.FileInputStream; 37import java.io.InputStream; 38 39public class DecodeUtils { 40 private static final String TAG = "DecodeUtils"; 41 42 private static class DecodeCanceller implements CancelListener { 43 Options mOptions; 44 45 public DecodeCanceller(Options options) { 46 mOptions = options; 47 } 48 49 @Override 50 public void onCancel() { 51 mOptions.requestCancelDecode(); 52 } 53 } 54 55 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) 56 public static void setOptionsMutable(Options options) { 57 if (ApiHelper.HAS_OPTIONS_IN_MUTABLE) options.inMutable = true; 58 } 59 60 public static Bitmap decode(JobContext jc, FileDescriptor fd, Options options) { 61 if (options == null) options = new Options(); 62 jc.setCancelListener(new DecodeCanceller(options)); 63 setOptionsMutable(options); 64 return ensureGLCompatibleBitmap( 65 BitmapFactory.decodeFileDescriptor(fd, null, options)); 66 } 67 68 public static void decodeBounds(JobContext jc, FileDescriptor fd, 69 Options options) { 70 Utils.assertTrue(options != null); 71 options.inJustDecodeBounds = true; 72 jc.setCancelListener(new DecodeCanceller(options)); 73 BitmapFactory.decodeFileDescriptor(fd, null, options); 74 options.inJustDecodeBounds = false; 75 } 76 77 public static Bitmap decode(JobContext jc, byte[] bytes, Options options) { 78 return decode(jc, bytes, 0, bytes.length, options); 79 } 80 81 public static Bitmap decode(JobContext jc, byte[] bytes, int offset, 82 int length, Options options) { 83 if (options == null) options = new Options(); 84 jc.setCancelListener(new DecodeCanceller(options)); 85 setOptionsMutable(options); 86 return ensureGLCompatibleBitmap( 87 BitmapFactory.decodeByteArray(bytes, offset, length, options)); 88 } 89 90 public static void decodeBounds(JobContext jc, byte[] bytes, int offset, 91 int length, Options options) { 92 Utils.assertTrue(options != null); 93 options.inJustDecodeBounds = true; 94 jc.setCancelListener(new DecodeCanceller(options)); 95 BitmapFactory.decodeByteArray(bytes, offset, length, options); 96 options.inJustDecodeBounds = false; 97 } 98 99 public static Bitmap decodeThumbnail( 100 JobContext jc, String filePath, Options options, int targetSize, int type) { 101 FileInputStream fis = null; 102 try { 103 fis = new FileInputStream(filePath); 104 FileDescriptor fd = fis.getFD(); 105 return decodeThumbnail(jc, fd, options, targetSize, type); 106 } catch (Exception ex) { 107 Log.w(TAG, ex); 108 return null; 109 } finally { 110 Utils.closeSilently(fis); 111 } 112 } 113 114 public static Bitmap decodeThumbnail( 115 JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) { 116 if (options == null) options = new Options(); 117 jc.setCancelListener(new DecodeCanceller(options)); 118 119 options.inJustDecodeBounds = true; 120 BitmapFactory.decodeFileDescriptor(fd, null, options); 121 if (jc.isCancelled()) return null; 122 123 int w = options.outWidth; 124 int h = options.outHeight; 125 126 if (type == MediaItem.TYPE_MICROTHUMBNAIL) { 127 // We center-crop the original image as it's micro thumbnail. In this case, 128 // we want to make sure the shorter side >= "targetSize". 129 float scale = (float) targetSize / Math.min(w, h); 130 options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); 131 132 // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding 133 // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here. 134 final int MAX_PIXEL_COUNT = 640000; // 400 x 1600 135 if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) { 136 options.inSampleSize = BitmapUtils.computeSampleSize( 137 FloatMath.sqrt((float) MAX_PIXEL_COUNT / (w * h))); 138 } 139 } else { 140 // For screen nail, we only want to keep the longer side >= targetSize. 141 float scale = (float) targetSize / Math.max(w, h); 142 options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); 143 } 144 145 options.inJustDecodeBounds = false; 146 setOptionsMutable(options); 147 148 Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options); 149 if (result == null) return null; 150 151 // We need to resize down if the decoder does not support inSampleSize 152 // (For example, GIF images) 153 float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL 154 ? Math.min(result.getWidth(), result.getHeight()) 155 : Math.max(result.getWidth(), result.getHeight())); 156 157 if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true); 158 return ensureGLCompatibleBitmap(result); 159 } 160 161 /** 162 * Decodes the bitmap from the given byte array if the image size is larger than the given 163 * requirement. 164 * 165 * Note: The returned image may be resized down. However, both width and height must be 166 * larger than the <code>targetSize</code>. 167 */ 168 public static Bitmap decodeIfBigEnough(JobContext jc, byte[] data, 169 Options options, int targetSize) { 170 if (options == null) options = new Options(); 171 jc.setCancelListener(new DecodeCanceller(options)); 172 173 options.inJustDecodeBounds = true; 174 BitmapFactory.decodeByteArray(data, 0, data.length, options); 175 if (jc.isCancelled()) return null; 176 if (options.outWidth < targetSize || options.outHeight < targetSize) { 177 return null; 178 } 179 options.inSampleSize = BitmapUtils.computeSampleSizeLarger( 180 options.outWidth, options.outHeight, targetSize); 181 options.inJustDecodeBounds = false; 182 setOptionsMutable(options); 183 184 return ensureGLCompatibleBitmap( 185 BitmapFactory.decodeByteArray(data, 0, data.length, options)); 186 } 187 188 // TODO: This function should not be called directly from 189 // DecodeUtils.requestDecode(...), since we don't have the knowledge 190 // if the bitmap will be uploaded to GL. 191 public static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) { 192 if (bitmap == null || bitmap.getConfig() != null) return bitmap; 193 Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false); 194 bitmap.recycle(); 195 return newBitmap; 196 } 197 198 public static BitmapRegionDecoder createBitmapRegionDecoder( 199 JobContext jc, byte[] bytes, int offset, int length, 200 boolean shareable) { 201 if (offset < 0 || length <= 0 || offset + length > bytes.length) { 202 throw new IllegalArgumentException(String.format( 203 "offset = %s, length = %s, bytes = %s", 204 offset, length, bytes.length)); 205 } 206 207 try { 208 return BitmapRegionDecoder.newInstance( 209 bytes, offset, length, shareable); 210 } catch (Throwable t) { 211 Log.w(TAG, t); 212 return null; 213 } 214 } 215 216 public static BitmapRegionDecoder createBitmapRegionDecoder( 217 JobContext jc, String filePath, boolean shareable) { 218 try { 219 return BitmapRegionDecoder.newInstance(filePath, shareable); 220 } catch (Throwable t) { 221 Log.w(TAG, t); 222 return null; 223 } 224 } 225 226 public static BitmapRegionDecoder createBitmapRegionDecoder( 227 JobContext jc, FileDescriptor fd, boolean shareable) { 228 try { 229 return BitmapRegionDecoder.newInstance(fd, shareable); 230 } catch (Throwable t) { 231 Log.w(TAG, t); 232 return null; 233 } 234 } 235 236 public static BitmapRegionDecoder createBitmapRegionDecoder( 237 JobContext jc, InputStream is, boolean shareable) { 238 try { 239 return BitmapRegionDecoder.newInstance(is, shareable); 240 } catch (Throwable t) { 241 // We often cancel the creating of bitmap region decoder, 242 // so just log one line. 243 Log.w(TAG, "requestCreateBitmapRegionDecoder: " + t); 244 return null; 245 } 246 } 247 248 @TargetApi(Build.VERSION_CODES.HONEYCOMB) 249 public static Bitmap decode(JobContext jc, byte[] data, int offset, 250 int length, BitmapFactory.Options options, BitmapPool pool) { 251 if (pool == null) { 252 return decode(jc, data, offset, length, options); 253 } 254 255 if (options == null) options = new BitmapFactory.Options(); 256 if (options.inSampleSize < 1) options.inSampleSize = 1; 257 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 258 options.inBitmap = (options.inSampleSize == 1) 259 ? findCachedBitmap(pool, jc, data, offset, length, options) : null; 260 try { 261 Bitmap bitmap = decode(jc, data, offset, length, options); 262 if (options.inBitmap != null && options.inBitmap != bitmap) { 263 pool.recycle(options.inBitmap); 264 options.inBitmap = null; 265 } 266 return bitmap; 267 } catch (IllegalArgumentException e) { 268 if (options.inBitmap == null) throw e; 269 270 Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap"); 271 pool.recycle(options.inBitmap); 272 options.inBitmap = null; 273 return decode(jc, data, offset, length, options); 274 } 275 } 276 277 // This is the same as the method above except the source data comes 278 // from a file descriptor instead of a byte array. 279 @TargetApi(Build.VERSION_CODES.HONEYCOMB) 280 public static Bitmap decode(JobContext jc, 281 FileDescriptor fileDescriptor, Options options, BitmapPool pool) { 282 if (pool == null) { 283 return decode(jc, fileDescriptor, options); 284 } 285 286 if (options == null) options = new BitmapFactory.Options(); 287 if (options.inSampleSize < 1) options.inSampleSize = 1; 288 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 289 options.inBitmap = (options.inSampleSize == 1) 290 ? findCachedBitmap(pool, jc, fileDescriptor, options) : null; 291 try { 292 Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options); 293 if (options.inBitmap != null && options.inBitmap != bitmap) { 294 pool.recycle(options.inBitmap); 295 options.inBitmap = null; 296 } 297 return bitmap; 298 } catch (IllegalArgumentException e) { 299 if (options.inBitmap == null) throw e; 300 301 Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap"); 302 pool.recycle(options.inBitmap); 303 options.inBitmap = null; 304 return decode(jc, fileDescriptor, options); 305 } 306 } 307 308 private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc, 309 byte[] data, int offset, int length, Options options) { 310 if (pool.isOneSize()) return pool.getBitmap(); 311 decodeBounds(jc, data, offset, length, options); 312 return pool.getBitmap(options.outWidth, options.outHeight); 313 } 314 315 private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc, 316 FileDescriptor fileDescriptor, Options options) { 317 if (pool.isOneSize()) return pool.getBitmap(); 318 decodeBounds(jc, fileDescriptor, options); 319 return pool.getBitmap(options.outWidth, options.outHeight); 320 } 321} 322