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