So yeah, we added a handful of SVG images to a Forms App we are working on. That is all fine and dandy. The problem is that the images are going to change.
How? Well, let's say that as long as the app is running (which, given its functionality, is almost an entire day) is going to receive a certain amount of data the is going to change the appearance of said SVG images. The data in question is a number and a string containing a colour (in hex).
What do we want to do with this number and hex? Modify the SVG, change the usual black colour into the hex and write the number on top of the image. Sounds simple, yet it's proven to be quite impossible.
Normally, what we do is fill the resources with the same image (png, obviously) in different colours so that, when necessary, changing from one colour to another is as easy as changing the Image's source. BUT, since we do not know the colours beforehand (and also, they are subject to change), we have to manipulate the SVG as the app runs. The integer is no easy feat either.
If someone knows about how to do something like this, we'll be grateful to hear. Please, ask about any info about our project we might have overlooked (packages, frameworks, etc.).
Regards.
P.S.: In ordrer to load the SVG,s, we use SkiaSharp. Any alternative will be welcome. As long as it works in our framework.
Visual Studio for Mac (Community) V. 8.8 (build 2913)
Xamarin.Forms (4.8.0.1687)
.NET Standard 2.0
Ok, I've got spare minutes to post the solution:
using System; using System.IO; using SkiaSharp; using SkiaSharp.Views.Forms; using Xamarin.Forms; using SKSvg = SkiaSharp.Extended.Svg.SKSvg; namespace PlannerConductores { public class Icon : Frame { private readonly SKCanvasView _canvasView = new SKCanvasView (); public Color TintColor { get; set; } public static readonly BindableProperty ResourceIdProperty = BindableProperty.Create ( nameof (ResourceId), typeof (string), typeof (Icon), default (string), propertyChanged: RedrawCanvas); public string ResourceId { get => (string) GetValue (ResourceIdProperty); set => SetValue (ResourceIdProperty, value); } public Icon () { Padding = new Thickness (0); BackgroundColor = Color.Transparent; HasShadow = false; Content = _canvasView; _canvasView.PaintSurface += CanvasViewOnPaintSurface; } private static void RedrawCanvas (BindableObject bindable, object oldvalue, object newvalue) { Icon svgIcon = bindable as Icon; svgIcon?._canvasView.InvalidateSurface (); } private void CanvasViewOnPaintSurface (object sender, SKPaintSurfaceEventArgs args) { SKCanvas canvas = args.Surface.Canvas; canvas.Clear (); if (string.IsNullOrEmpty (ResourceId)) return; SKSvg svg = new SKSvg (); using (Stream stream = GetType ().Assembly.GetManifestResourceStream (ResourceId)) { svg = new SKSvg (); svg.Load (stream); SKImageInfo info = args.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); } using (var paint = new SKPaint ()) { paint.ColorFilter = SKColorFilter.CreateBlendMode (SKColor.Parse (TintColor.ToHex ()), SKBlendMode.SrcIn); canvas.DrawPicture (svg.Picture); canvas.DrawPicture (svg.Picture, paint); } } } }
However, this only works when creating the image. Once the code starts you cannot change it, so we are looking for alternatives.
New modification!
Instead of calling tint, use this new method to change the colour of your SVG whenever you want:
using System; using System.IO; using SkiaSharp; using SkiaSharp.Views.Forms; using Xamarin.Forms; using SKSvg = SkiaSharp.Extended.Svg.SKSvg; namespace PlannerConductores { public class Icon : Frame { #region Private Members private readonly SKCanvasView _canvasView = new SKCanvasView (); public Color TintColor { get; set; } #endregion #region Bindable Properties #region ResourceId public static readonly BindableProperty ResourceIdProperty = BindableProperty.Create ( nameof (ResourceId), typeof (string), typeof (Icon), default (string), propertyChanged: RedrawCanvas); public string ResourceId { get => (string) GetValue (ResourceIdProperty); set => SetValue (ResourceIdProperty, value); } #endregion #endregion #region Constructor public Icon () { Padding = new Thickness (0); BackgroundColor = Color.Transparent; HasShadow = false; Content = _canvasView; _canvasView.PaintSurface += CanvasViewOnPaintSurface; } #endregion #region Private Methods private static void RedrawCanvas (BindableObject bindable, object oldvalue, object newvalue) { Icon svgIcon = bindable as Icon; svgIcon?._canvasView.InvalidateSurface (); } private void RedrawCanvas () { _canvasView.InvalidateSurface (); } private void CanvasViewOnPaintSurface (object sender, SKPaintSurfaceEventArgs args) { SKCanvas canvas = args.Surface.Canvas; canvas.Clear (); if (string.IsNullOrEmpty (ResourceId)) return; SKSvg svg = new SKSvg (); using (Stream stream = GetType ().Assembly.GetManifestResourceStream (ResourceId)) { svg = new SKSvg (); svg.Load (stream); SKImageInfo info = args.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); } using (var paint = new SKPaint ()) { paint.ColorFilter = SKColorFilter.CreateBlendMode (SKColor.Parse (TintColor.ToHex ()), SKBlendMode.SrcIn); canvas.DrawPicture (svg.Picture); canvas.DrawPicture (svg.Picture, paint); } } public void ChangeColour (Color tintcolour) { TintColor = tintcolour; RedrawCanvas (); } #endregion } }
Call ChangeColour during any event (a button press, for example) and voilá.
Answers
I wanted to include the SVG image. Unfortunately, we cannot load it, as a file nor as an image. Sorry.
Here is some similar thread about this ,you can check it here:
https://forums.xamarin.com/discussion/144150/how-to-change-the-colour-of-the-svg-image-at-run-time
The main code is like this:
Tried that one. Apparently the SkiaSharp version of SVG does not contain some of those variables. I will add the Icon Class we are using:
And I remembered, I should probably add a XAML snippet, for where I use the SVG code and such:
local is the local namespace:
xmlns:local="clr-namespace:PC;assembly=PC"
I did a test according to the code you post, but there were always some problems.
Could you please post a basic demo?
Besides, I've noticed that the package(
SkiaSharp.Extended
) you're using hasn't been updated for a long time.Yes, we've noticed it's quite old, but hey. If it works...
Here is the sample we've been using.
https://github.com/AlexPshul/SvgXF
I am sorry to reply you so long because of my health. Have you solved the problem ?
Yes, indeed. I will post the solution soon!
Also, I hope you feel alright now.
Congrats, could you please post your answer so that others who have similar thread will get help from here?
Thanks in advance.
Just a moment. I'm juggling like seven projects right now.
Ok, I've got spare minutes to post the solution:
However, this only works when creating the image. Once the code starts you cannot change it, so we are looking for alternatives.
New modification!
Instead of calling tint, use this new method to change the colour of your SVG whenever you want:
Call ChangeColour during any event (a button press, for example) and voilá.