-
-
Save davemorrissey/e2781ba5b966c9e95539 to your computer and use it in GitHub Desktop.
/** | |
* Created by gokhanbarisaker on 8/30/15. | |
*/ | |
public class PicassoDecoder implements ImageDecoder | |
{ | |
private String tag; | |
private String picasso; | |
public PicassoDecoder(String tag, Picasso picasso) { | |
this.tag = tag; | |
this.picasso = picasso; | |
} | |
@Override | |
public Bitmap decode(Context context, Uri uri) throws Exception { | |
return picasso | |
.load(uri) | |
.tag(tag) | |
.config(Bitmap.Config.RGB_565) | |
.memoryPolicy(MemoryPolicy.NO_CACHE) | |
.get(); | |
} | |
} |
/** | |
* Created by gokhanbarisaker on 8/30/15. | |
*/ | |
public class PicassoRegionDecoder implements ImageRegionDecoder { | |
private OkHttpClient client; | |
private BitmapRegionDecoder decoder; | |
private final Object decoderLock = new Object(); | |
public PicassoRegionDecoder (OkHttpClient client) { | |
this.client = client; | |
} | |
@Override | |
public Point init(Context context, Uri uri) throws Exception { | |
OkHttpDownloader downloader = new OkHttpDownloader(client); | |
InputStream inputStream = downloader.load(uri, 0).getInputStream(); | |
this.decoder = BitmapRegionDecoder.newInstance(inputStream, false); | |
return new Point(this.decoder.getWidth(), this.decoder.getHeight()); | |
} | |
@Override | |
public Bitmap decodeRegion(Rect rect, int sampleSize) { | |
synchronized(this.decoderLock) { | |
BitmapFactory.Options options = new BitmapFactory.Options(); | |
options.inSampleSize = sampleSize; | |
options.inPreferredConfig = Bitmap.Config.RGB_565; | |
Bitmap bitmap = this.decoder.decodeRegion(rect, options); | |
if(bitmap == null) { | |
throw new RuntimeException("Region decoder returned null bitmap - image format may not be supported"); | |
} else { | |
return bitmap; | |
} | |
} | |
} | |
@Override | |
public boolean isReady() { | |
return this.decoder != null && !this.decoder.isRecycled(); | |
} | |
@Override | |
public void recycle() { | |
this.decoder.recycle(); | |
} | |
} |
Can you provide us with full example of loading an image with this decoder please?
+1 : I've managed to declare these Factories into my imageView, but how to load an image with this ? :/
Edit : I've found out how to do this. Simply write imageView.setImage( ImageSource.uri( url ) );
Thank you, this helps a lot. I'd like to add that you need to set both a setBitmapDecoderFactory
and setRegionDecoderFactory
. Also, if someone else is having issues with the new OkHttp3Downloader(client)
part inside PicassoRegionDecoder
, take a look at this https://github.com/JakeWharton/picasso2-okhttp3-downloader
If someone will be helpful - that's a complete example:
final Picasso picasso = Picasso.with(imageView.getContext());
imageView.setBitmapDecoderFactory(new DecoderFactory<ImageDecoder>() {
public ImageDecoder make() {
return new PicassoDecoder(imageUrl, picasso);
}});
imageView.setRegionDecoderFactory(new DecoderFactory<ImageRegionDecoder>() {
@Override
public ImageRegionDecoder make() throws IllegalAccessException, InstantiationException {
return new PicassoRegionDecoder(new OkHttpClient());
}
});
imageView.setImage(ImageSource.uri(imageUrl));
Just a heads up for anyone using this view in a view pager with Picasso integration, when you do set the RegionDecoderFactory in this line:
imageView.setRegionDecoderFactory(new DecoderFactory<ImageRegionDecoder>() {
@Override
public ImageRegionDecoder make() throws IllegalAccessException, InstantiationException {
return new PicassoRegionDecoder(new OkHttpClient());
}
});
Be careful of the OkHttpClient here. Was running into an issue where the number of open connections were exhausted with a view pager count of around 70 images with this view, due to what I suspect are new OkHttpClients creating new open connections and not dropping them fast enough when the page was destroyed once a user scrolled away from the page. Instantiated a new OkHttpClient in the ViewPager adapter and passed it to each view page on the instantiateView call fixed this issue for me.
Final code that is being used is as follows:
SubsamplingScaleImageView scaleImageView;
public void loadImageByUrl(final String url, final OkHttpClient okHttpClient) {
scaleImageView.setMaxScale(5.0f);
final Picasso picasso = Picasso.with(getContext());
scaleImageView.setBitmapDecoderFactory(new DecoderFactory<ImageDecoder>() {
@Override
public ImageDecoder make() throws IllegalAccessException, java.lang.InstantiationException {
return new PicassoDecoder(url, picasso);
}
});
scaleImageView.setRegionDecoderFactory(new DecoderFactory<ImageRegionDecoder>() {
@Override
public ImageRegionDecoder make() throws IllegalAccessException, java.lang.InstantiationException {
return new PicassoRegionDecoder(okHttpClient);
}
});
scaleImageView.setOnImageEventListener(new SubScalingImageViewListener());
scaleImageView.setImage(ImageSource.uri(url));
}
I don't really understand how PicassoRegionDecoder
connected with Picasso library?
Has anyone tried to do this for Glide?
With the new Picasso snapshot the init
function of the PicassoRegionDecoder
looks like this:
@Override
public Point init(Context context, Uri uri) throws Exception {
OkHttp3Downloader downloader = new OkHttp3Downloader(client);
okhttp3.Request request = new Request.Builder().url(uri.toString()).build();
InputStream inputStream = downloader.load(request).body().byteStream();
this.decoder = BitmapRegionDecoder.newInstance(inputStream, false);
return new Point(this.decoder.getWidth(), this.decoder.getHeight());
}
Jesus! Combine libraries can be a nightmare sometimes. You need to line up the proper versions of each library.
These are the versions that work for me:
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation 'com.squareup.picasso:picasso:2.5.2'
implementation 'com.squareup.okhttp:okhttp:2.1.0'
Anyone got a code snippet that uses the isImageLoaded() override so I can have a "downloading...." display until the image comes up when using this with Picasso/Okhttp?
(Nevermind -- figured it out ;-) I like this a lot....)
@tickerguy how did you do that?
use the SSIV listener
image.setOnImageEventListener(new SubsamplingScaleImageView.OnImageEventListener() {
@Override
public void onReady() {
//You could start loading your image here, so the ssiv is ready.
}
@Override
public void onImageLoaded() {
//Do your stuff here.
}
@Override
public void onPreviewLoadError(Exception e) {}
@Override
public void onImageLoadError(Exception e) {}
@Override
public void onTileLoadError(Exception e) {}
@Override
public void onPreviewReleased() {}
});
** How to intercept the bitmap before the SSIV** : using Glide : (Working on 8k images with latest implementation 4.11.0)
Glide.with(ssiv.getContext())
.asBitmap()
.load("https://vah.dy.fi/testcard/7680x4320.png") //Your own link
.into(new CustomTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
//Do whatever you want with the bitmap (like saving it to storage)
//ex : ssiv.setImage(ImageSource.bitmap(resource);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
}
});
Provide a decoder based on HttpUrlConnection
, not OkHttp
or Picasso
dependency needed. Use only if you don't need image cache.
class URLImageDecoder : ImageDecoder {
private var bitmapConfig: Bitmap.Config =
SubsamplingScaleImageView.getPreferredBitmapConfig() ?: Bitmap.Config.RGB_565
@Throws(java.lang.Exception::class)
override fun decode(context: Context, uri: Uri): Bitmap {
val options = BitmapFactory.Options().apply { inPreferredConfig = bitmapConfig }
val bitmap: Bitmap?
var inputStream: InputStream? = null
try {
inputStream = URL(uri.toString()).openStream()
bitmap = BitmapFactory.decodeStream(inputStream, null, options)
} finally {
try {
inputStream?.close()
} catch (ignored: Exception) {
}
}
return bitmap
?: throw RuntimeException("Region decoder returned null bitmap - image format may not be supported")
}
}
class URLImageRegionDecoder : ImageRegionDecoder {
private lateinit var decoder: BitmapRegionDecoder
private val decoderLock: ReadWriteLock = ReentrantReadWriteLock(true)
private val bitmapConfig: Bitmap.Config =
SubsamplingScaleImageView.getPreferredBitmapConfig() ?: Bitmap.Config.RGB_565
@Throws(Exception::class)
override fun init(context: Context, uri: Uri): Point {
var inputStream: InputStream? = null
try {
inputStream = URL(uri.toString()).openStream()
decoder = BitmapRegionDecoder.newInstance(inputStream, false)
} finally {
try {
inputStream?.close()
} catch (ignored: Exception) {
}
}
return Point(decoder.width, decoder.height)
}
override fun decodeRegion(rect: Rect, sampleSize: Int): Bitmap {
getDecodeLock().lock()
return try {
if (!decoder.isRecycled) {
val options = BitmapFactory.Options()
options.inSampleSize = sampleSize
options.inPreferredConfig = bitmapConfig
decoder.decodeRegion(rect, options) ?: throw java.lang.RuntimeException(
"Skia image decoder returned null bitmap - image format may not be supported")
} else {
throw IllegalStateException("Cannot decode region after decoder has been recycled")
}
} finally {
getDecodeLock().unlock()
}
}
override fun isReady(): Boolean = !decoder.isRecycled
override fun recycle() = decoder.recycle()
/**
* Before SDK 21, BitmapRegionDecoder was not synchronized internally. Any attempt to decode
* regions from multiple threads with one decoder instance causes a segfault. For old versions
* use the write lock to enforce single threaded decoding.
*/
private fun getDecodeLock(): Lock {
return if (Build.VERSION.SDK_INT < 21) {
decoderLock.writeLock()
} else {
decoderLock.readLock()
}
}
}
Can you provide us with full example of loading an image with this decoder please?