{"id":619,"date":"2019-07-12T07:33:25","date_gmt":"2019-07-12T07:33:25","guid":{"rendered":"https:\/\/www.bverhue.nl\/delphisvg\/?p=619"},"modified":"2020-06-22T04:29:37","modified_gmt":"2020-06-22T04:29:37","slug":"drawing-outlines-around-svg-elements","status":"publish","type":"post","link":"https:\/\/www.bverhue.nl\/delphisvg\/2019\/07\/12\/drawing-outlines-around-svg-elements\/","title":{"rendered":"Drawing outlines around SVG elements"},"content":{"rendered":"<p>The SVG control package offers a number of controls to display SVG graphics. But if you want more control over how SVG graphics are rendered or if you want to post process graphics, you can also use the lower level classes, interfaces and functions of the package.<\/p>\n<p>Two of these interfaces are<\/p>\n<p><em>ISVGRenderContext<\/em><\/p>\n<p>and<\/p>\n<p><em>ISVGRoot<\/em><\/p>\n<p>ISVGRenderContext is a high quality canvas, very much like the Delphi Firemonkey canvas, but it has the advantage that you can use it in Delphi VCL also. It always draws to a bitmap which you have to supply.<\/p>\n<p>It has most of the drawing functions that the Firemonkey canvas has. Functions like DrawCircle, DrawRect, FillRect, FillPath, ApplyFill, ApplyStroke, MultiplyMatrix BeginScene, EndScene and so on.<\/p>\n<p>You can use the render context (RC) for example to add drawings before or after you render an SVG graphic, or you could just use it for high quality drawings, also supporting transparency, something Delphi VCL does not excel in.<\/p>\n<p>ISVGRoot is the root of the tree of SVG elements of the SVG graphic, equivalent to the Document Object Model (DOM)\u00a0 in an internet browser. This is created after parsing the SVG xml text and allows you to manipulate elements and parameters before actually rendering the graphic. You can also use it to measure element dimensions.<\/p>\n<p>I&#8217;ll give an example here how you can use both to render an SVG graphic an then to post process the graphic to draw some outlines around some of the elements in the SVG graphic.<\/p>\n<p>We use the following simple SVG graphic and we will draw outlines around the text, star group an rotated ellipse.<\/p>\n<pre class=\"lang:xhtml decode:true\">&lt;?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?&gt;\n&lt;svg\n  xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n  id=\"test_svg\"\n  version=\"1.1\"\n  height=\"320\"\n  width=\"480\"\n  viewBox=\"0 215 120 80\"&gt;\n  &lt;g \n    id=\"layer1\"&gt;\n    &lt;text\n      id=\"txt\" x=\"55\" y=\"225\" font-family=\"sans-serif\" font-size=\"8\"&gt;\n      &lt;tspan id=\"tspan1\"&gt;A star group&lt;\/tspan&gt;\n      &lt;tspan id=\"tspan2\" x=\"55\" dy=\"1em\"&gt;an ellipse&lt;\/tspan&gt;\n      &lt;tspan id=\"tspan3\" x=\"55\" dy=\"1em\"&gt;and some text&lt;\/tspan&gt;\n    &lt;\/text&gt;\n    &lt;ellipse\n      id=\"ellipse\" transform=\"rotate(25)\" cx=\"200\" cy=\"215\" rx=\"25\" ry=\"7.5\" fill=\"#00ff00\" stroke=\"#000000\" stroke-width=\"0.75\" \/&gt;\n    &lt;g\n      id=\"star_group\"&gt;\n      &lt;path\n        id=\"star1\" fill=\"#ff2a2a\" stroke=\"#000000\" stroke-width=\"0.75\"\n        d=\"m 41.010416,262.03718 -4.104352,-0.60791 -3.016279,2.84909 -0.690161,-4.09132 -3.641728,-1.98824 3.677809,-1.92067 0.765567,-4.07789 2.963173,2.90429 4.114874,-0.53204 -1.846468,3.71562 z\"\n        \/&gt;\n      &lt;path\n        id=\"star2\" fill=\"#ffcc00\" stroke=\"#000000\" stroke-width=\"0.75\"\n        d=\"m 25.31441,257.03553 -3.967414,-6.11754 -7.330735,-0.71095 4.676652,-5.60961 -1.579348,-7.09179 6.857743,2.6506 6.354646,-3.67203 -0.438334,7.24778 5.506734,4.82236 -7.128647,1.82876 z\"\n        \/&gt;\n    &lt;\/g&gt;\n  &lt;\/g&gt;\n&lt;\/svg&gt;<\/pre>\n<p>This SVG graphic looks like this<\/p>\n<p><a href=\"https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-627 aligncenter\" src=\"https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg-300x254.png\" alt=\"\" width=\"300\" height=\"254\" srcset=\"https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg-300x254.png 300w, https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg-600x508.png 600w, https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg.png 657w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Now for the code.<\/p>\n<p>In a new Delphi VCL application, using one form with a standard TButton and TImage control, on the OnClick of the button we do the following:<\/p>\n<ol>\n<li>First we will create an SVGRoot object and parse the SVG text<\/li>\n<li>Then we will calculate the size of the entire SVG and set the bitmap size accordingly<\/li>\n<li>Then we will render the SVG to the bitmap<\/li>\n<li>Then we will draw rectangles around the three elements<\/li>\n<li>Last we assign the bitmap to the TImage control<\/li>\n<\/ol>\n<p><strong>Step 1, create SVGRoot object and parse the SVG text.<\/strong><\/p>\n<pre class=\"lang:delphi decode:true \">uses\n  System.UITypes,\n  BVE.SVG2SaxParser,\n  BVE.SVG2Intf,\n  BVE.SVG2Context,\n  BVE.SVG2Types,\n  BVE.SVG2Elements,\n  BVE.SVG2Elements.VCL;\n\nconst\n  svg_text =\n    '&lt;?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?&gt;'\n  + '&lt;svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" id=\"test_svg\" version=\"1.1\" width=\"480\" height=\"320\" viewBox=\"0 215 120 80\"&gt;'\n    + '&lt;g id=\"layer1\"&gt;'\n      + '&lt;text id=\"txt\" x=\"55\" y=\"225\" font-family=\"sans-serif\" font-size=\"8\"&gt;'\n        + '&lt;tspan id=\"tspan1\"&gt;A star group&lt;\/tspan&gt;'\n        + '&lt;tspan id=\"tspan2\" x=\"55\" dy=\"1em\"&gt;an ellipse&lt;\/tspan&gt;'\n        + '&lt;tspan id=\"tspan3\" x=\"55\" dy=\"1em\"&gt;and some text&lt;\/tspan&gt;'\n      + '&lt;\/text&gt;'\n      + '&lt;ellipse id=\"ellipse\" transform=\"rotate(25)\" cx=\"200\" cy=\"215\" rx=\"25\" ry=\"7.5\" fill=\"#00ff00\" stroke=\"#000000\" stroke-width=\"0.75\" \/&gt;'\n      + '&lt;g id=\"star_group\"&gt;'\n       + ' &lt;path id=\"star1\" fill=\"#ff2a2a\" stroke=\"#000000\" stroke-width=\"0.75\"'\n          + ' d=\"m 41.010416,262.03718 -4.104352,-0.60791 -3.016279,2.84909 -0.690161,-4.09132 -3.641728,-1.98824 3.677809,-1.92067 0.765567,-4.07789 2.963173,2.90429 4.114874,-0.53204 -1.846468,3.71562 z\" \/&gt;'\n        + '&lt;path id=\"star2\" fill=\"#ffcc00\" stroke=\"#000000\" stroke-width=\"0.75\"'\n          + ' d=\"m 25.31441,257.03553 -3.967414,-6.11754 -7.330735,-0.71095 4.676652,-5.60961 -1.579348,-7.09179 6.857743,2.6506 6.354646,-3.67203 -0.438334,7.24778 5.506734,4.82236 -7.128647,1.82876 z\" \/&gt;'\n      + '&lt;\/g&gt;'\n    + '&lt;\/g&gt;'\n  + '&lt;\/svg&gt;';\n\nprocedure TForm1.Button1Click(Sender: TObject);\nvar\n  SVGRoot: ISVGRoot;\n  sl: TStringList;\n  SVGParser: TSVGSaxParser;\n  Bitmap: TBitmap;\n  RC: ISVGRenderContext;\n  ParentBounds, Bounds: TSVGRect;\nbegin\n\n  \/\/ Step 1\n  \/\/ Create a VCL SVG Root object\n\n  SVGRoot := TSVGRootVCL.Create;\n\n\n  \/\/ Put the SVG text in a stringlist and parse\n\n  sl := TStringList.Create;\n  SVGParser := TSVGSaxParser.Create(nil);\n  try\n    sl.Text := svg_text; \n\n    SVGParser.Parse(sl, SVGRoot);\n\n  finally\n    SVGParser.Free;\n    sl.Free;\n  end;\n\n\n  \/\/..\n\nend;\n<\/pre>\n<p><strong>Step 2, calculate the size of the entire SVG and set the bitmap size accordingly<\/strong><\/p>\n<p>Next we want to know the dimensions of the SVG graphic. In this case it is not very difficult because the svg element has a with (480) and a height(320) defined. But for this example I&#8217;ll show how to calculate the size of an arbitrary SVG graphic.<\/p>\n<p>The ISVGRoot interface has a method &#8220;CalcIntrinsicSize&#8221;\u00a0 for calculating the outer dimensions of an SVG graphic.<\/p>\n<p>This method is defined as follows:<\/p>\n<pre class=\"lang:delphi decode:true\"> function CalcIntrinsicSize(aRenderContext: ISVGRenderContext; const aParentBounds: TSVGRect): TSVGRect;\n\n<\/pre>\n<p>So it needs a render context and the bounds of a parent object, this is the outer container. The function returns a rectangle with the dimensions of the SVG graphic.<\/p>\n<p>Why does it need a render context? Because the SVG graphic may contain text and it needs a render context to have access to the font system.<\/p>\n<p>The aParentBounds is used in cases where the SVG size is a percentage of it&#8217;s parent or container object.<\/p>\n<p>So to use this we need to create a render context first. Since we don&#8217;t have to actually render the SVG yet, we can create a render context based on an arbitrary sized bitmap, so we take a bitmap of size 1 by 1.<\/p>\n<pre class=\"lang:delphi decode:true\">procedure TForm1.Button1Click(Sender: TObject);\nvar\n  SVGRoot: ISVGRoot;\n  sl: TStringList;\n  SVGParser: TSVGSaxParser;\n  Bitmap: TBitmap;\n  RC: ISVGRenderContext;\n  ParentBounds, Bounds: TSVGRect;\nbegin\n  \n  \/\/ Step 1\n  \/\/..\n\n  \/\/ Step 2\n  \/\/ Create a bitmap, we don't know how big it needs to be, so initially\n  \/\/ we make it the minimum size: 1 by 1\n\n  Bitmap := TBitmap.Create;\n  try\n\n    Bitmap.SetSize(1, 1);\n\n    \/\/ Now create a render context for the bitmap\n\n    RC := TSVGRenderContextManager.CreateRenderContextBitmap(Bitmap);\n\n    \/\/ Define some parent bounds, in case the SVG graphic has dimensions in percentages\n\n    ParentBounds := SVGRect(0, 0, Image1.Width, Image1.Height);\n\n    \/\/ Now we can calculate the dimensions of the SVG graphic\n\n    Bounds := SVGRoot.CalcIntrinsicSize(RC, ParentBounds);\n\n    \/\/ Resize the bitmap and make it 32bit transparent\n\n    Bitmap.SetSize(Round(Bounds.Width), Round(Bounds.Height));\n    Bitmap.PixelFormat := TPixelFormat.pf32bit;\n    Bitmap.AlphaFormat := TAlphaFormat.afPremultiplied;\n\n    \/\/ Recreate the render context with the newly sized Bitmap!!\n\n    RC := TSVGRenderContextManager.CreateRenderContextBitmap(Bitmap);\n\n    \/\/..\n\n  finally\n    Bitmap.Free;\n  end;\nend;\n<\/pre>\n<p><strong>Step 3, render the SVG to the bitmap<\/strong><\/p>\n<p>Now we are going to render the SVG to the bitmap using ISVGRoot and ISVGRenderContext.<\/p>\n<pre class=\"lang:delphi decode:true \">procedure TForm1.Button1Click(Sender: TObject);\nvar\n  SVGRoot: ISVGRoot;\n  sl: TStringList;\n  SVGParser: TSVGSaxParser;\n  Bitmap: TBitmap;\n  RC: ISVGRenderContext;\n  ParentBounds, Bounds: TSVGRect;\nbegin\n  \/\/ Step 1\n  \/\/..\n\n  \/\/ Step 2\n  \/\/..\n  \n    \/\/ Step 3\n    \/\/ Draw the SVG on the bitmap\n\n    RC.BeginScene;\n    try\n      RC.Clear(0); \/\/ Clear the bitmap with transparent color\n      RC.Matrix := TSVGMatrix.CreateIdentity;\n\n      SVGRenderToRenderContext(\n        SVGRoot,\n        RC,\n        Bounds.Width* 2,\n        Bounds.Height,\n        [sroEvents],   \/\/ Force the creation of an object state tree\n        FALSE          \/\/ No auto scaling in this case\n        );\n\n    finally\n      RC.EndScene;\n    end;\n\n    \/\/ ..\n\n  finally\n    Bitmap.Free;\n  end;\nend;<\/pre>\n<p>So just as with drawing on a Firemonkey canvas, we need to enclose any drawing with BeginScene and EndScene commands. We could also modify the matrix of the render context to scale or rotate or translate the SVG graphic.<\/p>\n<p>The global procedure &#8220;SVGRenderToRenderContext&#8221; is used to draw the SVG contained in &#8220;SVGRoot&#8221; to the render context &#8220;RC&#8221;.<\/p>\n<p>This procedure is defined as follows:<\/p>\n<pre class=\"lang:delphi decode:true \">procedure SVGRenderToRenderContext(\n  aSVGRoot: ISVGRoot;\n  aRenderContext: ISVGRenderContext;\n  const aWidth, aHeight: TSVGFloat;\n  const aRenderOptions: TSVGRenderOptions = [sroFilters, sroClippath];\n  const aAutoViewBox: boolean = True;\n  const aAspectRatioAlign: TSVGAspectRatioAlign = arXMidYMid;\n  const aAspectRatioMeetOrSlice: TSVGAspectRatioMeetOrSlice = arMeet);\n<\/pre>\n<p>The parameter &#8220;aRenderOptions&#8221; can have a combination of the following settings:<\/p>\n<ul>\n<li>sroFilters: SVG filters will be rendered if these are defined in the SVG graphic<\/li>\n<li>sroClippath: clippaths will be rendered if these are defined in the SVG graphic<\/li>\n<li>sroEvents: mouse pointer events will be enabled for the SVG graphic, for this a Object state tree will be generated<\/li>\n<\/ul>\n<p>Because filters, clippaths en events need extra, in some cases a lot of\u00a0resources, they are made optional.<\/p>\n<p>The last option &#8220;sroEvents&#8221; is interesting if we want to have measurements of individual elements in the SVG graphic. If we want to enable mouse pointer events, we need to know the dimensions of each visible element. The renderer will in that case create a so called &#8220;ObjectState&#8221; tree , while rendering the SVG graphic.<\/p>\n<p>The &#8220;ObjectState&#8221; tree will contain a ScreenBBox (bounding box in screen dimensions) of every element visible on the screen. Note that sometimes an element can be drawn multiple times on the screen, if it is referenced by a &#8220;use&#8221; element. In that case it will be present more than once in the Object State tree.<\/p>\n<p>So we will use the &#8220;ObjectState&#8221; tree to draw outlines around elements on the rendered SVG.<\/p>\n<p><strong>Step 4, draw rectangles around the three elements and step 5, assign bitmap to TImage control<\/strong><\/p>\n<pre class=\"lang:delphi decode:true\">procedure TForm1.Button1Click(Sender: TObject);\nvar\n  SVGRoot: ISVGRoot;\n  sl: TStringList;\n  SVGParser: TSVGSaxParser;\n  Bitmap: TBitmap;\n  RC: ISVGRenderContext;\n  ParentBounds, Bounds: TSVGRect;\n  StateRoot, State: ISVGObjectState;\n  Stroke: TSVGBrush;\n\n  function FindID(aParent: ISVGObjectState; const aID: string): ISVGObjectState;\n  var\n    Child: ISVGObjectState;\n  begin\n    \/\/ Find first occurence of aID in object state tree\n\n    for Child in aParent.Children do\n    begin\n      if Child.SVGObject.ID = aID then\n      begin\n        Result := Child;\n        Exit;\n      end else\n        Result := FindID(Child, aID);\n    end;\n  end;\n\nbegin\n  \/\/ Step 1\n  \/\/ ..\n\n\n  \/\/ Step 2\n  \/\/ ..\n\n    \/\/ Step 3\n    \/\/ Draw the SVG on the bitmap\n\n    RC.BeginScene;\n    try\n      RC.Clear(0); \/\/ Clear the bitmap with transparent color\n      RC.Matrix := TSVGMatrix.CreateIdentity;\n\n      SVGRenderToRenderContext(\n        SVGRoot,\n        RC,\n        Bounds.Width* 2,\n        Bounds.Height,\n        [sroEvents],   \/\/ Force the creation of an object state tree\n        FALSE          \/\/ No auto scaling in this case\n        );\n\n      \/\/ Step 4\n      \/\/ Create a brush for stroking the outline\n\n      \/\/ Get a reference to the object state tree\n\n      StateRoot := SVGRoot.SVG.ObjectStateRoot;\n\n      \/\/ Find the text element on ID and return the screen bounding box\n\n      State := FindID(StateRoot, 'txt');\n      if assigned(State) then\n      begin\n        \/\/ Get the screen bounds of the element\n\n        Bounds := State.ScreenBBox;\n\n        \/\/ Grow the box a bit\n\n        Bounds.Inflate(5, 5);\n\n        RC.ApplyStroke(TSVGSolidBrush.Create(SVGColorRed), 2.0);\n\n        \/\/ Draw a rectangle with rounded corners around the element\n\n        RC.DrawRect(Bounds, 5, 5);\n      end;\n\n      \/\/ Find the star group element on ID and return the screen bounding box\n\n      State := FindID(StateRoot, 'star_group');\n      if assigned(State) then\n      begin\n        Bounds := State.ScreenBBox;\n        Bounds.Inflate(5, 5);\n\n        RC.ApplyStroke(TSVGSolidBrush.Create(SVGColorBlue), 2.0);\n\n        RC.DrawRect(Bounds, 5, 5);\n      end;\n\n      \/\/ Find the ellipse element on ID and return the screen bounding box\n\n      State := FindID(StateRoot, 'ellipse');\n      if assigned(State) then\n      begin\n        Bounds := State.ScreenBBox;\n        Bounds.Inflate(5, 5);\n\n        RC.ApplyStroke(TSVGSolidBrush.Create(SVGColorGreen), 2.0);\n\n        RC.DrawRect(Bounds, 5, 5);\n      end;\n\n    finally\n      RC.EndScene;\n    end;\n\n    \/\/ Step 5\n    \/\/ Assign to Image1\n\n    Image1.Picture.Assign(Bitmap);\n  finally\n    Bitmap.Free;\n  end;\nend;\n<\/pre>\n<p>This produces the following output on the VCL form.<\/p>\n<p><a href=\"https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg_output.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-653 aligncenter\" src=\"https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg_output-300x243.png\" alt=\"\" width=\"300\" height=\"243\" srcset=\"https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg_output-300x243.png 300w, https:\/\/www.bverhue.nl\/delphisvg\/wp-content\/uploads\/2019\/07\/Test_svg_output.png 518w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>Sources can be found on <a href=\"https:\/\/github.com\/BVerhue\/delphi-svg-control-examples\">github<\/a>. To compile the examples, you need the demo or the full version of the SVG control package.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The SVG control package offers a number of controls to display SVG graphics. But if you want more control over how SVG graphics are rendered or if you want to post process graphics, you can also use the lower level classes, interfaces and functions of the package. Two of these interfaces are ISVGRenderContext and ISVGRoot &#8230; <a title=\"Drawing outlines around SVG elements\" class=\"read-more\" href=\"https:\/\/www.bverhue.nl\/delphisvg\/2019\/07\/12\/drawing-outlines-around-svg-elements\/\" aria-label=\"Read more about Drawing outlines around SVG elements\">Read more<\/a><\/p>\n","protected":false},"author":1,"featured_media":653,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-619","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/posts\/619","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/comments?post=619"}],"version-history":[{"count":38,"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/posts\/619\/revisions"}],"predecessor-version":[{"id":841,"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/posts\/619\/revisions\/841"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/media\/653"}],"wp:attachment":[{"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/media?parent=619"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/categories?post=619"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bverhue.nl\/delphisvg\/wp-json\/wp\/v2\/tags?post=619"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}