EtheaDev / SVGIconImageList

Three engines to render SVG (Delphi Image32, Skia4Delphi, Direct2D wrapper) and four components to simplify use of SVG images (resize, fixedcolor, grayscale...)
Apache License 2.0
327 stars 96 forks source link

Use Image32 as the back-end #179

Closed khongten001 closed 3 years ago

khongten001 commented 3 years ago

Hi,

angusj has created great library that support rendering SVG files. Please see https://sourceforge.net/projects/image32/

Could it be used as another back-end for rendering SVG files?

carloBarazzetta commented 3 years ago

Hi, reading the instructions here http://www.angusj.com/delphi/image32/Docs/_Body.htm it seems to not support SVG format of an image but only a subset of feature: Have you tried to paint some SVG images with this library? Can you send me an example?

khongten001 commented 3 years ago

Hi,

There is an example with some svg files for testing here: https://sourceforge.net/p/image32/code/ci/master/tree/Examples/SVG/

Thank you for taking a look!

carloBarazzetta commented 3 years ago

Downloaded now, compiling package VCL_Image32, problem at line, error in function InflatePaths of Image32_Clipper fixed. Compiled demo SVG101 but most of the files contained into Example\SVG folder are not properly rendered... Am I doing something wrong? image

pyscripter commented 3 years ago

Svg support was recently added to Image32 and it looks like it is work in progress. If/when it matures it would be a good addition.

AngusJohnson commented 3 years ago

Hi Carlo.

You wrote... Compiled demo SVG101 but most of the files contained into Example\SVG folder are not properly rendered...

I'm the author of Image32 and I test compiled these sample applications using Delphi 10.3 and also Delphi 7 without problems and all the supplied images rendered as expected. So I'd be very interested to know about any problems you're encountering.

But I do understand your comment was made a couple of weeks ago now, which was before my latest update (and that was a little over a week ago), so whatever was problematic may have been fixed. Although the Image32 SVG reader has undergone many changes over the last couple of months, I think I've pretty much finished everything I plan to do there, at least for the time being (except for fixing any bugs and repackaging whatever I've already uploaded to SourceForge's repository). And if i do decide to do more with SVG in the short term, it'll be to the writer as it's really only just barely useful in its current form.

Cheers.

Edit: More info here: https://sourceforge.net/p/image32/discussion/general/thread/2801d939b5/ Evidently there a significant problems with my SVG code when compiled using Delphi 10.4

AngusJohnson commented 3 years ago

Compiled demo SVG101 but most of the files contained into Example\SVG folder are not properly rendered... This problem was due to incorrectly localizing the DecimalSeparator and has now been fixed (version 2.25). https://sourceforge.net/projects/image32/

carloBarazzetta commented 3 years ago

Very good JOB! I'm working to try to add your engine into my librarary... stay tuned!

carloBarazzetta commented 3 years ago

I've added your library and created the Interface file: Image32SVGFactory.pas At the moment the interface don't implement any specific option like GrayScale, Opacity, FixedColor, ApplytoRootColor... can you add those features?

I've found a problem using function TSvgParser.LoadFromString(const str: string): Boolean; it seems to unable to convert the svg string to the memory stream so I've made a change using a TStringStream: now works correctly.

If you want to test your library, you must change the SVGIconImageList.inc

I've also added your lib to the benchmark project and it seems to be the fastest library (very fast)!

AngusJohnson commented 3 years ago

Hi again Carlo. Firstly I'm delighted that Image32 is now working in SVGIconImageList.

The benchmarks do look interesting too, though I'm not sure what those numbers mean - are they time units or occurrences or something else? Also, in your TImage32SVG.PaintTo there's a call to fImage.SaveToFile which won't help those benchmarks :).

And another thing, when I try to compile either the demo or the benchmark I get the following error:

[Exec Error] The command "powershell.exe Expand-Archive -Force ..\..\Cairo\Dlls\librsvg-Win32.zip ..\Bin\
" exited with code 9009.

