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         /** Returns whether loading was successful. */
    163         public boolean loadInBackground(InBitmapProvider bitmapProvider) {
    164             ExifInterface ei = new ExifInterface();
    165             if (readExif(ei)) {
    166                 Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
    167                 if (ori != null) {
    168                     mRotation = ExifInterface.getRotationForOrientationValue(ori.shortValue());
    169                 }
    170             }
    171             mDecoder = loadBitmapRegionDecoder();
    172             if (mDecoder == null) {
    173                 mState = State.ERROR_LOADING;
    174                 return false;
    175             } else {
    176                 int width = mDecoder.getWidth();
    177                 int height = mDecoder.getHeight();
    178 
    179                 BitmapFactory.Options opts = new BitmapFactory.Options();
    180                 opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
    181                 opts.inPreferQualityOverSpeed = true;
    182 
    183                 float scale = (float) MAX_PREVIEW_SIZE / Math.max(width, height);
    184                 opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
    185                 opts.inJustDecodeBounds = false;
    186                 opts.inMutable = true;
    187 
    188                 if (bitmapProvider != null) {
    189                     int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize);
    190                     Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles);
    191                     if (reusableBitmap != null) {
    192                         // Try loading with reusable bitmap
    193                         opts.inBitmap = reusableBitmap;
    194                         try {
    195                             mPreview = loadPreviewBitmap(opts);
    196                         } catch (IllegalArgumentException e) {
    197                             Log.d(TAG, "Unable to reuse bitmap", e);
    198                             opts.inBitmap = null;
    199                             mPreview = null;
    200                         }
    201                     }
    202                 }
    203                 if (mPreview == null) {
    204                     mPreview = loadPreviewBitmap(opts);
    205                 }
    206                 if (mPreview == null) {
    207                     mState = State.ERROR_LOADING;
    208                     return false;
    209                 }
    210 
    211                 // Verify that the bitmap can be used on GL surface
    212                 try {
    213                     GLUtils.getInternalFormat(mPreview);
    214                     GLUtils.getType(mPreview);
    215                     mState = State.LOADED;
    216                 } catch (IllegalArgumentException e) {
    217                     Log.d(TAG, "Image cannot be rendered on a GL surface", e);
    218                     mState = State.ERROR_LOADING;
    219                 }
    220                 return mState == State.LOADED;
    221             }
    222         }
    223 
    224         public State getLoadingState() {
    225             return mState;
    226         }
    227 
    228         public SimpleBitmapRegionDecoder getBitmapRegionDecoder() {
    229             return mDecoder;
    230         }
    231 
    232         public Bitmap getPreviewBitmap() {
    233             return mPreview;
    234         }
    235 
    236         public int getRotation() {
    237             return mRotation;
    238         }
    239 
    240         public abstract boolean readExif(ExifInterface ei);
    241         public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
    242         public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
    243 
    244         public interface InBitmapProvider {
    245             Bitmap forPixelCount(int count);
    246         }
    247     }
    248 
    249     public static class FilePathBitmapSource extends BitmapSource {
    250         private String mPath;
    251         public FilePathBitmapSource(String path) {
    252             mPath = path;
    253         }
    254         @Override
    255         public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
    256             SimpleBitmapRegionDecoder d;
    257             d = SimpleBitmapRegionDecoderWrapper.newInstance(mPath, true);
    258             if (d == null) {
    259                 d = DumbBitmapRegionDecoder.newInstance(mPath);
    260             }
    261             return d;
    262         }
    263         @Override
    264         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
    265             return BitmapFactory.decodeFile(mPath, options);
    266         }
    267         @Override
    268         public boolean readExif(ExifInterface ei) {
    269             try {
    270                 ei.readExif(mPath);
    271                 return true;
    272             } catch (NullPointerException e) {
    273                 Log.w("BitmapRegionTileSource", "reading exif failed", e);
    274                 return false;
    275             } catch (IOException e) {
    276                 Log.w("BitmapRegionTileSource", "getting decoder failed", e);
    277                 return false;
    278             }
    279         }
    280     }
    281 
    282     public static class UriBitmapSource extends BitmapSource {
    283         private Context mContext;
    284         private Uri mUri;
    285         public UriBitmapSource(Context context, Uri uri) {
    286             mContext = context;
    287             mUri = uri;
    288         }
    289         private InputStream regenerateInputStream() throws FileNotFoundException {
    290             InputStream is = mContext.getContentResolver().openInputStream(mUri);
    291             return new BufferedInputStream(is);
    292         }
    293         @Override
    294         public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
    295             try {
    296                 InputStream is = regenerateInputStream();
    297                 SimpleBitmapRegionDecoder regionDecoder =
    298                         SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
    299                 Utils.closeSilently(is);
    300                 if (regionDecoder == null) {
    301                     is = regenerateInputStream();
    302                     regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
    303                     Utils.closeSilently(is);
    304                 }
    305                 return regionDecoder;
    306             } catch (FileNotFoundException e) {
    307                 Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
    308                 return null;
    309             }
    310         }
    311         @Override
    312         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
    313             try {
    314                 InputStream is = regenerateInputStream();
    315                 Bitmap b = BitmapFactory.decodeStream(is, null, options);
    316                 Utils.closeSilently(is);
    317                 return b;
    318             } catch (FileNotFoundException | OutOfMemoryError e) {
    319                 Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
    320                 return null;
    321             }
    322         }
    323         @Override
    324         public boolean readExif(ExifInterface ei) {
    325             InputStream is = null;
    326             try {
    327                 is = regenerateInputStream();
    328                 ei.readExif(is);
    329                 Utils.closeSilently(is);
    330                 return true;
    331             } catch (FileNotFoundException e) {
    332                 Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
    333                 return false;
    334             } catch (IOException e) {
    335                 Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
    336                 return false;
    337             } catch (NullPointerException e) {
    338                 Log.d("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
    339                 return false;
    340             } finally {
    341                 Utils.closeSilently(is);
    342             }
    343         }
    344     }
    345 
    346     public static class ResourceBitmapSource extends BitmapSource {
    347         private Resources mRes;
    348         private int mResId;
    349         public ResourceBitmapSource(Resources res, int resId) {
    350             mRes = res;
    351             mResId = resId;
    352         }
    353         private InputStream regenerateInputStream() {
    354             InputStream is = mRes.openRawResource(mResId);
    355             return new BufferedInputStream(is);
    356         }
    357         @Override
    358         public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
    359             InputStream is = regenerateInputStream();
    360             SimpleBitmapRegionDecoder regionDecoder =
    361                     SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
    362             Utils.closeSilently(is);
    363             if (regionDecoder == null) {
    364                 is = regenerateInputStream();
    365                 regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
    366                 Utils.closeSilently(is);
    367             }
    368             return regionDecoder;
    369         }
    370         @Override
    371         public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
    372             return BitmapFactory.decodeResource(mRes, mResId, options);
    373         }
    374         @Override
    375         public boolean readExif(ExifInterface ei) {
    376             try {
    377                 InputStream is = regenerateInputStream();
    378                 ei.readExif(is);
    379                 Utils.closeSilently(is);
    380                 return true;
    381             } catch (IOException e) {
    382                 Log.e("BitmapRegionTileSource", "Error reading resource", e);
    383                 return false;
    384             }
    385         }
    386     }
    387 
    388     SimpleBitmapRegionDecoder mDecoder;
    389     int mWidth;
    390     int mHeight;
    391     int mTileSize;
    392     private BasicTexture mPreview;
    393     private final int mRotation;
    394 
    395     // For use only by getTile
    396     private Rect mWantRegion = new Rect();
    397     private BitmapFactory.Options mOptions;
    398 
    399     public BitmapRegionTileSource(Context context, BitmapSource source, byte[] tempStorage) {
    400         mTileSize = TiledImageRenderer.suggestedTileSize(context);
    401         mRotation = source.getRotation();
    402         mDecoder = source.getBitmapRegionDecoder();
    403         if (mDecoder != null) {
    404             mWidth = mDecoder.getWidth();
    405             mHeight = mDecoder.getHeight();
    406             mOptions = new BitmapFactory.Options();
    407             mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
    408             mOptions.inPreferQualityOverSpeed = true;
    409             mOptions.inTempStorage = tempStorage;
    410 
    411             Bitmap preview = source.getPreviewBitmap();
    412             if (preview != null &&
    413                     preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
    414                     mPreview = new BitmapTexture(preview);
    415             } else {
    416                 Log.w(TAG, String.format(
    417                         "Failed to create preview of apropriate size! "
    418                         + " in: %dx%d, out: %dx%d",
    419                         mWidth, mHeight,
    420                         preview == null ? -1 : preview.getWidth(),
    421                         preview == null ? -1 : preview.getHeight()));
    422             }
    423         }
    424     }
    425 
    426     public Bitmap getBitmap() {
    427         return mPreview instanceof BitmapTexture ? ((BitmapTexture) mPreview).getBitmap() : null;
    428     }
    429 
    430     @Override
    431     public int getTileSize() {
    432         return mTileSize;
    433     }
    434 
    435     @Override
    436     public int getImageWidth() {
    437         return mWidth;
    438     }
    439 
    440     @Override
    441     public int getImageHeight() {
    442         return mHeight;
    443     }
    444 
    445     @Override
    446     public BasicTexture getPreview() {
    447         return mPreview;
    448     }
    449 
    450     @Override
    451     public int getRotation() {
    452         return mRotation;
    453     }
    454 
    455     @Override
    456     public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
    457         int tileSize = getTileSize();
    458         int t = tileSize << level;
    459         mWantRegion.set(x, y, x + t, y + t);
    460 
    461         if (bitmap == null) {
    462             bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
    463         }
    464 
    465         mOptions.inSampleSize = (1 << level);
    466         mOptions.inBitmap = bitmap;
    467 
    468         try {
    469             bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
    470         } finally {
    471             if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
    472                 mOptions.inBitmap = null;
    473             }
    474         }
    475 
    476         if (bitmap == null) {
    477             Log.w("BitmapRegionTileSource", "fail in decoding region");
    478         }
    479         return bitmap;
    480     }
    481 }
    482