DecodeUtils.java revision 4bb5912e85f6d1bd8a6b78d6d52b4c4da7aeb740
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.graphics.Bitmap; 20import android.graphics.Bitmap.Config; 21import android.graphics.BitmapFactory; 22import android.graphics.BitmapFactory.Options; 23import android.graphics.BitmapRegionDecoder; 24import android.util.FloatMath; 25 26import com.android.gallery3d.common.BitmapUtils; 27import com.android.gallery3d.common.Utils; 28import com.android.gallery3d.util.ThreadPool.CancelListener; 29import com.android.gallery3d.util.ThreadPool.JobContext; 30 31import java.io.FileDescriptor; 32import java.io.FileInputStream; 33import java.io.InputStream; 34 35public class DecodeUtils { 36 private static final String TAG = "DecodeService"; 37 38 private static class DecodeCanceller implements CancelListener { 39 Options mOptions; 40 41 public DecodeCanceller(Options options) { 42 mOptions = options; 43 } 44 45 @Override 46 public void onCancel() { 47 mOptions.requestCancelDecode(); 48 } 49 } 50 51 public static Bitmap decode(JobContext jc, FileDescriptor fd, Options options) { 52 if (options == null) options = new Options(); 53 jc.setCancelListener(new DecodeCanceller(options)); 54 return ensureGLCompatibleBitmap( 55 BitmapFactory.decodeFileDescriptor(fd, null, options)); 56 } 57 58 public static Bitmap decode(JobContext jc, byte[] bytes, Options options) { 59 return decode(jc, bytes, 0, bytes.length, options); 60 } 61 62 public static Bitmap decode(JobContext jc, byte[] bytes, int offset, 63 int length, Options options) { 64 if (options == null) options = new Options(); 65 jc.setCancelListener(new DecodeCanceller(options)); 66 return ensureGLCompatibleBitmap( 67 BitmapFactory.decodeByteArray(bytes, offset, length, options)); 68 } 69 70 public static Bitmap decodeThumbnail( 71 JobContext jc, String filePath, Options options, int targetSize, int type) { 72 FileInputStream fis = null; 73 try { 74 fis = new FileInputStream(filePath); 75 FileDescriptor fd = fis.getFD(); 76 return decodeThumbnail(jc, fd, options, targetSize, type); 77 } catch (Exception ex) { 78 Log.w(TAG, ex); 79 return null; 80 } finally { 81 Utils.closeSilently(fis); 82 } 83 } 84 85 public static Bitmap decodeThumbnail( 86 JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) { 87 if (options == null) options = new Options(); 88 jc.setCancelListener(new DecodeCanceller(options)); 89 90 options.inJustDecodeBounds = true; 91 BitmapFactory.decodeFileDescriptor(fd, null, options); 92 if (jc.isCancelled()) return null; 93 94 int w = options.outWidth; 95 int h = options.outHeight; 96 97 if (type == MediaItem.TYPE_MICROTHUMBNAIL) { 98 // We center-crop the original image as it's micro thumbnail. In this case, 99 // we want to make sure the shorter side >= "targetSize". 100 float scale = (float) targetSize / Math.min(w, h); 101 options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); 102 103 // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding 104 // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here. 105 final int MAX_PIXEL_COUNT = 640000; // 400 x 1600 106 if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) { 107 options.inSampleSize = BitmapUtils.computeSampleSize( 108 FloatMath.sqrt((float) MAX_PIXEL_COUNT / (w * h))); 109 } 110 } else { 111 // For screen nail, we only want to keep the longer side >= targetSize. 112 float scale = (float) targetSize / Math.max(w, h); 113 options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); 114 } 115 116 options.inJustDecodeBounds = false; 117 118 Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options); 119 if (result == null) return null; 120 121 // We need to resize down if the decoder does not support inSampleSize 122 // (For example, GIF images) 123 float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL 124 ? Math.min(result.getWidth(), result.getHeight()) 125 : Math.max(result.getWidth(), result.getHeight())); 126 127 if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true); 128 return ensureGLCompatibleBitmap(result); 129 } 130 131 /** 132 * Decodes the bitmap from the given byte array if the image size is larger than the given 133 * requirement. 134 * 135 * Note: The returned image may be resized down. However, both width and height must be 136 * larger than the <code>targetSize</code>. 137 */ 138 public static Bitmap decodeIfBigEnough(JobContext jc, byte[] data, 139 Options options, int targetSize) { 140 if (options == null) options = new Options(); 141 jc.setCancelListener(new DecodeCanceller(options)); 142 143 options.inJustDecodeBounds = true; 144 BitmapFactory.decodeByteArray(data, 0, data.length, options); 145 if (jc.isCancelled()) return null; 146 if (options.outWidth < targetSize || options.outHeight < targetSize) { 147 return null; 148 } 149 options.inSampleSize = BitmapUtils.computeSampleSizeLarger( 150 options.outWidth, options.outHeight, targetSize); 151 options.inJustDecodeBounds = false; 152 return ensureGLCompatibleBitmap( 153 BitmapFactory.decodeByteArray(data, 0, data.length, options)); 154 } 155 156 // TODO: This function should not be called directly from 157 // DecodeUtils.requestDecode(...), since we don't have the knowledge 158 // if the bitmap will be uploaded to GL. 159 public static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) { 160 if (bitmap == null || bitmap.getConfig() != null) return bitmap; 161 Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false); 162 bitmap.recycle(); 163 return newBitmap; 164 } 165 166 public static BitmapRegionDecoder createBitmapRegionDecoder( 167 JobContext jc, byte[] bytes, int offset, int length, 168 boolean shareable) { 169 if (offset < 0 || length <= 0 || offset + length > bytes.length) { 170 throw new IllegalArgumentException(String.format( 171 "offset = %s, length = %s, bytes = %s", 172 offset, length, bytes.length)); 173 } 174 175 try { 176 return BitmapRegionDecoder.newInstance( 177 bytes, offset, length, shareable); 178 } catch (Throwable t) { 179 Log.w(TAG, t); 180 return null; 181 } 182 } 183 184 public static BitmapRegionDecoder createBitmapRegionDecoder( 185 JobContext jc, String filePath, boolean shareable) { 186 try { 187 return BitmapRegionDecoder.newInstance(filePath, shareable); 188 } catch (Throwable t) { 189 Log.w(TAG, t); 190 return null; 191 } 192 } 193 194 public static BitmapRegionDecoder createBitmapRegionDecoder( 195 JobContext jc, FileDescriptor fd, boolean shareable) { 196 try { 197 return BitmapRegionDecoder.newInstance(fd, shareable); 198 } catch (Throwable t) { 199 Log.w(TAG, t); 200 return null; 201 } 202 } 203 204 public static BitmapRegionDecoder createBitmapRegionDecoder( 205 JobContext jc, InputStream is, boolean shareable) { 206 try { 207 return BitmapRegionDecoder.newInstance(is, shareable); 208 } catch (Throwable t) { 209 // We often cancel the creating of bitmap region decoder, 210 // so just log one line. 211 Log.w(TAG, "requestCreateBitmapRegionDecoder: " + t); 212 return null; 213 } 214 } 215} 216