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