Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability for Picasso to return GIF stream rather than just bitmaps #1

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions picasso/src/main/java/com/squareup/picasso/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/
package com.squareup.picasso;

import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;

import com.squareup.picasso.Picasso.Priority;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
Expand Down Expand Up @@ -57,7 +57,7 @@ public RequestWeakReference(Action action, T referent, ReferenceQueue<? super T>
this.tag = (tag != null ? tag : this);
}

abstract void complete(Bitmap result, Picasso.LoadedFrom from);
abstract void complete(ImageLoadResult result, Picasso.LoadedFrom from);

abstract void error();

Expand Down
41 changes: 23 additions & 18 deletions picasso/src/main/java/com/squareup/picasso/BitmapHunter.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@
import static com.squareup.picasso.Utils.log;

class BitmapHunter implements Runnable {

/**
* Global lock for bitmap decoding to ensure that we are only are decoding one at a time. Since
* this will only ever happen in background threads we help avoid excessive memory thrashing as
* well as potential OOMs. Shamelessly stolen from Volley.
* Global lock for bitmap decoding to ensure that we are only are decoding one at a
* time. Since this will only ever happen in background threads we help avoid excessive
* memory thrashing as well as potential OOMs. Shamelessly stolen from Volley.
*/
private static final Object DECODE_LOCK = new Object();

Expand Down Expand Up @@ -76,7 +77,7 @@ class BitmapHunter implements Runnable {

Action action;
List<Action> actions;
Bitmap result;
ImageLoadResult result;
Future<?> future;
Picasso.LoadedFrom loadedFrom;
Exception exception;
Expand Down Expand Up @@ -134,56 +135,60 @@ class BitmapHunter implements Runnable {
}
}

Bitmap hunt() throws IOException {
Bitmap bitmap = null;
ImageLoadResult hunt() throws IOException {
ImageLoadResult huntResult = new ImageLoadResult();

if (!skipMemoryCache) {
bitmap = cache.get(key);
if (bitmap != null) {
huntResult.bitmap = cache.get(key);
if (huntResult.bitmap != null) {
stats.dispatchCacheHit();
loadedFrom = MEMORY;
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId(), "from cache");
}
return bitmap;
return huntResult;
}
}

data.loadFromLocalCacheOnly = (retryCount == 0);
RequestHandler.Result result = requestHandler.load(data);
if (result != null) {
bitmap = result.getBitmap();
loadedFrom = result.getLoadedFrom();
exifRotation = result.getExifOrientation();
huntResult.bitmap = result.getBitmap();
huntResult.gifStream = result.getGifStream();
}

if (bitmap != null) {
if (huntResult.bitmap != null) {
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_DECODED, data.logId());
}
stats.dispatchBitmapDecoded(bitmap);
stats.dispatchBitmapDecoded(huntResult.bitmap);
if (data.needsTransformation() || exifRotation != 0) {
synchronized (DECODE_LOCK) {
if (data.needsMatrixTransform() || exifRotation != 0) {
bitmap = transformResult(data, bitmap, exifRotation);
huntResult.bitmap = transformResult(data, huntResult.bitmap, exifRotation);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId());
}
}
if (data.hasCustomTransformations()) {
bitmap = applyCustomTransformations(data.transformations, bitmap);
huntResult.bitmap = applyCustomTransformations(data.transformations, huntResult.bitmap);
if (picasso.loggingEnabled) {
log(OWNER_HUNTER, VERB_TRANSFORMED, data.logId(), "from custom transformations");
}
}
}
if (bitmap != null) {
stats.dispatchBitmapTransformed(bitmap);
if (huntResult.bitmap != null) {
stats.dispatchBitmapTransformed(huntResult.bitmap);
}
}
} else if (huntResult.gifStream != null) {
// Pixate code to account for a GIF, which we handle
// separately.
}

return bitmap;
return huntResult;
}

