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
321 stars 93 forks source link

TSVGIconImageCollection Antialiasing question #151

Closed luebbe closed 3 years ago

luebbe commented 3 years ago

Hi Folks,

I'm working on converting our application from bitmap icons to TSVG drawn icons. When I compared the results, the Icons drawn from the TSVGIconImageCollection (TSVG rendering engine), didn't look as crisp as the bitmaps. The bitmaps were pre-rendered from the same SVG files using the Cairo library :)

Here are the rendered icons in light/dark design. Left = bitmap, right = TSVG. The language icon was scaled up a bit, because the letters just became blobs with TSVG.

Icons100%

You will notice that with TSVG the lines are thinner and the background "wins". This is confirmed by looking at the icons in 5x magification: Icons500%

Here we have five columns. The fifth column shows the same icons as the fourth the only difference is that AntiAliasColor of the TSVGIconImageCollection is set to clRed. Obviously the red parts are the missing bits. Setting the AntiAliasColor to the foreground color of the icons in the collection would solve the problem in this case. But only for monochrome one color icons. What if we have colored icons?

Now my question is:

It is impossible to choose an AntiAliasColor that works for all icons in the collection unless they all have one identical color.

carloBarazzetta commented 3 years ago

I remember that AntiAliasColor is useful if you define the same background color of the control where the icons are placed because in some situations the icons are painted using "getbitmap" instead of drawing directly on a canvas, so the drawing engine cannot know the background color. If you have a GUI with "dark" background you must use a dark antialias color: why you are using clRed in your example? If you have a GUI with light background and a dark background in the same "view" I suggests to use two Collections of the same icons (eg. IconsForDarkBackground and IconsForLightBackground) and setting the AntiAliasColor.

luebbe commented 3 years ago

I'm already using two sets of icon collections for dark and light background and I'm using clRed to illustrate the problem.

The problem is that the Antialias color "eats" part of the Icon, when TSVGIconImageCollection is used. The left column in light/dark design is drawn using plain cairo on a plain canvas, the right column is drawn with TSVGIconImageCollection. The left column looks as expected, in the right column there is material missing from the icons and the column using clRed as antialiasing color shows that this material is eaten by the antialiasing. If I set the antialiasing color to the foreground color, the icons are "complete" again.

See the following two images. Drawn with TSVGIconImageCollection and AntiAliasingColor set to the foreground color of the icon: IconWithForergroundAsAntialias100% 100% IconWithForergroundAsAntialias500% 500%

In my opinion the drawing handles Antialiasing the wrong way round. It lets the user defined background color grow into the icon, which it should not. We may not need this option at all. Instead the "antialiasing" color should be the foreground color of the icon.

The left column in both designs was created with plain cairo, no image collection involved. I didn't have to choose any Antialiasing color. Only set CairoContext.AntiAlias := CAIRO_ANTIALIAS_SUBPIXEL and let Cairo render the SVG.

The same is done under the hood when the Cairo engine is used in TSVG Image collection. Since the effect is visible independent of the rendering engine, it's probably not a fault of the rendering engines.

Is the image collection doing something wrong here with the rendered Images?

luebbe commented 3 years ago

OK, now I understood something. The problem is not rendering the SVG iself, but placing the rendered SVG on the background. I was wondering why the toolbar icons didn't exhibit the problem in the same way. So I set their antialiasing color to clGreen.

The toolbar icons have an additional rounded rectangle as their background (added programatically to the SVG upon loading).

The background rectangle has green antialiasing artifacts at the corners. The foreground icon is rendered as expected on the rectangle. See here: grafik

carloBarazzetta commented 3 years ago

Correct, because you must use clGreen as AntiAliasColor if you have a clGreen background in the toolbar.

luebbe commented 3 years ago

My problem is, that all transparent parts (where the background shines through) of the icons grow into the solid parts, when AntiAliasColor is set to the color of e.g the toolbar. They eat away material. The problem doesn't occur when I place the transparent icon on a solid rectangle with the toolbar color first. Just insert a "rect" programatically into the SVG as the first item.

This visible in the three "dark design" columns: above

What I would like to have is the visual appearance of the left column without having to create a separate imagelist with matching background rect for each possible background color. That kind of negates the advantage of having SVG icons.

I don't know if this can be solved by SVGIconImageCollection.

luebbe commented 3 years ago

OK, finally I found time to install the community edition at home and create a small example that illustrates the problem.

There are always the same two icons in the image collections:

The three image collections only differ in the selected AntiAlias color. Top = default (clBtnFace), middle = clRed, bottom = clBlack. The three image lists are tied to the image collection of the same name. Toolbars from top to bottom tied to the imagelists from top to bottom.

grafik The image is cut directly from the IDE.

Expected behaviour: The three left gears should look exactly like the three on the right. Only the third one does. AntiAliasColor shouldn't be neccessary at all.

SvgIconProblem.zip

pyscripter commented 3 years ago

Just a reminder of the source of the problem.

