package at.favre.lib.dali.builder.blur;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import at.favre.lib.dali.Dali;
import at.favre.lib.dali.builder.ImageReference;
import at.favre.lib.dali.builder.PerformanceProfiler;
import at.favre.lib.dali.builder.exception.BlurWorkerException;
import at.favre.lib.dali.builder.processor.IBitmapProcessor;
import at.favre.lib.dali.util.BenchmarkUtil;
import at.favre.lib.dali.util.BuilderUtil;
import at.favre.lib.dali.util.LegacySDKUtil;
/**
* This is the worker thread for a the {@link at.favre.lib.dali.builder.blur.BlurBuilder}. It
* contains all the business logic for processing the image.
*
*/
public class BlurWorker implements Callable<BlurWorker.Result> {
private final static String TAG = BlurWorker.class.getSimpleName();
private final String id = UUID.randomUUID().toString();
private BlurWorkerListener listener;
private BlurBuilder.BlurData builderData;
private final Semaphore semaphore = new Semaphore(0,true);
private Handler uiThreadHandler = new Handler(Looper.getMainLooper());
public BlurWorker(BlurBuilder.BlurData builderData) {
this(builderData,null);
}
public BlurWorker(BlurBuilder.BlurData builderData, BlurWorkerListener listener) {
this.builderData = builderData;
this.listener = listener;
}
@Override
public Result call() {
try {
Result r = new Result(process());
if(listener != null) {
listener.onResult(r);
}
return r;
} catch (Throwable t) {
Result r = new Result(t);
if(listener != null) {
listener.onResult(r);
}
return r;
}
}
/**
* The core process
*/
private Bitmap process() {
PerformanceProfiler profiler = new PerformanceProfiler("blur image task ["+builderData.tag+"] started at "+BenchmarkUtil.getCurrentTime(), Dali.getConfig().debugMode);
try {
final String cacheKey = BuilderUtil.getCacheKey(builderData);
if (builderData.shouldCache) {
profiler.startTask(-3, "cache lookup (key:" + cacheKey + ")");
Bitmap cache = builderData.diskCacheManager.get(cacheKey);
profiler.endTask(-3, cache == null ? "miss" : "hit");
if (cache != null) {
profiler.printResultToLog();
return cache;
}
} else {
builderData.diskCacheManager.purge(cacheKey);
}
if (builderData.imageReference.getSourceType().equals(ImageReference.SourceType.VIEW)) {
profiler.startTask(-2, "wait for view to be measured");
View v = builderData.imageReference.getView();
uiThreadHandler.post(new WaitForMeasurement(semaphore,v));
Dali.logV(TAG,"aquire lock for waiting for the view to be measured");
if(semaphore.tryAcquire(8000, TimeUnit.MILLISECONDS)) {
Dali.logV(TAG,"view seems measured, lock was released");
} else {
throw new InterruptedException("Timeout while waiting for the view to be measured");
}
profiler.endTask(-2);
}
// Thread.sleep(1000);
int width = 0, height = 0;
if (builderData.options.inSampleSize > 1 && builderData.rescaleIfDownscaled) {
profiler.startTask(-1, "measure image");
Point p = builderData.imageReference.measureImage(builderData.contextWrapper.getResources());
width = p.x;
height = p.y;
profiler.endTask(-1, height + "x" + width);
}
profiler.startTask(0, "load image");
builderData.imageReference.setDecoderOptions(builderData.options);
Bitmap bitmapToWorkWith = builderData.imageReference.synchronouslyLoadBitmap(builderData.contextWrapper.getResources());
profiler.endTask(0, "source: " + builderData.imageReference.getSourceType() + ", insample: " + builderData.options.inSampleSize + ", height:" + bitmapToWorkWith.getHeight() + ", width:" + bitmapToWorkWith.getWidth() + ", memory usage " + BenchmarkUtil.getScalingUnitByteSize(LegacySDKUtil.byteSizeOf(bitmapToWorkWith)));
if (builderData.copyBitmapBeforeBlur) {
profiler.startTask(1, "copy bitmap");
bitmapToWorkWith = bitmapToWorkWith.copy(bitmapToWorkWith.getConfig(), true);
profiler.endTask(1);
}
int profileIdPreProcessor = 100;
for (IBitmapProcessor postProcessor : builderData.preProcessors) {
profiler.startTask(profileIdPreProcessor, postProcessor.getProcessorTag());
bitmapToWorkWith = postProcessor.manipulate(bitmapToWorkWith);
profiler.endTask(profileIdPreProcessor++);
}
profiler.startTask(10000, "blur with radius " + builderData.blurRadius + "px (" + builderData.blurRadius * builderData.options.inSampleSize + "spx) and algorithm " + builderData.blurAlgorithm.getClass().getSimpleName());
bitmapToWorkWith = builderData.blurAlgorithm.blur(builderData.blurRadius, bitmapToWorkWith);
profiler.endTask(10000);
int profileIdPostProcessor = 20000;
for (IBitmapProcessor postProcessor : builderData.postProcessors) {
profiler.startTask(profileIdPostProcessor, postProcessor.getProcessorTag());
bitmapToWorkWith = postProcessor.manipulate(bitmapToWorkWith);
profiler.endTask(profileIdPostProcessor++);
}
if (builderData.options.inSampleSize > 1 && builderData.rescaleIfDownscaled && height > 0 && width > 0) {
profiler.startTask(40000, "rescale to " + height + "x" + width);
bitmapToWorkWith = Bitmap.createScaledBitmap(bitmapToWorkWith, width, height, false);
profiler.endTask(40000,"memory usage "+BenchmarkUtil.getScalingUnitByteSize(LegacySDKUtil.byteSizeOf(bitmapToWorkWith)));
}
if (builderData.shouldCache) {
profiler.startTask(40001, "async try to disk cache (ignore result)");
Dali.getExecutorManager().executeOnFireAndForgetThreadPool(new AddToCacheTask(bitmapToWorkWith, builderData, cacheKey));
profiler.endTask(40001);
}
return bitmapToWorkWith;
}catch (Throwable t) {
throw new BlurWorkerException(t);
} finally {
profiler.printResultToLog();
}
}
public String getId() {
return id;
}
public static class WaitForMeasurement implements Runnable {
private Semaphore semaphore;
private View v;
public WaitForMeasurement(Semaphore semaphore, View v) {
this.semaphore = semaphore;
this.v = v;
}
@Override
public void run() {
v.post(new Runnable() {
@Override
public void run() {
Dali.logV(TAG,"in view message queue, seems measured, will unlock");
semaphore.release();
}
});
}
}
public static class AddToCacheTask implements Runnable {
private Bitmap bitmap;
private BlurBuilder.BlurData data;
private String cacheKey;
public AddToCacheTask(Bitmap bitmap, BlurBuilder.BlurData data, String cacheKey) {
this.bitmap = bitmap;
this.data = data;
this.cacheKey = cacheKey;
}
@Override
public void run() {
data.diskCacheManager.putInCache(bitmap, cacheKey);
}
}
public static class Result {
private Bitmap bitmap;
private Throwable throwable;
public Result(Bitmap bitmap) {
this.bitmap = bitmap;
}
public Result(Throwable t) {
this.throwable = t;
}
public Bitmap getBitmap() {
return bitmap;
}
public Throwable getThrowable() {
return throwable;
}
public boolean isError() {
return throwable != null;
}
}
public static interface BlurWorkerListener {
public void onResult(Result result);
}
}