• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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.photos;
18 
19 import android.annotation.TargetApi;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.BitmapFactory;
24 import android.graphics.BitmapRegionDecoder;
25 import android.graphics.Canvas;
26 import android.graphics.Paint;
27 import android.graphics.Rect;
28 import android.net.Uri;
29 import android.opengl.GLUtils;
30 import android.os.Build;
31 import android.util.Log;
32 
33 import com.android.gallery3d.common.BitmapUtils;
34 import com.android.gallery3d.common.Utils;
35 import com.android.gallery3d.exif.ExifInterface;
36 import com.android.gallery3d.glrenderer.BasicTexture;
37 import com.android.gallery3d.glrenderer.BitmapTexture;
38 import com.android.photos.views.TiledImageRenderer;
39 
40 import java.io.BufferedInputStream;
41 import java.io.FileNotFoundException;
42 import java.io.IOException;
43 import java.io.InputStream;
44 
45 interface SimpleBitmapRegionDecoder {
getWidth()46     int getWidth();
getHeight()47     int getHeight();
decodeRegion(Rect wantRegion, BitmapFactory.Options options)48     Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options);
49 }
50 
51 class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder {
52     BitmapRegionDecoder mDecoder;
SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder)53     private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) {
54         mDecoder = decoder;
55     }
newInstance( String pathName, boolean isShareable)56     public static SimpleBitmapRegionDecoderWrapper newInstance(
57             String pathName, boolean isShareable) {
58         try {
59             BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(pathName, isShareable);
60             if (d != null) {
61                 return new SimpleBitmapRegionDecoderWrapper(d);
62             }
63         } catch (IOException e) {
64             Log.w("BitmapRegionTileSource", "getting decoder failed for path " + pathName, e);
65             return null;
66         }
67         return null;
68     }
newInstance( InputStream is, boolean isShareable)69     public static SimpleBitmapRegionDecoderWrapper newInstance(
70             InputStream is, boolean isShareable) {
71         try {
72             BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable);
73             if (d != null) {
74                 return new SimpleBitmapRegionDecoderWrapper(d);
75             }
76         } catch (IOException e) {
77             Log.w("BitmapRegionTileSource", "getting decoder failed", e);
78             return null;
79         }
80         return null;
81     }
getWidth()82     public int getWidth() {
83         return mDecoder.getWidth();
84     }
getHeight()85     public int getHeight() {
86         return mDecoder.getHeight();
87     }
decodeRegion(Rect wantRegion, BitmapFactory.Options options)88     public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
89         return mDecoder.decodeRegion(wantRegion, options);
90     }
91 }
92 
93 class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder {
94     Bitmap mBuffer;
95     Canvas mTempCanvas;
96     Paint mTempPaint;
DumbBitmapRegionDecoder(Bitmap b)97     private DumbBitmapRegionDecoder(Bitmap b) {
98         mBuffer = b;
99     }
newInstance(String pathName)100     public static DumbBitmapRegionDecoder newInstance(String pathName) {
101         Bitmap b = BitmapFactory.decodeFile(pathName);
102         if (b != null) {
103             return new DumbBitmapRegionDecoder(b);
104         }
105         return null;
106     }
newInstance(InputStream is)107     public static DumbBitmapRegionDecoder newInstance(InputStream is) {
108         Bitmap b = BitmapFactory.decodeStream(is);
109         if (b != null) {
110             return new DumbBitmapRegionDecoder(b);
111         }
112         return null;
113     }
getWidth()114     public int getWidth() {
115         return mBuffer.getWidth();
116     }
getHeight()117     public int getHeight() {
118         return mBuffer.getHeight();
119     }
decodeRegion(Rect wantRegion, BitmapFactory.Options options)120     public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
121         if (mTempCanvas == null) {
122             mTempCanvas = new Canvas();
123             mTempPaint = new Paint();
124             mTempPaint.setFilterBitmap(true);
125         }
126         int sampleSize = Math.max(options.inSampleSize, 1);
127         Bitmap newBitmap = Bitmap.createBitmap(
128                 wantRegion.width() / sampleSize,
129                 wantRegion.height() / sampleSize,
130                 Bitmap.Config.ARGB_8888);
131         mTempCanvas.setBitmap(newBitmap);
132         mTempCanvas.save();
133         mTempCanvas.scale(1f / sampleSize, 1f / sampleSize);
134         mTempCanvas.drawBitmap(mBuffer, -wantRegion.left, -wantRegion.top, mTempPaint);
135         mTempCanvas.restore();
136         mTempCanvas.setBitmap(null);
137         return newBitmap;
138     }
139 }
140 
141 /**
142  * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
143  * {@link BitmapRegionDecoder} to wrap a local file
144  */
145 @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
146 public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
147 
148     private static final String TAG = "BitmapRegionTileSource";
149 
150     private static final int GL_SIZE_LIMIT = 2048;
151     // This must be no larger than half the size of the GL_SIZE_LIMIT
152     // due to decodePreview being allowed to be up to 2x the size of the target
153     private static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;
154 
155     public static abstract class BitmapSource {
156         private SimpleBitmapRegionDecoder mDecoder;
157         private Bitmap mPreview;
158         private int mRotation;
159         public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
160         private State mState = State.NOT_LOADED;
161 
loadInBackground(InBitmapProvider bitmapProvider)162         public boolean loadInBackground(InBitmapProvider bitmapProvider) {
163             ExifInterface ei = new ExifInterface();
164             if (readExif(ei)) {
165                 Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
166                 if (ori != null) {
167                     mRotation = ExifInterface.getRotationForOrientationValue(ori.shortValue());
168                 }
169             }
170             mDecoder = loadBitmapRegionDecoder();
171             if (mDecoder == null) {
172                 mState = State.ERROR_LOADING;
173                 return false;
174             } else {
175                 int width = mDecoder.getWidth();
176                 int height = mDecoder.getHeight();
177 
178                 BitmapFactory.Options opts = new BitmapFactory.Options();
179                 opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
180                 opts.inPreferQualityOverSpeed = true;
181 
182                 float scale = (float) MAX_PREVIEW_SIZE / Math.max(width, height);
183                 opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
184                 opts.inJustDecodeBounds = false;
185                 opts.inMutable = true;
186 
187                 if (bitmapProvider != null) {
188                     int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize);
189                     Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles);
190                     if (reusableBitmap != null) {
191                         // Try loading with reusable bitmap
192                         opts.inBitmap = reusableBitmap;
193                         try {
194                             mPreview = loadPreviewBitmap(opts);
195                         } catch (IllegalArgumentException e) {
196                             Log.d(TAG, "Unable to reusage bitmap", e);
197                             opts.inBitmap = null;
198                             mPreview = null;
199                         }
200                     }
201                 }
202                 if (mPreview == null) {
203                     mPreview = loadPreviewBitmap(opts);
204                 }
205 
206                 // Verify that the bitmap can be used on GL surface
207                 try {
208                     GLUtils.getInternalFormat(mPreview);
209                     GLUtils.getType(mPreview);
210                     mState = State.LOADED;
211                 } catch (IllegalArgumentException e) {
212                     Log.d(TAG, "Image cannot be rendered on a GL surface", e);
213                     mState = State.ERROR_LOADING;
214                 }
215                 return true;
216             }
217         }
218 
getLoadingState()219         public State getLoadingState() {
220             return mState;
221         }
222 
getBitmapRegionDecoder()223         public SimpleBitmapRegionDecoder getBitmapRegionDecoder() {
224             return mDecoder;
225         }
226 
getPreviewBitmap()227         public Bitmap getPreviewBitmap() {
228             return mPreview;
229         }
230 
getRotation()231         public int getRotation() {
232             return mRotation;
233         }
234 
readExif(ExifInterface ei)235         public abstract boolean readExif(ExifInterface ei);
loadBitmapRegionDecoder()236         public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
loadPreviewBitmap(BitmapFactory.Options options)237         public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
238 
239         public interface InBitmapProvider {
forPixelCount(int count)240             Bitmap forPixelCount(int count);
241         }
242     }
243 
244     public static class FilePathBitmapSource extends BitmapSource {
245         private String mPath;
FilePathBitmapSource(String path)246         public FilePathBitmapSource(String path) {
247             mPath = path;
248         }
249         @Override
loadBitmapRegionDecoder()250         public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
251             SimpleBitmapRegionDecoder d;
252             d = SimpleBitmapRegionDecoderWrapper.newInstance(mPath, true);
253             if (d == null) {
254                 d = DumbBitmapRegionDecoder.newInstance(mPath);
255             }
256             return d;
257         }
258         @Override
loadPreviewBitmap(BitmapFactory.Options options)259         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
260             return BitmapFactory.decodeFile(mPath, options);
261         }
262         @Override
readExif(ExifInterface ei)263         public boolean readExif(ExifInterface ei) {
264             try {
265                 ei.readExif(mPath);
266                 return true;
267             } catch (NullPointerException e) {
268                 Log.w("BitmapRegionTileSource", "reading exif failed", e);
269                 return false;
270             } catch (IOException e) {
271                 Log.w("BitmapRegionTileSource", "getting decoder failed", e);
272                 return false;
273             }
274         }
275     }
276 
277     public static class UriBitmapSource extends BitmapSource {
278         private Context mContext;
279         private Uri mUri;
UriBitmapSource(Context context, Uri uri)280         public UriBitmapSource(Context context, Uri uri) {
281             mContext = context;
282             mUri = uri;
283         }
regenerateInputStream()284         private InputStream regenerateInputStream() throws FileNotFoundException {
285             InputStream is = mContext.getContentResolver().openInputStream(mUri);
286             return new BufferedInputStream(is);
287         }
288         @Override
loadBitmapRegionDecoder()289         public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
290             try {
291                 InputStream is = regenerateInputStream();
292                 SimpleBitmapRegionDecoder regionDecoder =
293                         SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
294                 Utils.closeSilently(is);
295                 if (regionDecoder == null) {
296                     is = regenerateInputStream();
297                     regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
298                     Utils.closeSilently(is);
299                 }
300                 return regionDecoder;
301             } catch (FileNotFoundException e) {
302                 Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
303                 return null;
304             }
305         }
306         @Override
loadPreviewBitmap(BitmapFactory.Options options)307         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
308             try {
309                 InputStream is = regenerateInputStream();
310                 Bitmap b = BitmapFactory.decodeStream(is, null, options);
311                 Utils.closeSilently(is);
312                 return b;
313             } catch (FileNotFoundException e) {
314                 Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
315                 return null;
316             }
317         }
318         @Override
readExif(ExifInterface ei)319         public boolean readExif(ExifInterface ei) {
320             InputStream is = null;
321             try {
322                 is = regenerateInputStream();
323                 ei.readExif(is);
324                 Utils.closeSilently(is);
325                 return true;
326             } catch (FileNotFoundException e) {
327                 Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
328                 return false;
329             } catch (IOException e) {
330                 Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
331                 return false;
332             } catch (NullPointerException e) {
333                 Log.d("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
334                 return false;
335             } finally {
336                 Utils.closeSilently(is);
337             }
338         }
339     }
340 
341     public static class ResourceBitmapSource extends BitmapSource {
342         private Resources mRes;
343         private int mResId;
ResourceBitmapSource(Resources res, int resId)344         public ResourceBitmapSource(Resources res, int resId) {
345             mRes = res;
346             mResId = resId;
347         }
regenerateInputStream()348         private InputStream regenerateInputStream() {
349             InputStream is = mRes.openRawResource(mResId);
350             return new BufferedInputStream(is);
351         }
352         @Override
loadBitmapRegionDecoder()353         public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
354             InputStream is = regenerateInputStream();
355             SimpleBitmapRegionDecoder regionDecoder =
356                     SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
357             Utils.closeSilently(is);
358             if (regionDecoder == null) {
359                 is = regenerateInputStream();
360                 regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
361                 Utils.closeSilently(is);
362             }
363             return regionDecoder;
364         }
365         @Override
loadPreviewBitmap(BitmapFactory.Options options)366         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
367             return BitmapFactory.decodeResource(mRes, mResId, options);
368         }
369         @Override
readExif(ExifInterface ei)370         public boolean readExif(ExifInterface ei) {
371             try {
372                 InputStream is = regenerateInputStream();
373                 ei.readExif(is);
374                 Utils.closeSilently(is);
375                 return true;
376             } catch (IOException e) {
377                 Log.e("BitmapRegionTileSource", "Error reading resource", e);
378                 return false;
379             }
380         }
381     }
382 
383     SimpleBitmapRegionDecoder mDecoder;
384     int mWidth;
385     int mHeight;
386     int mTileSize;
387     private BasicTexture mPreview;
388     private final int mRotation;
389 
390     // For use only by getTile
391     private Rect mWantRegion = new Rect();
392     private BitmapFactory.Options mOptions;
393 
BitmapRegionTileSource(Context context, BitmapSource source, byte[] tempStorage)394     public BitmapRegionTileSource(Context context, BitmapSource source, byte[] tempStorage) {
395         mTileSize = TiledImageRenderer.suggestedTileSize(context);
396         mRotation = source.getRotation();
397         mDecoder = source.getBitmapRegionDecoder();
398         if (mDecoder != null) {
399             mWidth = mDecoder.getWidth();
400             mHeight = mDecoder.getHeight();
401             mOptions = new BitmapFactory.Options();
402             mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
403             mOptions.inPreferQualityOverSpeed = true;
404             mOptions.inTempStorage = tempStorage;
405 
406             Bitmap preview = source.getPreviewBitmap();
407             if (preview != null &&
408                     preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
409                     mPreview = new BitmapTexture(preview);
410             } else {
411                 Log.w(TAG, String.format(
412                         "Failed to create preview of apropriate size! "
413                         + " in: %dx%d, out: %dx%d",
414                         mWidth, mHeight,
415                         preview.getWidth(), preview.getHeight()));
416             }
417         }
418     }
419 
getBitmap()420     public Bitmap getBitmap() {
421         return mPreview instanceof BitmapTexture ? ((BitmapTexture) mPreview).getBitmap() : null;
422     }
423 
424     @Override
getTileSize()425     public int getTileSize() {
426         return mTileSize;
427     }
428 
429     @Override
getImageWidth()430     public int getImageWidth() {
431         return mWidth;
432     }
433 
434     @Override
getImageHeight()435     public int getImageHeight() {
436         return mHeight;
437     }
438 
439     @Override
getPreview()440     public BasicTexture getPreview() {
441         return mPreview;
442     }
443 
444     @Override
getRotation()445     public int getRotation() {
446         return mRotation;
447     }
448 
449     @Override
getTile(int level, int x, int y, Bitmap bitmap)450     public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
451         int tileSize = getTileSize();
452         int t = tileSize << level;
453         mWantRegion.set(x, y, x + t, y + t);
454 
455         if (bitmap == null) {
456             bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
457         }
458 
459         mOptions.inSampleSize = (1 << level);
460         mOptions.inBitmap = bitmap;
461 
462         try {
463             bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
464         } finally {
465             if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
466                 mOptions.inBitmap = null;
467             }
468         }
469 
470         if (bitmap == null) {
471             Log.w("BitmapRegionTileSource", "fail in decoding region");
472         }
473         return bitmap;
474     }
475 }
476