diff options
Diffstat (limited to 'src/com/android/photos/drawables/AutoThumbnailDrawable.java')
-rw-r--r-- | src/com/android/photos/drawables/AutoThumbnailDrawable.java | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/src/com/android/photos/drawables/AutoThumbnailDrawable.java b/src/com/android/photos/drawables/AutoThumbnailDrawable.java new file mode 100644 index 000000000..b51b6709f --- /dev/null +++ b/src/com/android/photos/drawables/AutoThumbnailDrawable.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.photos.drawables; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import com.android.photos.data.GalleryBitmapPool; + +import java.io.InputStream; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public abstract class AutoThumbnailDrawable<T> extends Drawable { + + private static final String TAG = "AutoThumbnailDrawable"; + + private static ExecutorService sThreadPool = Executors.newSingleThreadExecutor(); + private static GalleryBitmapPool sBitmapPool = GalleryBitmapPool.getInstance(); + private static byte[] sTempStorage = new byte[64 * 1024]; + + // UI thread only + private Paint mPaint = new Paint(); + private Matrix mDrawMatrix = new Matrix(); + + // Decoder thread only + private BitmapFactory.Options mOptions = new BitmapFactory.Options(); + + // Shared, guarded by mLock + private Object mLock = new Object(); + private Bitmap mBitmap; + protected T mData; + private boolean mIsQueued; + private int mImageWidth, mImageHeight; + private Rect mBounds = new Rect(); + private int mSampleSize = 1; + + public AutoThumbnailDrawable() { + mPaint.setAntiAlias(true); + mPaint.setFilterBitmap(true); + mDrawMatrix.reset(); + mOptions.inTempStorage = sTempStorage; + } + + protected abstract byte[] getPreferredImageBytes(T data); + protected abstract InputStream getFallbackImageStream(T data); + protected abstract boolean dataChangedLocked(T data); + + public void setImage(T data, int width, int height) { + if (!dataChangedLocked(data)) return; + synchronized (mLock) { + mImageWidth = width; + mImageHeight = height; + mData = data; + setBitmapLocked(null); + refreshSampleSizeLocked(); + } + invalidateSelf(); + } + + private void setBitmapLocked(Bitmap b) { + if (b == mBitmap) { + return; + } + if (mBitmap != null) { + sBitmapPool.put(mBitmap); + } + mBitmap = b; + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + synchronized (mLock) { + mBounds.set(bounds); + if (mBounds.isEmpty()) { + mBitmap = null; + } else { + refreshSampleSizeLocked(); + updateDrawMatrixLocked(); + } + } + invalidateSelf(); + } + + @Override + public void draw(Canvas canvas) { + if (mBitmap != null) { + canvas.save(); + canvas.clipRect(mBounds); + canvas.concat(mDrawMatrix); + canvas.drawBitmap(mBitmap, 0, 0, mPaint); + canvas.restore(); + } else { + // TODO: Draw placeholder...? + } + } + + private void updateDrawMatrixLocked() { + if (mBitmap == null || mBounds.isEmpty()) { + mDrawMatrix.reset(); + return; + } + + float scale; + float dx = 0, dy = 0; + + int dwidth = mBitmap.getWidth(); + int dheight = mBitmap.getHeight(); + int vwidth = mBounds.width(); + int vheight = mBounds.height(); + + // Calculates a matrix similar to ScaleType.CENTER_CROP + if (dwidth * vheight > vwidth * dheight) { + scale = (float) vheight / (float) dheight; + dx = (vwidth - dwidth * scale) * 0.5f; + } else { + scale = (float) vwidth / (float) dwidth; + dy = (vheight - dheight * scale) * 0.5f; + } + if (scale < .8f) { + Log.w(TAG, "sample size was too small! Overdrawing! " + scale + ", " + mSampleSize); + } else if (scale > 1.5f) { + Log.w(TAG, "Potential quality loss! " + scale + ", " + mSampleSize); + } + + mDrawMatrix.setScale(scale, scale); + mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); + } + + private int calculateSampleSizeLocked(int dwidth, int dheight) { + float scale; + + int vwidth = mBounds.width(); + int vheight = mBounds.height(); + + // Inverse of updateDrawMatrixLocked + if (dwidth * vheight > vwidth * dheight) { + scale = (float) dheight / (float) vheight; + } else { + scale = (float) dwidth / (float) vwidth; + } + int result = Math.round(scale); + return result > 0 ? result : 1; + } + + private void refreshSampleSizeLocked() { + if (mBounds.isEmpty() || mImageWidth == 0 || mImageHeight == 0) { + return; + } + + int sampleSize = calculateSampleSizeLocked(mImageWidth, mImageHeight); + if (sampleSize != mSampleSize || mBitmap == null) { + mSampleSize = sampleSize; + loadBitmapLocked(); + } + } + + private void loadBitmapLocked() { + if (!mIsQueued && !mBounds.isEmpty()) { + unscheduleSelf(mUpdateBitmap); + sThreadPool.execute(mLoadBitmap); + mIsQueued = true; + } + } + + public float getAspectRatio() { + return (float) mImageWidth / (float) mImageHeight; + } + + @Override + public int getIntrinsicWidth() { + return -1; + } + + @Override + public int getIntrinsicHeight() { + return -1; + } + + @Override + public int getOpacity() { + Bitmap bm = mBitmap; + return (bm == null || bm.hasAlpha() || mPaint.getAlpha() < 255) ? + PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; + } + + @Override + public void setAlpha(int alpha) { + int oldAlpha = mPaint.getAlpha(); + if (alpha != oldAlpha) { + mPaint.setAlpha(alpha); + invalidateSelf(); + } + } + + @Override + public void setColorFilter(ColorFilter cf) { + mPaint.setColorFilter(cf); + invalidateSelf(); + } + + private final Runnable mLoadBitmap = new Runnable() { + @Override + public void run() { + T data; + synchronized (mLock) { + data = mData; + } + int preferredSampleSize = 1; + byte[] preferred = getPreferredImageBytes(data); + boolean hasPreferred = (preferred != null && preferred.length > 0); + if (hasPreferred) { + mOptions.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions); + mOptions.inJustDecodeBounds = false; + } + int sampleSize, width, height; + synchronized (mLock) { + if (dataChangedLocked(data)) { + return; + } + width = mImageWidth; + height = mImageHeight; + if (hasPreferred) { + preferredSampleSize = calculateSampleSizeLocked( + mOptions.outWidth, mOptions.outHeight); + } + sampleSize = calculateSampleSizeLocked(width, height); + mIsQueued = false; + } + Bitmap b = null; + InputStream is = null; + try { + if (hasPreferred) { + mOptions.inSampleSize = preferredSampleSize; + mOptions.inBitmap = sBitmapPool.get( + mOptions.outWidth / preferredSampleSize, + mOptions.outHeight / preferredSampleSize); + b = BitmapFactory.decodeByteArray(preferred, 0, preferred.length, mOptions); + if (mOptions.inBitmap != null && b != mOptions.inBitmap) { + sBitmapPool.put(mOptions.inBitmap); + mOptions.inBitmap = null; + } + } + if (b == null) { + is = getFallbackImageStream(data); + mOptions.inSampleSize = sampleSize; + mOptions.inBitmap = sBitmapPool.get(width / sampleSize, height / sampleSize); + b = BitmapFactory.decodeStream(is, null, mOptions); + if (mOptions.inBitmap != null && b != mOptions.inBitmap) { + sBitmapPool.put(mOptions.inBitmap); + mOptions.inBitmap = null; + } + } + } catch (Exception e) { + Log.d(TAG, "Failed to fetch bitmap", e); + return; + } finally { + try { + if (is != null) { + is.close(); + } + } catch (Exception e) {} + if (b != null) { + synchronized (mLock) { + if (!dataChangedLocked(data)) { + setBitmapLocked(b); + scheduleSelf(mUpdateBitmap, 0); + } + } + } + } + } + }; + + private final Runnable mUpdateBitmap = new Runnable() { + @Override + public void run() { + synchronized (AutoThumbnailDrawable.this) { + updateDrawMatrixLocked(); + invalidateSelf(); + } + } + }; + +} |