void attach(Action action) {
Expand Down Expand Up @@ -292,7 +297,7 @@ boolean supportsReplay() {
return requestHandler.supportsReplay();
}

Bitmap getResult() {
ImageLoadResult getResult() {
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.io.IOException;
import java.io.InputStream;

import com.squareup.picasso.Picasso.LoadedFrom;

import static android.content.ContentResolver.SCHEME_CONTENT;
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;

Expand All @@ -37,25 +39,37 @@ class ContentStreamRequestHandler extends RequestHandler {
}

@Override public Result load(Request data) throws IOException {
return new Result(decodeContentStream(data), DISK);
return decodeContentStream(data, DISK, /* exifOrientation */ 0);
}

protected Bitmap decodeContentStream(Request data) throws IOException {
protected Result decodeContentStream(Request data, LoadedFrom loadedFrom, int exifOrientation)
throws IOException {
ContentResolver contentResolver = context.getContentResolver();
final BitmapFactory.Options options = createBitmapOptions(data);
if (requiresInSampleSize(options)) {
InputStream is = null;
try {
is = contentResolver.openInputStream(data.uri);
GifPrecheckResult precheck = precheckForGif(is);
if (precheck.isGif) {
return new Result(precheck.inputStream, loadedFrom);
}
is = precheck.inputStream;
BitmapFactory.decodeStream(is, null, options);
} finally {
Utils.closeQuietly(is);
}
calculateInSampleSize(data.targetWidth, data.targetHeight, options, data);
}
InputStream is = contentResolver.openInputStream(data.uri);
GifPrecheckResult precheck = precheckForGif(is);
if (precheck.isGif) {
return new Result(precheck.inputStream, loadedFrom);
}
is = precheck.inputStream;
try {
return BitmapFactory.decodeStream(is, null, options);
Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);
return new Result(bitmap, loadedFrom, exifOrientation);
} finally {
Utils.closeQuietly(is);
}
Expand Down
5 changes: 3 additions & 2 deletions picasso/src/main/java/com/squareup/picasso/Dispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,9 @@ void performRetry(BitmapHunter hunter) {
}

void performComplete(BitmapHunter hunter) {
if (!hunter.shouldSkipMemoryCache()) {
cache.set(hunter.getKey(), hunter.getResult());
ImageLoadResult result = hunter.getResult();
if (!hunter.shouldSkipMemoryCache() && result.bitmap != null) {
cache.set(hunter.getKey(), result.bitmap);
}
hunterMap.remove(hunter.getKey());
batch(hunter);
Expand Down
4 changes: 1 addition & 3 deletions picasso/src/main/java/com/squareup/picasso/FetchAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@
*/
package com.squareup.picasso;

import android.graphics.Bitmap;

class FetchAction extends Action<Void> {
FetchAction(Picasso picasso, Request data, boolean skipCache, String key, Object tag) {
super(picasso, null, data, skipCache, false, 0, null, key, tag);
}

@Override void complete(Bitmap result, Picasso.LoadedFrom from) {
@Override void complete(ImageLoadResult result, Picasso.LoadedFrom from) {
}

@Override public void error() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class FileRequestHandler extends ContentStreamRequestHandler {
}

@Override public Result load(Request data) throws IOException {
return new Result(decodeContentStream(data), DISK, getFileExifRotation(data.uri));
return decodeContentStream(data, DISK, getFileExifRotation(data.uri));
}

static int getFileExifRotation(Uri uri) throws IOException {
Expand Down
4 changes: 1 addition & 3 deletions picasso/src/main/java/com/squareup/picasso/GetAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@
*/
package com.squareup.picasso;

import android.graphics.Bitmap;

class GetAction extends Action<Void> {
GetAction(Picasso picasso, Request data, boolean skipCache, String key, Object tag) {
super(picasso, null, data, skipCache, false, 0, null, key, tag);
}

@Override void complete(Bitmap result, Picasso.LoadedFrom from) {
@Override void complete(ImageLoadResult result, Picasso.LoadedFrom from) {
}

@Override public void error() {
Expand Down
22 changes: 22 additions & 0 deletions picasso/src/main/java/com/squareup/picasso/ImageLoadResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.squareup.picasso;

import java.io.InputStream;

import android.graphics.Bitmap;

public class ImageLoadResult {

public Bitmap bitmap;
public InputStream gifStream;

ImageLoadResult() {
this.bitmap = null;
this.gifStream = null;
}

ImageLoadResult(Bitmap bitmap) {
this.bitmap = bitmap;
this.gifStream = null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
package com.squareup.picasso;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;

Expand All @@ -31,10 +30,10 @@ class ImageViewAction extends Action<ImageView> {
this.callback = callback;
}

@Override public void complete(Bitmap result, Picasso.LoadedFrom from) {
@Override public void complete(ImageLoadResult result, Picasso.LoadedFrom from) {
if (result == null) {
throw new AssertionError(
String.format("Attempted to complete action with no result!\n%s", this));
throw new AssertionError(String.format("Attempted to complete action with no result!\n%s",
this));
}

ImageView target = this.target.get();
Expand All @@ -44,7 +43,7 @@ class ImageViewAction extends Action<ImageView> {

Context context = picasso.context;
boolean indicatorsEnabled = picasso.indicatorsEnabled;
PicassoDrawable.setBitmap(target, context, result, from, noFade, indicatorsEnabled);
PicassoDrawable.setBitmap(target, context, result.bitmap, from, noFade, indicatorsEnabled);

if (callback != null) {
callback.onSuccess();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class MediaStoreRequestHandler extends ContentStreamRequestHandler {
if (data.hasSize()) {
PicassoKind picassoKind = getPicassoKind(data.targetWidth, data.targetHeight);
if (!isVideo && picassoKind == FULL) {
return new Result(decodeContentStream(data), DISK, exifOrientation);
return decodeContentStream(data, DISK, exifOrientation);
}

long id = parseId(data.uri);
Expand Down Expand Up @@ -89,7 +89,7 @@ class MediaStoreRequestHandler extends ContentStreamRequestHandler {
}
}

return new Result(decodeContentStream(data), DISK, exifOrientation);
return decodeContentStream(data, DISK, exifOrientation);
}

static PicassoKind getPicassoKind(int targetWidth, int targetHeight) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@
*/
package com.squareup.picasso;

import static com.squareup.picasso.Picasso.LoadedFrom.DISK;
import static com.squareup.picasso.Picasso.LoadedFrom.NETWORK;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.NetworkInfo;
import java.io.IOException;
import java.io.InputStream;

import static com.squareup.picasso.Downloader.Response;
import static com.squareup.picasso.Picasso.LoadedFrom.DISK;
import static com.squareup.picasso.Picasso.LoadedFrom.NETWORK;
import com.squareup.picasso.Downloader.Response;

class NetworkRequestHandler extends RequestHandler {
static final int RETRY_COUNT = 2;
private static final int MARKER = 65536;

private static final String SCHEME_HTTP = "http";
private static final String SCHEME_HTTPS = "https";
Expand Down Expand Up @@ -62,7 +63,8 @@ public NetworkRequestHandler(Downloader downloader, Stats stats) {
if (is == null) {
return null;
}
// Sometimes response content length is zero when requests are being replayed. Haven't found
// Sometimes response content length is zero when requests are being
// replayed. Haven't found
// root cause to this but retrying the request seems safe to do so.
if (response.getContentLength() == 0) {
Utils.closeQuietly(is);
Expand All @@ -71,10 +73,20 @@ public NetworkRequestHandler(Downloader downloader, Stats stats) {
if (loadedFrom == NETWORK && response.getContentLength() > 0) {
stats.dispatchDownloadFinished(response.getContentLength());
}

boolean isGif = false;
try {
return new Result(decodeStream(is, data), loadedFrom);
GifPrecheckResult result = precheckForGif(is);
isGif = result.isGif;
if (isGif) {
return new Result(result.inputStream, loadedFrom);
} else {
return new Result(decodeStream(result.inputStream, data), loadedFrom);
}
} finally {
Utils.closeQuietly(is);
if (!isGif) {
Utils.closeQuietly(is);
}
}
}

Expand All @@ -91,8 +103,14 @@ public NetworkRequestHandler(Downloader downloader, Stats stats) {
}

private Bitmap decodeStream(InputStream stream, Request data) throws IOException {
MarkableInputStream markStream = new MarkableInputStream(stream);
stream = markStream;
MarkableInputStream markStream;

if (stream instanceof MarkableInputStream) {
markStream = (MarkableInputStream) stream;
} else {
markStream = new MarkableInputStream(stream);
stream = markStream;
}

long mark = markStream.savePosition(MARKER);

Expand All @@ -101,7 +119,8 @@ private Bitmap decodeStream(InputStream stream, Request data) throws IOException

boolean isWebPFile = Utils.isWebPFile(stream);
markStream.reset(mark);
// When decode WebP network stream, BitmapFactory throw JNI Exception and make app crash.
// When decode WebP network stream, BitmapFactory throw JNI Exception and
// make app crash.
// Decode byte array instead
if (isWebPFile) {
byte[] bytes = Utils.toByteArray(stream);
Expand Down
Loading