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

Render not smooth #218

Closed MazighusDZ closed 2 years ago

MazighusDZ commented 2 years ago

The render with Skia is great but with SvgIconImage in Firemonkey is not smooth, can you fix it image

carloBarazzetta commented 2 years ago

Plese note that support of SKIA4Delphi into SVGIconImageList is only for VCL, at the moment... FMX is coming...

carloBarazzetta commented 2 years ago

I've notice this problem in the recent version of Image32 Library

AngusJohnson commented 2 years ago

I can't duplicate this. Could you please attach an SVG that shows the problem. Also, try downloading Image32 (ver 4.1) again.

carloBarazzetta commented 2 years ago

I'll investigate, because it seems a problem with an FMX Windows application with HIGH-DPI support "PerMonitor V2" on a 4K monitor...

MazighusDZ commented 2 years ago

Hi Carlo Barazzetta I send you a demo with the code take a look Demo Render.zip

AngusJohnson commented 2 years ago

Please try Image32's latest repository 'snapshot' here.

MazighusDZ commented 2 years ago

The proleme is in FMX.Image32SVG at line 175 when assigned Image32 to Bitmap I found an alternative function by saving Imag32 to stream and load it by Bitmap

procedure Image32ToFmxBitmap(const ASource: TImage32; const ATarget: FMX.Graphics.TBitmap);
var
  LStream: TMemoryStream;
begin
    LStream := TMemoryStream.Create;
    try
        ASource.SaveToStream(LStream, 'PNG');
        LStream.Position := 0;
        ATarget.LoadFromStream(LStream);
    finally
      LStream.Free;
    end;
end;

Example

procedure TForm1.PaintBox2Paint(Sender: TObject; Canvas: TCanvas);
var
  bmp: TBitmap;
  img32: TImage32;
  rec: TRectF;
begin
    img32 := TImage32.Create(80, 80);
    try
       img32.LoadFromFile('C:\Users\COAMA\Desktop\icons8_ok.svg');

        bmp := TBitmap.Create;
        try
          Image32ToFmxBitmap(img32, bmp);
          rec := RectF(0,0,bmp.Width,bmp.Height);
          Canvas.Lock;
          Canvas.DrawBitmap(bmp, rec, rec, 1.0);
          Canvas.Unlock;
        finally
          bmp.Free;
        end;

    finally
       img32.Free;
    end;
end;
MazighusDZ commented 2 years ago

Result image

MazighusDZ commented 2 years ago

SVG file

icons8_ok

AngusJohnson commented 2 years ago

I suspect the problem you're trying to circumvent is due to a misunderstanding of how Image32 loads SVG files (which are very different to raster image files).

With raster images (BMP, PNG, JPG) the image size is effectively fixed and these raster images will be loaded at that resolution. But with SVGs, the TImage32 object should be resized before loading the image, as this will scale the vectors to the correct size before rasterization. In other words, if you have an SVG icon that has a view box width and height of say 32 x 32px, but you want to display this image at 512 x 512px, then you should set the image size to 512 x 512 before loading from file.

See also the documentation here: http://www.angusj.com/delphi/image32/Docs/Units/Img32.Fmt.SVG/_Body.htm

