Comments (19)
You should re-use ImageLoader
instances as much as possible - similar to OkHttpClient
. From your stack trace it looks like you're instantiating new ImageLoader
s, but not calling ImageLoader.shutdown()
when you're done with them.
Adding SvgDecoder
to your ImageLoader
's component registry should automatically handle any new SVGs (assuming the file ends with .svg
) without having to specify that it's an SVG when creating the request.
If you're using the Coil singleton (probably best to put this in your Application
class):
val imageLoader = ImageLoader(context) {
componentRegistry {
add(SvgDecoder())
}
}
Coil.setDefaultImageLoader(imageLoader)
then all subsequent calls to imageView.load
should automatically handle SVGs.
From what I can tell, the SVG is being decoded fine, but it's not scaling correctly within the ImageView bounds.
Can you post a screenshot of the issue?
from coil.
@rharter Looks good! Minor correction: I would replace SVG.getFromInputStream(source.inputStream())
with source.use { SVG.getFromInputStream(it.inputStream()) }
. Coil doesn't automatically close the decoder's source in case a decoder wants to return a Drawable, but continue to read the source
after returning.
from coil.
@FrostRocket It doesn't look like you're handling scaling in either case. Both libraries give you the sizing information, you just need to pass it to the SVG library.
class SvgDecoder : Decoder {
override fun handles(source: BufferedSource, mimeType: String?) = mimeType == "image/svg+xml"
override suspend fun decode(pool: BitmapPool, source: BufferedSource, size: Size, options: Options): DecodeResult {
try {
val svg = source.use { SVG.getFromInputStream(it.inputStream()) }
return DecodeResult(
drawable = PictureDrawable(svg.renderToPicture()),
isSampled = false
)
} catch (e: SVGParseException) {
throw IllegalStateException("Failed to load SVG.", e)
}
}
}
from coil.
On it.
Adding auto-discovery to automatically add decoders that are on the classpath to the default ImageLoader adds the requirement of a service locator, which adds overhead to an otherwise simple architecture. That's part of what led Glide to all of it's extra bloat, so I'd argue keeping it a one line addition.
from coil.
Android (and Coil) already supports WebP from API 14+. Are you looking for it to support animated WebP on pre-P?
For SVGs, I think this might be a good candidate for an external library. However, it should be straightforward to write a custom Decoder for this using AndroidSVG. Something like:
class SvgDecoder : Decoder {
override fun handles(source: BufferedSource, mimeType: String?) = mimeType == "image/svg+xml"
override suspend fun decode(pool: BitmapPool, source: BufferedSource, size: Size, options: Options): DecodeResult {
return DecodeResult(
drawable = SvgDrawable(source.use { SVG.getFromInputStream(it.inputStream()) }),
isSampled = false
)
}
}
class SvgDrawable(private val svg: SVG) : Drawable() {
override fun draw(canvas: Canvas) {
svg.renderToCanvas(canvas)
}
override fun setAlpha(alpha: Int) {
// Unsupported.
}
override fun getOpacity() = PixelFormat.TRANSLUCENT
override fun setColorFilter(colorFilter: ColorFilter?) {
// Unsupported
}
}
should work well.
Disclaimer: The above code is completely untested.
from coil.
I am currently using SVGDecoder via Glide, so it wasn't a heavy lift to add some of the sample code you wrote and try it out myself. From what I can tell, the SVG is being decoded fine, but it's not scaling correctly within the ImageView bounds.
Before (SvgModule.kt):
@GlideModule
class SvgModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
registry
.register(SVG::class.java, PictureDrawable::class.java, SvgDrawableTranscoder())
.append(InputStream::class.java, SVG::class.java, SvgDecoder())
}
class SvgDecoder : ResourceDecoder<InputStream, SVG> {
override fun handles(source: InputStream, options: Options): Boolean = true
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<SVG> =
SimpleResource(SVG.getFromInputStream(source))
}
class SvgDrawableTranscoder : ResourceTranscoder<SVG, PictureDrawable> {
override fun transcode(toTranscode: Resource<SVG>, options: Options): Resource<PictureDrawable> =
SimpleResource(PictureDrawable(toTranscode.get().renderToPicture()))
}
(SvgLayerTypeListener.kt):
class SvgLayerTypeListener : RequestListener<PictureDrawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<PictureDrawable>?, isFirstResource: Boolean): Boolean {
if (target is ImageViewTarget<*>) {
val view = (target as ImageViewTarget<*>).view
view.setLayerType(ImageView.LAYER_TYPE_NONE, null)
}
return false
}
override fun onResourceReady(resource: PictureDrawable, model: Any, target: Target<PictureDrawable>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
if (target is ImageViewTarget<*>) {
val view = (target as ImageViewTarget<*>).view
view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null)
}
return false
}
Usage (Extensions.kt):
fun ImageView.load(url: String?) {
Glide.with(context)
.`as`(PictureDrawable::class.java)
.load(url)
.listener(SvgLayerTypeListener())
.into(this)`
After (SvgDecoder.kt):
class SvgDecoder : Decoder {
override fun handles(source: BufferedSource, mimeType: String?) = mimeType == "image/svg+xml"
override suspend fun decode(pool: BitmapPool, source: BufferedSource, size: Size, options: Options): DecodeResult =
DecodeResult(
drawable = SvgDrawable(SVG.getFromInputStream(source.inputStream())),
isSampled = false
)
class SvgDrawable(private val svg: SVG) : Drawable() {
override fun draw(canvas: Canvas) {
canvas.drawPicture(svg.renderToPicture())
}
override fun setAlpha(alpha: Int) {
// Unsupported.
}
override fun getOpacity() = PixelFormat.TRANSLUCENT
override fun setColorFilter(colorFilter: ColorFilter?) {
// Unsupported
}
}
Usage (Extensions.kt):
fun ImageView.loadSvg(url: String?) = load(url, ImageLoader(context) {
componentRegistry {
add(SvgDecoder())
}
})
How would you recommend using decoders and other components via the componentRegistry without instantiating a new ImageLoader every time? The default "load" extension does allow it to be passed in as a parameter, but needs to be refactored because of the excessive loading in my ViewHolders:
2019-08-12 22:28:44.661 28475-28475/? E/AndroidRuntime: FATAL EXCEPTION: main Process: com.frostrocket.myapp, PID: 28475 android.net.ConnectivityManager$TooManyRequestsException at android.net.ConnectivityManager.convertServiceException(ConnectivityManager.java:3378) at android.net.ConnectivityManager.sendRequestForNetwork(ConnectivityManager.java:3564) at android.net.ConnectivityManager.registerNetworkCallback(ConnectivityManager.java:3885) at android.net.ConnectivityManager.registerNetworkCallback(ConnectivityManager.java:3866) at coil.network.NetworkObserverStrategyApi21.start(NetworkObserverStrategy.kt:102) at coil.network.NetworkObserver.<init>(NetworkObserver.kt:27) at coil.RealImageLoader.<init>(RealImageLoader.kt:94) at coil.ImageLoaderBuilder.build(ImageLoaderBuilder.kt:177) at com.frostrocket.myapp.core.ExtensionsKt.loadSvg(Extensions.kt:181)
I thought about calling Coil.setDefaultImageLoader() in the application class but that seems like a meh way to handle it.
I can wait until there's further documentation around this topic, but I figured it wouldn't hurt to start a conversation to get some examples out there 👍
from coil.
I'm using a singleton for the ImageLoader (thanks @colinrtwhite) and now accommodating for the size options (thanks @rharter). Performance has greatly improved, but assets are still not resizing properly. Some are "blank" (likely getting a transparent corner of the image), some are blown up, and some are perfect. I'm wondering if the metadata is weird, and the way Glide was decoding the image accommodated for that?
@colinrtwhite I can't give you a sample screenshot publicly but I can point you toward the SVGs.
Sample asset that is "blank" (loaded, but scaled out of view): https://cdn.coinranking.com/Sy33Krudb/btc.svg
Sample asset that scales fine: https://cdn.coinranking.com/B1oPuTyfX/xrp.svg
Sample asset that is blown up (can see part of it but it's very large, scaling issues): https://cdn.coinranking.com/behejNqQs/trx.svg
Let me try a few more things in case I'm missing something in my implementation, but it's basically a cleaned up version of the examples you both have provided.
from coil.
Try updating the documentWidth
and documentHeight
of the SVG. I see that the assets that don't work correctly have a huge width/height
property set on the root element, and the one that works correctly doesn't. Rendering into a 500x500 viewport when the document is hard coded at 2378x2500 would explain what you are seeing.
I've updated the code in the original comment to reflect this.
from coil.
Scratch that, I did some testing and you actually don't want to do that with a PictureDrawable
(in my app I'm drawing to a custom canvas since I have to use a special paint, so I grab the target size).
When you render to a Picture, it's simply storing drawing commands, not any actual pixels. The PictureDrawable
will then render that to a properly sized canvas, so things should be scaled appropriately.
I've updated the comment above with the tested code.
from coil.
@colinrtwhite I created an external SVG lib, similar to the Gif one, in a branch on my fork. If that's something you'd be open to adding, I'll submit a PR.
from coil.
SVGs are now being properly rendered in the ImageViews. Thanks for the help @rharter!
Parting thought: it'd be nice to include the SVG decoder (and other custom decoders) in the default ImageLoader, similar to how Retrofit handles Converters via an extra dependency. So it won't bloat the library but allows for additional customization. i.e:
implementation 'io.coil-kt:coil:0.6.0'
implementation 'io.coil-kt:decoder-svg:0.6.0'
I'll leave it up to you to decide to investigate this option or to close this issue down and add it to the documentation instead.
Kudos for setting the bar high on a 100% Kotlin lib with native language features! 🎉
from coil.
@rharter I think that's a good idea. Feel free to submit a PR, thanks. To keep things consistent, we should use the module name coil-svg
.
from coil.
@rharter On pre-Nougat devices I'm noticing extreme performance issues while loading in RecyclerViews. I noticed the same with Glide until I realized that I was using hardware rendering.
I can take a look to see if we can disable hardware rendering when loading SVGs, but if you have a quick fix in mind you can also address it. 👍
https://bigbadaboom.github.io/androidsvg/faq.html
from coil.
@FrostRocket I think I have a fix for this here: #81, though do you know why there are performance issues pre-Nougat? According to the PictureDrawable
docs, Canvas
doesn't support rendering Picture
s/SVG
s on hardware canvases until Marshmallow.
from coil.
Looks like even on Marshmallow, rendering an SVG on a hardware canvas will end up blurry. #81 should handle this limitation if you don't manually disable software rendering on your view.
from coil.
I wasn't seeing blurry SVGs on my Marshmallow Nexus 5x, but I was running into those performance issues I mentioned. Took a look at the diff for your change so when you release 0.70 I'll update you if this resolves it. Thanks!
from coil.
@FrostRocket Thanks, you can also test out the 0.7.0
snapshots by following the instructions here if you'd like. The full 0.7.0
release should also be out later today.
from coil.
@colinrtwhite Latest version of Coil unusable for the SVG use case, still seeing performance issues and a number of crashes now:
I'll continue the discussion on these other issues. 👍
from coil.
Any update about load svg natively in coil?
from coil.
Related Issues (20)
- Asynchronous initialization of ImageLoader HOT 8
- Unable to sync coil3:coil-gif while using cocoapods to include a pod HOT 2
- Update ContentPainterModifier with ModifierNodeElement to allow reuse on LazyList
- [Coil 3] AsyncImage does not respect default placeholder/error/fallback
- Memory leak problem of ImageRequest.context HOT 1
- Flicker when changing `ByteArray` data HOT 2
- java.lang.IllegalStateException: BitmapFactory returned a null bitmap. Often this means BitmapFactory could not decode the image data read from the input source (e.g. network, disk, or memory) as it's not HOT 5
- Changing date/time into the future breaks showing the image HOT 2
- VideoFrameDecoder HLS Support HOT 4
- rememberAsyncImagePainter renders nothing when using dragAndDropSource HOT 4
- [Coil 3] Crash on Desktop HOT 3
- Cant install coil :( HOT 1
- Use WeakRef on JS/WASM
- Crashing when trying to load image.9.png using coil compose function rememberAsyncImagePainter HOT 1
- Only support https? HOT 2
- [Coil 3] Consider replacing coil internal LruCache with androidx one HOT 1
- Excessive API Calls in Image Loading with RecyclerView When Using custom fetcher HOT 4
- "java.lang.IllegalStateException: Unable to create a fetcher that supports" in MIUI Global 14.0.12 with Compose HOT 1
- 2.6.0 AsyncImage doesn't work on AndroidStudio - Attempted all variants HOT 2
- Publish the sample wasmJs app automatically when deploying the docs.
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from coil.