Skip to content

Commit

Permalink
fixes bumptech#738: Add optional threading lock for bitmap manipulati…
Browse files Browse the repository at this point in the history
…on (affects only Moto X 2nd gen on api 22)

this addresses a threading bug on this specific device, the bug manifests itself by displaying
black images instead of resized images.
  • Loading branch information
Matthijs Mullender committed Nov 20, 2015
1 parent 4f159aa commit b393232
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.bumptech.glide.load.resource.bitmap;

import android.os.Build;
import android.support.annotation.NonNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* https://github.com/bumptech/glide/issues/738 On some devices (Moto X with android 5.1) bitmap
* drawing is not thread safe.
*
* This class provides a lock implementation that only really locks for these devices. For other
* types of devices the lock is always available and therefore does not impact performance The
* actual lock logic is delegated to {@see ReentrantLock}
*/
final class BitmapDrawLock implements Lock {
private static final BitmapDrawLock INSTANCE = new BitmapDrawLock();
private final Lock lock = new ReentrantLock();
// only enable the lock for Moto X 2nd gen on android 5.1
private final boolean shouldUseLock = "XT1097".equals(Build.MODEL)
&& Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1;

private BitmapDrawLock() {
}

public static BitmapDrawLock getInstance() {
return INSTANCE;
}

@Override
public void lock() {
if (!shouldUseLock) {
return;
}
lock.lock();
}

@Override
public void lockInterruptibly() throws InterruptedException {
if (!shouldUseLock) {
return;
}
lock.lockInterruptibly();
}

@NonNull
@Override
public Condition newCondition() {
if (!shouldUseLock) {
return null;
}
return lock.newCondition();
}

@Override
public boolean tryLock() {
if (!shouldUseLock) {
return true;
}
return lock.tryLock();
}

@Override
public boolean tryLock(long l, TimeUnit timeUnit) throws InterruptedException {
if (!shouldUseLock) {
return true;
}
return lock.tryLock(l, timeUnit);
}

@Override
public void unlock() {
if (!shouldUseLock) {
return;
}
lock.unlock();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -400,17 +400,19 @@ private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options
int sourceHeight = options.outHeight;
String outMimeType = options.outMimeType;
final Bitmap result;
BitmapDrawLock.getInstance().lock();
try {
result = BitmapFactory.decodeStream(is, null, options);
} catch (IllegalArgumentException e) {
throw newIoExceptionForInBitmapAssertion(e, sourceWidth, sourceHeight, outMimeType, options);
} finally {
BitmapDrawLock.getInstance().unlock();
}

if (options.inJustDecodeBounds) {
is.reset();

}

return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ public static Bitmap centerCrop(@NonNull BitmapPool pool, @NonNull Bitmap inBitm
// We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.
TransformationUtils.setAlpha(inBitmap, result);

Canvas canvas = new Canvas(result);
canvas.drawBitmap(inBitmap, m, DEFAULT_PAINT);
clear(canvas);
applyMatrix(inBitmap, result, m);
return result;
}

Expand Down Expand Up @@ -129,15 +127,25 @@ public static Bitmap fitCenter(@NonNull BitmapPool pool, @NonNull Bitmap inBitma
Log.v(TAG, "minPct: " + minPercentage);
}

Canvas canvas = new Canvas(toReuse);
Matrix matrix = new Matrix();
matrix.setScale(minPercentage, minPercentage);
canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT);
clear(canvas);
applyMatrix(inBitmap, toReuse, matrix);

return toReuse;
}

private static void applyMatrix(@NonNull Bitmap inBitmap, @NonNull Bitmap targetBitmap,
Matrix matrix) {
BitmapDrawLock.getInstance().lock();
try {
Canvas canvas = new Canvas(targetBitmap);
canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT);
clear(canvas);
} finally {
BitmapDrawLock.getInstance().unlock();
}
}

/**
* Sets the alpha of the Bitmap we're going to re-use to the alpha of the Bitmap we're going to
* transform. This keeps {@link android.graphics.Bitmap#hasAlpha()}} consistent before and after
Expand Down Expand Up @@ -240,10 +248,7 @@ public static Bitmap rotateImageExif(@NonNull BitmapPool pool, @NonNull Bitmap i

matrix.postTranslate(-newRect.left, -newRect.top);

final Canvas canvas = new Canvas(result);
canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT);
clear(canvas);

applyMatrix(inBitmap, result, matrix);
return result;
}

Expand Down Expand Up @@ -275,15 +280,20 @@ public static Bitmap circleCrop(@NonNull BitmapPool pool, @NonNull Bitmap inBitm

Bitmap result = pool.get(destWidth, destHeight, getSafeConfig(toTransform));
setAlphaIfAvailable(result, true /*hasAlpha*/);
Canvas canvas = new Canvas(result);

// Draw a circle
canvas.drawCircle(destRect.left + radius, destRect.top + radius, radius,
CIRCLE_CROP_SHAPE_PAINT);

// Draw the bitmap in the circle
canvas.drawBitmap(toTransform, srcRect, destRect, CIRCLE_CROP_BITMAP_PAINT);
clear(canvas);
BitmapDrawLock.getInstance().lock();
try {
Canvas canvas = new Canvas(result);
// Draw a circle
canvas.drawCircle(destRect.left + radius, destRect.top + radius, radius,
CIRCLE_CROP_SHAPE_PAINT);
// Draw the bitmap in the circle
canvas.drawBitmap(toTransform, srcRect, destRect, CIRCLE_CROP_BITMAP_PAINT);
clear(canvas);
} finally {
BitmapDrawLock.getInstance().unlock();
}

if (!toTransform.equals(inBitmap)) {
pool.put(toTransform);
Expand Down Expand Up @@ -335,10 +345,15 @@ public static Bitmap roundedCorners(@NonNull BitmapPool pool, @NonNull Bitmap in
paint.setAntiAlias(true);
paint.setShader(shader);
RectF rect = new RectF(0, 0, result.getWidth(), result.getHeight());
Canvas canvas = new Canvas(result);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawRoundRect(rect, roundingRadius, roundingRadius, paint);
clear(canvas);
BitmapDrawLock.getInstance().lock();
try {
Canvas canvas = new Canvas(result);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
canvas.drawRoundRect(rect, roundingRadius, roundingRadius, paint);
clear(canvas);
} finally {
BitmapDrawLock.getInstance().unlock();
}

if (!toTransform.equals(inBitmap)) {
pool.put(toTransform);
Expand Down

0 comments on commit b393232

Please sign in to comment.