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