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