Fun with TFNCImage and svg

I was looking for a way to easily create a simple rendering of a multi-octave piano keyboard.

If anyone is interested, here's how I filled a TFNCImage with an svg at run time...

Drop a TFNCImage on a form.
Set Anchors to Left, top, right, bottom.
Set Stretch to true, center to true.

Add a TStringList to form private section and create/free in Form create/destroy.
  FPianoKBD: TStringList;


Drop a button on the form.

Add two procedures to Form:

The first one adds a single octave to the SVG data.

procedure TForm2.AddAnOctave;
begin
  FPianoKBD.Add('<!-- White keys  -->');
  FPianoKBD.Add('<rect style="fill:white;stroke:black" x=  "0" y="0" width="23" height="120" />');
  FPianoKBD.Add('<rect style="fill:white;stroke:black" x= "23" y="0" width="23" height="120" />');
  FPianoKBD.Add('<rect style="fill:white;stroke:black" x= "46" y="0" width="23" height="120" />');
  FPianoKBD.Add('<rect style="fill:white;stroke:black" x= "69" y="0" width="23" height="120" />');
  FPianoKBD.Add('<rect style="fill:white;stroke:black" x= "92" y="0" width="23" height="120" />');
  FPianoKBD.Add('<rect style="fill:white;stroke:black" x="115" y="0" width="23" height="120" />');
  FPianoKBD.Add('<rect style="fill:white;stroke:black" x="138" y="0" width="23" height="120" />');
  FPianoKBD.Add('');
  FPianoKBD.Add('<!-- Black keys (overlap with the white keys) -->');
  FPianoKBD.Add('<rect style="fill:black;stroke:black" x="14.33333" y="0" width="13" height="80" />');
  FPianoKBD.Add('<rect style="fill:black;stroke:black" x="41.66666" y="0" width="13" height="80" />');
  FPianoKBD.Add('<rect style="fill:black;stroke:black" x="82.25" y="0" width="13" height="80" />');
  FPianoKBD.Add('<rect style="fill:black;stroke:black" x="108.25" y="0" width="13" height="80" />');
  FPianoKBD.Add('<rect style="fill:black;stroke:black" x="134.75" y="0" width="13" height="80" />');
end;



The second one adds an octave, sets a "translate" to offset x coordinates, then adds another octave at that new offset.

procedure TForm2.BuildKbdSVG;
{ original svg at
  https://commons.wikimedia.org/wiki/File:PianoKeyboard.svg
}
begin
  FPianoKBD.Clear;
  FPianoKBD.Add('');
  FPianoKBD.Add('<svg xml:space="preserve"  preserveAspectRatio="xMinYMid meet" width="483px" height="120" viewBox="0 0 483 120">');
  FPianoKBD.Add('<!--');
  FPianoKBD.Add('     Copyright (c)  2005 Lauri Kaila.');
  FPianoKBD.Add('     Permission is granted to copy, distribute and/or modify this document');
  FPianoKBD.Add('     under the terms of the GNU Free Documentation License, Version 1.2');
  FPianoKBD.Add('     or any later version published by the Free Software Foundation;');
  FPianoKBD.Add('     with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.');
  FPianoKBD.Add('     A copy of the license is included in the section entitled "GNU');
  FPianoKBD.Add('     Free Documentation License".');
  FPianoKBD.Add('');
  FPianoKBD.Add('     Intented to create a keyboard where key widths are');
  FPianoKBD.Add('     accurately in position.');
  FPianoKBD.Add('');
  FPianoKBD.Add('     See http://www.mathpages.com/home/kmath043.htm');
  FPianoKBD.Add('     for the math.');
  FPianoKBD.Add('');
  FPianoKBD.Add('     This keyboard has following properties (x=octave width).');
  FPianoKBD.Add('     1. All white keys have equal width in front (W=x/7).');
  FPianoKBD.Add('     2. All black keys have equal width (B=x/12).');
  FPianoKBD.Add('     3. The narrow part of white keys C, D and E is W - B*2/3');
  FPianoKBD.Add('     4. The narrow part of white keys F, G, A, and B is W - B*3/4');
  FPianoKBD.Add('');
  FPianoKBD.Add('-->');
  FPianoKBD.Add('');

  FPianoKBD.Add('<g id="octave-1">');
  AddAnOctave;
  FPianoKBD.Add('</g>');

  FPianoKBD.Add('<g id="octave-2" transform="translate(161.000000, 0.000000)">');
  AddAnOctave;
  FPianoKBD.Add('</g>');

  FPianoKBD.Add('<g id="octave-3"    transform="translate(322.000000, 0.000000)">');
  AddAnOctave;
  FPianoKBD.Add('</g>');

  FPianoKBD.Add('');
  FPianoKBD.Add('</svg>');
