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.data; 18 19 import android.annotation.TargetApi; 20 import android.graphics.Bitmap; 21 import android.graphics.Bitmap.Config; 22 import android.graphics.BitmapFactory; 23 import android.graphics.BitmapFactory.Options; 24 import android.graphics.BitmapRegionDecoder; 25 import android.os.Build; 26 27 import com.android.gallery3d.common.ApiHelper; 28 import com.android.gallery3d.common.BitmapUtils; 29 import com.android.gallery3d.common.Utils; 30 import com.android.photos.data.GalleryBitmapPool; 31 import com.android.gallery3d.ui.Log; 32 import com.android.gallery3d.util.ThreadPool.CancelListener; 33 import com.android.gallery3d.util.ThreadPool.JobContext; 34 35 import java.io.FileDescriptor; 36 import java.io.FileInputStream; 37 import java.io.InputStream; 38 39 public class DecodeUtils { 40 private static final String TAG = "DecodeUtils"; 41 42 private static class DecodeCanceller implements CancelListener { 43 Options mOptions; 44 DecodeCanceller(Options options)45 public DecodeCanceller(Options options) { 46 mOptions = options; 47 } 48 49 @Override onCancel()50 public void onCancel() { 51 mOptions.requestCancelDecode(); 52 } 53 } 54 55 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) setOptionsMutable(Options options)56 public static void setOptionsMutable(Options options) { 57 if (ApiHelper.HAS_OPTIONS_IN_MUTABLE) options.inMutable = true; 58 } 59 decode(JobContext jc, FileDescriptor fd, Options options)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 decodeBounds(JobContext jc, FileDescriptor fd, Options options)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 decode(JobContext jc, byte[] bytes, Options options)77 public static Bitmap decode(JobContext jc, byte[] bytes, Options options) { 78 return decode(jc, bytes, 0, bytes.length, options); 79 } 80 decode(JobContext jc, byte[] bytes, int offset, int length, Options options)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 decodeBounds(JobContext jc, byte[] bytes, int offset, int length, Options options)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 decodeThumbnail( JobContext jc, String filePath, Options options, int targetSize, int type)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 decodeThumbnail( JobContext jc, FileDescriptor fd, Options options, int targetSize, int type)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 (float) Math.sqrt((double) 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 */ decodeIfBigEnough(JobContext jc, byte[] data, Options options, int targetSize)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. ensureGLCompatibleBitmap(Bitmap bitmap)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 createBitmapRegionDecoder( JobContext jc, byte[] bytes, int offset, int length, boolean shareable)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 createBitmapRegionDecoder( JobContext jc, String filePath, boolean shareable)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 createBitmapRegionDecoder( JobContext jc, FileDescriptor fd, boolean shareable)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 createBitmapRegionDecoder( JobContext jc, InputStream is, boolean shareable)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) decodeUsingPool(JobContext jc, byte[] data, int offset, int length, BitmapFactory.Options options)249 public static Bitmap decodeUsingPool(JobContext jc, byte[] data, int offset, 250 int length, BitmapFactory.Options options) { 251 if (options == null) options = new BitmapFactory.Options(); 252 if (options.inSampleSize < 1) options.inSampleSize = 1; 253 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 254 options.inBitmap = (options.inSampleSize == 1) 255 ? findCachedBitmap(jc, data, offset, length, options) : null; 256 try { 257 Bitmap bitmap = decode(jc, data, offset, length, options); 258 if (options.inBitmap != null && options.inBitmap != bitmap) { 259 GalleryBitmapPool.getInstance().put(options.inBitmap); 260 options.inBitmap = null; 261 } 262 return bitmap; 263 } catch (IllegalArgumentException e) { 264 if (options.inBitmap == null) throw e; 265 266 Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap"); 267 GalleryBitmapPool.getInstance().put(options.inBitmap); 268 options.inBitmap = null; 269 return decode(jc, data, offset, length, options); 270 } 271 } 272 273 // This is the same as the method above except the source data comes 274 // from a file descriptor instead of a byte array. 275 @TargetApi(Build.VERSION_CODES.HONEYCOMB) decodeUsingPool(JobContext jc, FileDescriptor fileDescriptor, Options options)276 public static Bitmap decodeUsingPool(JobContext jc, 277 FileDescriptor fileDescriptor, Options options) { 278 if (options == null) options = new BitmapFactory.Options(); 279 if (options.inSampleSize < 1) options.inSampleSize = 1; 280 options.inPreferredConfig = Bitmap.Config.ARGB_8888; 281 options.inBitmap = (options.inSampleSize == 1) 282 ? findCachedBitmap(jc, fileDescriptor, options) : null; 283 try { 284 Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options); 285 if (options.inBitmap != null && options.inBitmap != bitmap) { 286 GalleryBitmapPool.getInstance().put(options.inBitmap); 287 options.inBitmap = null; 288 } 289 return bitmap; 290 } catch (IllegalArgumentException e) { 291 if (options.inBitmap == null) throw e; 292 293 Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap"); 294 GalleryBitmapPool.getInstance().put(options.inBitmap); 295 options.inBitmap = null; 296 return decode(jc, fileDescriptor, options); 297 } 298 } 299 findCachedBitmap(JobContext jc, byte[] data, int offset, int length, Options options)300 private static Bitmap findCachedBitmap(JobContext jc, byte[] data, 301 int offset, int length, Options options) { 302 decodeBounds(jc, data, offset, length, options); 303 return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight); 304 } 305 findCachedBitmap(JobContext jc, FileDescriptor fileDescriptor, Options options)306 private static Bitmap findCachedBitmap(JobContext jc, FileDescriptor fileDescriptor, 307 Options options) { 308 decodeBounds(jc, fileDescriptor, options); 309 return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight); 310 } 311 } 312