Also, if you want to convert from Image32 to TBitmap via streaming (though there's a much more efficient way, at least on Windows), I suggest you don't use the PNG format, as this will waste a lot of time/resources compressing and decompressing the image unnecessarily. Just use a BMP stream and set the bitmap to pf32bits pixelformat. You may also want to set AlphaFormat to afDefined too if you're displaying semi-transparent images.

carloBarazzetta commented 2 years ago

I'll try to fix as soon as possibile.

carloBarazzetta commented 2 years ago

I've fixed the problem using the suggestion of @MazighusDZ but using ASource.SaveToStream(LStream, 'BMP'); as suggested by @AngusJohnson. In the previous version I'm using Img32.FMX.AssignImage32ToFmxBitmap(FImage32, ABitmap); written by @AngusJohnson but the result is not "smoot".

AngusJohnson commented 2 years ago

In the previous version I'm using Img32.FMX.AssignImage32ToFmxBitmap(FImage32, ABitmap); written by @AngusJohnson but the result is not "smoot".

OK. I don't understand why a seemingly straight pixel copy (via a TBitMapData object) would be doing this. I'll investigate.

AngusJohnson commented 2 years ago

I now understand what's happening ...

  1. Using TBitMapData, the image copy really is perfect (as I expected)
  2. Using TMemoryStream, saving to stream is just fine but, when TBitmap loads this streamed image, it premultiplies the color channels using the alpha channel (see below).

Evidently TBitmap automatically premultiplies images on load which I believe is problematic. Firstly, it (marginally) degrades image quality. But the real problem is when you save a premultiplied image there's no way to tell it has been premultiplied. So in loading it again there's every likelihood that it will be premultiplied once again which IS NOT a good thing. Nevertheless when premultiply is managed sensibly it does is make displaying images faster (and is a prerequisite at least for Windows alpha blending).

Here's my code. (I've made temporary changes to FMX.Image32SVG by added Img32.FMX to the uses clause and modifying the TFmxImage32SVG.PaintToBitmap method as per below):

With the following SVG loaded into the SVGIconImageFMX Demo - low_priority

and zooming (X10) into the top of this image we see the following ...

Test_Image32_Zoomed10 FImage32 (STARTING IMAGE)

      LSource := TBitMapData.Create(FImage32.Width, FImage32.Height, TPixelFormat.BGRA);
      LSource.Data := FImage32.PixelBase;
      LSource.Pitch := FImage32.Width * 4;
      ABitmap.SetSize(FImage32.Width, FImage32.Height);
      if ABitmap.Map(TMapAccess.Write, LDest) then
      try
        LDest.Copy(LSource);
      finally
        ABitmap.Unmap(LDest);
      end;
      ABitmap.SaveToFile('c:\temp\Test_TBitMapData.bmp');

Test_TBitMapData_Zoomed10 A PERFECT COPY

     LStream := TMemoryStream.Create;
      try
        FImage32.SaveToStream(LStream, 'BMP');
        LStream.Position := 0;
        ABitmap.LoadFromStream(LStream);
      finally
        LStream.Free;
      end;
      ABitmap.SaveToFile('c:\temp\Test_TMemoryStream.bmp');
    end;

Test_TMemoryStream_Zoomed10 Note blackening behind antialias

As you can see FMX.TBitmap has modified the image during the load (using premultiplied alpha)! Evidently FMX requires premultiplied alphas to properly display semi-transparent images. But there doesn't seem to be any mention of this in the FMX.TBitmap documentation. And there's no AlphaFormat property either as there is in VCL.TBitmap.

However TImage32 does allow you to apply premultiply alpha through its PreMultiply method. But because this process is mildly destructive it can't be perfectly reversed, so there's no UnPreMultiply method. (If you will later need to reverse PreMultiply, then you'll need to save a copy of the original image.)

     FImage32.PreMultiply;
     LSource := TBitMapData.Create(FImage32.Width, FImage32.Height, TPixelFormat.BGRA);
     LSource.Data := FImage32.PixelBase;
     LSource.Pitch := FImage32.Width * 4;
     ABitmap.SetSize(FImage32.Width, FImage32.Height);
     if ABitmap.Map(TMapAccess.Write, LDest) then
     try
         LDest.Copy(LSource);
     finally
         ABitmap.Unmap(LDest);
     end;
     ABitmap.SaveToFile('c:\temp\Test_Image32_PreMultiply.bmp');

Test_TBitMapData_Premultiplied10 Image32 + premultiply

Recommendation:
Use Image32's PreMultiply method before converting it to a TBitmap.
This will be more efficient than adding the extra steps of saving to and loading from stream.
pyscripter commented 2 years ago

As a background to this you may want to read Anders's comment at https://quality.embarcadero.com/browse/RSP-26621.

AngusJohnson commented 2 years ago

Yes, VCL TBitmap alpha management is a real mess. Unfortunately in FMX, TBitmap alpha management even worse, because the mess is simply hidden from view.

carloBarazzetta commented 2 years ago

Done and pushed now.