Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider splitting rendering and processing #590

Open
gvheertum opened this issue Oct 15, 2019 · 43 comments
Open

Consider splitting rendering and processing #590

gvheertum opened this issue Oct 15, 2019 · 43 comments

Comments

@gvheertum
Copy link
Member

In the light of the issue #576 (where the author indicated a .NET standard build might be helpful) we might consider splitting some of the parts from the SVG library. As far as I known part of the rendering (System.Drawing) is the main culprit for the library not being able to be built against .NET standard. From my understanding the SVG library can facilitate 2 things: processing and transforming SVG files (the XML part) and rendering an SVG to a bitmap.

The first part could classify for a .NET standard build. Downside however is that this might result in 2 libraries/packages, but it might help people that want to use the SVG part in .NET standard applications.

Just a random thought, but perhaps something to consider in the future.

@wieslawsoltes
Copy link
Contributor

@mrbean-bremen
Copy link
Member

So, I understand you are volunteering for this? 😛

@wieslawsoltes
Copy link
Contributor

wieslawsoltes commented Nov 6, 2019

So, I understand you are volunteering for this? 😛

I will not be easy to decouple the System.Drawing dependencies from current implementation.

Anyway I will have pretty good idea and some implementation (for Skia) for further discussion when I finish implementing basic support for rendering in the Svg.Skia library.

@mrbean-bremen
Copy link
Member

Sounds good! 👍

@tebjan
Copy link
Contributor

tebjan commented Nov 9, 2019

See the linked issue, we could evaluate whether we could join forces with the skia drawing repo.

@paolofulgoni
Copy link

As I wrote on Gitter yesterday, I was thinking about extracting the SVG manipulation part in a separate library too, and make it fully netstandard compliant.
My original idea was a completely separate lib, not a split of the this one. But the latter may actually make more sense. I'm just a bit afraid it would be harder and more constrained.

@paolofulgoni
Copy link

@wieslawsoltes do you think you could achieve the full rendering functionality of vvvv/Svg with your Svg.Skia implementation?

@wieslawsoltes
Copy link
Contributor

@wieslawsoltes do you think you could achieve the full rendering functionality of vvvv/Svg with your Svg.Skia implementation?

I think we can, a lot of rendering functionality already there.

@paolofulgoni
Copy link

So creating the new svg parsing/manipulation library seems a good idea for many reasons.
If vvvv/Svg will use such libary, should it maintain backward compatibility?
I mean, splitting the library without breaking changes could be difficult.
What do you think @tebjan?

@wieslawsoltes
Copy link
Contributor

@tebjan @mrbean-bremen

So my current approach in Svg.Skia for rendering using SkiaSharp and document processing from Svg library is:

Step 1 - load document

var svgDocument = SvgDocument.Open<SvgDocument>(path, null);
if (svgDocument != null)
{
    // ...
}

Step 2 - draw using SKCanvas

float width = svgFragment.Width.ToDeviceValue(null, UnitRenderingType.Horizontal, svgFragment);
float height = svgFragment.Height.ToDeviceValue(null, UnitRenderingType.Vertical, svgFragment);
var skSize = new SKSize(width, height);
var cullRect = SKRect.Create(skSize);
using (var renderer = new SKSvgRenderer(skSize))
using (var skPictureRecorder = new SKPictureRecorder())
using (var skCanvas = skPictureRecorder.BeginRecording(cullRect))
{
    renderer.DrawFragment(skCanvas, svgFragment);
    return skPictureRecorder.EndRecording();
}

Here I am also using SKPictureRecorder to record SKPicture. The SKPicture is nice class that records draw command and you can cheaply redraw the SKPicture without the costly processing and allocations.


My current custom rendering interface:

