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.http.libcore.IoUtils;
import com.koushikdutta.ion.Ion;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
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();
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 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 || ret.bitmaps != 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);
}
public BitmapFactory.Options prepareBitmapOptions(File file, int minx, int miny) {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.toString(), o);
return prepareBitmapOptions(o, minx, miny);
}
private BitmapFactory.Options prepareBitmapOptions(BitmapFactory.Options o, int minx, int miny) {
if (o.outWidth < 0 || o.outHeight < 0)
return null;
Point target = computeTarget(minx, miny);
int scale = Math.max(o.outWidth / target.x, 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(byte[] bytes, int offset, int length, int minx, int miny) {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(bytes, offset, length, o);
return prepareBitmapOptions(o, minx, miny);
}
public 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);
if (rotation == 0)
return bitmap;
Matrix matrix = new Matrix();
matrix.postRotate(rotation);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
@TargetApi(Build.VERSION_CODES.GINGERBREAD_MR1)
public Bitmap loadRegion(final BitmapRegionDecoder decoder, Rect sourceRect, int inSampleSize) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = inSampleSize;
return decoder.decodeRegion(sourceRect, options);
}
public Bitmap loadBitmap(File file, BitmapFactory.Options o) {
// stream = new BufferedInputStream(stream, 64 * 1024);
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;
}
IoUtils.closeQuietly(fin);
Bitmap bitmap = BitmapFactory.decodeFile(file.toString(), o);
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);
}
private static int getHeapSize(final Context context) {
return ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass() * 1024 * 1024;
}
}