Code Monkey home page Code Monkey logo

Comments (19)

colinrtwhite avatar colinrtwhite commented on May 12, 2024 2

You should re-use ImageLoader instances as much as possible - similar to OkHttpClient. From your stack trace it looks like you're instantiating new ImageLoaders, 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.

colinrtwhite avatar colinrtwhite commented on May 12, 2024 2

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

rharter avatar rharter commented on May 12, 2024 1

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

rharter avatar rharter commented on May 12, 2024 1

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.

colinrtwhite avatar colinrtwhite commented on May 12, 2024

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.

0xMatthewGroves avatar 0xMatthewGroves commented on May 12, 2024

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.

0xMatthewGroves avatar 0xMatthewGroves commented on May 12, 2024

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.

rharter avatar rharter commented on May 12, 2024

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.

rharter avatar rharter commented on May 12, 2024

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.

device-2019-08-15-162557

I've updated the comment above with the tested code.

from coil.

rharter avatar rharter commented on May 12, 2024

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

0xMatthewGroves avatar 0xMatthewGroves commented on May 12, 2024

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.

colinrtwhite avatar colinrtwhite commented on May 12, 2024

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

0xMatthewGroves avatar 0xMatthewGroves commented on May 12, 2024

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

colinrtwhite avatar colinrtwhite commented on May 12, 2024

@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 Pictures/SVGs on hardware canvases until Marshmallow.

from coil.

colinrtwhite avatar colinrtwhite commented on May 12, 2024

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.

0xMatthewGroves avatar 0xMatthewGroves commented on May 12, 2024

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.

colinrtwhite avatar colinrtwhite commented on May 12, 2024

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

0xMatthewGroves avatar 0xMatthewGroves commented on May 12, 2024

@colinrtwhite Latest version of Coil unusable for the SVG use case, still seeing performance issues and a number of crashes now:

#92
#91

I'll continue the discussion on these other issues. 👍

from coil.

atonamy avatar atonamy commented on May 12, 2024

Any update about load svg natively in coil?

from coil.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.