public interface ISvgRenderer : IDisposable
{
    void DrawFragment(object canvas, SvgFragment svgFragment);
    void DrawImage(object canvas, SvgImage svgImage);
    void DrawSwitch(object canvas, SvgSwitch svgSwitch);
    void DrawSymbol(object canvas, SvgSymbol svgSymbol);
    void DrawUse(object canvas, SvgUse svgUse);
    void DrawForeignObject(object canvas, SvgForeignObject svgForeignObject);
    void DrawCircle(object canvas, SvgCircle svgCircle);
    void DrawEllipse(object canvas, SvgEllipse svgEllipse);
    void DrawRectangle(object canvas, SvgRectangle svgRectangle);
    void DrawMarker(object canvas, SvgMarker svgMarker);
    void DrawGlyph(object canvas, SvgGlyph svgGlyph);
    void DrawGroup(object canvas, SvgGroup svgGroup);
    void DrawLine(object canvas, SvgLine svgLine);
    void DrawPath(object canvas, SvgPath svgPath);
    void DrawPolyline(object canvas, SvgPolyline svgPolyline);
    void DrawPolygon(object canvas, SvgPolygon svgPolygon);
    void DrawText(object canvas, SvgText svgText);
    void DrawTextPath(object canvas, SvgTextPath svgTextPath);
    void DrawTextRef(object canvas, SvgTextRef svgTextRef);
    void DrawTextSpan(object canvas, SvgTextSpan svgTextSpan);
}

You can see in all cases I am passing drawing context object canvas as object.

In each the draw method I am casting drawing context canvas to appropriate SkiaSharp canvas (it can be Graphics object when using System.Drawing).

if (!(canvas is SKCanvas skCanvas))
{
    return;
}

Example how draw SvgCircle element using SkiaSharp:

float cx = svgCircle.CenterX.ToDeviceValue(null, UnitRenderingType.Horizontal, svgCircle);
float cy = svgCircle.CenterY.ToDeviceValue(null, UnitRenderingType.Vertical, svgCircle);
float radius = svgCircle.Radius.ToDeviceValue(null, UnitRenderingType.Other, svgCircle);
SKRect bounds = SKRect.Create(cx - radius, cy - radius, radius + radius, radius + radius);
SKMatrix matrix = SkiaUtil.GetSKMatrix(svgCircle.Transforms);

skCanvas.Save();

var skPaintOpacity = SkiaUtil.SetOpacity(skCanvas, svgCircle, _disposable);
var skPaintFilter = SkiaUtil.SetFilter(skCanvas, svgCircle, _disposable);
SkiaUtil.SetTransform(skCanvas, matrix);

if (svgCircle.Fill != null)
{
    var skPaintFill = SkiaUtil.GetFillSKPaint(svgCircle, _skSize, bounds, _disposable);
    skCanvas.DrawCircle(cx, cy, radius, skPaintFill);
}

if (svgCircle.Stroke != null)
{
    var skPaintStroke = SkiaUtil.GetStrokeSKPaint(svgCircle, _skSize, bounds, _disposable);
    skCanvas.DrawCircle(cx, cy, radius, skPaintStroke);
}

if (skPaintFilter != null)
{
    skCanvas.Restore();
}

if (skPaintOpacity != null)
{
    skCanvas.Restore();
}

skCanvas.Restore();

When we would have spitted processing of documents from the rendering of them each SvgElement would implement simple interface with Draw method:

void Draw(object canvas, ISvgRenderer renderer)

Currently I have to have this ugly hack method as I did not wan;t to change Svg library yet:

private void Draw(object canvas, SvgElement svgElement)
{
    // HACK: Normally 'SvgElement' object itself would call appropriate 'Draw' on current render.
    switch (svgElement)
    {
        case SvgFragment svgFragment:
            DrawFragment(canvas, svgFragment);
            break;
        case SvgImage svgImage:
            DrawImage(canvas, svgImage);
            break;
        case SvgSwitch svgSwitch:
            DrawSwitch(canvas, svgSwitch);
            break;
        case SvgUse svgUse:
            DrawUse(canvas, svgUse);
            break;
        case SvgForeignObject svgForeignObject:
            DrawForeignObject(canvas, svgForeignObject);
            break;
        case SvgCircle svgCircle:
            DrawCircle(canvas, svgCircle);
            break;
        case SvgEllipse svgEllipse:
            DrawEllipse(canvas, svgEllipse);
            break;
        case SvgRectangle svgRectangle:
            DrawRectangle(canvas, svgRectangle);
            break;
        case SvgMarker svgMarker:
            DrawMarker(canvas, svgMarker);
            break;
        case SvgGlyph svgGlyph:
            DrawGlyph(canvas, svgGlyph);
            break;
        case SvgGroup svgGroup:
            DrawGroup(canvas, svgGroup);
            break;
        case SvgLine svgLine:
            DrawLine(canvas, svgLine);
            break;
        case SvgPath svgPath:
            DrawPath(canvas, svgPath);
            break;
        case SvgPolyline svgPolyline:
            DrawPolyline(canvas, svgPolyline);
            break;
        case SvgPolygon svgPolygon:
            DrawPolygon(canvas, svgPolygon);
            break;
        case SvgText svgText:
            DrawText(canvas, svgText);
            break;
        case SvgTextPath svgTextPath:
            DrawTextPath(canvas, svgTextPath);
            break;
        case SvgTextRef svgTextRef:
            DrawTextRef(canvas, svgTextRef);
            break;
        case SvgTextSpan svgTextSpan:
            DrawTextSpan(canvas, svgTextSpan);
            break;
        default:
            break;
    }
}

