blob: 7870b26e3a7e5fe178dc9770ea560c668f18a391 [file] [log] [blame]
/*
* 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.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.net.Uri;
import android.opengl.GLUtils;
import android.os.Build;
import android.util.Log;
import com.android.gallery3d.common.ExifOrientation;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.BitmapTexture;
import com.android.photos.views.TiledImageRenderer;
import com.android.wallpaperpicker.common.InputStreamProvider;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
interface SimpleBitmapRegionDecoder {
int getWidth();
int getHeight();
Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options);
}
class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder {
BitmapRegionDecoder mDecoder;
private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) {
mDecoder = decoder;
}
public static SimpleBitmapRegionDecoderWrapper newInstance(
InputStream is, boolean isShareable) {
try {
BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable);
if (d != null) {
return new SimpleBitmapRegionDecoderWrapper(d);
}
} catch (IOException e) {
Log.w("BitmapRegionTileSource", "getting decoder failed", e);
return null;
}
return null;
}
public int getWidth() {
return mDecoder.getWidth();
}
public int getHeight() {
return mDecoder.getHeight();
}
public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
return mDecoder.decodeRegion(wantRegion, options);
}
}
class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder {
Bitmap mBuffer;
Canvas mTempCanvas;
Paint mTempPaint;
private DumbBitmapRegionDecoder(Bitmap b) {
mBuffer = b;
}
public static DumbBitmapRegionDecoder newInstance(InputStream is) {
Bitmap b = BitmapFactory.decodeStream(is);
if (b != null) {
return new DumbBitmapRegionDecoder(b);
}
return null;
}
public int getWidth() {
return mBuffer.getWidth();
}
public int getHeight() {
return mBuffer.getHeight();
}
public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
if (mTempCanvas == null) {
mTempCanvas = new Canvas();
mTempPaint = new Paint();
mTempPaint.setFilterBitmap(true);
}
int sampleSize = Math.max(options.inSampleSize, 1);
Bitmap newBitmap = Bitmap.createBitmap(
wantRegion.width() / sampleSize,
wantRegion.height() / sampleSize,
Bitmap.Config.ARGB_8888);
mTempCanvas.setBitmap(newBitmap);
mTempCanvas.save();
mTempCanvas.scale(1f / sampleSize, 1f / sampleSize);
mTempCanvas.drawBitmap(mBuffer, -wantRegion.left, -wantRegion.top, mTempPaint);
mTempCanvas.restore();
mTempCanvas.setBitmap(null);
return newBitmap;
}
}
/**
* 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 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 = GL_SIZE_LIMIT / 2;
public static abstract class BitmapSource {
private SimpleBitmapRegionDecoder mDecoder;
private Bitmap mPreview;
private int mRotation;
public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
private State mState = State.NOT_LOADED;
/** Returns whether loading was successful. */
public boolean loadInBackground(InBitmapProvider bitmapProvider) {
mRotation = getExifRotation();
mDecoder = loadBitmapRegionDecoder();
if (mDecoder == null) {
mState = State.ERROR_LOADING;
return false;
} else {
int width = mDecoder.getWidth();
int height = mDecoder.getHeight();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
opts.inPreferQualityOverSpeed = true;
float scale = (float) MAX_PREVIEW_SIZE / Math.max(width, height);
opts.inSampleSize = Utils.computeSampleSizeLarger(scale);
opts.inJustDecodeBounds = false;
opts.inMutable = true;
if (bitmapProvider != null) {
int expectedPixles = (width / opts.inSampleSize) * (height / opts.inSampleSize);
Bitmap reusableBitmap = bitmapProvider.forPixelCount(expectedPixles);
if (reusableBitmap != null) {
// Try loading with reusable bitmap
opts.inBitmap = reusableBitmap;
try {
mPreview = loadPreviewBitmap(opts);
} catch (IllegalArgumentException e) {
Log.d(TAG, "Unable to reuse bitmap", e);
opts.inBitmap = null;
mPreview = null;
}
}
}
if (mPreview == null) {
mPreview = loadPreviewBitmap(opts);
}
if (mPreview == null) {
mState = State.ERROR_LOADING;
return false;
}
// Verify that the bitmap can be used on GL surface
try {
GLUtils.getInternalFormat(mPreview);
GLUtils.getType(mPreview);
mState = State.LOADED;
} catch (IllegalArgumentException e) {
Log.d(TAG, "Image cannot be rendered on a GL surface", e);
mState = State.ERROR_LOADING;
}
return mState == State.LOADED;
}
}
public State getLoadingState() {
return mState;
}
public SimpleBitmapRegionDecoder getBitmapRegionDecoder() {
return mDecoder;
}
public Bitmap getPreviewBitmap() {
return mPreview;
}
public int getRotation() {
return mRotation;
}
public abstract int getExifRotation();
public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
public interface InBitmapProvider {
Bitmap forPixelCount(int count);
}
}
public static class InputStreamSource extends BitmapSource {
private final InputStreamProvider mStreamProvider;
private final Context mContext;
public InputStreamSource(Context context, Uri uri) {
this(InputStreamProvider.fromUri(context, uri), context);
}
public InputStreamSource(Resources res, int resId, Context context) {
this(InputStreamProvider.fromResource(res, resId), context);
}
public InputStreamSource(InputStreamProvider streamProvider, Context context) {
mStreamProvider = streamProvider;
mContext = context;
}
@Override
public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
try {
InputStream is = mStreamProvider.newStreamNotNull();
SimpleBitmapRegionDecoder regionDecoder =
SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
Utils.closeSilently(is);
if (regionDecoder == null) {
is = mStreamProvider.newStreamNotNull();
regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
Utils.closeSilently(is);
}
return regionDecoder;
} catch (IOException e) {
Log.e("InputStreamSource", "Failed to load stream", e);
return null;
}
}
@Override
public int getExifRotation() {
return mStreamProvider.getRotationFromExif(mContext);
}
@Override
public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
try {
InputStream is = mStreamProvider.newStreamNotNull();
Bitmap b = BitmapFactory.decodeStream(is, null, options);
Utils.closeSilently(is);
return b;
} catch (IOException | OutOfMemoryError e) {
Log.e("InputStreamSource", "Failed to load stream", e);
return null;
}
}
}
public static class FilePathBitmapSource extends InputStreamSource {
private String mPath;
public FilePathBitmapSource(File file, Context context) {
super(context, Uri.fromFile(file));
mPath = file.getAbsolutePath();
}
@Override
public int getExifRotation() {
return ExifOrientation.readRotation(mPath);
}
}
SimpleBitmapRegionDecoder 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 BitmapFactory.Options mOptions;
public BitmapRegionTileSource(Context context, BitmapSource source, byte[] tempStorage) {
mTileSize = TiledImageRenderer.suggestedTileSize(context);
mRotation = source.getRotation();
mDecoder = source.getBitmapRegionDecoder();
if (mDecoder != null) {
mWidth = mDecoder.getWidth();
mHeight = mDecoder.getHeight();
mOptions = new BitmapFactory.Options();
mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
mOptions.inPreferQualityOverSpeed = true;
mOptions.inTempStorage = tempStorage;
Bitmap preview = source.getPreviewBitmap();
if (preview != null &&
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 == null ? -1 : preview.getWidth(),
preview == null ? -1 : preview.getHeight()));
}
}
}
public Bitmap getBitmap() {
return mPreview instanceof BitmapTexture ? ((BitmapTexture) mPreview).getBitmap() : null;
}
@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();
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;
}
}