Reducing memory usage of wallpaper picker
> Loading preview bitmap only once, instead of loading it twice at BitmapRegionTileSource and BitmapSource
> Maintaing a weak-set of reusable bitmaps and reusing them for decoding bitmaps
> Loading images on a HandlerThread (instead of AsyncTask) and removing any non-started task before submitting a new task
> Loading inbuild images (from resources) on HandlerThread instead of UIThread
> Freeing up unbound GL textures before binding a new texture.
Bug: 18382606
Change-Id: Ic4ca630dd113ded65d2853eb0d291c9e5823637e
(cherry picked from commit 283c2261bd)
This commit is contained in:
@@ -696,6 +696,7 @@ public class GLES20Canvas implements GLCanvas {
|
||||
}
|
||||
|
||||
private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) {
|
||||
deleteRecycledResources();
|
||||
GLES20.glUseProgram(program);
|
||||
checkError();
|
||||
enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA);
|
||||
|
||||
@@ -25,25 +25,36 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.RectF;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
import android.view.Display;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.android.gallery3d.common.BitmapCropTask;
|
||||
import com.android.gallery3d.common.BitmapUtils;
|
||||
import com.android.gallery3d.common.Utils;
|
||||
import com.android.photos.BitmapRegionTileSource;
|
||||
import com.android.photos.BitmapRegionTileSource.BitmapSource;
|
||||
import com.android.photos.BitmapRegionTileSource.BitmapSource.InBitmapProvider;
|
||||
import com.android.photos.views.TiledImageRenderer.TileSource;
|
||||
|
||||
public class WallpaperCropActivity extends Activity {
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
public class WallpaperCropActivity extends Activity implements Handler.Callback {
|
||||
private static final String LOGTAG = "Launcher3.CropActivity";
|
||||
|
||||
protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
|
||||
@@ -59,13 +70,29 @@ public class WallpaperCropActivity extends Activity {
|
||||
public static final int MAX_BMAP_IN_INTENT = 750000;
|
||||
public static final float WALLPAPER_SCREENS_SPAN = 2f;
|
||||
|
||||
private static final int MSG_LOAD_IMAGE = 1;
|
||||
|
||||
protected CropView mCropView;
|
||||
protected View mProgressView;
|
||||
protected Uri mUri;
|
||||
protected View mSetWallpaperButton;
|
||||
|
||||
private HandlerThread mLoaderThread;
|
||||
private Handler mLoaderHandler;
|
||||
private LoadRequest mCurrentLoadRequest;
|
||||
private byte[] mTempStorageForDecoding = new byte[16 * 1024];
|
||||
// A weak-set of reusable bitmaps
|
||||
private Set<Bitmap> mReusableBitmaps =
|
||||
Collections.newSetFromMap(new WeakHashMap<Bitmap, Boolean>());
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
mLoaderThread = new HandlerThread("wallpaper_loader");
|
||||
mLoaderThread.start();
|
||||
mLoaderHandler = new Handler(mLoaderThread.getLooper(), this);
|
||||
|
||||
init();
|
||||
if (!enableRotation()) {
|
||||
setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT);
|
||||
@@ -76,6 +103,7 @@ public class WallpaperCropActivity extends Activity {
|
||||
setContentView(R.layout.wallpaper_cropper);
|
||||
|
||||
mCropView = (CropView) findViewById(R.id.cropView);
|
||||
mProgressView = findViewById(R.id.loading);
|
||||
|
||||
Intent cropIntent = getIntent();
|
||||
final Uri imageUri = cropIntent.getData();
|
||||
@@ -116,7 +144,7 @@ public class WallpaperCropActivity extends Activity {
|
||||
}
|
||||
}
|
||||
};
|
||||
setCropViewTileSource(bitmapSource, true, false, onLoad);
|
||||
setCropViewTileSource(bitmapSource, true, false, null, onLoad);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -124,65 +152,134 @@ public class WallpaperCropActivity extends Activity {
|
||||
if (mCropView != null) {
|
||||
mCropView.destroy();
|
||||
}
|
||||
if (mLoaderThread != null) {
|
||||
mLoaderThread.quit();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
public void setCropViewTileSource(
|
||||
final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
|
||||
final boolean moveToLeft, final Runnable postExecute) {
|
||||
final Context context = WallpaperCropActivity.this;
|
||||
final View progressView = findViewById(R.id.loading);
|
||||
final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
|
||||
protected Void doInBackground(Void...args) {
|
||||
if (!isCancelled()) {
|
||||
try {
|
||||
bitmapSource.loadInBackground();
|
||||
} catch (SecurityException securityException) {
|
||||
if (isDestroyed()) {
|
||||
// Temporarily granted permissions are revoked when the activity
|
||||
// finishes, potentially resulting in a SecurityException here.
|
||||
// Even though {@link #isDestroyed} might also return true in different
|
||||
// situations where the configuration changes, we are fine with
|
||||
// catching these cases here as well.
|
||||
cancel(false);
|
||||
} else {
|
||||
// otherwise it had a different cause and we throw it further
|
||||
throw securityException;
|
||||
/**
|
||||
* This is called on {@link #mLoaderThread}
|
||||
*/
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
if (msg.what == MSG_LOAD_IMAGE) {
|
||||
final LoadRequest req = (LoadRequest) msg.obj;
|
||||
try {
|
||||
req.src.loadInBackground(new InBitmapProvider() {
|
||||
|
||||
@Override
|
||||
public Bitmap forPixelCount(int count) {
|
||||
synchronized (mReusableBitmaps) {
|
||||
Iterator<Bitmap> itr = mReusableBitmaps.iterator();
|
||||
while (itr.hasNext()) {
|
||||
Bitmap b = itr.next();
|
||||
if (b.getWidth() * b.getHeight() >= count) {
|
||||
itr.remove();
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
protected void onPostExecute(Void arg) {
|
||||
if (!isCancelled()) {
|
||||
progressView.setVisibility(View.INVISIBLE);
|
||||
if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
|
||||
mCropView.setTileSource(
|
||||
new BitmapRegionTileSource(context, bitmapSource), null);
|
||||
mCropView.setTouchEnabled(touchEnabled);
|
||||
if (moveToLeft) {
|
||||
mCropView.moveToLeft();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (postExecute != null) {
|
||||
postExecute.run();
|
||||
});
|
||||
} catch (SecurityException securityException) {
|
||||
if (isDestroyed()) {
|
||||
// Temporarily granted permissions are revoked when the activity
|
||||
// finishes, potentially resulting in a SecurityException here.
|
||||
// Even though {@link #isDestroyed} might also return true in different
|
||||
// situations where the configuration changes, we are fine with
|
||||
// catching these cases here as well.
|
||||
return true;
|
||||
} else {
|
||||
// otherwise it had a different cause and we throw it further
|
||||
throw securityException;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
req.result = new BitmapRegionTileSource(this, req.src, mTempStorageForDecoding);
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (req == mCurrentLoadRequest) {
|
||||
onLoadRequestComplete(req,
|
||||
req.src.getLoadingState() == BitmapSource.State.LOADED);
|
||||
} else {
|
||||
addReusableBitmap(req.result);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void addReusableBitmap(TileSource src) {
|
||||
synchronized (mReusableBitmaps) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
|
||||
&& src instanceof BitmapRegionTileSource) {
|
||||
Bitmap preview = ((BitmapRegionTileSource) src).getBitmap();
|
||||
if (preview != null && preview.isMutable()) {
|
||||
mReusableBitmaps.add(preview);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onLoadRequestComplete(LoadRequest req, boolean success) {
|
||||
mCurrentLoadRequest = null;
|
||||
if (success) {
|
||||
TileSource oldSrc = mCropView.getTileSource();
|
||||
mCropView.setTileSource(req.result, null);
|
||||
mCropView.setTouchEnabled(req.touchEnabled);
|
||||
if (req.moveToLeft) {
|
||||
mCropView.moveToLeft();
|
||||
}
|
||||
if (req.scaleProvider != null) {
|
||||
mCropView.setScale(req.scaleProvider.getScale(req.result));
|
||||
}
|
||||
|
||||
// Free last image
|
||||
if (oldSrc != null) {
|
||||
// Call yield instead of recycle, as we only want to free GL resource.
|
||||
// We can still reuse the bitmap for decoding any other image.
|
||||
oldSrc.getPreview().yield();
|
||||
}
|
||||
addReusableBitmap(oldSrc);
|
||||
}
|
||||
if (req.postExecute != null) {
|
||||
req.postExecute.run();
|
||||
}
|
||||
}
|
||||
|
||||
public final void setCropViewTileSource(BitmapSource bitmapSource, boolean touchEnabled,
|
||||
boolean moveToLeft, CropViewScaleProvider scaleProvider, Runnable postExecute) {
|
||||
final LoadRequest req = new LoadRequest();
|
||||
req.moveToLeft = moveToLeft;
|
||||
req.src = bitmapSource;
|
||||
req.touchEnabled = touchEnabled;
|
||||
req.postExecute = postExecute;
|
||||
req.scaleProvider = scaleProvider;
|
||||
mCurrentLoadRequest = req;
|
||||
|
||||
// Remove any pending requests
|
||||
mLoaderHandler.removeMessages(MSG_LOAD_IMAGE);
|
||||
Message.obtain(mLoaderHandler, MSG_LOAD_IMAGE, req).sendToTarget();
|
||||
|
||||
// We don't want to show the spinner every time we load an image, because that would be
|
||||
// annoying; instead, only start showing the spinner if loading the image has taken
|
||||
// longer than 1 sec (ie 1000 ms)
|
||||
progressView.postDelayed(new Runnable() {
|
||||
mProgressView.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) {
|
||||
progressView.setVisibility(View.VISIBLE);
|
||||
if (mCurrentLoadRequest == req) {
|
||||
mProgressView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
loadBitmapTask.execute();
|
||||
}
|
||||
|
||||
|
||||
public boolean enableRotation() {
|
||||
return getResources().getBoolean(R.bool.allow_rotation);
|
||||
}
|
||||
@@ -372,4 +469,18 @@ public class WallpaperCropActivity extends Activity {
|
||||
wallpaperManager.suggestDesiredDimensions(savedWidth, savedHeight);
|
||||
}
|
||||
}
|
||||
|
||||
static class LoadRequest {
|
||||
BitmapSource src;
|
||||
boolean touchEnabled;
|
||||
boolean moveToLeft;
|
||||
Runnable postExecute;
|
||||
CropViewScaleProvider scaleProvider;
|
||||
|
||||
TileSource result;
|
||||
}
|
||||
|
||||
interface CropViewScaleProvider {
|
||||
float getScale(TileSource src);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ import com.android.gallery3d.common.BitmapUtils;
|
||||
import com.android.gallery3d.common.Utils;
|
||||
import com.android.photos.BitmapRegionTileSource;
|
||||
import com.android.photos.BitmapRegionTileSource.BitmapSource;
|
||||
import com.android.photos.views.TiledImageRenderer.TileSource;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -168,7 +169,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
}
|
||||
mBitmapSource = new BitmapRegionTileSource.UriBitmapSource(
|
||||
a, mUri, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
|
||||
a.setCropViewTileSource(mBitmapSource, true, false, onLoad);
|
||||
a.setCropViewTileSource(mBitmapSource, true, false, null, onLoad);
|
||||
}
|
||||
@Override
|
||||
public void onSave(final WallpaperPickerActivity a) {
|
||||
@@ -205,7 +206,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
public void onClick(WallpaperPickerActivity a) {
|
||||
BitmapRegionTileSource.UriBitmapSource bitmapSource =
|
||||
new BitmapRegionTileSource.UriBitmapSource(a, Uri.fromFile(mFile), 1024);
|
||||
a.setCropViewTileSource(bitmapSource, false, true, null);
|
||||
a.setCropViewTileSource(bitmapSource, false, true, null, null);
|
||||
}
|
||||
@Override
|
||||
public void onSave(WallpaperPickerActivity a) {
|
||||
@@ -231,22 +232,22 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
mThumb = thumb;
|
||||
}
|
||||
@Override
|
||||
public void onClick(WallpaperPickerActivity a) {
|
||||
public void onClick(final WallpaperPickerActivity a) {
|
||||
BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
|
||||
new BitmapRegionTileSource.ResourceBitmapSource(
|
||||
mResources, mResId, BitmapRegionTileSource.MAX_PREVIEW_SIZE);
|
||||
bitmapSource.loadInBackground();
|
||||
BitmapRegionTileSource source = new BitmapRegionTileSource(a, bitmapSource);
|
||||
CropView v = a.getCropView();
|
||||
v.setTileSource(source, null);
|
||||
Point wallpaperSize = BitmapUtils.getDefaultWallpaperSize(
|
||||
a.getResources(), a.getWindowManager());
|
||||
RectF crop = Utils.getMaxCropRect(
|
||||
source.getImageWidth(), source.getImageHeight(),
|
||||
wallpaperSize.x, wallpaperSize.y, false);
|
||||
v.setScale(wallpaperSize.x / crop.width());
|
||||
v.setTouchEnabled(false);
|
||||
a.setSystemWallpaperVisiblity(false);
|
||||
a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
|
||||
|
||||
@Override
|
||||
public float getScale(TileSource src) {
|
||||
Point wallpaperSize = BitmapUtils.getDefaultWallpaperSize(
|
||||
a.getResources(), a.getWindowManager());
|
||||
RectF crop = Utils.getMaxCropRect(
|
||||
src.getImageWidth(), src.getImageHeight(),
|
||||
wallpaperSize.x, wallpaperSize.y, false);
|
||||
return wallpaperSize.x / crop.width();
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
@Override
|
||||
public void onSave(WallpaperPickerActivity a) {
|
||||
@@ -271,21 +272,26 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
@Override
|
||||
public void onClick(WallpaperPickerActivity a) {
|
||||
CropView c = a.getCropView();
|
||||
|
||||
Drawable defaultWallpaper = WallpaperManager.getInstance(a).getBuiltInDrawable(
|
||||
c.getWidth(), c.getHeight(), false, 0.5f, 0.5f);
|
||||
|
||||
if (defaultWallpaper == null) {
|
||||
Log.w(TAG, "Null default wallpaper encountered.");
|
||||
c.setTileSource(null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
c.setTileSource(
|
||||
new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE), null);
|
||||
c.setScale(1f);
|
||||
c.setTouchEnabled(false);
|
||||
a.setSystemWallpaperVisiblity(false);
|
||||
LoadRequest req = new LoadRequest();
|
||||
req.moveToLeft = false;
|
||||
req.touchEnabled = false;
|
||||
req.scaleProvider = new CropViewScaleProvider() {
|
||||
|
||||
@Override
|
||||
public float getScale(TileSource src) {
|
||||
return 1f;
|
||||
}
|
||||
};
|
||||
req.result = new DrawableTileSource(a, defaultWallpaper, DrawableTileSource.MAX_PREVIEW_SIZE);
|
||||
a.onLoadRequestComplete(req, true);
|
||||
}
|
||||
@Override
|
||||
public void onSave(WallpaperPickerActivity a) {
|
||||
@@ -348,24 +354,11 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCropViewTileSource(BitmapSource bitmapSource,
|
||||
boolean touchEnabled,
|
||||
boolean moveToLeft,
|
||||
final Runnable postExecute) {
|
||||
// we also want to show our own wallpaper instead of the one in the background
|
||||
Runnable showPostExecuteRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if(postExecute != null) {
|
||||
postExecute.run();
|
||||
}
|
||||
setSystemWallpaperVisiblity(false);
|
||||
}
|
||||
};
|
||||
super.setCropViewTileSource(bitmapSource,
|
||||
touchEnabled,
|
||||
moveToLeft,
|
||||
showPostExecuteRunnable);
|
||||
protected void onLoadRequestComplete(LoadRequest req, boolean success) {
|
||||
super.onLoadRequestComplete(req, success);
|
||||
if (success) {
|
||||
setSystemWallpaperVisiblity(false);
|
||||
}
|
||||
}
|
||||
|
||||
// called by onCreate; this is subclassed to overwrite WallpaperCropActivity
|
||||
@@ -375,6 +368,9 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
mCropView = (CropView) findViewById(R.id.cropView);
|
||||
mCropView.setVisibility(View.INVISIBLE);
|
||||
|
||||
mProgressView = findViewById(R.id.loading);
|
||||
|
||||
|
||||
mWallpaperStrip = findViewById(R.id.wallpaper_strip);
|
||||
mCropView.setTouchCallback(new CropView.TouchCallback() {
|
||||
ViewPropertyAnimator mAnim;
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.graphics.Canvas;
|
||||
@@ -28,7 +27,6 @@ import android.graphics.Paint;
|
||||
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;
|
||||
@@ -148,8 +146,6 @@ 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
|
||||
@@ -158,14 +154,14 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
public static abstract class BitmapSource {
|
||||
private SimpleBitmapRegionDecoder mDecoder;
|
||||
private Bitmap mPreview;
|
||||
private int mPreviewSize;
|
||||
private final int mPreviewSize;
|
||||
private int mRotation;
|
||||
public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
|
||||
private State mState = State.NOT_LOADED;
|
||||
public BitmapSource(int previewSize) {
|
||||
mPreviewSize = previewSize;
|
||||
mPreviewSize = Math.min(previewSize, MAX_PREVIEW_SIZE);
|
||||
}
|
||||
public boolean loadInBackground() {
|
||||
public boolean loadInBackground(InBitmapProvider bitmapProvider) {
|
||||
ExifInterface ei = new ExifInterface();
|
||||
if (readExif(ei)) {
|
||||
Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
|
||||
@@ -181,15 +177,33 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
int width = mDecoder.getWidth();
|
||||
int height = mDecoder.getHeight();
|
||||
if (mPreviewSize != 0) {
|
||||
int previewSize = Math.min(mPreviewSize, MAX_PREVIEW_SIZE);
|
||||
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||
opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
opts.inPreferQualityOverSpeed = true;
|
||||
|
||||
float scale = (float) previewSize / Math.max(width, height);
|
||||
float scale = (float) mPreviewSize / Math.max(width, height);
|
||||
opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
|
||||
opts.inJustDecodeBounds = false;
|
||||
mPreview = loadPreviewBitmap(opts);
|
||||
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 reusage bitmap", e);
|
||||
opts.inBitmap = null;
|
||||
mPreview = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mPreview == null) {
|
||||
mPreview = loadPreviewBitmap(opts);
|
||||
}
|
||||
}
|
||||
mState = State.LOADED;
|
||||
return true;
|
||||
@@ -208,10 +222,6 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
return mPreview;
|
||||
}
|
||||
|
||||
public int getPreviewSize() {
|
||||
return mPreviewSize;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
@@ -219,6 +229,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
public abstract boolean readExif(ExifInterface ei);
|
||||
public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
|
||||
public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
|
||||
|
||||
public interface InBitmapProvider {
|
||||
Bitmap forPixelCount(int count);
|
||||
}
|
||||
}
|
||||
|
||||
public static class FilePathBitmapSource extends BitmapSource {
|
||||
@@ -306,13 +320,13 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
Utils.closeSilently(is);
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
|
||||
Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
|
||||
Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
|
||||
return false;
|
||||
} catch (NullPointerException e) {
|
||||
Log.e("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
|
||||
Log.d("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
|
||||
return false;
|
||||
} finally {
|
||||
Utils.closeSilently(is);
|
||||
@@ -372,11 +386,9 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
|
||||
// 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, BitmapSource source) {
|
||||
public BitmapRegionTileSource(Context context, BitmapSource source, byte[] tempStorage) {
|
||||
mTileSize = TiledImageRenderer.suggestedTileSize(context);
|
||||
mRotation = source.getRotation();
|
||||
mDecoder = source.getBitmapRegionDecoder();
|
||||
@@ -386,27 +398,26 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
mOptions = new BitmapFactory.Options();
|
||||
mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;
|
||||
mOptions.inPreferQualityOverSpeed = true;
|
||||
mOptions.inTempStorage = new byte[16 * 1024];
|
||||
int previewSize = source.getPreviewSize();
|
||||
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(source, previewSize);
|
||||
if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) {
|
||||
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.getWidth(), preview.getHeight()));
|
||||
}
|
||||
} 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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return mPreview instanceof BitmapTexture ? ((BitmapTexture) mPreview).getBitmap() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTileSize() {
|
||||
return mTileSize;
|
||||
@@ -435,10 +446,6 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
@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);
|
||||
|
||||
@@ -462,64 +469,4 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
}
|
||||
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(BitmapSource source, int targetSize) {
|
||||
Bitmap result = source.getPreviewBitmap();
|
||||
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)
|
||||
float 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,10 @@ public class TiledImageView extends FrameLayout {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public TileSource getTileSource() {
|
||||
return mRenderer.source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right,
|
||||
int bottom) {
|
||||
|
||||
Reference in New Issue
Block a user