Home | History | Annotate | Download | only in photos
      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 {
     46     int getWidth();
     47     int getHeight();
     48     Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options);
     49 }
     50 
     51 class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder {
     52     BitmapRegionDecoder mDecoder;
     53     private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) {
     54         mDecoder = decoder;
     55     }
     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     }
     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     }
     82     public int getWidth() {
     83         return mDecoder.getWidth();
     84     }
     85     public int getHeight() {
     86         return mDecoder.getHeight();
     87     }
     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;
     97     private DumbBitmapRegionDecoder(Bitmap b) {
     98         mBuffer = b;
     99     }
    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     }
    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     }
    114     public int getWidth() {
    115         return mBuffer.getWidth();
    116     }
    117     public int getHeight() {
    118         return mBuffer.getHeight();
    119     }
    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 
    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 
    219         public State getLoadingState() {
    220             return mState;
    221         }
    222 
    223         public SimpleBitmapRegionDecoder getBitmapRegionDecoder() {
    224             return mDecoder;
    225         }
    226 
    227         public Bitmap getPreviewBitmap() {
    228             return mPreview;
    229         }
    230 
    231         public int getRotation() {
    232             return mRotation;
    233         }
    234 
    235         public abstract boolean readExif(ExifInterface ei);
    236         public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
    237         public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
    238 
    239         public interface InBitmapProvider {
    240             Bitmap forPixelCount(int count);
    241         }
    242     }
    243 
    244     public static class FilePathBitmapSource extends BitmapSource {
    245         private String mPath;
    246         public FilePathBitmapSource(String path) {
    247             mPath = path;
    248         }
    249         @Override
    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
    259         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
    260             return BitmapFactory.decodeFile(mPath, options);
    261         }
    262         @Override
    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;
    280         public UriBitmapSource(Context context, Uri uri) {
    281             mContext = context;
    282             mUri = uri;
    283         }
    284         private InputStream regenerateInputStream() throws FileNotFoundException {
    285             InputStream is = mContext.getContentResolver().openInputStream(mUri);
    286             return new BufferedInputStream(is);
    287         }
    288         @Override
    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
    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
    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;
    344         public ResourceBitmapSource(Resources res, int resId) {
    345             mRes = res;
    346             mResId = resId;
    347         }
    348         private InputStream regenerateInputStream() {
    349             InputStream is = mRes.openRawResource(mResId);
    350             return new BufferedInputStream(is);
    351         }
    352         @Override
    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
    366         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
    367             return BitmapFactory.decodeResource(mRes, mResId, options);
    368         }
    369         @Override
    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 
    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 
    420     public Bitmap getBitmap() {
    421         return mPreview instanceof BitmapTexture ? ((BitmapTexture) mPreview).getBitmap() : null;
    422     }
    423 
    424     @Override
    425     public int getTileSize() {
    426         return mTileSize;
    427     }
    428 
    429     @Override
    430     public int getImageWidth() {
    431         return mWidth;
    432     }
    433 
    434     @Override
    435     public int getImageHeight() {
    436         return mHeight;
    437     }
    438 
    439     @Override
    440     public BasicTexture getPreview() {
    441         return mPreview;
    442     }
    443 
    444     @Override
    445     public int getRotation() {
    446         return mRotation;
    447     }
    448 
    449     @Override
    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