Please note that I have not yet implement drawing for some element, so there might be some changes, this is just some ideas from my work with Svg.Skia.

@wieslawsoltes
Copy link
Contributor

Also for reference regarding usage of SkiaSharp, I have created recently pretty advanced drawing application based on SkiaSharp. Much of inspiration and design of Svg.Skia is based on this project as the Svg.Skia was meant to be used in my Draw2D app.

https://github.com/wieslawsoltes/Draw2D/blob/cc147f6b2980c391a610e27b505c9f664e4ea02d/src/Draw2D.Skia/Renderers/SkiaShapeRenderer.cs#L13

https://github.com/wieslawsoltes/Draw2D/blob/cc147f6b2980c391a610e27b505c9f664e4ea02d/src/Draw2D.Skia/SkiaHelper.cs#L23

@wieslawsoltes
Copy link
Contributor

Here is current Svg.Skia implementation with above mentioned interface:

Rendering interface ISvgRenderer

https://github.com/wieslawsoltes/Svg.Skia/blob/334b3f8bbc29136057e7a808ab8cb4becd59c6f5/src/Svg.Skia/Core/ISvgRenderer.cs#L8

SkiaSharp Rendering interface implementation

https://github.com/wieslawsoltes/Svg.Skia/blob/334b3f8bbc29136057e7a808ab8cb4becd59c6f5/src/Svg.Skia/SKSvgRenderer.cs#L12

Utilities for SkiaSharp

https://github.com/wieslawsoltes/Svg.Skia/blob/334b3f8bbc29136057e7a808ab8cb4becd59c6f5/src/Svg.Skia/Skia/SkiaUtil.cs#L16

The SkiaUtil class is most important part of Svg.Skia code base. Basically here the Svg drawing model is translated to SkiaSharp way of drawing (except few things done in renderer class).

@tebjan
Copy link
Contributor

tebjan commented Nov 10, 2019

You can see in all cases I am passing drawing context object canvas as object.

In each the draw method I am casting drawing context canvas to appropriate SkiaSharp canvas (it can be Graphics object when using System.Drawing).

if (!(canvas is SKCanvas skCanvas))
{
    return;
}

i'm wondering why you use object here? you could use ISvgCanvas with different implementations or just leave the parameter out. and pass an ISvgRenderer that manages the canvas.

@wieslawsoltes
Copy link
Contributor

You can see in all cases I am passing drawing context object canvas as object.
In each the draw method I am casting drawing context canvas to appropriate SkiaSharp canvas (it can be Graphics object when using System.Drawing).

if (!(canvas is SKCanvas skCanvas))
{
    return;
}

i'm wondering why you use object here? you could use ISvgCanvas with different implementations or just leave the parameter out. and pass an ISvgRenderer that manages the canvas.

@tebjan

The idea is for all drawable Svg element to implement simple interface:

void Draw(object canvas, ISvgRenderer renderer);

example:

public class SvgCircle
{
    // ...
    public void Draw(object canvas, ISvgRenderer renderer)
    {
       renderer.DrawCircle(canvas, this);
    }
    // ...
}

@tebjan
Copy link
Contributor

tebjan commented Nov 10, 2019

not sure if the canvas object is necessary, why not simply? or am i missing something?

