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.Bitmap.Config;
     24 import android.graphics.BitmapFactory;
     25 import android.graphics.BitmapRegionDecoder;
     26 import android.graphics.Canvas;
     27 import android.graphics.Rect;
     28 import android.net.Uri;
     29 import android.os.Build;
     30 import android.os.Build.VERSION_CODES;
     31 import android.util.Log;
     32 
     33 import com.android.gallery3d.common.BitmapUtils;
     34 import com.android.gallery3d.glrenderer.BasicTexture;
     35 import com.android.gallery3d.glrenderer.BitmapTexture;
     36 import com.android.photos.views.TiledImageRenderer;
     37 
     38 import java.io.BufferedInputStream;
     39 import java.io.IOException;
     40 import java.io.InputStream;
     41 
     42 /**
     43  * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
     44  * {@link BitmapRegionDecoder} to wrap a local file
     45  */
     46 @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
     47 public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
     48 
     49     private static final String TAG = "BitmapRegionTileSource";
     50 
     51     private static final boolean REUSE_BITMAP =
     52             Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
     53     private static final int GL_SIZE_LIMIT = 2048;
     54     // This must be no larger than half the size of the GL_SIZE_LIMIT
     55     // due to decodePreview being allowed to be up to 2x the size of the target
     56     private static final int MAX_PREVIEW_SIZE = 1024;
     57 
     58     BitmapRegionDecoder mDecoder;
     59     int mWidth;
     60     int mHeight;
     61     int mTileSize;
     62     private BasicTexture mPreview;
     63     private final int mRotation;
     64 
     65     // For use only by getTile
     66     private Rect mWantRegion = new Rect();
     67     private Rect mOverlapRegion = new Rect();
     68     private BitmapFactory.Options mOptions;
     69     private Canvas mCanvas;
     70 
     71     public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
     72         this(null, context, path, null, 0, previewSize, rotation);
     73     }
     74 
     75     public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) {
     76         this(null, context, null, uri, 0, previewSize, rotation);
     77     }
     78 
     79     public BitmapRegionTileSource(Resources res,
     80             Context context, int resId, int previewSize, int rotation) {
     81         this(res, context, null, null, resId, previewSize, rotation);
     82     }
     83 
     84     private BitmapRegionTileSource(Resources res,
     85             Context context, String path, Uri uri, int resId, int previewSize, int rotation) {
     86         mTileSize = TiledImageRenderer.suggestedTileSize(context);
     87         mRotation = rotation;
     88         try {
     89             if (path != null) {
     90                 mDecoder = BitmapRegionDecoder.newInstance(path, true);
     91             } else if (uri != null) {
     92                 InputStream is = context.getContentResolver().openInputStream(uri);
     93                 BufferedInputStream bis = new BufferedInputStream(is);
     94                 mDecoder = BitmapRegionDecoder.newInstance(bis, true);
     95             } else {
     96                 InputStream is = res.openRawResource(resId);
     97                 BufferedInputStream bis = new BufferedInputStream(is);
     98                 mDecoder = BitmapRegionDecoder.newInstance(bis, true);
     99             }
    100             mWidth = mDecoder.getWidth();
    101             mHeight = mDecoder.getHeight();
    102         } catch (IOException e) {
    103             Log.w("BitmapRegionTileSource", "ctor failed", e);
    104         }
    105         mOptions = new BitmapFactory.Options();
    106         mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
    107         mOptions.inPreferQualityOverSpeed = true;
    108         mOptions.inTempStorage = new byte[16 * 1024];
    109         if (previewSize != 0) {
    110             previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
    111             // Although this is the same size as the Bitmap that is likely already
    112             // loaded, the lifecycle is different and interactions are on a different
    113             // thread. Thus to simplify, this source will decode its own bitmap.
    114             Bitmap preview = decodePreview(res, context, path, uri, resId, previewSize);
    115             if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
    116                 mPreview = new BitmapTexture(preview);
    117             } else {
    118                 Log.w(TAG, String.format(
    119                         "Failed to create preview of apropriate size! "
    120                         + " in: %dx%d, out: %dx%d",
    121                         mWidth, mHeight,
    122                         preview.getWidth(), preview.getHeight()));
    123             }
    124         }
    125     }
    126 
    127     @Override
    128     public int getTileSize() {
    129         return mTileSize;
    130     }
    131 
    132     @Override
    133     public int getImageWidth() {
    134         return mWidth;
    135     }
    136 
    137     @Override
    138     public int getImageHeight() {
    139         return mHeight;
    140     }
    141 
    142     @Override
    143     public BasicTexture getPreview() {
    144         return mPreview;
    145     }
    146 
    147     @Override
    148     public int getRotation() {
    149         return mRotation;
    150     }
    151 
    152     @Override
    153     public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
    154         int tileSize = getTileSize();
    155         if (!REUSE_BITMAP) {
    156             return getTileWithoutReusingBitmap(level, x, y, tileSize);
    157         }
    158 
    159         int t = tileSize << level;
    160         mWantRegion.set(x, y, x + t, y + t);
    161 
    162         if (bitmap == null) {
    163             bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
    164         }
    165 
    166         mOptions.inSampleSize = (1 << level);
    167         mOptions.inBitmap = bitmap;
    168 
    169         try {
    170             bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
    171         } finally {
    172             if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
    173                 mOptions.inBitmap = null;
    174             }
    175         }
    176 
    177         if (bitmap == null) {
    178             Log.w("BitmapRegionTileSource", "fail in decoding region");
    179         }
    180         return bitmap;
    181     }
    182 
    183     private Bitmap getTileWithoutReusingBitmap(
    184             int level, int x, int y, int tileSize) {
    185 
    186         int t = tileSize << level;
    187         mWantRegion.set(x, y, x + t, y + t);
    188 
    189         mOverlapRegion.set(0, 0, mWidth, mHeight);
    190 
    191         mOptions.inSampleSize = (1 << level);
    192         Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);
    193 
    194         if (bitmap == null) {
    195             Log.w(TAG, "fail in decoding region");
    196         }
    197 
    198         if (mWantRegion.equals(mOverlapRegion)) {
    199             return bitmap;
    200         }
    201 
    202         Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
    203         if (mCanvas == null) {
    204             mCanvas = new Canvas();
    205         }
    206         mCanvas.setBitmap(result);
    207         mCanvas.drawBitmap(bitmap,
    208                 (mOverlapRegion.left - mWantRegion.left) >> level,
    209                 (mOverlapRegion.top - mWantRegion.top) >> level, null);
    210         mCanvas.setBitmap(null);
    211         return result;
    212     }
    213 
    214     /**
    215      * Note that the returned bitmap may have a long edge that's longer
    216      * than the targetSize, but it will always be less than 2x the targetSize
    217      */
    218     private Bitmap decodePreview(
    219             Resources res, Context context, String file, Uri uri, int resId, int targetSize) {
    220         float scale = (float) targetSize / Math.max(mWidth, mHeight);
    221         mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
    222         mOptions.inJustDecodeBounds = false;
    223 
    224         Bitmap result = null;
    225         if (file != null) {
    226             result = BitmapFactory.decodeFile(file, mOptions);
    227         } else if (uri != null) {
    228             try {
    229                 InputStream is = context.getContentResolver().openInputStream(uri);
    230                 BufferedInputStream bis = new BufferedInputStream(is);
    231                 result = BitmapFactory.decodeStream(bis, null, mOptions);
    232             } catch (IOException e) {
    233                 Log.w("BitmapRegionTileSource", "getting preview failed", e);
    234             }
    235         } else {
    236             result = BitmapFactory.decodeResource(res, resId, mOptions);
    237         }
    238         if (result == null) {
    239             return null;
    240         }
    241 
    242         // We need to resize down if the decoder does not support inSampleSize
    243         // or didn't support the specified inSampleSize (some decoders only do powers of 2)
    244         scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
    245 
    246         if (scale <= 0.5) {
    247             result = BitmapUtils.resizeBitmapByScale(result, scale, true);
    248         }
    249         return ensureGLCompatibleBitmap(result);
    250     }
    251 
    252     private static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
    253         if (bitmap == null || bitmap.getConfig() != null) {
    254             return bitmap;
    255         }
    256         Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
    257         bitmap.recycle();
    258         return newBitmap;
    259     }
    260 }
    261