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 com.android.gallery3d.common.BitmapUtils;
20import com.android.gallery3d.common.Utils;
21import com.android.gallery3d.util.ThreadPool.CancelListener;
22import com.android.gallery3d.util.ThreadPool.JobContext;
23
24import android.content.ContentResolver;
25import android.graphics.Bitmap;
26import android.graphics.Bitmap.Config;
27import android.graphics.BitmapFactory;
28import android.graphics.BitmapFactory.Options;
29import android.graphics.BitmapRegionDecoder;
30import android.graphics.Rect;
31import android.net.Uri;
32import android.os.ParcelFileDescriptor;
33
34import java.io.FileDescriptor;
35import java.io.FileInputStream;
36import java.io.InputStream;
37
38public class DecodeUtils {
39    private static final String TAG = "DecodeService";
40
41    private static class DecodeCanceller implements CancelListener {
42        Options mOptions;
43        public DecodeCanceller(Options options) {
44            mOptions = options;
45        }
46        public void onCancel() {
47            mOptions.requestCancelDecode();
48        }
49    }
50
51    public static Bitmap requestDecode(JobContext jc, final String filePath,
52            Options options) {
53        if (options == null) options = new Options();
54        jc.setCancelListener(new DecodeCanceller(options));
55        return ensureGLCompatibleBitmap(
56                BitmapFactory.decodeFile(filePath, options));
57    }
58
59    public static Bitmap requestDecode(JobContext jc, FileDescriptor fd, Options options) {
60        if (options == null) options = new Options();
61        jc.setCancelListener(new DecodeCanceller(options));
62        return ensureGLCompatibleBitmap(
63                BitmapFactory.decodeFileDescriptor(fd, null, options));
64    }
65
66    public static Bitmap requestDecode(JobContext jc, byte[] bytes,
67            Options options) {
68        return requestDecode(jc, bytes, 0, bytes.length, options);
69    }
70
71    public static Bitmap requestDecode(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 Bitmap requestDecode(JobContext jc, final String filePath,
80            Options options, int targetSize) {
81        FileInputStream fis = null;
82        try {
83            fis = new FileInputStream(filePath);
84            FileDescriptor fd = fis.getFD();
85            return requestDecode(jc, fd, options, targetSize);
86        } catch (Exception ex) {
87            Log.w(TAG, ex);
88            return null;
89        } finally {
90            Utils.closeSilently(fis);
91        }
92    }
93
94    public static Bitmap requestDecode(JobContext jc, FileDescriptor fd,
95            Options options, int targetSize) {
96        if (options == null) options = new Options();
97        jc.setCancelListener(new DecodeCanceller(options));
98
99        options.inJustDecodeBounds = true;
100        BitmapFactory.decodeFileDescriptor(fd, null, options);
101        if (jc.isCancelled()) return null;
102
103        options.inSampleSize = BitmapUtils.computeSampleSizeLarger(
104                options.outWidth, options.outHeight, targetSize);
105        options.inJustDecodeBounds = false;
106
107        Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
108        // We need to resize down if the decoder does not support inSampleSize.
109        // (For example, GIF images.)
110        result = BitmapUtils.resizeDownIfTooBig(result, targetSize, true);
111        return ensureGLCompatibleBitmap(result);
112    }
113
114    /**
115     * Decodes the bitmap from the given byte array if the image size is larger than the given
116     * requirement.
117     *
118     * Note: The returned image may be resized down. However, both width and height must be
119     * larger than the <code>targetSize</code>.
120     */
121    public static Bitmap requestDecodeIfBigEnough(JobContext jc, byte[] data,
122            Options options, int targetSize) {
123        if (options == null) options = new Options();
124        jc.setCancelListener(new DecodeCanceller(options));
125
126        options.inJustDecodeBounds = true;
127        BitmapFactory.decodeByteArray(data, 0, data.length, options);
128        if (jc.isCancelled()) return null;
129        if (options.outWidth < targetSize || options.outHeight < targetSize) {
130            return null;
131        }
132        options.inSampleSize = BitmapUtils.computeSampleSizeLarger(
133                options.outWidth, options.outHeight, targetSize);
134        options.inJustDecodeBounds = false;
135        return ensureGLCompatibleBitmap(
136                BitmapFactory.decodeByteArray(data, 0, data.length, options));
137    }
138
139    public static Bitmap requestDecode(JobContext jc,
140            FileDescriptor fileDescriptor, Rect paddings, Options options) {
141        if (options == null) options = new Options();
142        jc.setCancelListener(new DecodeCanceller(options));
143        return ensureGLCompatibleBitmap(BitmapFactory.decodeFileDescriptor
144                (fileDescriptor, paddings, options));
145    }
146
147    // TODO: This function should not be called directly from
148    // DecodeUtils.requestDecode(...), since we don't have the knowledge
149    // if the bitmap will be uploaded to GL.
150    public static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
151        if (bitmap == null || bitmap.getConfig() != null) return bitmap;
152        Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
153        bitmap.recycle();
154        return newBitmap;
155    }
156
157    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
158            JobContext jc, byte[] bytes, int offset, int length,
159            boolean shareable) {
160        if (offset < 0 || length <= 0 || offset + length > bytes.length) {
161            throw new IllegalArgumentException(String.format(
162                    "offset = %s, length = %s, bytes = %s",
163                    offset, length, bytes.length));
164        }
165
166        try {
167            return BitmapRegionDecoder.newInstance(
168                    bytes, offset, length, shareable);
169        } catch (Throwable t)  {
170            Log.w(TAG, t);
171            return null;
172        }
173    }
174
175    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
176            JobContext jc, String filePath, boolean shareable) {
177        try {
178            return BitmapRegionDecoder.newInstance(filePath, shareable);
179        } catch (Throwable t)  {
180            Log.w(TAG, t);
181            return null;
182        }
183    }
184
185    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
186            JobContext jc, FileDescriptor fd, boolean shareable) {
187        try {
188            return BitmapRegionDecoder.newInstance(fd, shareable);
189        } catch (Throwable t)  {
190            Log.w(TAG, t);
191            return null;
192        }
193    }
194
195    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
196            JobContext jc, InputStream is, boolean shareable) {
197        try {
198            return BitmapRegionDecoder.newInstance(is, shareable);
199        } catch (Throwable t)  {
200            // We often cancel the creating of bitmap region decoder,
201            // so just log one line.
202            Log.w(TAG, "requestCreateBitmapRegionDecoder: " + t);
203            return null;
204        }
205    }
206
207    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
208            JobContext jc, Uri uri, ContentResolver resolver,
209            boolean shareable) {
210        ParcelFileDescriptor pfd = null;
211        try {
212            pfd = resolver.openFileDescriptor(uri, "r");
213            return BitmapRegionDecoder.newInstance(
214                    pfd.getFileDescriptor(), shareable);
215        } catch (Throwable t) {
216            Log.w(TAG, t);
217            return null;
218        } finally {
219            Utils.closeSilently(pfd);
220        }
221    }
222}
223