public class SvgCircle
{
    // ...
    public void Draw(ISvgRenderer renderer)
    {
       renderer.DrawCircle(this);
    }
    // ...
}

@wieslawsoltes
Copy link
Contributor

not sure if the canvas object is necessary, why not simply? or am i missing something?

public class SvgCircle
{
    // ...
    public void Draw(ISvgRenderer renderer)
    {
       renderer.DrawCircle(this);
    }
    // ...
}

Than you have to initialize ISvgRenderer implementation with canvas. In my experience I prefer to pass canvas as object and just cast to appropriate drawing context.

@wieslawsoltes
Copy link
Contributor

The canvas in SkiaSharp is short-lived, but renderer can persist longer.

@tebjan
Copy link
Contributor

tebjan commented Nov 10, 2019

before drawing renderer.BeginDraw() and after renderer.EndDraw() in those methods each renderer implementation can do what's necessary. then you can do svgDoc.Draw(renderer) and in Draw begin and end is called on the interface. this is cleaner design of the abstraction, i think, This also avoids the casting in each method.

@wieslawsoltes
Copy link
Contributor

usually you have some drawing context that is used for drawing like SKCanvas in SkiaSharp and Graphics in System.Drawing that's why I like to pass object canvas as parameter

not sure how this would work in your example

@wieslawsoltes
Copy link
Contributor

@tebjan ok I will update my proposal shortly, just need to test 😄

@tebjan
Copy link
Contributor

tebjan commented Nov 10, 2019

using (var renderer = new SKSvgRenderer(skSize))
using (var skPictureRecorder = new SKPictureRecorder())
using (var skCanvas = skPictureRecorder.BeginRecording(cullRect))
{
    renderer.DrawFragment(skCanvas, svgFragment);
    return skPictureRecorder.EndRecording();
}

would become

//fields
SkCanvas canvas;
SKPictureRecorder skPictureRecorder;
...

//Begin
skPictureRecorder = new SKPictureRecorder())
skCanvas = skPictureRecorder.BeginRecording(cullRect))

//Draw
skCanvas.DrawSomething()

//End
canvas.Dispose()
skPictureRecorder.Dispose()
....

//and in SvgDocument.Draw(renderer)
try
{
    renderer.BeginDraw()
    renderer.Draw(this);
}
finally
{
    render.EndDraw()
}

@wieslawsoltes
Copy link
Contributor

@tebjan ok I have refactored renderer interface in Svg.Skia and it working

public interface ISvgRenderer : IDisposable
{
    void DrawFragment(SvgFragment svgFragment);
    void DrawImage(SvgImage svgImage);
    void DrawSwitch(SvgSwitch svgSwitch);
    void DrawSymbol(SvgSymbol svgSymbol);
    void DrawUse(SvgUse svgUse);
    void DrawForeignObject(SvgForeignObject svgForeignObject);
    void DrawCircle(SvgCircle svgCircle);
    void DrawEllipse(SvgEllipse svgEllipse);
    void DrawRectangle(SvgRectangle svgRectangle);
    void DrawMarker(SvgMarker svgMarker);
    void DrawGlyph(SvgGlyph svgGlyph);
    void DrawGroup(SvgGroup svgGroup);
    void DrawLine(SvgLine svgLine);
    void DrawPath(SvgPath svgPath);
    void DrawPolyline(SvgPolyline svgPolyline);
    void DrawPolygon(SvgPolygon svgPolygon);
    void DrawText(SvgText svgText);
    void DrawTextPath(SvgTextPath svgTextPath);
    void DrawTextRef(SvgTextRef svgTextRef);
    void DrawTextSpan(SvgTextSpan svgTextSpan);
}

sample usage:

float width = svgFragment.Width.ToDeviceValue(null, UnitRenderingType.Horizontal, svgFragment);
float height = svgFragment.Height.ToDeviceValue(null, UnitRenderingType.Vertical, svgFragment);
var skSize = new SKSize(width, height);
var cullRect = SKRect.Create(skSize);
using (var skPictureRecorder = new SKPictureRecorder())
using (var skCanvas = skPictureRecorder.BeginRecording(cullRect))
using (var renderer = new SKSvgRenderer(skCanvas, skSize))
{
    renderer.DrawFragment(svgFragment);
    return skPictureRecorder.EndRecording();
}