I'm still investigating the logic of your SetOpacity, SetGrayScale and SetFixedColor methods. I initially assumed this just affected the already rendered image (which would make things very easy with my library. However, when I grayscaled the rendered image nothing happened, so on closer inspection it appears you're modifying the stored svg elements. This is somewhat problematic for my library as I haven't yet integrated the SVG reader with the writer so there's no quick and easy way to provide those features.

Finally, making Image32 the default library causes major problems, at least for your demo. In developing my SVG reader, I didn't envisige the TSvgReader class being constructed very many times concurrently, as happens in your demo. This is problematic since your TImage32SVG constructor loads 9 font libraries for every new object, and this appears to be overloading memory. The short term fix it to install just one font in the TImage32SVG constructor. Nevertheless, the long term solution will be for me to create a font library that's independant of TSvgReader yet accessable to any number of TSvgReader objects. (I hope that makes sense.)

Edit: I've just now updated Image32 in the SourceForge repository: https://sourceforge.net/p/image32/code/ci/master/tree/ . I've added in a new FontLibrary to manage TFontReader objects and avoid creating multiple instances of the same font object. And of course, this improves performance too :).

luebbe commented 3 years ago

The benchmarks do look interesting too, though I'm not sure what those numbers mean - are they time units or occurrences or something else?

The numbers are the milliseconds needed to perform the checked operations loop number of times on the loaded image using all factories. The checked factory at the top right only selects the factory for loading and displaying the image. So you can see if everything zooms and paints as expected when you resize the window.

And another thing, when I try to compile either the demo or the benchmark I get the following error:

[Exec Error] The command "powershell.exe Expand-Archive -Force ..\..\Cairo\Dlls\librsvg-Win32.zip ..\Bin\
" exited with code 9009.

The cairo wrapper needs the cairo dlls which are included in the repository. This is a post build event which uses powershell to unzip the dlls. The error 9009 doesn't mean anything to me. Assuming that you have checked out everything, the zipped dlls should be there. Maybe the ..\bin folder wasn't created yet?

carloBarazzetta commented 3 years ago

Sorry for the call to SaveToFile, it was only for debug... The problem with powershell is not important, is useful to copy alle the .dll of Cairo library to Bin folder... Thank you for the update to the font libraries, I'm updating...

carloBarazzetta commented 3 years ago

Ok, now I've fixed all the units according to the last version of image32, removed wrong savetofile. At the moment the interface don't load any font library. Now the performance are very, very fast with your library, and many svg options that don't work with TSVG and Direct 2D now work very well: blur, gradient, merge, drop-shadow, markers, simbol, pattern...

Here my benchmark using Cowboy drawed 50 times:

Factory    |  Load  |  Draw  |  Total
Pascal     |    937 |  21656 |  22593
Direct 2D  |    766 |    391 |   1157
Cairo      |   1750 |   1672 |   3422
Image32    |    953 |    515 |   1468
pyscripter commented 3 years ago

Factory | Load | Draw | Total Pascal | 937 | 21656 | 22593

The above is misleading. TSvg is the fastest in drawing if you uncheck Draw Visible. The other libraries do double buffering but TSvg does not, so the comparison is not fair.

Which are the Svg elements and attributes that are supported by Image32 but not TSvg?

It would be great if Image32 supported the Opacity, Greyscale etc options.

carloBarazzetta commented 3 years ago

Benchmark without "visible":

Benchmark: Repeat 50 times. Draw invisible.
Factory    |  Load  |  Draw  |  Total
Pascal     |    906 |    125 |   1031
Direct 2D  |    750 |    297 |   1047
Cairo      |   1735 |    672 |   2407
Image32    |    797 |    297 |   1094

I've added more example/test files into Demo\svg_examples so you can see that many features not supported by TSvg are supported by Image32, like blur, gradient, merge, drop-shadow, markers, simbol, pattern... At rendering quality I've notice that the circles made by Image32 are not perfect, like in this example: image

AngusJohnson commented 3 years ago

At rendering quality I've notice that the circles made by Image32 are not perfect, like in this example:

Yes, I've noticed that too. For some reason (at least in you're benchmark app), you're drawing the image twice, with the first time the image size at 32x32px. And the second time, a much larger image is drawn. However, with this first draw of the very small image, Image32's reader determines the most efficient precision for curve rendering (flattening) at that size (so there aren't too many vertices that only degrade performance without adding to quality). Unfortunately, these vertices are remembered for subsequent draws which in this case happens at a much larger resolution, hence the poor curve quality seen. Anyhow, the good news is this won't be too hard to fix.

carloBarazzetta commented 3 years ago

Angus, would you like to collaborate directly on this project? I would like it very much. I can add you as a collaborator.

AngusJohnson commented 3 years ago

carlo, thanks for the offer, but while I'd like to help, I'm really trying to get back to another project (Clipper) that's been badly neglected for too long. Image32 has been a fun diversion while I took a break from Clipper, but version 2 has been stuck in the pipeline for 4-5 years.

AngusJohnson commented 3 years ago

It would be great if Image32 supported the Opacity, Greyscale etc options.

Image32 does support these features, but evidently not the way SVGIconImageList currently does this. http://www.angusj.com/delphi/image32/Docs/Units/Image32/Classes/TImage32/Methods/Grayscale.htm http://www.angusj.com/delphi/image32/Docs/Units/Image32/Classes/TImage32/Methods/SetRGB.htm http://www.angusj.com/delphi/image32/Docs/Units/Image32/Classes/TImage32/Methods/ReduceOpacity.htm

pyscripter commented 3 years ago

Image32 does support these features, but evidently not the way SVGIconImageList currently does this.

The key here is that when say you use the Grayscale, to be able to go back without having to reload the Svg.

Also a major limitation of TSvg is that it does not support sub-pixel rendering (a limitation of GDI+) A line with a stroke of 0.5 is the same as a line with a stroke of 1. Other factories (D2D and Cairo) handle this much better.
Another limitation of TSvg is the handling of text.

How does Image32 scores in these two areas?

luebbe commented 3 years ago

When I load the butterfly into the benchmark and switch between the four factories, I see that TSvg, D2D and Cairo stretch it in both directions without keeping the aspect ratio and Image32 keeps the aspect ratio. I think the behaviour should be identical in all four cases. Should I add a "stretch" checkbox to the benchmark app?

carloBarazzetta commented 3 years ago
carloBarazzetta commented 3 years ago

A question to @AngusJohnson : I've notice that Image32 is compatible also with FMX library, but only for Windows platform or for any platform: Android and iOS?

AngusJohnson commented 3 years ago

The key here is that when say you use the Grayscale, to be able to go back without having to reload the Svg. Yes, that would be very inefficient. However, Image32 separates the parsing of the SVG xml from image rendering, so it's easy to re-render the stored tree of data objects that contain the SVG element instructions.

Also a major limitation of TSvg is that it does not support sub-pixel rendering (a limitation of GDI+) A line with a stroke of 0.5 is the same as a line with a stroke of 1. ... How does Image32 scores in these two areas? WRT sub-pixel rendering, yes Image32 does support this: http://www.angusj.com/delphi/image32/Docs/Units/Image32_Draw/Routines/DrawPolygon_ClearType.htm Nevertheless, IMHO sub-pixel rendering is no longer widely useful. It was very useful when most monitors were 96ppi. At that resolution smallish text looked very blurry. But most monitors bought today have significantly higher resolutions (the laptop I'm typing on now has 267ppi), and blurry text is no longer an issue. Having said that, it's not really possible to draw lines thinner than 1px (though subpixel rendering comes close). What most graphics libraries do (including Image32), is make sub-1px lines proportionally lighter.

Another way to accommodate blurry text is for font libraries to include instructions that adjust glyph contours depending on size, and especially with smaller sizes, to (somewhat simplistically) maximize black and white pixels, and minimize gray ones. And most truetype/opentype fonts contain these instructions. But this too has become much less of an issue with higher resolution monitors, and I suggest it won't be long before font creators give up on that very tricky part of font design. (And no, Image32 doesn't use these instructions when drawing text. IMHO, it's not worth the very considerable effort.)

I've notice that Image32 is compatible also with FMX library, but only for Windows platform or for any platform: Android and iOS? Yes, Image32 has been designed to be fully cross-platform (including Android and iOS). There are a few conditional defines in the library's code which add (a very few) additional features when compiling for Windows. There are also 2 sample applications that use the FMX framework that compile cross-platform (though I've only tested these on my Android phone). There's a screen capture (from my Android phone) of one of these apps at the bottom of the page in the following link: http://www.angusj.com/delphi/image32/Docs/Examples.htm (Note: the blurriness of the text in that image is a function of image scaling, so it really doesn't do justice to the crispness of the text in the app itself.)

AngusJohnson commented 3 years ago

And a suggestion for the updated Benchmark app to add text support for the Image32 library:

  1. Add Image32_Ttf to the uses clause
  2. Add FontLibrary.Add('Arial'); into TImage32SVG's constructor (+/- other fonts).

Edit: And I've just updated Image32_SVG_Reader.pas to fix the problem of poor curve rendering (see above). https://sourceforge.net/p/image32/code/ci/master/tree/source/Image32_SVG/

pyscripter commented 3 years ago

@AngusJohnson With sub-pixel drawing I was referring to shapes not text. Say for instance you have an svg:

<svg viewBox="0 0 16 16" >
  <circle cx="8" cy="8" r="5" stroke="green"
          stroke-width="0.5" />
</svg>

The line will look much thicker with TSvg than D2D or Cairo. Same if stroke-width="1.5". You get a lot of fractional values with svgs designed with Adobe Illustrator or Inkscape. And there are still many (desktop) monitors, even modern ones with 96 ppi.

AngusJohnson commented 3 years ago

Try this with the various libraries:

<svg viewBox="0 0 32 16"  xmlns="http://www.w3.org/2000/svg">
<def>
  <circle id="circ" cx="8" cy="8" r="5" fill="none"/>
</def>
  <use href="#circ"   stroke="green" stroke-width="0.25"/>
  <use href="#circ" transform="translate(16)"   stroke="green" stroke-width="1.0"/>
</svg>
carloBarazzetta commented 3 years ago

I've added this test into Demo\svg_examples\subpixel.svg. I've fixed circle drawing and font like suggested by @AngusJohnson Now we can use a simple Viewer to compare the four engines rendering quality and support: you can find it into Demo\SvgViewer folder. For example a couple of comparisons: image image

carloBarazzetta commented 3 years ago

Now I'm working to update the project documentation to include the Image32 engine as a new and complete engine written in native Delphi code. Thaks a lot to @AngusJohnson for the great Job! The only functionality not yet implemented is the options to applyfixedcolortorootonly, used by @pyscripter in the icons of PyScripter project, like the Pyton.svg file committed into Demo\svg_examples. https://github.com/EtheaDev/SVGIconImageList/issues/161 if we could implement this feature, my opinion is to use Image32 library as the default library (also for the packages installed into Delphi) because it seems to be the most complete library: what do you think?

carloBarazzetta commented 3 years ago

During some test/benchmark I've notice that adding FontLibrary.Add('Arial') every time the TImage32SVG is created reduce the performances during loading of thousands icons... we must considering a more flexible approach to add a font to the library... Now I've removed FontLibrary.Add

pyscripter commented 3 years ago

The only functionality not yet implemented is the options to applyfixedcolortorootonly

Currently Image32 hides completely the svg representation. Somehow, one needs to have access to the TSvgReader.RootElement to be able to manipulate the svg element tree on the fly. This is what some of the other factories do.

Maybe TImageFormat_SVG should expose that RootElement.

Just a reminder about applyfixedcolortorootonly. It changes the FillColor and the StrokeColor (if not none) only at the Root element. This means that only elements that inherit the fill and and stroke from the root are affected. All other colors stay the same. This allows some very interesting recoloring of svgs. Also it is a lot more efficient for recoloring monochrome svgs, where the fill color is inherited from the root.

AngusJohnson commented 3 years ago

I've notice that adding FontLibrary.Add('Arial') every time the TImage32SVG is created reduce the performances

Yes, that wasn't a good place to do that. I didn't realise TImage32SVG was being constructed so frequently. This could safely be done pretty much anywhere (eg in TfrmBenchmark.FormCreate or even in the initialisation section).

Maybe TImageFormat_SVG should expose that RootElement.

That's easily done. Edit: I've just checked and it's already exposed as a public property.

pyscripter commented 3 years ago

I've just checked and it's already exposed as a public property.

I see. Then all you need is to implement applyfixedcolortorootonly is to change FsvgReader.RootElement.fDrawInfo. Unfortunately there is no public access to this property.

By the way I see this code at the bottom of TSvgReader.DrawImage:

  fTempImage := TImage32.Create(img.Width, img.Height);
  try
    fRootElement.Draw(img, di);
  finally
    fTempImage.Free;
  end;

What is the purpose of fTempImage? It does not seem to be used.

AngusJohnson commented 3 years ago

Unfortunately there is no public access to this property.

No, not currently. I'll need to check that changing the DrawImage record will produce the desired effect.

What is the purpose of fTempImage? It does not seem to be used.

It's definitely used (via the protected TempImage property). And it's used when drawing stuff that can't safely be drawn directly onto the final image (eg when shape elements need clipping). It's more efficient to construct a single temp image, than to do this every time one's needed. Even so, occasionally a second temp image is required.

Also, I've just updated Image32 to fix a couple of minor bugs parsing <path> elements: https://sourceforge.net/p/image32/code/ci/master/tree/source/Image32_SVG/

AngusJohnson commented 3 years ago

OK, I've just updated Image32 again to enable changing the root element's colors (both fill and stroke). FsvgReader.RootElement.SetFillColor($40FF0000); FsvgReader.RootElement.SetStrokeColor($FF660000);

https://sourceforge.net/p/image32/code/ci/master/tree/source/Image32_SVG/

carloBarazzetta commented 3 years ago

I've imported the last version, and updated all packages from D10.1 to D10.4 to include new SVGImage32Package runtime package. I don't understand how to implement applyfixedcolortorootonly because I'm using SetRGB to apply fixed color but only to Image32, after svgrender.DrawImage... if I use FsvgReader.RootElement.SetFillColor(FixedColor); I cannot obtain the monocrome image calling DrawImage... or I'm doing something wrong...

Please @AngusJohnson take a look to TImage32SVG.PaintTo procedure: now it supports centering image in the output region, as the other engines and painting, but I don't know if I've made the correct calls to your library.

carloBarazzetta commented 3 years ago

Another little problem: using SVGIconImageListDemo When I open the component editor and dialog do "add" an image from disk, when I select different images from the folder the preview panel is not cleared before preview the new image, so the effect is this: image Probably into TImage32SVG.PaintTo the input DC is not clean... I don't know if it's correct that this method must clean the surface before painting to: it appears that the other engines do it.

pyscripter commented 3 years ago

You probably need to call Clear on the TImage32 before you paint.

carloBarazzetta commented 3 years ago

You probably need to call Clear on the TImage32 before you paint.

Already done, nothing appens...

AngusJohnson commented 3 years ago

3,20am here so I'll be brief 😜, ... you can only 'clear' the destination tcanvas by using FillRect.

AngusJohnson commented 3 years ago

Please @AngusJohnson take a look to TImage32SVG.PaintTo procedure: now it supports centering image in the output region, as the other engines and painting, but I don't know if I've made the correct calls to your library.

Edit: Try this one ... Image32SVGFactory_ver2.zip

AngusJohnson commented 3 years ago

... you can only 'clear' the destination tcanvas by using FillRect.

This is a bit of a hack, but it works 😁...

type
  TOpenPictureDialogSvg = class(TOpenPictureDialog)
  protected
    procedure DoSelectionChange; override;
  public
    function Execute(ParentWnd: HWND): Boolean; override;
  end;
procedure TOpenPictureDialogSvg.DoSelectionChange;
begin
  ImageCtrl.Picture := nil;
  inherited;
end;

Edit 2: Much simpler 😁.

carloBarazzetta commented 3 years ago

Please @AngusJohnson take a look to TImage32SVG.PaintTo procedure: now it supports centering image in the output region, as the other engines and painting, but I don't know if I've made the correct calls to your library.

Edit: Try this one ... Image32SVGFactory_ver2.zip

Updated but don't work... I'm testing by the demo using Pyton.svg: this is the result expected without fixedcolor, with fixedcolor and with applytorootonly (compiled with TSVG): image Notice that wifi, wikipedia and workflow, when applying on root only don't paint anyting by Red, monocrome paint all the icon, pyton only the "root" color.

carloBarazzetta commented 3 years ago

Another little problem: Image32 has very full SVG support but is unable to render the official SVG logo :-( image

carloBarazzetta commented 3 years ago

Warning: I've moved svg samples folders (Demo\NewSydneyVectors, Demo\Papirus-icons, Demo\flat-color-icons) inside Demo\svg_examples

AngusJohnson commented 3 years ago

Another little problem: Image32 has very full SVG support but is unable to render the official SVG logo :-( LOL 😱. However, I can see why. I've blocked nested <use> elements because I considered them too great a risk of recursion. Evidently, I'm going to have to finesse that a lille more.

AngusJohnson commented 3 years ago

Updated but don't work

Oops, sorry try this instead (and remove same from what I tried in the LoadFromSource method) ...

procedure TImage32SVG.PaintTo(DC: HDC; R: TRectF; KeepAspectRatio: Boolean);
begin
  //Define Image32 output size
  FImage32.SetSize(Round(R.Width), Round(R.Height));

  ///////////////////////////////////////////////////////////////////
  if FApplyFixedColorToRootOnly and
    (FFixedColor <> TColors.SysDefault) then
    fSvgReader.RootElement.SetFillColor(Color32(FFixedColor));
  ///////////////////////////////////////////////////////////////////

  //Draw SVG image to Image32
  FsvgReader.DrawImage(FImage32, True);
pyscripter commented 3 years ago

if FApplyFixedColorToRootOnly then fSvgReader.RootElement.SetFillColor(Color32(FFixedColor));

A check is needed if the FFixedColor is not default. Also, if the stroke of the root is not None it should also be changed.

AngusJohnson commented 3 years ago
A check is needed if the FFixedColor is not default.
Also, if the stroke of the root is not None it should also be changed.

Noted. I can see the logic of reassigning the <svg> element's fill color (since it defaults to black). But it seems to me somewhat odd that someone would assign this element a stroke color too, since it defaults to none and it isn't a display element.

@carloBarazzetta I've noticed that the Benchmark doesn't correctly load UTF-8 and I think I've narrowed it down to line 176 in UBenchmark.pas ... FSvgSource := TFile.ReadAllText(OpenDialog.FileName); Evidently ReadAllText looks for BOMs and if not found assumes it ANSI text which is a reasonable normally EXCEPT for SVGs which are practically always UTF-8 (because everything designed for the web is UTF-8). I suggest using a custom function to check text encoding (eg GetXmlEncoding in Image32_SVG_Core.pas) where non-unicode is assumed to be UTF-8.

pyscripter commented 3 years ago

But it seems to me somewhat odd that someone would assign this element a stroke color too, since it defaults to none and it isn't a display element.

You reassign it only if it is not none. It could then be inherited by other display elements.

AngusJohnson commented 3 years ago
procedure TImage32SVG.PaintTo(DC: HDC; R: TRectF; KeepAspectRatio: Boolean);
begin
  //Define Image32 output size
  FImage32.SetSize(Round(R.Width), Round(R.Height));

  ///////////////////////////////////////////////////////////////////
  if FApplyFixedColorToRootOnly and
    (FFixedColor <> TColors.SysDefault) then
  begin
    fSvgReader.RootElement.SetFillColor(Color32(FFixedColor));
    with fSvgReader.RootElement.DrawData do
      if (strokeColor <> clInvalid) and (strokeColor <> clNone32) then
        fSvgReader.RootElement.SetStrokeColor(Color32(FFixedColor));
  end;
  ///////////////////////////////////////////////////////////////////

  //Draw SVG image to Image32
  FsvgReader.DrawImage(FImage32, True);

😁