Home | History | Annotate | Download | only in drawables
      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.drawables;
     18 
     19 import android.graphics.Bitmap;
     20 import android.graphics.BitmapFactory;
     21 import android.graphics.Canvas;
     22 import android.graphics.ColorFilter;
     23 import android.graphics.Matrix;
     24 import android.graphics.Paint;
     25 import android.graphics.PixelFormat;
     26 import android.graphics.Rect;
     27 import android.graphics.drawable.Drawable;
     28 import android.util.Log;
     29 
     30 import com.android.photos.data.GalleryBitmapPool;
     31 
     32 import java.io.InputStream;
     33 import java.util.concurrent.ExecutorService;
     34 import java.util.concurrent.Executors;
     35 
     36 public abstract class AutoThumbnailDrawable<T> extends Drawable {
     37 
     38     private static final String TAG = "AutoThumbnailDrawable";
     39 
     40     private static ExecutorService sThreadPool = Executors.newSingleThreadExecutor();
     41     private static GalleryBitmapPool sBitmapPool = GalleryBitmapPool.getInstance();
     42     private static byte[] sTempStorage = new byte[64 * 1024];
     43 
     44     // UI thread only
     45     private Paint mPaint = new Paint();
     46     private Matrix mDrawMatrix = new Matrix();
     47 
     48     // Decoder thread only
     49     private BitmapFactory.Options mOptions = new BitmapFactory.Options();
     50 
     51     // Shared, guarded by mLock
     52     private Object mLock = new Object();
     53     private Bitmap mBitmap;
     54     protected T mData;
     55     private boolean mIsQueued;
     56     private int mImageWidth, mImageHeight;
     57     private Rect mBounds = new Rect();
     58     private int mSampleSize = 1;
     59 
     60     public AutoThumbnailDrawable() {
     61         mPaint.setAntiAlias(true);
     62         mPaint.setFilterBitmap(true);
     63         mDrawMatrix.reset();
     64         mOptions.inTempStorage = sTempStorage;
     65     }
     66 
     67     protected abstract byte[] getPreferredImageBytes(T data);
     68     protected abstract InputStream getFallbackImageStream(T data);
     69     protected abstract boolean dataChangedLocked(T data);
     70 
     71     public void setImage(T data, int width, int height) {
     72         if (!dataChangedLocked(data)) return;
     73         synchronized (mLock) {
     74             mImageWidth = width;
     75             mImageHeight = height;
     76             mData = data;
     77             setBitmapLocked(null);
     78             refreshSampleSizeLocked();
     79         }
     80         invalidateSelf();
     81     }
     82 
     83     private void setBitmapLocked(Bitmap b) {
     84         if (b == mBitmap) {
     85             return;
     86         }
     87         if (mBitmap != null) {
     88             sBitmapPool.put(mBitmap);
     89         }
     90         mBitmap = b;
     91     }
     92 
     93     @Override
     94     protected void onBoundsChange(Rect bounds) {
     95         super.onBoundsChange(bounds);
     96         synchronized (mLock) {
     97             mBounds.set(bounds);
     98             if (mBounds.isEmpty()) {
     99                 mBitmap = null;
    100             } else {
    101                 refreshSampleSizeLocked();
    102                 updateDrawMatrixLocked();
    103             }
    104         }
    105         invalidateSelf();
    106     }
    107 
    108     @Override
    109     public void draw(Canvas canvas) {
    110         if (mBitmap != null) {
    111             canvas.save();
    112             canvas.clipRect(mBounds);
    113             canvas.concat(mDrawMatrix);
    114             canvas.drawBitmap(mBitmap, 0, 0, mPaint);
    115             canvas.restore();
    116         } else {
    117             // TODO: Draw placeholder...?
    118         }
    119     }
    120 
    121     private void updateDrawMatrixLocked() {
    122         if (mBitmap == null || mBounds.isEmpty()) {
    123             mDrawMatrix.reset();
    124             return;
    125         }
    126 
    127         float scale;
    128         float dx = 0, dy = 0;
    129 
    130         int dwidth = mBitmap.getWidth();
    131         int dheight = mBitmap.getHeight();
    132         int vwidth = mBounds.width();
    133         int vheight = mBounds.height();
    134 
    135         // Calculates a matrix similar to ScaleType.CENTER_CROP
    136         if (dwidth * vheight > vwidth * dheight) {
    137             scale = (float) vheight / (float) dheight;
    138             dx = (vwidth - dwidth * scale) * 0.5f;
    139         } else {
    140             scale = (float) vwidth / (float) dwidth;
    141             dy = (vheight - dheight * scale) * 0.5f;
    142         }
    143         if (scale < .8f) {
    144             Log.w(TAG, "sample size was too small! Overdrawing! " + scale + ", " + mSampleSize);
    145         } else if (scale > 1.5f) {
    146             Log.w(TAG, "Potential quality loss! " + scale + ", " + mSampleSize);
    147         }
    148 
    149         mDrawMatrix.setScale(scale, scale);
    150         mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
    151     }
    152 
    153     private int calculateSampleSizeLocked(int dwidth, int dheight) {
    154         float scale;
    155 
    156         int vwidth = mBounds.width();
    157         int vheight = mBounds.height();
    158 
    159         // Inverse of updateDrawMatrixLocked
    160         if (dwidth * vheight > vwidth * dheight) {
    161             scale = (float) dheight / (float) vheight;
    162         } else {
    163             scale = (float) dwidth / (float) vwidth;
    164         }
    165         int result = Math.round(scale);
    166         return result > 0 ? result : 1;
    167     }
    168 
    169     private void refreshSampleSizeLocked() {
    170         if (mBounds.isEmpty() || mImageWidth == 0 || mImageHeight == 0) {
    171             return;
    172         }
    173 
    174         int sampleSize = calculateSampleSizeLocked(mImageWidth, mImageHeight);
    175         if (sampleSize != mSampleSize || mBitmap == null) {
    176             mSampleSize = sampleSize;
    177             loadBitmapLocked();
    178         }
    179     }
    180 
    181     private void loadBitmapLocked() {
    182         if (!mIsQueued && !mBounds.isEmpty()) {
    183             unscheduleSelf(mUpdateBitmap);
    184             sThreadPool.execute(mLoadBitmap);
    185             mIsQueued = true;
    186         }
    187     }
    188 
    189     public float getAspectRatio() {
    190         return (float) mImageWidth / (float) mImageHeight;
    191     }
    192 
    193     @Override
    194     public int getIntrinsicWidth() {
    195         return -1;
    196     }
    197 
    198     @Override
    199     public int getIntrinsicHeight() {
    200         return -1;
    201     }
    202 
    203     @Override
    204     public int getOpacity() {
    205         Bitmap bm = mBitmap;
    206         return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ?
    207                 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
    208     }
    209 
    210     @Override
    211     public void setAlpha(int alpha) {
    212         int oldAlpha = mPaint.getAlpha();
    213         if (alpha != oldAlpha) {
    214             mPaint.setAlpha(alpha);
    215             invalidateSelf();
    216         }
    217     }
    218 
    219     @Override
    220     public void setColorFilter(ColorFilter cf) {
    221         mPaint.setColorFilter(cf);
    222         invalidateSelf();
    223     }
    224 
    225     private final Runnable mLoadBitmap = new Runnable() {
    226         @Override
    227         public void run() {
    228             T data;
    229             synchronized (mLock) {
    230                 data = mData;
    231             }
    232             int preferredSampleSize = 1;
    233             byte[] preferred = getPreferredImageBytes(data);
    234             boolean hasPreferred = (preferred != null && preferred.length > 0);
    235             if (hasPreferred) {
    236                 mOptions.inJustDecodeBounds = true;
    237                 BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions);
    238                 mOptions.inJustDecodeBounds = false;
    239             }
    240             int sampleSize, width, height;
    241             synchronized (mLock) {
    242                 if (dataChangedLocked(data)) {
    243                     return;
    244                 }
    245                 width = mImageWidth;
    246                 height = mImageHeight;
    247                 if (hasPreferred) {
    248                     preferredSampleSize = calculateSampleSizeLocked(
    249                             mOptions.outWidth, mOptions.outHeight);
    250                 }
    251                 sampleSize = calculateSampleSizeLocked(width, height);
    252                 mIsQueued = false;
    253             }
    254             Bitmap b = null;
    255             InputStream is = null;
    256             try {
    257                 if (hasPreferred) {
    258                     mOptions.inSampleSize = preferredSampleSize;
    259                     mOptions.inBitmap = sBitmapPool.get(
    260                             mOptions.outWidth / preferredSampleSize,
    261                             mOptions.outHeight / preferredSampleSize);
    262                     b = BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions);
    263                     if (mOptions.inBitmap != null && b != mOptions.inBitmap) {
    264                         sBitmapPool.put(mOptions.inBitmap);
    265                         mOptions.inBitmap = null;
    266                     }
    267                 }
    268                 if (b == null) {
    269                     is = getFallbackImageStream(data);
    270                     mOptions.inSampleSize = sampleSize;
    271                     mOptions.inBitmap = sBitmapPool.get(width / sampleSize, height / sampleSize);
    272                     b = BitmapFactory.decodeStream(is, null, mOptions);
    273                     if (mOptions.inBitmap != null && b != mOptions.inBitmap) {
    274                         sBitmapPool.put(mOptions.inBitmap);
    275                         mOptions.inBitmap = null;
    276                     }
    277                 }
    278             } catch (Exception e) {
    279                 Log.d(TAG, "Failed to fetch bitmap", e);
    280                 return;
    281             } finally {
    282                 try {
    283                     if (is != null) {
    284                         is.close();
    285                     }
    286                 } catch (Exception e) {}
    287                 if (b != null) {
    288                     synchronized (mLock) {
    289                         if (!dataChangedLocked(data)) {
    290                             setBitmapLocked(b);
    291                             scheduleSelf(mUpdateBitmap, 0);
    292                         }
    293                     }
    294                 }
    295             }
    296         }
    297     };
    298 
    299     private final Runnable mUpdateBitmap = new Runnable() {
    300         @Override
    301         public void run() {
    302             synchronized (AutoThumbnailDrawable.this) {
    303                 updateDrawMatrixLocked();
    304                 invalidateSelf();
    305             }
    306         }
    307     };
    308 
    309 }
    310