end;



And the TButton OnClick builds the svg, saves it to a MemoryStream, then loads the MemoryStream into the TMSFNCImage.



procedure TForm2.btnGenerateClick(Sender: TObject);
var
  ms: TMemoryStream;
begin
  BuildKbdSVG; // clear stringlist and add svg definition
  ms:=TMemoryStream.Create;
  try
    FPianoKBD.SaveToStream(ms); // copy svg content in stringlitst to stream
    ms.Position:=0;
    TMSFNCImage2.Bitmap.LoadFromStream(ms);
  finally
    ms.Free;
  end;
end;



This is my first work with SVG - so there may be somewhat more clever ways of preparing the actual svg data - but hopefully this saves someone time...



Cheers,
EdB

Hi,


Thank you very much for you sample! It looks great and shows the capabilities of SVG rendering. We have added a LoadFromText method that allows you to directly add SVG text instead of saving it to a stream first:



procedure TForm1.Button1Click(Sender: TObject);
begin
  BuildKbdSVG; // clear stringlist and add svg definition
  TMSFNCImage1.Bitmap.LoadFromText(FPianoKBD.Text);
end;

Nice!

(and thanks)

One question, is there any plan to add even rudimentary support for text?

It would be useful to be able to set the default font for the TTMSFNCImage, then do something like:

FPianoKBD.Add('<text x="02.945" y="114.10">C1</text>');



Cheers,
EdB

Hi,


Yes, This is being worked at and will be available in the next release, albeit very very rudimentary. Text is very complex, but we'll add initial support, and work on that in the future.



Pieter Scheldeman2020-05-15 10:29:31

Hi,

I've tried with the latest release of FNC Core this example because I need to act of the colors of part of a map at runtime but :

  • for me, the example does not work anymore. Nothing happen when I press the button
  • I don't see the loadFromText method that has been added but only the loadFromStream

Can someone checks if it works ?

Thanks

Hi,

You can use the following code:

procedure TForm1.LoadSVG;
var
  svg: TTMSFNCGraphicsSVGImport;
  ms: TMemoryStream;
begin
  FPianoKBD := TStringList.Create;
  BuildKbdSVG; // clear stringlist and add svg definition
  svg := TTMSFNCGraphicsSVGImport.Create;
  ms := TMemoryStream.Create;
  try
    svg.LoadFromText(FPianoKBD.Text);
    svg.SaveToStream(ms);
    ms.Position := 0;
    TMSFNCImage1.Bitmap.LoadFromStream(ms);
  finally
    ms.Free;
    svg.Free;
  end;
end;

Thanks for th quick answer.

I still have a problem : the TTMSFNCGraphicsSVGImport seems to be unknown.
I've try to use :
VCL.TMSFNCTypes,
VCL.TMSFNCGraphics,
VCL.TMSFNCGraphicsPDFEngine, // not recognized
WEBLib.TMSFNCGraphics,
WEBLib.TMSFNCTypes;

without success. Can you tell me where it's defined ?

Thanks

Hi,

It's included in VCL.TMSFNCGraphicsSVGEngine unit.

As soon as I put VCL.TMSFNCGraphicsSVGEngine, the compiler stops requesting also
the WEBLib..TMSFNCGraphicsSVGEngine that I don't have ...
Sorry for this continuous requests

Are you targeting TMS WEB Core? There is out of the box SVG support in TMS WEB Core, so this engine is only for VCL, LCL and FMX. You should be able to define your text inside an XML file and then load the XML with a LoadFromFile directly on the image.

Yes I'm targeting TMS WEB Core, but I don't want to receive the file from the server as I intend to modify the colors of the Italian regions according to some values. I want to be able to do it client side having the Italian map as a svg template and according to the context modify it before display (as it is shown in the first example of this topic with the piano keyboard)

You should be able to create in the client-side your SVG (as XML text code) and create a data URI to set for a TWebImageControl to have it rendered.

Data URI will look like:
WebImageControl.URL := 'data:image/svg+xml;base64,' + base64 encoded XML SVG text data

With the function btoa(), you can base64 encode your XML SVG text data

Hello,
is it possible change via code the properties of any "object" in a already rendered SVG image ?
I mean change at runtime, for example the fill-color or visibility of a single shape present in a schema.

Like I see in TMS Software | Blog
but for single objects inside the image, instead on full SVG image...

No currently that is not possible. We will write this down for investigation.