@paolofulgoni
Copy link

@wieslawsoltes

When we would have spitted processing of documents from the rendering of them each SvgElement would implement simple interface with Draw method

If we split the library, then the new manipulation-only library shouldn't have any method related to drawing.

I think it's not so bad to have those methods in the ISvgRenderer interface. But maybe I'm missing something...

Another option could be the extension methods, such as:

public static class SvgCircleExtensions
{
    public void Draw(this SvgCircle svgCircle, object canvas, ISvgRenderer renderer)
    { }
}

@wieslawsoltes
Copy link
Contributor

@wieslawsoltes

When we would have spitted processing of documents from the rendering of them each SvgElement would implement simple interface with Draw method

If we split the library, then the new manipulation-only library shouldn't have any method related to drawing.

I think it's not so bad to have those methods in the ISvgRenderer interface. But maybe I'm missing something...

Another option could be the extension methods, such as:

public static class SvgCircleExtensions
{
    public void Draw(this SvgCircle svgCircle, object canvas, ISvgRenderer renderer)
    { }
}

The renderer interface depends on backanends and functionality needed to implement complete and correct rendering of supported SVG specification (or parts of spec.).

Each rendering backend (e.g. Gdi, SkiaSharp etc.) have different concepts and may require some additional abstraction in renderer. For example how do you handle transforms, layer, clipping etc..

In my library for now (as its not complete) I have abstract all of internals and above interface in enough, but in my experience it may require some tweaks.

@tebjan
Copy link
Contributor

tebjan commented Nov 12, 2019

public interface ISvgRenderer : IDisposable
{
    void DrawFragment(SvgFragment svgFragment);
    void DrawImage(SvgImage svgImage);
    void DrawSwitch(SvgSwitch svgSwitch);
    void DrawSymbol(SvgSymbol svgSymbol);
    void DrawUse(SvgUse svgUse);
    void DrawForeignObject(SvgForeignObject svgForeignObject);
    void DrawCircle(SvgCircle svgCircle);
    void DrawEllipse(SvgEllipse svgEllipse);
    void DrawRectangle(SvgRectangle svgRectangle);
    void DrawMarker(SvgMarker svgMarker);
    void DrawGlyph(SvgGlyph svgGlyph);
    void DrawGroup(SvgGroup svgGroup);
    void DrawLine(SvgLine svgLine);
    void DrawPath(SvgPath svgPath);
    void DrawPolyline(SvgPolyline svgPolyline);
    void DrawPolygon(SvgPolygon svgPolygon);
    void DrawText(SvgText svgText);
    void DrawTextPath(SvgTextPath svgTextPath);
    void DrawTextRef(SvgTextRef svgTextRef);
    void DrawTextSpan(SvgTextSpan svgTextSpan);
}

i would even simplify this into:

public interface ISvgRenderer : IDisposable
{
      void DrawElement(SvgBaseClass svgElement);
}

in general it's good to keep interfaces as small as possible.

you can then have this switch in the DrawElement:

public void DrawElement(SvgElement svgElement)
{
    switch (svgElement)
    {
        case SvgFragment svgFragment:
            DrawFragment(canvas, svgFragment);
            break;
        case SvgImage svgImage:
            DrawImage(canvas, svgImage);
            break;
        case SvgSwitch svgSwitch:
            DrawSwitch(canvas, svgSwitch);
            break;
        case SvgUse svgUse:
            DrawUse(canvas, svgUse);
            break;
        case SvgForeignObject svgForeignObject:
            DrawForeignObject(canvas, svgForeignObject);
            break;
        case SvgCircle svgCircle:
            DrawCircle(canvas, svgCircle);
            break;
        case SvgEllipse svgEllipse:
            DrawEllipse(canvas, svgEllipse);
            break;
        case SvgRectangle svgRectangle:
            DrawRectangle(canvas, svgRectangle);
            break;
        case SvgMarker svgMarker:
            DrawMarker(canvas, svgMarker);
            break;
        case SvgGlyph svgGlyph:
            DrawGlyph(canvas, svgGlyph);
            break;
        case SvgGroup svgGroup:
            DrawGroup(canvas, svgGroup);
            break;
        case SvgLine svgLine:
            DrawLine(canvas, svgLine);
            break;
        case SvgPath svgPath:
            DrawPath(canvas, svgPath);
            break;
        case SvgPolyline svgPolyline:
            DrawPolyline(canvas, svgPolyline);
            break;
        case SvgPolygon svgPolygon:
            DrawPolygon(canvas, svgPolygon);
            break;
        case SvgText svgText:
            DrawText(canvas, svgText);
            break;
        case SvgTextPath svgTextPath:
            DrawTextPath(canvas, svgTextPath);
            break;
        case SvgTextRef svgTextRef:
            DrawTextRef(canvas, svgTextRef);
            break;
        case SvgTextSpan svgTextSpan:
            DrawTextSpan(canvas, svgTextSpan);
            break;
        default:
            break;
    }
}

@tebjan
Copy link
Contributor

tebjan commented Nov 12, 2019

If we split the library, then the new manipulation-only library shouldn't have any method related to drawing.

the question is then, who is responsible for traversing the element tree correctly and calling the draw methods? the render library shouldn't have to do that, since it is the same for all rendering backends.

@tebjan tebjan pinned this issue Nov 12, 2019
@paolofulgoni
Copy link

the question is then, who is responsible for traversing the element tree correctly and calling the draw methods? the render library shouldn't have to do that, since it is the same for all rendering backends.

In this case, the manipulation library should expose methods to traverse the elements tree. But still it is something not strictly related to rendering.

I mean, the traversal methods shouldn't have any IRenderer parameter. It's the renderer itself to traverse the tree and do the job.

@mrbean-bremen
Copy link
Member

Agreed. A generic traversal method shall probably live in the manipulation library and be called from the renderer.
We could pass it a generic delegate that can be used to do the drawing, or put the recursive drawing code in some rendering base class, that uses the interface for drawing.

@paolofulgoni
Copy link

I see there has been another attempt to use SkiaSharp for rendering in #291

@paolofulgoni
Copy link

Back to my question in a previous comment: should we maintain backward compatibility after the lib split?
I would say no, as there are methods which are tightly coupled with the current GDI rendering, such as:

public GraphicsPath Path(ISvgRenderer renderer);
protected void Render(ISvgRenderer renderer);

One option could be to extend the classes of the manipulation-only library in the GDI rendering library to add those methods, but is it worthwhile?

@paolofulgoni
Copy link

oh, interesting, this is a WPF-based rendering: https://github.com/ElinamLLC/SharpVectors

@wieslawsoltes
Copy link
Contributor

wieslawsoltes commented Nov 13, 2019

Back to my question in a previous comment: should we maintain backward compatibility after the lib split?
I would say no, as there are methods which are tightly coupled with the current GDI rendering, such as:

public GraphicsPath Path(ISvgRenderer renderer);
protected void Render(ISvgRenderer renderer);

One option could be to extend the classes of the manipulation-only library in the GDI rendering library to add those methods, but is it worthwhile?

Also we should consider if we want/can remove dependency from System.Drawing, and it's not just rendering part, also processing part may be affected.

@dittodhole
Copy link

This change would be awesome!

Given that rendering isn't based on GDI, I would happily migrate my contrib-project (https://github.com/dittodhole/dotnet-Svg.Contrib.Render) on that new approach :)

@paolofulgoni
Copy link

Also we should consider if we want/can remove dependency from System.Drawing, and it's not just rendering part, also processing part may be affected.

From my point of view, reducing the dependencies as much as possible is a goal.

@gvheertum
Copy link
Member Author

gvheertum commented Nov 20, 2019

Nice work all. When creating this item I thought it would take a while and it was something nice to have in the future. Seeing you all working on this item and seeing the results I am really amazed, nice work!

(nothing really to add to the ticket itself, and unfortunately I'm a bit swamped at the moment, so I am also not able to help in the development, but just wanted to say I really love the work that's happening 😄 )

@Almis90
Copy link

Almis90 commented Nov 20, 2019

Also we should consider if we want/can remove dependency from System.Drawing, and it's not just rendering part, also processing part may be affected.

