Skip to content

Instantly share code, notes, and snippets.

@davemorrissey
Forked from gokhanbarisaker/PicassoDecoder.java
Last active November 26, 2021 14:17
Show Gist options
  • Save davemorrissey/e2781ba5b966c9e95539 to your computer and use it in GitHub Desktop.
Save davemorrissey/e2781ba5b966c9e95539 to your computer and use it in GitHub Desktop.
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();
}
}
@kalyaganov
Copy link

kalyaganov commented Nov 22, 2016

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

@sregg
Copy link

sregg commented Jan 16, 2017

Has anyone tried to do this for Glide?

@markini
Copy link

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
Copy link

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
Copy link

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
Copy link

@tickerguy how did you do that?

@arnaudlvq
Copy link

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
Copy link

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
Copy link

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