Xamarin.Forms.Plugins icon indicating copy to clipboard operation
Xamarin.Forms.Plugins copied to clipboard

Change the source of the image

Open prompteus opened this issue 9 years ago • 5 comments
trafficstars

I need to change the SvgImage, so I tried

  • binding in xaml: SvgPath="{Binding CurrentWeather.IconSource, StringFormat='Umbrella.Images.{0:F0}.svg'}"
  • or to change it directly from C#: icon.SvgPath = "Umbrella.Images." + currentWeather.IconSource + ".svg";

The SvgPath attribute does change, but the image doesn't rerender.

Is there any workaround?

prompteus avatar Jul 23 '16 16:07 prompteus

I had the same problem. I ended up re-creating entirely the SvgImage in the codebehind when the source changes, and assigning it in a container (defined in XAML).

toverux avatar Jul 27 '16 14:07 toverux

Well, I ended up doing the same, it's the easiest workaround, but the binding would be much cleaner.

prompteus avatar Jul 31 '16 19:07 prompteus

Another workaround is to implement the missing logic in a custom renderer, as described here:

iOS Example

protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
    base.OnElementPropertyChanged(sender, e);
    if (e.PropertyName == "SvgPath")
    {
        var svgStream = _formsControl.SvgAssembly.GetManifestResourceStream(_formsControl.SvgPath);
        var reader = new SvgReader(new StreamReader(svgStream), new StylesParser(new ValuesParser()), new ValuesParser());
        var graphics = reader.Graphic;
        var canvas = new ApplePlatform().CreateImageCanvas(graphics.Size);
        graphics.Draw(canvas);
        var image = canvas.GetImage();
        var uiImage = image.GetUIImage();
        Control.Image = uiImage;
    }
}

gtbuchanan avatar Sep 23 '16 13:09 gtbuchanan

Yes, finally I dropped this library because it embeds an old version of NGraphics (the SVG parser and renderer) that couldn't parse some basic SVGs. Basically copy-pasted the renderers of this lib and fixed them...

toverux avatar Sep 23 '16 15:09 toverux

A little update for those who are interested in a better SVG renderer, I improved the original Android renderer (coming from this library) and now I'm handling :

  • SvgImage auto-sizing without WidthRequest or HeightRequest (but only if you have width="x" & height="x" SVG attributes set).
  • Changing the SvgPath re-renders the bitmap (and re-calculates the element size)
  • Handles Xamarin Form's Image.Aspect to control the aspect ratio (choose between Fill, AspectFill, AspectFit and FitCenter which is the default).

Do not forget to add NGraphics as a NuGet dependency.

Portable Element (in the portable project):

using System.Reflection;
using Xamarin.Forms;

namespace MYAPP.Controls
{
    public class SvgImage : Image
    {
        public static readonly Assembly SvgAssembly = typeof(App).GetTypeInfo().Assembly;

        public static readonly BindableProperty SvgPathProperty = BindableProperty.Create(nameof(SvgPath), typeof(string), typeof(SvgImage), default(string));

        public string SvgPath
        {
            get { return (string)GetValue(SvgPathProperty); }
            set { SetValue(SvgPathProperty, value); }
        }
    }
}

Android renderer (in the Android project):

using Android.Graphics;
using Android.Widget;
using MYAPP.Controls;
using MYAPP.Droid.CustomRenderers;
using NGraphics;
using System;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(SvgImage), typeof(SvgImageRenderer))]
namespace MYAPP.Droid.CustomRenderers
{
    public class SvgImageRenderer : ViewRenderer<SvgImage, ImageView>
    {
        protected override void OnElementChanged(ElementChangedEventArgs<SvgImage> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement == null) {
                SetNativeControl(new ImageView(Context));
            }

            UpdateBitmap();
            UpdateAspect();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            switch (e.PropertyName) {
                case nameof(SvgImage.SvgPath):
                    UpdateBitmap();
                    break;
                case nameof(SvgImage.Aspect):
                    UpdateAspect();
                    break;
            }
        }

        private static ImageView.ScaleType ToNativeScaleType(Aspect aspect)
        {
            switch (aspect) {
                case Aspect.Fill:
                    return ImageView.ScaleType.FitXy;
                case Aspect.AspectFill:
                    return ImageView.ScaleType.CenterCrop;
                case Aspect.AspectFit:
                default:
                    return ImageView.ScaleType.FitCenter;
            }
        }

        private void UpdateAspect()
        {
            Control.SetScaleType(ToNativeScaleType(Element.Aspect));
        }

        private async void UpdateBitmap()
        {
            var bitmap = await Task.Run(() => SvgToBitmap(Element.SvgPath, Element.WidthRequest, Element.HeightRequest));

            Control.SetImageBitmap(bitmap);
            bitmap.Dispose();

            ((IVisualElementController)Element).NativeSizeChanged();
        }

        private int DpToPx(double dp)
        {
            return (int)(dp * Resources.DisplayMetrics.Density);
        }

        private Bitmap SvgToBitmap(string svgPath, double widthRequest, double heightRequest)
        {
            var svgStream = SvgImage.SvgAssembly.GetManifestResourceStream(svgPath);

            if (svgStream == null) {
                throw new Exception($"Error retrieving {svgPath} ; make sure the Build Action is set to Embedded Resource");
            }

            var reader = new SvgReader(new StreamReader(svgStream));
            var graphics = reader.Graphic;

            var width = widthRequest > -1 ? DpToPx(widthRequest) : graphics.Size.Width;
            var height = heightRequest > -1 ? DpToPx(heightRequest) : graphics.Size.Height;

            var scale = height >= width ? (height / graphics.Size.Height) : (width / graphics.Size.Width);

            var canvas = new AndroidPlatform().CreateImageCanvas(graphics.Size, scale);
            graphics.Draw(canvas);

            return ((BitmapImage)canvas.GetImage()).Bitmap;
        }
    }
}

Will probably post an updated iOS renderer later (still using the old one).

toverux avatar Nov 22 '16 22:11 toverux