TSVGCollection paints the SVG on a bitmap which is then stored inside the TVirtualImageList, so that it is compatible with the Windows ImageLists. At the time of rendering the color of the SVG background is not known. Anti-aliasing blends the image with the given background, hence the bitmap background color is important.

pyscripter commented 3 years ago

Here is the solution:

We need to use

    Graphics.SetCompositingMode(CompositingModeSourceCopy);
    Graphics.SetCompositingQuality(CompositingQualityHighQuality);

in

procedure TSVG.PaintTo(DC: HDC; Bounds: TGPRectF;
  Rects: PRectArray; RectCount: Integer);

But only when the ImageList bitmaps are created. When the Svg is painted directly to a control canvas, the default compositing mode is probably better. (needs testing).

With the above the AntialiasColor has no effect. I am not sure about the impact of Antialiased color on the other factories.

luebbe commented 3 years ago

Thanks for your research @pyscripter. IMO the AntialiasColor is not necessary at all, because it attacks the problem from the wrong side. The mistake is made earlier and what you write makes total sense to me. The result of a SVG with a gray background painting itself onto any surface should always be identical to painting the same SVG "without" background onto a gray surface.

pyscripter commented 3 years ago

@luebbe Does Cairo suffer from the same issue? The equivalent in Cairo is CAIRO_OPERATOR_SOURCE https://www.cairographics.org/operators/

pyscripter commented 3 years ago

I suggest the following:

luebbe commented 3 years ago

The choice of factory didn't make any difference. The left icons showed the problem, the right icons looked as expected. So the internal rendering of the SVGs seems to be identical for all factories.

carloBarazzetta commented 3 years ago

In this period I don't have much time ... @pyscripter: can you make the changes proposed and make a pull-request?

pyscripter commented 3 years ago

It just occurred to me.

Does replacing in TSVGIconItem.GetBitmap

  if TStyleManager.IsCustomStyleActive then
    Result.Canvas.Brush.Color := ColorToRGB(StyleServices.GetSystemColor(LAntiAliasColor))
  else
    Result.Canvas.Brush.Color := ColorToRGB(LAntiAliasColor);

with

Result.Canvas.Brush.Color := 0;

work well with all backgrounds?

Update: It looks like it is not working.

pyscripter commented 3 years ago

CompositingModeSourceCopy works on the svgs of SvgIconProblem but has undesirable effects on other svgs. I tried a few things but nothing worked well.

I give up. What exists now (AntiAliasedColor) is the best I can think of.

luebbe commented 3 years ago

Too bad, thanks a lot for your effort.

pyscripter commented 3 years ago

I haven't given up after all.

Please see #154

I have added a new flag:

// When IgnoreAntiAliasedColor is set it svgs paints well but not perfect on
// all backgrounds.  Works best with Delphi_SVGEngine
{.$DEFINE IgnoreAntiAliasedColor}
{$IF defined(Delphi_SVGEngine) and not defined(PreferNativeSvgSupport)}
  {$DEFINE IgnoreAntiAliasedColor}
{$IFEND}

To have perfect antialiasing you need to render on the actual background. But what I did (make bitmap transparent) works well with the pascal engine with SetCompositingQuality(CompositingQualityHighQuality) which does gamma correction. It also works OK with the other engines, but it is not activated by default.

@luebbe @carloBarazzetta Could you please test before merging?

carloBarazzetta commented 3 years ago

Thank you very much @pyscripter. I can made a test next week-end. I've already merged the changes into master branch.

luebbe commented 3 years ago

Thanks a lot @pyscripter! A lot better. The results however are still "interesting"

I added two icons from the papirus set to the "black" image collection and tool bars with different icon sizes. Here's another magnified view. Left IDE design time, right run time. The design time doesn't know about "ignoreantialiascolor", because I haven't reinstalled the component. So we have old vs. new next to each other. The change seems to affect the internal drawing of the TSVG icon as well, because the right gears look different in old vs. new. I wouldn't have expected that.

grafik

pyscripter commented 3 years ago

The change seems to affect the internal drawing of the TSVG icon as well, because the right gears look different in old vs. new. I wouldn't have expected that.

Different doesn't mean worse. Svgs are now painted with

Graphics.SetCompositingQuality(CompositingQualityHighQuality)

in all cases. This is the only difference. This makes a difference when blending an svg or an Svg element with the background. It also performs gamma color correction. It supposed to be a bit slower, but I could not see any noticeable difference.

luebbe commented 3 years ago

I didn't say "worse" :) Just wanted to point out the visible difference. It looks like it's not possible to solve the "two different cases of black gear on gray background" so that they look identical.

pyscripter commented 3 years ago

It looks like it's not possible to solve the "two different cases of black gear on gray background" so that they look identical.

I am afraid not unless you disable antialiasing. This is how antialiasing works. It takes into account the background and blends the image into it. If you change the background color of the rectangular in your right images, the svg will look slightly different.

The purpose of the last PR was to paint svgs well (enough) irrespective of the background color.

This article describes the problem and the solution I have implemented. Please note that in this article, although the result is good, it will not be identical to drawing the image on blue background.