diff --git a/picasso/src/main/java/com/squareup/picasso/BitmapHunter.java b/picasso/src/main/java/com/squareup/picasso/BitmapHunter.java index 3b56e14d50..4becea44b3 100644 --- a/picasso/src/main/java/com/squareup/picasso/BitmapHunter.java +++ b/picasso/src/main/java/com/squareup/picasso/BitmapHunter.java @@ -15,11 +15,9 @@ */ package com.squareup.picasso; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Matrix; +import android.graphics.*; import android.net.NetworkInfo; + import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; @@ -51,7 +49,8 @@ class BitmapHunter implements Runnable { private static final Object DECODE_LOCK = new Object(); private static final ThreadLocal NAME_BUILDER = new ThreadLocal() { - @Override protected StringBuilder initialValue() { + @Override + protected StringBuilder initialValue() { return new StringBuilder(Utils.THREAD_PREFIX); } }; @@ -59,38 +58,40 @@ class BitmapHunter implements Runnable { private static final AtomicInteger SEQUENCE_GENERATOR = new AtomicInteger(); private static final RequestHandler ERRORING_HANDLER = new RequestHandler() { - @Override public boolean canHandleRequest(Request data) { + @Override + public boolean canHandleRequest(Request data) { return true; } - @Override public Result load(Request request, int networkPolicy) throws IOException { + @Override + public Result load(Request request, int networkPolicy) throws IOException { throw new IllegalStateException("Unrecognized type of request: " + request); } }; - final int sequence; - final Picasso picasso; + final int sequence; + final Picasso picasso; final Dispatcher dispatcher; - final Cache cache; - final Stats stats; - final String key; - final Request data; - final int memoryPolicy; + final Cache cache; + final Stats stats; + final String key; + final Request data; + final int memoryPolicy; int networkPolicy; final RequestHandler requestHandler; - Action action; - List actions; - Bitmap result; - Future future; + Action action; + List actions; + Bitmap result; + Future future; Picasso.LoadedFrom loadedFrom; - Exception exception; - int exifRotation; // Determined during decoding of original resource. - int retryCount; - Priority priority; + Exception exception; + int exifRotation; // Determined during decoding of original resource. + int retryCount; + Priority priority; BitmapHunter(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, Action action, - RequestHandler requestHandler) { + RequestHandler requestHandler) { this.sequence = SEQUENCE_GENERATOR.incrementAndGet(); this.picasso = picasso; this.dispatcher = dispatcher; @@ -128,12 +129,13 @@ static Bitmap decodeStream(InputStream stream, Request request) throws IOExcepti byte[] bytes = Utils.toByteArray(stream); if (calculateSize) { BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); - RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options, - request); + if (request.hasSize()) { + RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options, + request); + } } - if (request.cropRect != null) { + if (request.cropRect != null && validateCrop(options, request.cropRect)) { BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(bytes, 0, bytes.length, false); - // TODO check if cropRect fits in bitmap Bitmap bitmap = bitmapRegionDecoder.decodeRegion(request.cropRect, options); bitmapRegionDecoder.recycle(); return bitmap; @@ -143,16 +145,16 @@ static Bitmap decodeStream(InputStream stream, Request request) throws IOExcepti } else { if (calculateSize) { BitmapFactory.decodeStream(stream, null, options); - RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options, - request); - + if (request.hasSize()) { + RequestHandler.calculateInSampleSize(request.targetWidth, request.targetHeight, options, + request); + } markStream.reset(mark); } final Bitmap bitmap; - if (request.cropRect != null) { + if (request.cropRect != null && validateCrop(options, request.cropRect)) { BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(stream, false); - // TODO check if cropRect fits in bitmap bitmap = bitmapRegionDecoder.decodeRegion(request.cropRect, options); bitmapRegionDecoder.recycle(); } else { @@ -167,7 +169,16 @@ static Bitmap decodeStream(InputStream stream, Request request) throws IOExcepti } } - @Override public void run() { + static boolean validateCrop(BitmapFactory.Options options, Rect cropRect) throws IOException { + if (cropRect.width() > 0 && cropRect.height() > 0 && cropRect.width() <= options.outWidth && cropRect.height() <= options.outHeight) { + return true; + } else { + throw new IOException("Invalid crop dimensions."); + } + } + + @Override + public void run() { try { updateThreadName(data); @@ -424,7 +435,7 @@ static void updateThreadName(Request data) { } static BitmapHunter forRequest(Picasso picasso, Dispatcher dispatcher, Cache cache, Stats stats, - Action action) { + Action action) { Request request = action.getRequest(); List requestHandlers = picasso.getRequestHandlers(); @@ -448,7 +459,8 @@ static Bitmap applyCustomTransformations(List transformations, B newResult = transformation.transform(result); } catch (final RuntimeException e) { Picasso.HANDLER.post(new Runnable() { - @Override public void run() { + @Override + public void run() { throw new RuntimeException( "Transformation " + transformation.key() + " crashed with exception.", e); } @@ -467,7 +479,8 @@ static Bitmap applyCustomTransformations(List transformations, B builder.append(t.key()).append('\n'); } Picasso.HANDLER.post(new Runnable() { - @Override public void run() { + @Override + public void run() { throw new NullPointerException(builder.toString()); } }); @@ -476,7 +489,8 @@ static Bitmap applyCustomTransformations(List transformations, B if (newResult == result && result.isRecycled()) { Picasso.HANDLER.post(new Runnable() { - @Override public void run() { + @Override + public void run() { throw new IllegalStateException("Transformation " + transformation.key() + " returned input Bitmap but recycled it."); @@ -488,7 +502,8 @@ static Bitmap applyCustomTransformations(List transformations, B // If the transformation returned a new bitmap ensure they recycled the original. if (newResult != result && !result.isRecycled()) { Picasso.HANDLER.post(new Runnable() { - @Override public void run() { + @Override + public void run() { throw new IllegalStateException("Transformation " + transformation.key() + " mutated input Bitmap but failed to recycle the original."); @@ -584,7 +599,7 @@ static Bitmap transformResult(Request data, Bitmap result, int exifRotation) { } private static boolean shouldResize(boolean onlyScaleDown, int inWidth, int inHeight, - int targetWidth, int targetHeight) { + int targetWidth, int targetHeight) { return !onlyScaleDown || inWidth > targetWidth || inHeight > targetHeight; } } diff --git a/picasso/src/main/java/com/squareup/picasso/RequestCreator.java b/picasso/src/main/java/com/squareup/picasso/RequestCreator.java index ae9da6b079..05522201ab 100644 --- a/picasso/src/main/java/com/squareup/picasso/RequestCreator.java +++ b/picasso/src/main/java/com/squareup/picasso/RequestCreator.java @@ -19,6 +19,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.widget.ImageView; @@ -239,6 +240,15 @@ public RequestCreator centerInside() { return this; } + /** + * Crops image using provided rectangle dimensions. + * @param cropRect - dimensions used for crop + */ + public RequestCreator crop(Rect cropRect) { + data.crop(cropRect); + return this; + } + /** * Only resize an image if the original image size is bigger than the target size * specified by {@link #resize(int, int)}. diff --git a/picasso/src/main/java/com/squareup/picasso/RequestHandler.java b/picasso/src/main/java/com/squareup/picasso/RequestHandler.java index 7cf8c5840c..d882c6e682 100644 --- a/picasso/src/main/java/com/squareup/picasso/RequestHandler.java +++ b/picasso/src/main/java/com/squareup/picasso/RequestHandler.java @@ -129,7 +129,7 @@ boolean supportsReplay() { * {@link Request}, only instantiating them if needed. */ static BitmapFactory.Options createBitmapOptions(Request data) { - final boolean justBounds = data.hasSize(); + final boolean justBounds = data.hasSize() || data.cropRect != null; final boolean hasConfig = data.config != null; BitmapFactory.Options options = null; if (justBounds || hasConfig) {