summaryrefslogtreecommitdiffstats
path: root/src/com/android/photos/BitmapRegionTileSource.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/photos/BitmapRegionTileSource.java')
-rw-r--r--src/com/android/photos/BitmapRegionTileSource.java257
1 files changed, 257 insertions, 0 deletions
diff --git a/src/com/android/photos/BitmapRegionTileSource.java b/src/com/android/photos/BitmapRegionTileSource.java
new file mode 100644
index 000000000..964748523
--- /dev/null
+++ b/src/com/android/photos/BitmapRegionTileSource.java
@@ -0,0 +1,257 @@
+/*
+ * 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;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.util.Log;
+
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.glrenderer.BasicTexture;
+import com.android.gallery3d.glrenderer.BitmapTexture;
+import com.android.photos.views.TiledImageRenderer;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
+ * {@link BitmapRegionDecoder} to wrap a local file
+ */
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
+
+ private static final String TAG = "BitmapRegionTileSource";
+
+ private static final boolean REUSE_BITMAP =
+ Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN;
+ private static final int GL_SIZE_LIMIT = 2048;
+ // This must be no larger than half the size of the GL_SIZE_LIMIT
+ // due to decodePreview being allowed to be up to 2x the size of the target
+ private static final int MAX_PREVIEW_SIZE = 1024;
+
+ BitmapRegionDecoder mDecoder;
+ int mWidth;
+ int mHeight;
+ int mTileSize;
+ private BasicTexture mPreview;
+ private final int mRotation;
+
+ // For use only by getTile
+ private Rect mWantRegion = new Rect();
+ private Rect mOverlapRegion = new Rect();
+ private BitmapFactory.Options mOptions;
+ private Canvas mCanvas;
+
+ public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) {
+ this(context, path, null, 0, previewSize, rotation);
+ }
+
+ public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) {
+ this(context, null, uri, 0, previewSize, rotation);
+ }
+
+ public BitmapRegionTileSource(Context context, int resId, int previewSize, int rotation) {
+ this(context, null, null, resId, previewSize, rotation);
+ }
+
+ private BitmapRegionTileSource(
+ Context context, String path, Uri uri, int resId, int previewSize, int rotation) {
+ mTileSize = TiledImageRenderer.suggestedTileSize(context);
+ mRotation = rotation;
+ try {
+ if (path != null) {
+ mDecoder = BitmapRegionDecoder.newInstance(path, true);
+ } else if (uri != null) {
+ InputStream is = context.getContentResolver().openInputStream(uri);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ mDecoder = BitmapRegionDecoder.newInstance(bis, true);
+ } else {
+ InputStream is = context.getResources().openRawResource(resId);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ mDecoder = BitmapRegionDecoder.newInstance(bis, true);
+ }
+ mWidth = mDecoder.getWidth();
+ mHeight = mDecoder.getHeight();
+ } catch (IOException e) {
+ Log.w("BitmapRegionTileSource", "ctor failed", e);
+ }
+ mOptions = new BitmapFactory.Options();
+ mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
+ mOptions.inPreferQualityOverSpeed = true;
+ mOptions.inTempStorage = new byte[16 * 1024];
+ if (previewSize != 0) {
+ previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
+ // Although this is the same size as the Bitmap that is likely already
+ // loaded, the lifecycle is different and interactions are on a different
+ // thread. Thus to simplify, this source will decode its own bitmap.
+ Bitmap preview = decodePreview(context, path, uri, resId, previewSize);
+ if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
+ mPreview = new BitmapTexture(preview);
+ } else {
+ Log.w(TAG, String.format(
+ "Failed to create preview of apropriate size! "
+ + " in: %dx%d, out: %dx%d",
+ mWidth, mHeight,
+ preview.getWidth(), preview.getHeight()));
+ }
+ }
+ }
+
+ @Override
+ public int getTileSize() {
+ return mTileSize;
+ }
+
+ @Override
+ public int getImageWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getImageHeight() {
+ return mHeight;
+ }
+
+ @Override
+ public BasicTexture getPreview() {
+ return mPreview;
+ }
+
+ @Override
+ public int getRotation() {
+ return mRotation;
+ }
+
+ @Override
+ public Bitmap getTile(int level, int x, int y, Bitmap bitmap) {
+ int tileSize = getTileSize();
+ if (!REUSE_BITMAP) {
+ return getTileWithoutReusingBitmap(level, x, y, tileSize);
+ }
+
+ int t = tileSize << level;
+ mWantRegion.set(x, y, x + t, y + t);
+
+ if (bitmap == null) {
+ bitmap = Bitmap.createBitmap(tileSize, tileSize, Bitmap.Config.ARGB_8888);
+ }
+
+ mOptions.inSampleSize = (1 << level);
+ mOptions.inBitmap = bitmap;
+
+ try {
+ bitmap = mDecoder.decodeRegion(mWantRegion, mOptions);
+ } finally {
+ if (mOptions.inBitmap != bitmap && mOptions.inBitmap != null) {
+ mOptions.inBitmap = null;
+ }
+ }
+
+ if (bitmap == null) {
+ Log.w("BitmapRegionTileSource", "fail in decoding region");
+ }
+ return bitmap;
+ }
+
+ private Bitmap getTileWithoutReusingBitmap(
+ int level, int x, int y, int tileSize) {
+
+ int t = tileSize << level;
+ mWantRegion.set(x, y, x + t, y + t);
+
+ mOverlapRegion.set(0, 0, mWidth, mHeight);
+
+ mOptions.inSampleSize = (1 << level);
+ Bitmap bitmap = mDecoder.decodeRegion(mOverlapRegion, mOptions);
+
+ if (bitmap == null) {
+ Log.w(TAG, "fail in decoding region");
+ }
+
+ if (mWantRegion.equals(mOverlapRegion)) {
+ return bitmap;
+ }
+
+ Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888);
+ if (mCanvas == null) {
+ mCanvas = new Canvas();
+ }
+ mCanvas.setBitmap(result);
+ mCanvas.drawBitmap(bitmap,
+ (mOverlapRegion.left - mWantRegion.left) >> level,
+ (mOverlapRegion.top - mWantRegion.top) >> level, null);
+ mCanvas.setBitmap(null);
+ return result;
+ }
+
+ /**
+ * Note that the returned bitmap may have a long edge that's longer
+ * than the targetSize, but it will always be less than 2x the targetSize
+ */
+ private Bitmap decodePreview(Context context, String file, Uri uri, int resId, int targetSize) {
+ float scale = (float) targetSize / Math.max(mWidth, mHeight);
+ mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+ mOptions.inJustDecodeBounds = false;
+
+ Bitmap result = null;
+ if (file != null) {
+ result = BitmapFactory.decodeFile(file, mOptions);
+ } else if (uri != null) {
+ try {
+ InputStream is = context.getContentResolver().openInputStream(uri);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ result = BitmapFactory.decodeStream(bis, null, mOptions);
+ } catch (IOException e) {
+ Log.w("BitmapRegionTileSource", "getting preview failed", e);
+ }
+ } else {
+ result = BitmapFactory.decodeResource(context.getResources(), resId, mOptions);
+ }
+ if (result == null) {
+ return null;
+ }
+
+ // We need to resize down if the decoder does not support inSampleSize
+ // or didn't support the specified inSampleSize (some decoders only do powers of 2)
+ scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight()));
+
+ if (scale <= 0.5) {
+ result = BitmapUtils.resizeBitmapByScale(result, scale, true);
+ }
+ return ensureGLCompatibleBitmap(result);
+ }
+
+ private static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
+ if (bitmap == null || bitmap.getConfig() != null) {
+ return bitmap;
+ }
+ Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
+ bitmap.recycle();
+ return newBitmap;
+ }
+}