Improved version of Ab2d.ReaderSvg and Ab2d.ReaderWmf available

by abenedik 23. May 2014 21:43

I am happy to announce that a new version of Ab2d.ReaderSvg and Ab2d.ReaderWmf have been published.

I am particularly proud of adding some very interesting hierarchy optimizations to ReaderSvg. Drawings in svg files are many times defined in many groups and with using transformations. In case you do want to simply show your vector assets, than it is much more convenient to have the drawing objects compact and without the complex hierarchies. With the new version of ReaderSvg this is not very easy task. More about that later in this post.

First let me show what is new for Ab2d.ReaderWmf – metafile reader for WPF:

  • Improved reading geometries when rectangle or ellipse have both Canvas position (Canvas.Left or Canvas.Top) and transformation set.
  • Improved xaml writer to prevent writing Canvas.Left = "0" and Canvas.Top = "0" (zero values can be skipped).
  • Prevented writing "Canvas.Left" and "Canvas.Top" properties in XAML two times.
  • Added IgnoreRootElementSize property to GeometrySetting - added possibility to remove the transformations or RectangleGeometry based on the size or root element.
  • Fixed not showing some texts under some circumstances.

Most of the improvements in ReaderWmf are also available in ReaderSvg. Here is the full list:

  • Clip path now works on all elements and not just on Canvas.
  • Improved size measurement when AutoSize is set to true and some objects are hidden.
  • Added objects grouping optimizations to ReaderSvg - they can be controlled by new properties in ReaderSvg: OptimizeObjectGroups, FlattenHierarchies, TransformShapes
  • Improved transforming read objects with calling Transform method.
  • Updated SvgBounds after calling Transform method with updateLastReadViewbox set to true.
  • Prevented throwing exception in GetObjectsWithCustomProperties when svg file does not have any custom properties.
  • Added IgnoreRootElementSize property to GeometrySetting - added possibility to remove the transformations or RectangleGeometry based on the size or root element. This can produce nicer XAML.
  • Fixed using transformations on EllipseGeometry and RectGeometry when reading svg file as geometry.
  • Prevented writing "Canvas.Left" and "Canvas.Top" properties in XAML two times.
  • Improved reading geometries when rectangle or ellipse have both Canvas position (Canvas.Left or Canvas.Top) and transformation set.
  • Improved XAML writer to prevent writing Canvas.Left = "0" and Canvas.Top = "0" (zero values can be skipped).

As mentioned before ReaderSvg now support optimizing groups. The best way to describe this new functionality is to show it in action. Let’s start with the following XAML that can be get when using no optimization:

<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="200" Height="100">
    <Canvas>
        <Canvas Name="g1">
            <Rectangle Fill="Yellow" Width="200" Height="100"/>
            <Canvas Name="g2">
                <Canvas Name="g3">
                    <Ellipse Name="e1" Fill="Red" Width="20" Height="20"/>
                    <Canvas Name="g4">
                        <Canvas Name="g6" RenderTransform="2,0,0,1,-60,-10">
                            <Canvas Name="g5" Canvas.Left="50" Canvas.Top="10">
                                <Ellipse Name="e2" Fill="Green" Width="20" Height="20" Canvas.Left="10" Canvas.Top="10"/>
                                <Canvas Name="g7" RenderTransform="0.5,0,0,1,20,20">
                                    <Canvas Name="g8">
                                        <Canvas Name="e3" RenderTransform="1,0,0,2,20,0">
                                            <Ellipse Fill="Blue" Width="20" Height="20" Canvas.Left="20"/>
                                        </Canvas>
                                    </Canvas>
                                </Canvas>
                                <Canvas Name="g9" RenderTransform="5,0,0,5,0,0">
                                    <Canvas Name="g10">
                                        <Ellipse Name="e4" Fill="Orange" Width="4" Height="4" Canvas.Left="11" Canvas.Top="3"/>
                                    </Canvas>
                                </Canvas>
                                <Canvas Name="g11" Canvas.Left="-10" Canvas.Top="30">
                                    <Polygon Name="p1" Points="10,10 30,10 20,20" Fill="Lime" StrokeThickness="1"/>
                                    <Canvas Name="g12" RenderTransform="1.5,0,0,1,0,10">
                                        <Polygon Name="p2" Points="10,10 30,10 20,20" Fill="Aqua" StrokeThickness="1"/>
                                        <Canvas Name="g13" RenderTransform="0.866025388240814,0.5,-0.5,0.866025388240814,0,0">
                                            <Polygon Name="p3" Points="10,10 30,10 20,20" Fill="Purple" StrokeThickness="1"/>
                                        </Canvas>
                                    </Canvas>
                                </Canvas>
                            </Canvas>
                        </Canvas>
                    </Canvas>
                </Canvas>
            </Canvas>
        </Canvas>
    </Canvas>
</Canvas>

The XAML shows that there are many Canvases that contains only other Canvases. In the first step we can set OptimizeObjectGroups to true. This reduces the size of XAML to:

