diff options
Diffstat (limited to 'src/com/android/gallery3d/data/DecodeUtils.java')
-rw-r--r-- | src/com/android/gallery3d/data/DecodeUtils.java | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java new file mode 100644 index 000000000..fa709157d --- /dev/null +++ b/src/com/android/gallery3d/data/DecodeUtils.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.data; + +import android.annotation.TargetApi; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.BitmapFactory; +import android.graphics.BitmapFactory.Options; +import android.graphics.BitmapRegionDecoder; +import android.os.Build; +import android.util.FloatMath; + +import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.common.BitmapUtils; +import com.android.gallery3d.common.Utils; +import com.android.photos.data.GalleryBitmapPool; +import com.android.gallery3d.ui.Log; +import com.android.gallery3d.util.ThreadPool.CancelListener; +import com.android.gallery3d.util.ThreadPool.JobContext; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.InputStream; + +public class DecodeUtils { + private static final String TAG = "DecodeUtils"; + + private static class DecodeCanceller implements CancelListener { + Options mOptions; + + public DecodeCanceller(Options options) { + mOptions = options; + } + + @Override + public void onCancel() { + mOptions.requestCancelDecode(); + } + } + + @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) + public static void setOptionsMutable(Options options) { + if (ApiHelper.HAS_OPTIONS_IN_MUTABLE) options.inMutable = true; + } + + public static Bitmap decode(JobContext jc, FileDescriptor fd, Options options) { + if (options == null) options = new Options(); + jc.setCancelListener(new DecodeCanceller(options)); + setOptionsMutable(options); + return ensureGLCompatibleBitmap( + BitmapFactory.decodeFileDescriptor(fd, null, options)); + } + + public static void decodeBounds(JobContext jc, FileDescriptor fd, + Options options) { + Utils.assertTrue(options != null); + options.inJustDecodeBounds = true; + jc.setCancelListener(new DecodeCanceller(options)); + BitmapFactory.decodeFileDescriptor(fd, null, options); + options.inJustDecodeBounds = false; + } + + public static Bitmap decode(JobContext jc, byte[] bytes, Options options) { + return decode(jc, bytes, 0, bytes.length, options); + } + + public static Bitmap decode(JobContext jc, byte[] bytes, int offset, + int length, Options options) { + if (options == null) options = new Options(); + jc.setCancelListener(new DecodeCanceller(options)); + setOptionsMutable(options); + return ensureGLCompatibleBitmap( + BitmapFactory.decodeByteArray(bytes, offset, length, options)); + } + + public static void decodeBounds(JobContext jc, byte[] bytes, int offset, + int length, Options options) { + Utils.assertTrue(options != null); + options.inJustDecodeBounds = true; + jc.setCancelListener(new DecodeCanceller(options)); + BitmapFactory.decodeByteArray(bytes, offset, length, options); + options.inJustDecodeBounds = false; + } + + public static Bitmap decodeThumbnail( + JobContext jc, String filePath, Options options, int targetSize, int type) { + FileInputStream fis = null; + try { + fis = new FileInputStream(filePath); + FileDescriptor fd = fis.getFD(); + return decodeThumbnail(jc, fd, options, targetSize, type); + } catch (Exception ex) { + Log.w(TAG, ex); + return null; + } finally { + Utils.closeSilently(fis); + } + } + + public static Bitmap decodeThumbnail( + JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) { + if (options == null) options = new Options(); + jc.setCancelListener(new DecodeCanceller(options)); + + options.inJustDecodeBounds = true; + BitmapFactory.decodeFileDescriptor(fd, null, options); + if (jc.isCancelled()) return null; + + int w = options.outWidth; + int h = options.outHeight; + + if (type == MediaItem.TYPE_MICROTHUMBNAIL) { + // We center-crop the original image as it's micro thumbnail. In this case, + // we want to make sure the shorter side >= "targetSize". + float scale = (float) targetSize / Math.min(w, h); + options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); + + // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding + // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here. + final int MAX_PIXEL_COUNT = 640000; // 400 x 1600 + if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) { + options.inSampleSize = BitmapUtils.computeSampleSize( + FloatMath.sqrt((float) MAX_PIXEL_COUNT / (w * h))); + } + } else { + // For screen nail, we only want to keep the longer side >= targetSize. + float scale = (float) targetSize / Math.max(w, h); + options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); + } + + options.inJustDecodeBounds = false; + setOptionsMutable(options); + + Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options); + if (result == null) return null; + + // We need to resize down if the decoder does not support inSampleSize + // (For example, GIF images) + float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL + ? Math.min(result.getWidth(), result.getHeight()) + : Math.max(result.getWidth(), result.getHeight())); + + if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true); + return ensureGLCompatibleBitmap(result); + } + + /** + * Decodes the bitmap from the given byte array if the image size is larger than the given + * requirement. + * + * Note: The returned image may be resized down. However, both width and height must be + * larger than the <code>targetSize</code>. + */ + public static Bitmap decodeIfBigEnough(JobContext jc, byte[] data, + Options options, int targetSize) { + if (options == null) options = new Options(); + jc.setCancelListener(new DecodeCanceller(options)); + + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(data, 0, data.length, options); + if (jc.isCancelled()) return null; + if (options.outWidth < targetSize || options.outHeight < targetSize) { + return null; + } + options.inSampleSize = BitmapUtils.computeSampleSizeLarger( + options.outWidth, options.outHeight, targetSize); + options.inJustDecodeBounds = false; + setOptionsMutable(options); + + return ensureGLCompatibleBitmap( + BitmapFactory.decodeByteArray(data, 0, data.length, options)); + } + + // TODO: This function should not be called directly from + // DecodeUtils.requestDecode(...), since we don't have the knowledge + // if the bitmap will be uploaded to GL. + public static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) { + if (bitmap == null || bitmap.getConfig() != null) return bitmap; + Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false); + bitmap.recycle(); + return newBitmap; + } + + public static BitmapRegionDecoder createBitmapRegionDecoder( + JobContext jc, byte[] bytes, int offset, int length, + boolean shareable) { + if (offset < 0 || length <= 0 || offset + length > bytes.length) { + throw new IllegalArgumentException(String.format( + "offset = %s, length = %s, bytes = %s", + offset, length, bytes.length)); + } + + try { + return BitmapRegionDecoder.newInstance( + bytes, offset, length, shareable); + } catch (Throwable t) { + Log.w(TAG, t); + return null; + } + } + + public static BitmapRegionDecoder createBitmapRegionDecoder( + JobContext jc, String filePath, boolean shareable) { + try { + return BitmapRegionDecoder.newInstance(filePath, shareable); + } catch (Throwable t) { + Log.w(TAG, t); + return null; + } + } + + public static BitmapRegionDecoder createBitmapRegionDecoder( + JobContext jc, FileDescriptor fd, boolean shareable) { + try { + return BitmapRegionDecoder.newInstance(fd, shareable); + } catch (Throwable t) { + Log.w(TAG, t); + return null; + } + } + + public static BitmapRegionDecoder createBitmapRegionDecoder( + JobContext jc, InputStream is, boolean shareable) { + try { + return BitmapRegionDecoder.newInstance(is, shareable); + } catch (Throwable t) { + // We often cancel the creating of bitmap region decoder, + // so just log one line. + Log.w(TAG, "requestCreateBitmapRegionDecoder: " + t); + return null; + } + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static Bitmap decodeUsingPool(JobContext jc, byte[] data, int offset, + int length, BitmapFactory.Options options) { + if (options == null) options = new BitmapFactory.Options(); + if (options.inSampleSize < 1) options.inSampleSize = 1; + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + options.inBitmap = (options.inSampleSize == 1) + ? findCachedBitmap(jc, data, offset, length, options) : null; + try { + Bitmap bitmap = decode(jc, data, offset, length, options); + if (options.inBitmap != null && options.inBitmap != bitmap) { + GalleryBitmapPool.getInstance().put(options.inBitmap); + options.inBitmap = null; + } + return bitmap; + } catch (IllegalArgumentException e) { + if (options.inBitmap == null) throw e; + + Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap"); + GalleryBitmapPool.getInstance().put(options.inBitmap); + options.inBitmap = null; + return decode(jc, data, offset, length, options); + } + } + + // This is the same as the method above except the source data comes + // from a file descriptor instead of a byte array. + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static Bitmap decodeUsingPool(JobContext jc, + FileDescriptor fileDescriptor, Options options) { + if (options == null) options = new BitmapFactory.Options(); + if (options.inSampleSize < 1) options.inSampleSize = 1; + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + options.inBitmap = (options.inSampleSize == 1) + ? findCachedBitmap(jc, fileDescriptor, options) : null; + try { + Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options); + if (options.inBitmap != null && options.inBitmap != bitmap) { + GalleryBitmapPool.getInstance().put(options.inBitmap); + options.inBitmap = null; + } + return bitmap; + } catch (IllegalArgumentException e) { + if (options.inBitmap == null) throw e; + + Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap"); + GalleryBitmapPool.getInstance().put(options.inBitmap); + options.inBitmap = null; + return decode(jc, fileDescriptor, options); + } + } + + private static Bitmap findCachedBitmap(JobContext jc, byte[] data, + int offset, int length, Options options) { + decodeBounds(jc, data, offset, length, options); + return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight); + } + + private static Bitmap findCachedBitmap(JobContext jc, FileDescriptor fileDescriptor, + Options options) { + decodeBounds(jc, fileDescriptor, options); + return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight); + } +} |