'Xamarin.Forms set Button ImageSource as ffimageloading Svg embedded resource

I would to put a svg embedded image as ImageSource for a Button in Xamarin.Forms, something like this

<Button Text="Button" ImageSource="resource://fullname.svg">
</Button>

possibly applying a Transformation to svg (from FFImageLoading.Transformations), but this is a plus.

I've tried this syntax

<Button Text="Button"
  ImageSource="{ext:ImageResourceExtension fullname.svg}" />

c#

    public class ImageResourceExtension : IMarkupExtension
    {
        private static Assembly Assembly = typeof(ImageResourceExtension).GetTypeInfo().Assembly;

        public string Source { get; set; }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (Source == null) return null;
            return SvgImageSource.FromResource(Source, Assembly, 32, 32);

        }
   }

But it's not working.

Moreover I can't make working this syntax

Source="{resource://fullname.svg, Converter={StaticResource SvgImageSourceConverter}}"

Any help? Thanks



Solution 1:[1]

As Jason said, FFImageLoading support SVG files. Follow the steps below.

Create a Resource folder in your Xamarin.Forms instead of Android part. And then add the SVG file as Embedded resource.

enter image description here

Usage: Use SvgCachedImage to show the embedded svg image and use TapGestureRecognizer to simulate the button click event.

<ffimageloadingsvg:SvgCachedImage
            HeightRequest="50"
            Source="resource://XamarinDemo.Resources.brightness2.svg"
            WidthRequest="50">
            <ffimageloadingsvg:SvgCachedImage.GestureRecognizers>
                <TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped"></TapGestureRecognizer>
            </ffimageloadingsvg:SvgCachedImage.GestureRecognizers>
        </ffimageloadingsvg:SvgCachedImage>

Do not forget to add namespace.

xmlns:ffimageloadingsvg="clr-namespace:FFImageLoading.Svg.Forms;assembly=FFImageLoading.Svg.Forms"

enter image description here

Updated: We could use SkiaSharp to draw a image with svg file.

MyControl.cs

public class MyControl : Frame
{
    private readonly SKCanvasView _canvasView = new SKCanvasView();
    public MyControl()
    {
        Padding = new Thickness(0);
        BackgroundColor = Color.Transparent;
        Content = _canvasView;

        _canvasView.PaintSurface += CanvasViewOnPaintSurface;
    }
    public static readonly BindableProperty ImageProperty = BindableProperty.Create(
       nameof(Image), typeof(string), typeof(MyControl), default(string), propertyChanged: RedrawCanvas);

    public string Image
    {
        get => (string)GetValue(ImageProperty);
        set => SetValue(ImageProperty, value);
    }
    private static void RedrawCanvas(BindableObject bindable, object oldvalue, object newvalue)
    {
        MyControl svgIcon = bindable as MyControl;
        svgIcon?._canvasView.InvalidateSurface();
    }
    private void CanvasViewOnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
    {
        SKCanvas canvas = e.Surface.Canvas;
        canvas.Clear();


        using (Stream stream = GetType().Assembly.GetManifestResourceStream(Image))
        {
            SkiaSharp.Extended.Svg.SKSvg svg = new SkiaSharp.Extended.Svg.SKSvg();
            svg.Load(stream);

            SKImageInfo info = e.Info;
            canvas.Translate(info.Width / 2f, info.Height / 2f);

            SKRect bounds = svg.ViewBox;
            float xRatio = info.Width / bounds.Width;
            float yRatio = info.Height / bounds.Height;

            float ratio = Math.Min(xRatio, yRatio);

            canvas.Scale(ratio);
            canvas.Translate(-bounds.MidX, -bounds.MidY);

            canvas.DrawPicture(svg.Picture);

        }
    }
}

Usage:

<local:MyControl
            HeightRequest="50"
            Image="XamarinDemo.Resources.brightness2.svg"
            WidthRequest="50" />

And you could use TapGestureRecognizer to simulate the button click event.

Solution 2:[2]

Updated original image resource extension class for Xamarin. OP's class had two issues:

  1. Missing ContentProperty attribute which allows to skip "Source" property name.
  2. Part where partial resource name is converted to a full resource name.

Added optional "Assembly" property, which allows to specify different assembly. In the code, you can also get the full name, resource path, and ImageSource object with the optional replace string map applied, which allows to modify the original SVG content on the fly, e.g. colors).

Usage:

<ff:SvgCachedImage Source="{ImageFromResource file_name.svg}" />
<ff:SvgCachedImage Source="{ImageFromResource Icons/file_name.svg}" />
<Image Source="{ImageFromResource file_name.png}" />
<Image Source="{ImageFromResource file_name.png, Assembly=MyAssembly}" />

ImageFromResourceExtension class:

using FFImageLoading.Svg.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

/// <summary>
/// Get image source from assembly resource by using partial name.
/// </summary>
/// <example>
/// &lt;ff:SvgCachedImage Source="{ImageFromResource file_name.svg}" /&gt;
/// &lt;ff:SvgCachedImage Source="{ImageFromResource Icons/file_name.svg}" /&gt;
/// &lt;ff:Image Source="{ImageFromResource file_name.png}" /&gt;
/// &lt;ff:Image Source="{ImageFromResource file_name.png, Assembly=MyAssembly}" /&gt;
/// </example>
/// <remarks>
/// Parameter format without extension:
/// Source="resource://{AssemblyName}.{PathName}.{FileName}"
/// </remarks>
[ContentProperty(nameof(Source))]
public class ImageFromResourceExtension : IMarkupExtension
{

    public static Assembly DefaultAssembly = typeof(ImageResourceExtension).GetTypeInfo().Assembly;

    public string Source { get; set; }
    public string Assembly { get; set; }

    public object ProvideValue(IServiceProvider serviceProvider)
        => GetResourcePath(Source, Assembly);

    public static string GetResourcePath(string name, string assembly = null)
        => "resource://" + GetFullName(name, assembly);

    public static string GetFullName(string name, string assembly = null)
    {
        if (string.IsNullOrEmpty(name))
            return null;
        // Replace folder separators with dots.
        name = name.Replace('/', '.');
        // Use different assembly if specified.
        var asm = string.IsNullOrEmpty(assembly)
            ? DefaultAssembly
            : System.Reflection.Assembly.Load(assembly);
        // Find full name of the resource by partial name.
        var fullName = asm.GetManifestResourceNames()
            .FirstOrDefault(x => x.EndsWith("." + name));
        return fullName;
    }

    public static ImageSource GetImage(string name, string assembly = null, StringDictionaryCollection map = null)
    {
        var fullName = GetFullName(name);
        if (fullName == null)
            return null;
        // Use different assembly if specified.
        var asm = string.IsNullOrEmpty(assembly)
            ? DefaultAssembly
            : System.Reflection.Assembly.Load(assembly);
        // Return image source.
        return fullName.EndsWith(".svg", StringComparison.OrdinalIgnoreCase)
            ? SvgImageSource.FromResource(fullName, asm, replaceStringMap: map)
            : ImageSource.FromResource(fullName, asm);
    }

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2