package at.favre.lib.dali.builder.blur;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Future;
import at.favre.lib.dali.Dali;
import at.favre.lib.dali.R;
import at.favre.lib.dali.blur.EBlurAlgorithm;
import at.favre.lib.dali.blur.IBlur;
import at.favre.lib.dali.blur.algorithms.RenderScriptGaussianBlur;
import at.favre.lib.dali.builder.ABuilder;
import at.favre.lib.dali.builder.ContextWrapper;
import at.favre.lib.dali.builder.ExecutorManager;
import at.favre.lib.dali.builder.ImageReference;
import at.favre.lib.dali.builder.TwoLevelCache;
import at.favre.lib.dali.builder.exception.BlurWorkerException;
import at.favre.lib.dali.builder.processor.ColorFilterProcessor;
import at.favre.lib.dali.builder.processor.ContrastProcessor;
import at.favre.lib.dali.builder.processor.IBitmapProcessor;
import at.favre.lib.dali.builder.processor.RenderscriptBrightnessProcessor;
import at.favre.lib.dali.util.BuilderUtil;
/**
* Created by PatrickF on 26.05.2014.
*/
public class BlurBuilder extends ABuilder {
private final static String TAG = BlurBuilder.class.getSimpleName();
private final static int FADE_IN_MS = 200;
private BlurData data;
private Handler uiThreadHandler = new Handler(Looper.getMainLooper());
public static class BlurData extends ABuilder.Data {
public BitmapFactory.Options options = new BitmapFactory.Options();
public boolean copyBitmapBeforeBlur = false;
public boolean rescaleIfDownscaled = false;
public boolean shouldCache = true;
public ImageReference imageReference;
public ContextWrapper contextWrapper;
public List<IBitmapProcessor> preProcessors = new ArrayList<IBitmapProcessor>();
public List<IBitmapProcessor> postProcessors = new ArrayList<IBitmapProcessor>();
public TwoLevelCache diskCacheManager;
public String tag = UUID.randomUUID().toString();
public int errorResId = R.drawable.ic_error_pic;
public boolean alphaFadeIn = true;
public boolean onConcurrentThreadPool = false;
public int placeholder = Dali.NO_RESID;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public BlurBuilder(ContextWrapper contextWrapper, ImageReference imageReference, TwoLevelCache diskCacheManager) {
data = new BlurData();
data.imageReference = imageReference;
data.contextWrapper = contextWrapper;
data.blurAlgorithm = new RenderScriptGaussianBlur(data.contextWrapper.getRenderScript());
data.diskCacheManager = diskCacheManager;
data.options.inMutable = true;
}
/**
* @param blurRadius the views use to blur the view, default is {@link at.favre.lib.dali.builder.BuilderDefaults#BLUR_RADIUS};
* @throws java.lang.IllegalStateException if blurradius not in range [{@link at.favre.lib.dali.builder.BuilderDefaults#BLUR_RADIUS_MIN},{@link at.favre.lib.dali.builder.BuilderDefaults#BLUR_RADIUS_MAX}}
*/
public BlurBuilder blurRadius(int blurRadius) {
BuilderUtil.checkBlurRadiusPrecondition(blurRadius);
data.blurRadius = blurRadius;
return this;
}
/**
* If this the image bitmap should be copied before blur.
*
* This will increase memory (RAM) usage while blurring, but
* if the bitmap's object is used anywhere else it would
* create side effects.
*
*/
public BlurBuilder copyBitmapBeforeProcess() {
data.copyBitmapBeforeBlur = true;
return this;
}
/**
* Will scale the image down before processing for
* performance enhancement and less memory usage
* sacrificing image quality.
*
* @param scaleInSample value greater than 1 will scale the image width/height, so 2 will getFromDiskCache you 1/4
* of the original size and 4 will getFromDiskCache you 1/16 of the original size - this just sets
* the inSample size in {@link android.graphics.BitmapFactory.Options#inSampleSize } and
* behaves exactly the same, so keep the value 2^n for least scaling artifacts
*/
public BlurBuilder downScale(int scaleInSample) {
data.options.inSampleSize = Math.min(Math.max(1, scaleInSample), 16384);
return this;
}
/**
* Artificially rescales the image if downscaled before to
* it's original width/height
*/
public BlurBuilder reScale() {
data.rescaleIfDownscaled = true;
return this;
}
/**
* Set your custom decoder options here. Mind that that may
* overwrite the value in {@link #downScale(int)} ()};
*
* @param options non-null
*/
public BlurBuilder options(BitmapFactory.Options options) {
if(options != null) {
data.options = options;
}
return this;
}
/**
* Add custom processor. This will be applied to the
* image BEFORE blurring. The order in which this is
* calls defines the order the processors are applied.
*
* @param processor
*/
public BlurBuilder addPreProcessor(IBitmapProcessor processor) {
data.preProcessors.add(processor);
return this;
}
/**
* Set brightness to eg. darken the resulting image for use as background
*
* @param brightness default is 0, pos values increase brightness, neg. values decrease brightness
* .-100 is black, positive goes up to 1000+
*/
public BlurBuilder brightness(float brightness) {
data.preProcessors.add(new RenderscriptBrightnessProcessor(data.contextWrapper.getRenderScript(),brightness, data.contextWrapper.getResources()));
return this;
}
/**
* Change contrast of the image
*
* @param contrast default is 0, pos values increase contrast, neg. values decrease contrast
*/
public BlurBuilder contrast(float contrast) {
data.preProcessors.add(new ContrastProcessor(data.contextWrapper.getRenderScript(),Math.max(Math.min(1500.f,contrast),-1500.f)));
return this;
}
public BlurBuilder colorFilter(int colorResId) {
data.preProcessors.add(new ColorFilterProcessor(colorResId, PorterDuff.Mode.MULTIPLY));
return this;
}
/**
* Add custom processor. This will be applied to the
* image AFTER blurring. The order in which this is
* calls defines the order the processors are applied.
*
* @param processor
*/
public BlurBuilder addPostProcessor(IBitmapProcessor processor) {
data.postProcessors.add(processor);
return this;
}
/**
* Sets the blur algorithm.
*
* NOTE: this probably never is necessary to do except for testing purpose, the default
* algorithm, which uses Android's {@link android.support.v8.renderscript.ScriptIntrinsicBlur}
* which is the best and fastest you getFromDiskCache on Android suffices in nearly every situation
*
* @param algorithm
*/
public BlurBuilder algorithm(EBlurAlgorithm algorithm) {
data.blurAlgorithm = BuilderUtil.getIBlurAlgorithm(algorithm, data.contextWrapper);
return this;
}
/**
* Provide your custom blur implementation
* @param blurAlgorithm
*/
public BlurBuilder algorithm(IBlur blurAlgorithm) {
data.blurAlgorithm = blurAlgorithm;
return this;
}
/**
* Skips the cache (lookup and save). This will also delete all
* saved caches for this configuration. Use this if you only
* use this image once or want to purge the cache for this.
*/
public BlurBuilder skipCache() {
data.shouldCache = false;
return this;
}
/**
* Tags this builder's worker, so it could be later canceld by {@link at.favre.lib.dali.builder.ExecutorManager#cancelByTag(String)}
*/
public BlurBuilder tag(String tag) {
data.tag = tag;
return this;
}
/**
* Set the image that is set when an error occurs
* @param resId - e.g. R.drawable.error_image or {@link at.favre.lib.dali.Dali#NO_RESID} if you want to disable error image
*/
public BlurBuilder error(int resId) {
data.errorResId = resId;
return this;
}
/**
* Per default the the process image will alpha fade in. Use this
* to disable the animation.
*/
public BlurBuilder noFade() {
data.alphaFadeIn = false;
return this;
}
/**
* If this is called the processing will happen on the
* concurrent threadpool. The max parallel threads are
* defined in the global config {@link at.favre.lib.dali.Dali#resetAndSetNewConfig(android.content.Context, at.favre.lib.dali.Dali.Config)}.
*
* This may make execution faster, but be aware that if processing too many big pictures concurrently
* may throw {@link java.lang.OutOfMemoryError}. So only call this when
* processing small pictures like thumbnails in a listview.
*/
public BlurBuilder concurrent() {
data.onConcurrentThreadPool = true;
return this;
}
public BlurBuilder placeholder(int resId) {
data.placeholder = resId;
return this;
}
/* GETTER METHODS ************************************************************************* */
public JobDescription into(final ImageView imageView) {
if(data.placeholder != Dali.NO_RESID) {
imageView.setImageResource(data.placeholder);
}
return start(new BlurWorker.BlurWorkerListener() {
@Override
public void onResult(final BlurWorker.Result result) {
//run on ui thread because we need to modify ui
uiThreadHandler.post(new Runnable() {
@Override
public void run() {
if (result.isError()) {
Log.e(TAG, "Could not set into imageview", result.getThrowable());
if(data.errorResId == Dali.NO_RESID) {
imageView.setImageResource(data.errorResId);
}
} else {
if(data.alphaFadeIn) {
//use what is currently in the imageview to fade
Drawable placeholder;
if(imageView.getDrawable() != null) {
placeholder = imageView.getDrawable();
} else {
placeholder= new ColorDrawable(Color.parseColor("#00FFFFFF"));
}
final TransitionDrawable transition = new TransitionDrawable(new Drawable[] {
placeholder,new BitmapDrawable(data.contextWrapper.getResources(), result.getBitmap())
});
imageView.setImageDrawable(transition);
transition.startTransition(FADE_IN_MS);
//after the transition set only the processed bitmap to avoid keeping both images in memory
} else {
imageView.setImageDrawable(new BitmapDrawable(data.contextWrapper.getResources(), result.getBitmap()));
}
}
}
});
}
});
}
public JobDescription start(BlurWorker.BlurWorkerListener listener) {
Dali.getExecutorManager().submitThreadPool(new BlurWorker(data,listener),data.tag, data.onConcurrentThreadPool ? ExecutorManager.ThreadPoolType.CONCURRENT : ExecutorManager.ThreadPoolType.SERIAL);
return getJobDescription();
}
public BitmapDrawable get() {
return new BitmapDrawable(data.contextWrapper.getResources(),getAsBitmap());
}
public Bitmap getAsBitmap() {
Future<BlurWorker.Result> result = Dali.getExecutorManager().submitThreadPool(new BlurWorker(data),data.tag,data.onConcurrentThreadPool ? ExecutorManager.ThreadPoolType.CONCURRENT : ExecutorManager.ThreadPoolType.SERIAL);
BlurWorker.Result r = null;
try {
r = result.get();
} catch (Exception e) {
throw new BlurWorkerException("Could not get bitmap from future",e);
}
if(r != null) {
if(r.isError()) {
throw new BlurWorkerException(r.getThrowable());
} else {
return r.getBitmap();
}
}
throw new BlurWorkerException("result was null");
}
public JobDescription getJobDescription() {
return new JobDescription(BuilderUtil.getCacheKey(data),BuilderUtil.getBuilderDescription(data), data.tag);
}
public static class JobDescription {
public final String cacheKey;
public final String builderDescription;
public final String tag;
public JobDescription(String cacheKey, String builderDescription, String tag) {
this.cacheKey = cacheKey;
this.builderDescription = builderDescription;
this.tag = tag;
}
}
public interface TaskFinishedListener {
public void onBitmapReady(Bitmap manipulatedBitmap);
public void onError(Throwable t);
}
}