<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="200" Height="100">
    <Canvas Name="g1">
        <Rectangle Fill="Yellow" Width="200" Height="100"/>
        <Canvas Name="g3">
            <Ellipse Name="e1" Fill="Red" Width="20" Height="20"/>
            <Canvas Name="g5" RenderTransform="2,0,0,1,40,0">
                <Ellipse Name="e2" Fill="Green" Width="20" Height="20" Canvas.Left="10" Canvas.Top="10"/>
                <Canvas Name="e3" RenderTransform="0.5,0,0,2,30,20">
                    <Ellipse Fill="Blue" Width="20" Height="20" Canvas.Left="20"/>
                </Canvas>
                <Canvas Name="g10" RenderTransform="5,0,0,5,0,0">
                    <Ellipse Name="e4" Fill="Orange" Width="4" Height="4" Canvas.Left="11" Canvas.Top="3"/>
                </Canvas>
                <Canvas Name="g11" Canvas.Left="-10" Canvas.Top="30">
                    <Polygon Name="p1" Points="10,10 30,10 20,20" Fill="Lime" StrokeThickness="1"/>
                    <Canvas Name="g12" RenderTransform="1.5,0,0,1,0,10">
                        <Polygon Name="p2" Points="10,10 30,10 20,20" Fill="Aqua" StrokeThickness="1"/>
                        <Canvas Name="g13" RenderTransform="0.866025388240814,0.5,-0.5,0.866025388240814,0,0">
                            <Polygon Name="p3" Points="10,10 30,10 20,20" Fill="Purple" StrokeThickness="1"/>
                        </Canvas>
                    </Canvas>
                </Canvas>
            </Canvas>
        </Canvas>
    </Canvas>
</Canvas>

But there are still many Canvases. A more radical step is to remove the all the hierarchies. This can be simply done with setting FlattenHierarhy to true (OptimizeObjectGroups can be set to false). The results speak for themselves:

<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="200" Height="100">
    <Rectangle Fill="Yellow" Width="200" Height="100"/>
    <Ellipse Name="e1" Fill="Red" Width="20" Height="20"/>
    <Ellipse Name="e2" Fill="Green" Width="20" Height="20" RenderTransform="2,0,0,1,40,0" Canvas.Left="20" Canvas.Top="10"/>
    <Ellipse Fill="Blue" Width="20" Height="20" RenderTransform="1,0,0,2,100,20" Canvas.Left="20"/>
    <Ellipse Name="e4" Fill="Orange" Width="4" Height="4" RenderTransform="10,0,0,5,40,0" Canvas.Left="110" Canvas.Top="15"/>
    <Polygon Name="p1" Points="10,10 30,10 20,20" Fill="Lime" StrokeThickness="1" RenderTransform="2,0,0,1,20,30"/>
    <Polygon Name="p2" Points="10,10 30,10 20,20" Fill="Aqua" StrokeThickness="1" RenderTransform="3,0,0,1,20,40"/>
    <Polygon Name="p3" Points="10,10 30,10 20,20" Fill="Purple" StrokeThickness="1" RenderTransform="2.59807616472244,0.5,-1.5,0.866025388240814,20,40"/>
</Canvas>

With using just one simple property we have come from a complex hierarchy to one simple Canvas that contain all the objects. But we can go even one more step further. As seen in the last step, almost all the objects use RenderTransform to transform them. With setting TransformShapes to true we can eliminate almost all RenderTransforms from the objects. So after setting both FlattenHierarhy and TransformShapes to true, we are left with the following XAML:

<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="200" Height="100">
    <Rectangle Fill="Yellow" Width="200" Height="100"/>
    <Ellipse Name="e1" Fill="Red" Width="20" Height="20"/>
    <Ellipse Name="e2" Fill="Green" StrokeThickness="1.414" Width="40" Height="20" Canvas.Left="60" Canvas.Top="10"/>
    <Ellipse Fill="Blue" StrokeThickness="1.414" Width="20" Height="40" Canvas.Left="120" Canvas.Top="20"/>
    <Ellipse Name="e4" Fill="Orange" StrokeThickness="7.071" Width="40" Height="20" Canvas.Left="150" Canvas.Top="15"/>
    <Polygon Name="p1" Points="40,40 80,40 60,50" Fill="Lime" StrokeThickness="1.414"/>
    <Polygon Name="p2" Points="50,50 110,50 80,60" Fill="Aqua" StrokeThickness="1.732"/>
    <Polygon Name="p3" Points="30.981,53.66 82.942,63.66 41.962,67.321" Fill="Purple" StrokeThickness="1.732"/>
</Canvas>

Nice and clean!

No more transformations. Instead the values of the shapes were transformed to give us clean and simple object model.

Note that when the Canvas defines the clipping area, than even when FlattenHierarhy is set to true, this Canvas is preserved. This way the end results are still the same as with having the whole hierarchy. 

In the same fashion, the TransformShapes could preserve some RenderTransforms. This happens when it is not possible to preserve the object with only transform the object’s values – for example if Ellipse or Rectangle is rotated with RenderTransforms,  then we cannot preserve this rotation without using transformation (with Ellipse and Rectangle we can only change the position and size).

I am sure that those new features are great for anyone that wants to use vector assets instead of bitmap. I think that using vector assets is the future. They have many advantages over bitmaps. For example they are DPI independent and can be scaled up and down without any loss. They usually take less space on disk. They can be simply manipulated – for example if you want that one icon changes color when the mouse is over it, this can be done with simply changing the Stroke or Fill color. With bitmap you need to use provide the bitmaps in all possible colors.

Therefore I am already working on a new application that will allow users to simply create ResourceDictionaries with many vector objects created from svg or metafiles. The application will allow the user to optimize and transform the objects before they are added to ResourceDictionary. For example it will be possible to create vector assets that will all have the same size and the same padding regardless from which source they are created. And what is more, the application will be available with full source code.

At the end of this post I would like to remind you, that the XAML that is created with our converter applications (ViewerSvg and Paste2Xaml) are not meant only for WPF. They can be also used for Silverlight and also for Windows Phone and Windows Store apps.

Tags: ,

ReaderSvg | ReaderWmf