Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Picasso decoder for subsampling-scale-image-view
/**
* 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();
}
}
@ktchernov

This comment has been minimized.

Copy link

@ktchernov ktchernov commented Jan 3, 2016

private String picasso; should be private Picasso picasso;

@ThePromoter

This comment has been minimized.

Copy link

@ThePromoter ThePromoter commented Jan 5, 2016

If CompatDecoderFactory expects your custom Decoder to have a no-arg constructor, how can we go about setting the tag when using this decoder? Do you have an example where you set this custom decoder as the bitmapDecoderClass, and properly use it with picasso?

@gokhanbarisaker

This comment has been minimized.

Copy link

@gokhanbarisaker gokhanbarisaker commented Mar 15, 2016

Let's assume that you are using your image url string as a tag. You have 2 options;

  1. Declaring url & picasso at outer scope of your factory

final String url = "http://example.com/image001.jpg";
final Picasso picasso = Picasso.with(imageView.getContext());

and accessing them within factory

imageView.setBitmapDecoderFactory(new DecoderFactory<ImageDecoder>() {
public ImageDecoder make() {
return new PicassoDecoder(url, picasso);
}});

  1. Subclassing factory & adding a constructor that takes tag and Picasso instance as argument

public class PicassoDecoderFactory implements new DecoderFactory<ImageDecoder> {
private final String tag;
private final Picasso picasso;
public PicassoDecoderFactory(String tag, Picasso picasso) { this.tag = tag; this.picasso = picasso; }

public ImageDecoder make() {
return new PicassoDecoder(tag, picasso);
}
}

and using that as

String url = "http://example.com/image001.jpg";
Picasso picasso = Picasso.with(imageView.getContext());
imageView.setBitmapDecoderFactory(new PicassoDecoderFactory(url, picasso));

@VladimirYugay

This comment has been minimized.

Copy link

@VladimirYugay VladimirYugay commented Mar 23, 2016

Can you provide us with full example of loading an image with this decoder please?

@dinesh-tech1990

This comment has been minimized.

Copy link

@dinesh-tech1990 dinesh-tech1990 commented May 12, 2016

Can you provide us with full example of loading an image with this decoder please?

@Orabig

This comment has been minimized.

Copy link

@Orabig Orabig commented May 16, 2016

+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 ) );

@okacat

This comment has been minimized.

Copy link

@okacat okacat commented May 17, 2016

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

@gmikhail

This comment has been minimized.

Copy link

@gmikhail gmikhail commented Jun 7, 2016

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));
@timothythlau

This comment has been minimized.

Copy link

@timothythlau timothythlau commented Jun 29, 2016

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));
    }
@kalyaganov

This comment has been minimized.

Copy link

@kalyaganov kalyaganov commented Nov 22, 2016

I don't really understand how PicassoRegionDecoder connected with Picasso library?

@sregg

This comment has been minimized.

Copy link

@sregg sregg commented Jan 16, 2017

Has anyone tried to do this for Glide?

@markini

This comment has been minimized.

Copy link

@markini markini commented Feb 27, 2017

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());
}
@lordscales91

This comment has been minimized.

Copy link

@lordscales91 lordscales91 commented Apr 8, 2018

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'
@tickerguy

This comment has been minimized.

Copy link

@tickerguy tickerguy commented May 16, 2018

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....)

@aldrinjoemathew

This comment has been minimized.

Copy link

@aldrinjoemathew aldrinjoemathew commented Mar 30, 2020

@tickerguy how did you do that?

@arnaudlvq

This comment has been minimized.

Copy link

@arnaudlvq arnaudlvq commented Apr 14, 2020

@aldrinjoemathew

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() {}
    });
@arnaudlvq

This comment has been minimized.

Copy link

@arnaudlvq arnaudlvq commented Apr 20, 2020

** 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) {
                }
            });
@kyze8439690

This comment has been minimized.

Copy link

@kyze8439690 kyze8439690 commented Dec 26, 2020

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()
            }
        }
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment