microsoft / Win2D

Win2D is an easy-to-use Windows Runtime API for immediate mode 2D graphics rendering with GPU acceleration. It is available to C#, C++ and VB developers writing apps for the Windows Universal Platform (UWP). It utilizes the power of Direct2D, and integrates seamlessly with XAML and CoreWindow.
http://microsoft.github.io/Win2D
Other
1.81k stars 285 forks source link

DrawSvg does not support scaling? #649

Closed jevansAN closed 5 years ago

jevansAN commented 5 years ago

The CanvasDrawingSession method DrawSvg is welcome, but it appears that scaling the scalable vector graphic is not supported - unless I'm misunderstanding something. The Size argument used in this method is described as referring to the viewport, not the viewbox, of the svg. Changing the dimensions of the Size has no effect on the rendering, which always produces an image at the original size of the svg. Is there going to be a way to scale the drawing of svg images, or is there already one that I'm missing?

clandrew commented 5 years ago

I'd expect SVG to be scaleable the same way practically anything in Win2D is scaleable- you can set CanvasDrawingSession.Transform to a scale transform.

jevansAN commented 5 years ago

clandrew, I need to draw lots and lots - hundreds - of musical symbols, each of which may have a different scaling factor. Do you mean that I should set the transform for the session each time one is drawn? Would the drawing position also be affected?

jevansAN commented 5 years ago

I mean: the canvas surface needs to keep its coordinates as I draw images at various positions, each of which may itself be scaled - without changing the overall size of the canvas.

shawnhar commented 5 years ago

Yes - you should change the transform property every time you want to draw something using a different transform.

jevansAN commented 5 years ago

Thanks - And that will not require a change in the coordinates of the item to be drawn?

jevansAN commented 5 years ago

Perhaps it would be better to pre-render each of these into a bitmap image and draw that instead.

jevansAN commented 5 years ago

shawnhar - what about CanvasSvgAspectScaling? One of the enumerated values appears to mean that the image will be drawn to entirely fill the viewport "scale viewbox up to the maximum needed to make entire viewbox visible in viewport." I haven't been able to find a way to set this attribute, but the description makes it seem like exactly the right tool for the job.

shawnhar commented 5 years ago

Like Claire said, CanvasDrawingSession.Transform is the right way to scale an SVG graphic (just the same as it can transform anything drawn by Win2D).

jevansAN commented 5 years ago

Thanks, shawnhar. Maybe I'll have to learn how to do matrix transforms - I was concerned that the transform would throw off the coordinates of the image to be drawn (wouldn't it?). But is there any purpose to that CanvasSvgAspectScaling attribute?

clandrew commented 5 years ago

CanvasSvgAspectScaling is a way of programmatically setting the SVG attribute preserveAspectRatio. You can read more about this attribute here: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio

jevansAN commented 5 years ago

Thanks much, clandrew - I think I'm pointed in the right direction. I was just doing some experimenting and found that if I removed the Width and Height properties of an svg image and set the preserveAspectRatio to xMidyMid I could get the svg to scale to different sizes sharp and clear without setting any transform on the environment. Haven't quite worked out controlling it yet; need more of an understanding of the viewBox, but this looks hopeful.

clandrew commented 5 years ago

Glad to hear. In the event that doesn't work, please don't be afraid of transforms. The System.Numerics types are set up to make working with them not too bad. For example, you can use Matrix3x2.CreateScale to create a scale transform about a certain point. If you specify the location of the thing you're drawing as the point, you don't have to think about different co-ordinate spaces.

jevansAN commented 5 years ago

clandrew, remembering that I'm drawing hundreds of images (musical symbols) in a scrolling view, do you think that the transform method would be more efficient time-wise? Changing the attributes of each svg could at least be done in advance (would still need to be reloaded at each change in zoom, though).

clandrew commented 5 years ago

The overhead of setting a transform on the drawing session is really low. It's designed to be called lots of times. Ballpark it's generally faster to set a transform than to set an SVG attribute, because setting a transform doesn't require looking at any part of SVG DOM. Of course if you're comparing setting an attribute once vs. setting transform frame-over-frame that's a slightly different issue, but I'd be really surprised if it was any kind of bottleneck. I would be more concerned about programmatic changes to SVG in the zoom scenario you mentioned, because if that's a frame-by-frame zoom animation then it would absolutely be better to use a transform.

jevansAN commented 5 years ago

That's a good argument for transform, clandrew. The app does want to be able to zoom smoothly. The only save would be if I can set the svg's attributes in advance in such a way that the viewport argument to DrawSvg is all that is required to do the scale. Haven't quite got it worked out yet, but a sample svg scales to different sizes depending on the Size argument to DrawSvg if I do certain changes to the svg attributes, like removing its Width and Height and setting the preserveAspectRatio attribute.

clandrew commented 5 years ago