I think it's very important to remove dependency of System.Drawing. UWP doesn't support System.Drawing, for some Xamarin developers (as myself) that would be a problem.

@alexeid1337
Copy link

@wieslawsoltes

I salute you for your effort, and I believe it's the correct choice. SVG library is great for working with SVG tree, SkiaSharp is great for rendering. Should we have the bridge between them - awesome.

Recently I've been busy with the task of parsing big SVG files, updating and displaying them live based on some conditions (updating mostly includes property changes, like "fill", "stroke", "display", etc.)
So far I had to settle down with updating SvgDocument tree, converting SvgDocument to bitmap, then to bitmap stream, then to SKBitmap, to paint it to SKCanvas later. But this solution has its cons, plus it's not really great performance wise.
So I've tried Skia.Svg, and it worked great. I'm using the "FromSvgDocument" method and converting SKPicture to SKImage, to paint it to SKCanvas later. This solution is much-much more performant. Plus I get better image quality (especially with texts, which are much more crisp with Skia).
The downside is the library is not production-ready (which I can see in the source code), not all the styles are supported (from the first glance, "display" style isn't used, some text styles too), etc. But still it's a great magnitude of work in such a small time, you're doing great, sir. When I get some spare time in the following weeks, I sincerely hope to contribute to the case too.

@paolofulgoni
Copy link

@wieslawsoltes wrote:

Also we should consider if we want/can remove dependency from System.Drawing, and it's not just rendering part, also processing part may be affected.

Yes, unfortunately the processing part depends on System.Drawing too.

Here are some examples:

  • SvgPath is based on System.Drawing.Drawing2D.GraphicsPath
  • Many classes and interfaces use primitives such as PointF, RectangleF, etc.
  • The colors are based on System.Drawing.Color

So removing the System.Drawing dependency looks quite hard to me... 😞
It may be a second step to do later

@Almis90
Copy link

Almis90 commented Nov 30, 2019

I got it partially working on UWP with Xamarin.Forms

  1. Made new build of this library but replaced everywhere System.Drawing* with System.DrawingCore* (https://github.com/zkweb-framework/ZKWeb.System.Drawing)
  2. Updated Fizzler to 1.2.0
  3. Made new build of Svg.Skia that reference the new build of this library
  4. And finally used SkiaSharp to render the picture that was made from Svg.Skia

I used 12mb file, didn't notice any performance issue but it was missing the fill colors.

As you can see the library is deprecated now but maybe someone can resurrect it again and continue supporting it? at least only for platforms that don't support System.Drawing.

@chucker
Copy link
Contributor

chucker commented Dec 5, 2019

Yes, unfortunately the processing part depends on System.Drawing too.

Here are some examples:

  • SvgPath is based on System.Drawing.Drawing2D.GraphicsPath
  • Many classes and interfaces use primitives such as PointF, RectangleF, etc.
  • The colors are based on System.Drawing.Color

So removing the System.Drawing dependency looks quite hard to me... 😞
It may be a second step to do later

Note that System.Drawing.Primitives exists as a separate package, and does include Color, PointF, etc.

Not GraphicsPath, though.

And SkiaSharp comes with its own incompatible set of primitives regardless, so maybe that isn't going to help.

@yoshiask
Copy link

It's been a few months now, are there any updates on this?

@AraHaan
Copy link

AraHaan commented Nov 6, 2021

I am tempted myself to make another fork of this, and decouple from System.Drawing using stuff like:

public struct SvgColor
{
    // alpha component of this color.
    public int A { get; set; }
    // Red component of this color.
    public int R { get; set; }
    // green component of this color.
    public int G { get; set; }
    // blue component of this color.
    public int B { get; set; }
}

// struct that simulates a pixel in an rasterized image (basically it simulates using System.Drawing to "rasterize" it)
public struct SvgPixel
{
    public SvgColor Color { get; set; }
    public int Location { get; set; }
}

public struct SvgBitmap
{
    public SvgPixel[] Pixels { get; set; }
}

Only issue however would be converting each thing to SvgPixel instances for an instance of SvgBitmap. But I am trying to find a way to return the information that they would need to then implement the rendering themselves.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests