bumptech / bumptech/glide

Solution of "Failed to find source encoder for data class: Bitmap" for a custom ModalLoader

Open
#4,688 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
35k
Forks
6.2k
Avg merge
1d 11h
Merged PRs (30d)
8

Description

Hi, firstly I want to thank you for this amazing library! I used this library in my multiple apps. 1 of them has millions of downloads.

**Glide Version**:
4.12.0

**Code for my custom ModalLoader**:
I have a custom model loader as follow:
```kotlin
internal class IconUrlDataFetcher(private val context: Context, private val model: GlideIconUrl) : DataFetcher {

override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) {
var bitmap: Bitmap? = null
// ...
// getting image from url, convert to bitmap then do some refactor on the bitmap
// in that way, glide cache the modified bitmap as I wanted
callback.onDataReady(bitmap)
}

override fun getDataClass() = Bitmap::class.java
override fun getDataSource() = DataSource.REMOTE

override fun cleanup() {}
override fun cancel() {}

}
```
```kotlin
internal class IconUrlModelLoader(private val context: Context) : ModelLoader {

override fun handles(model: GlideIconUrl) = true
override fun buildLoadData(model: GlideIconUrl, width: Int, height: Int, options: Options) =
LoadData(ObjectKey("icon:${model.url}"), IconUrlDataFetcher(context, model))

}
```
```kotlin
internal class IconUrlModelLoaderFactory(private val context: Context) : ModelLoaderFactory {

override fun teardown() {}
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader {
return IconUrlModelLoader(context)
}
}
```

**Problem**:
When I use:
```kotlin
GlideApp.with(imageView)
.load(GlideIconUrl(url))
.diskCacheStrategy(DiskCacheStrategy.DATA)
.into(imageView)
```
I got the following error:
```
com.bumptech.glide.Registry$NoSourceEncoderAvailableException: Failed to find source encoder for data class: Bitmap
...
...
```

**Solution**:
To solve that I wrote a custom Bitmap Encoder as below:
```kotlin
internal class BitmapEncoder : Encoder {

override fun encode(bitmap: Bitmap, file: File, options: Options): Boolean {
val format = getFormat(bitmap, options)
GlideTrace.beginSectionFormat(
"encode: [%dx%d] %s", bitmap.width, bitmap.height, format
)
return try {
val start = LogTime.getLogTime()
val quality = options.get(COMPRESSION_QUALITY)!!
var success = false
var os: OutputStream? = null
try {
os = FileOutputStream(file)
bitmap.compress(format, quality, os)
os.close()
success = true
} catch (e: IOException) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to encode Bitmap", e)
}
} finally {
if (os != null) {
try {
os.close()
} catch (e: IOException) {
// Do nothing.
}
}
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(
TAG,
"Compressed with type: "
+ format
+ " of size "
+ Util.getBitmapByteSize(bitmap)
+ " in "
+ LogTime.getElapsedMillis(start)
+ ", options format: "
+ options.get(COMPRESSION_FORMAT)
+ ", hasAlpha: "
+ bitmap.hasAlpha()
)
}
success
} finally {
GlideTrace.endSection()
}
}

private fun getFormat(bitmap: Bitmap, options: Options): CompressFormat {
val format = options.get(COMPRESSION_FORMAT)
return format ?: if (bitmap.hasAlpha()) {
CompressFormat.PNG
} else {
CompressFormat.JPEG
}
}

companion object {
/**
* An integer option between 0 and 100 that is used as the compression quality.
*
*
* Defaults to 90.
*/
val COMPRESSION_QUALITY = Option.memory("com.bumptech.glide.load.resource.bitmap.BitmapEncoder.CompressionQuality", 90)

/**
* An [android.graphics.Bitmap.CompressFormat] option used as the format to encode the
* [android.graphics.Bitmap].
*
*
* Defaults to [android.graphics.Bitmap.CompressFormat.JPEG] for images without alpha and
* [android.graphics.Bitmap.CompressFormat.PNG] for images with alpha.
*/
val COMPRESSION_FORMAT = Option.memory("com.bumptech.glide.load.resource.bitmap.BitmapEncoder.CompressionFormat")
private const val TAG = "BitmapEncoder"
}
}
```
```kotlin
@GlideModule
internal class IconGlideModule : AppGlideModule() {

override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
registry.prepend(GlideIconUrl::class.java, Bitmap::class.java, IconUrlModelLoaderFactory(context))

registry.append(Bitmap::class.java, BitmapEncoder())
}

}
```

And this solved the problem! This is just copy paste of your class as you can see in here: https://github.com/bumptech/glide/blob/d2bb3e8a6a2296b20ecaeb7759c16ef15aacbb28/library/src/main/java/com/bumptech/glide/load/resource/bitmap/BitmapEncoder.java

**Question**:
I copied pasted your class to let it work. I don't understand why glide doesn't use your BitmapEncoder class. To solve that, I have to copy paste the BitmapEncoder which is in your library and append that to registry. But the problem in here is that I can't use arrayPool, which is internal in the library, in my BitmapEncoder. That can cause performance issues? Why doesn't glide use the BitmapEncoder in the library and I have to write it again and append that?

**Request**:
Could you please also support BitmapEncoder? I think many people like me want to write `Bitmap` type custom modal loader instead of the InputStream or ByteBuffer. I think this should be very easy to support that for you. And would be a huge help for people like me. I spend about 10 hours to understand the problem and solve that :(

Thanks!

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.