Ok, well, feel free to get in touch if there are any issues. Also if I were completely dead-set on manipulating transform through SVG, I would probably change the transform attribute of a top-level geometry node rather than deal with other attributes that are more finicky

jevansAN commented 5 years ago

clandrew, I wouldn't say I'm dead-set exactly, but I have discovered now that if I merely remove the Height and Width attributes of each svg before loading them, they will all draw to fill the viewport that is the argument for DrawingSession.DrawSvg - which is such a simple and apparently efficient way to do this I think I'm just going to go with it. But this is not documented anywhere that I can find; that's the only downside I can see right now.

clandrew commented 5 years ago

The Width and Height attribute of the top-level \<svg> element represent viewport extents. See this page for a more complete explanation. If you specify those in your SVG document, they take precedence. If you don't (because you programmatically removed them, or what not) the default from Win2D passed in through DrawSvg will get used. Yeah maybe we could document this but with the caveat that we really don't want to encourage resizing this way in general.

jevansAN commented 5 years ago

Clandrew, for my own code safety - why don't you want to encourage this simple way to resize? Seems to work great, though the drawing is not quite as sharp and clear at say, zoom 1.7 than it is at 1.0.

clandrew commented 5 years ago

Sure- the reasons, in order of importance, are

jevansAN commented 5 years ago

Thanks, clandrew. The first two won't matter much to me - for performance I should run a test: will it be quicker to just call DrawSvg, or quicker to include a transform before the draw? That part I don't get: can't see why performance would improve by adding a step - ? On the other hand, I will also want to draw text and the occasional geometric shape. Not worried about corrupting svgs, though - I intend to just prepare them in advance by stripping their Width/Height attributes. These are all svgs converted from pdfs. My main problem with this - which may also apply to transforms - is that the canvas bitmap when zoomed doesn't take full advantage of the screen's resolution. This is on a retina screen but I don't get retina results. I don't know what can be done about that.

jevansAN commented 5 years ago

I mean, the last two won't matter much to me. But I'm curious about the efficiency question - I suppose the transform could be more efficient if it prevented DrawSvg from taking some additional internal step -?

clandrew commented 5 years ago

In general, it's Fast:

void ForEachFrame()
{
    drawingSession.Transform = Matrix3x2.CreateScale(desiredWidth, desiredHeight, location)
    DrawSvg(originalSvg);
}

Slow:

void ForEachFrame()
{
    var modifiedSvg = StripOutWidthAndHeightAttributes(originalSvg);
    DrawSvg(modifiedSvg with desiredWidth, desiredHeight as viewport);
}

reason being is the time it takes to strip out the attributes.

You should treat setting the transform as literally 0 cost in most circumstances. Setting the transform is basically just copying a value. Internally, Direct2D (which Win2D is built on top of) always draws with consideration of a transform. Always. That transform might be identity, or it might be something else, but it always uses it and drawing at different transforms something Direct2D has been designed for.

Now I think what you're suggesting is something like

var modifiedSvg;
void OnStartup()
{
    modifiedSvg = StripOutWidthAndHeightAttributes(originalSvg);
}
void ForEachFrame()
{
    DrawSvg(modifiedSvg with desiredWidth, desiredHeight as viewport);
}

which, like... okay. It is not going to be noticeably faster than the "Fast" pseudo-code above. It is weirder, though.

clandrew commented 5 years ago

canvas bitmap when zoomed doesn't take full advantage of the screen's resolution

This sounds like a different issue... Is something here rendering to an intermediate (CanvasBitmap)?

jevansAN commented 5 years ago

Very helpful, clandrew. For the svg stripping - I'm doing it at the file level, so these are pre-stripped. But I'm interested in trying the transform, especially since you provided a clear example. I'll give that a shot. Meanwhile, in my experimenting here all I've done is to create a CanvasControl and make it the content of a ScrollView. Then I answer its Draw event and try out these various images. But horizontal lines in particular do not anti-alias well and I have the impression that the resolution of the CanvasControl is less than that of the retina screen. Very sharp and clear at 1.0 though.

clandrew commented 5 years ago

Ok, glad to hear. I don't immediately know what's up with the horizontal lines scaling/aliasing, but if it persists feel free to get in touch through this or a separate issue. In general the geometry aliasing is controlled by CanvasDrawingSession.Antialiasing which is anti-aliased by default.

jevansAN commented 5 years ago

Is Matrix3x2 available to a C++/winrt project? The namespace is System.Numerics, which doesn't seem to exist for this.

clandrew commented 5 years ago

Oh-- yes, it's called a different name for C++. It's called float3x2, and the method to create the scale is called make_float3x2_scale available through #include . The documentation for that is here

jevansAN commented 5 years ago

Ah, thanks.

shawnhar commented 5 years ago

Closing issue as the question has been answered.