package com.koushikdutta.ion.bitmap;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Build;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.WindowManager;
import com.koushikdutta.async.util.StreamUtility;
import com.koushikdutta.ion.Ion;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* Created by koush on 5/23/13.
*/
public class IonBitmapCache {
public static final long DEFAULT_ERROR_CACHE_DURATION = 30000L;
Resources resources;
DisplayMetrics metrics;
LruBitmapCache cache;
Ion ion;
long errorCacheDuration = DEFAULT_ERROR_CACHE_DURATION;
public long getErrorCacheDuration() {
return errorCacheDuration;
}
public void setErrorCacheDuration(long errorCacheDuration) {
this.errorCacheDuration = errorCacheDuration;
}
public IonBitmapCache(Ion ion) {
Context context = ion.getContext().getApplicationContext();
this.ion = ion;
metrics = new DisplayMetrics();
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getMetrics(metrics);
final AssetManager mgr = context.getAssets();
resources = new Resources(mgr, metrics, context.getResources().getConfiguration());
cache = new LruBitmapCache(getHeapSize(context) / 7);
}
public BitmapInfo remove(String key) {
return cache.removeBitmapInfo(key);
}
public void clear() {
cache.evictAllBitmapInfo();
}
double heapRatio = 1d / 7d;
public double getHeapRatio() {
return heapRatio;
}
public void setHeapRatio(double heapRatio) {
this.heapRatio = heapRatio;
}
public void put(BitmapInfo info) {
assert Thread.currentThread() == Looper.getMainLooper().getThread();
int maxSize = (int)(getHeapSize(ion.getContext()) * heapRatio);
if (maxSize != cache.maxSize())
cache.setMaxSize(maxSize);
cache.put(info.key, info);
}
public void putSoft(BitmapInfo info) {
assert Thread.currentThread() == Looper.getMainLooper().getThread();
cache.putSoft(info.key, info);
}
public BitmapInfo get(String key) {
if (key == null)
return null;
// see if this thing has an immediate cache hit
BitmapInfo ret = cache.getBitmapInfo(key);
if (ret == null)
return null;
if (ret.bitmap != null && ret.bitmap.isRecycled()) {
Log.w("ION", "Cached bitmap was recycled.");
Log.w("ION", "This may happen if passing Ion bitmaps directly to notification builders or remote media clients.");
Log.w("ION", "Create a deep copy before doing this.");
cache.remove(key);
return null;
}
if (ret.exception == null)
return ret;
// if this bitmap load previously errored out, see if it is time to retry
// the fetch. connectivity error, server failure, etc, shouldn't be
// cached indefinitely...
if (ret.loadTime + errorCacheDuration > System.currentTimeMillis())
return ret;
cache.remove(key);
return null;
}
public void dump() {
Log.i("IonBitmapCache", "bitmap cache: " + cache.size());
Log.i("IonBitmapCache", "freeMemory: " + Runtime.getRuntime().freeMemory());
}
private Point computeTarget(int minx, int miny) {
int targetWidth = minx;
int targetHeight = miny;
if (targetWidth == 0)
targetWidth = metrics.widthPixels;
if (targetWidth <= 0)
targetWidth = Integer.MAX_VALUE;
if (targetHeight == 0)
targetHeight = metrics.heightPixels;
if (targetHeight <= 0)
targetHeight = Integer.MAX_VALUE;
return new Point(targetWidth, targetHeight);
}
private BitmapFactory.Options prepareBitmapOptions(BitmapFactory.Options o, int minx, int miny) throws BitmapDecodeException {
if (o.outWidth < 0 || o.outHeight < 0)
throw new BitmapDecodeException(o.outWidth, o.outHeight);
Point target = computeTarget(minx, miny);
int scale = Math.round(Math.max((float)o.outWidth / target.x, (float)o.outHeight / target.y));
BitmapFactory.Options ret = new BitmapFactory.Options();
ret.inSampleSize = scale;
ret.outWidth = o.outWidth;
ret.outHeight = o.outHeight;
ret.outMimeType = o.outMimeType;
return ret;
}
public BitmapFactory.Options prepareBitmapOptions(File file, int minx, int miny) throws BitmapDecodeException {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.toString(), o);
return prepareBitmapOptions(o, minx, miny);
}
public BitmapFactory.Options prepareBitmapOptions(byte[] bytes, int offset, int length, int minx, int miny) throws BitmapDecodeException {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, offset, length, o);
return prepareBitmapOptions(o, minx, miny);
}
public BitmapFactory.Options prepareBitmapOptions(Resources res, int id, int minx, int miny) throws BitmapDecodeException {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, id, o);
return prepareBitmapOptions(o, minx, miny);
}
public BitmapFactory.Options prepareBitmapOptions(InputStream in, int minx, int miny) throws BitmapDecodeException {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(in, null, o);
return prepareBitmapOptions(o, minx, miny);
}
private static Bitmap getRotatedBitmap(Bitmap bitmap, int rotation) {
if (bitmap == null)
return null;
if (rotation == 0)
return bitmap;
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
public static Bitmap loadBitmap(byte[] bytes, int offset, int length, BitmapFactory.Options o) {
assert Thread.currentThread() != Looper.getMainLooper().getThread();
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, offset, length, o);
if (bitmap == null)
return null;
int rotation = Exif.getOrientation(bytes, offset, length);
return getRotatedBitmap(bitmap, rotation);
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
public static Bitmap loadRegion(final BitmapRegionDecoder decoder, Rect sourceRect, int inSampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
return decoder.decodeRegion(sourceRect, options);
}
public static Bitmap loadBitmap(Resources res, int id, BitmapFactory.Options o) {
assert Thread.currentThread() != Looper.getMainLooper().getThread();
int rotation;
InputStream in = null;
try {
in = res.openRawResource(id);
byte[] bytes = new byte[50000];
int length = in.read(bytes);
rotation = Exif.getOrientation(bytes, 0, length);
}
catch (Exception e) {
rotation = 0;
}
StreamUtility.closeQuietly(in);
Bitmap bitmap = BitmapFactory.decodeResource(res, id, o);
return getRotatedBitmap(bitmap, rotation);
}
public static Bitmap loadBitmap(InputStream stream, BitmapFactory.Options o) throws IOException {
assert Thread.currentThread() != Looper.getMainLooper().getThread();
int rotation;
MarkableInputStream in = new MarkableInputStream(stream);
in.mark(50000);
try {
byte[] bytes = new byte[50000];
int length = in.read(bytes);
rotation = Exif.getOrientation(bytes, 0, length);
}
catch (Exception e) {
rotation = 0;
}
in.reset();
Bitmap bitmap = BitmapFactory.decodeStream(in, null, o);
return getRotatedBitmap(bitmap, rotation);
}
public static Bitmap loadBitmap(File file, BitmapFactory.Options o) {
assert Thread.currentThread() != Looper.getMainLooper().getThread();
int rotation;
FileInputStream fin = null;
try {
fin = new FileInputStream(file);
byte[] bytes = new byte[50000];
int length = fin.read(bytes);
rotation = Exif.getOrientation(bytes, 0, length);
}
catch (Exception e) {
rotation = 0;
}
StreamUtility.closeQuietly(fin);
Bitmap bitmap = BitmapFactory.decodeFile(file.toString(), o);
return getRotatedBitmap(bitmap, rotation);
}
private static int getHeapSize(final Context context) {
return ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass() * 1024 * 1024;
}
}