Search code examples
delphidelphi-10.3-rioskiaskia4delphi

How to convert SVG file to PNG file with transparency using Delphi


I want to convert a SVG file to PNG file programmatically.

I'm using Delphi 10.3.3 with Skia4Delphi, but this code snippet doesn't respect transparency. It creates black background.

var
  LBitmap: TBitmap;
  MyPng: TPNGImage;
begin
  if opendialog1.Execute then
  begin
    LBitmap := TBitmap.Create;
    try
      LBitmap.SetSize(1000,1000);
      LBitmap.Transparent := True;

      LBitmap.SkiaDraw(
        procedure (const ACanvas: ISKCanvas)
        var
          LSvgBrush: TSkSvgBrush;
        begin
          LSvgBrush := TSkSvgBrush.Create;
          try
            LSvgBrush.Source := TFile.ReadAllText(opendialog1.FileName);
            LSvgBrush.Render(ACanvas, RectF(0, 0, LBitmap.Width, LBitmap.Height), 1);
          finally
            LSvgBrush.Free;
          end;
        end);

        if savedialog1.Execute then
        begin
          MyPng := TPngImage.Create;
          try
            MyPng.Assign(LBitmap);
            MyPng.SaveToFile(savedialog1.FileName);
          finally
            MyPng.Free;
          end;
        end;
    finally
      LBitmap.Free;
    end;
  end;
end;

Solution

  • Your problem is after SkiaDraw, save the bitmap in png format. This could be done very simply without using TPNGImage:

    if savedialog1.Execute then
      LBitmap.ToSkImage.EncodeToFile(savedialog1.FileName);
    

    However, in the current version (3.4.1) there is an issue related to Bitmap.ToSkImage: https://github.com/skia4delphi/skia4delphi/issues/150

    Another solution would be to use TPNGImage, but in a different way:

    function CreatePNGFromTransparentBitmap(const ABitmap: TBitmap): TPNGImage;
    type
      TRGB = packed record
        B, G, R: Byte;
      end;
      TRGBAArray = array[0..$effffff] of packed record
        B, G, R, A: Byte;
      end;
    var
      X, Y: Integer;
      BmpRGBA: ^TRGBAArray;
      PngRGB: ^TRGB;
    begin
      Result := TPNGImage.CreateBlank(COLOR_RGBALPHA, 8, ABitmap.Width , ABitmap.Height);
      try
        Result.CreateAlpha;
        Result.Canvas.CopyMode := cmSrcCopy;
        Result.Canvas.Draw(0, 0, ABitmap);
        for Y := 0 to Pred(ABitmap.Height) do
        begin
          BmpRGBA := ABitmap.ScanLine[Y];
          PngRGB := Result.ScanLine[Y];
          for X := 0 to Pred(ABitmap.Width) do
          begin
            Result.AlphaScanline[Y][X] := BmpRGBA[X].A;
            if ABitmap.AlphaFormat in [afDefined, afPremultiplied] then
            begin
              if BmpRGBA[X].A <> 0 then
              begin
                PngRGB^.B := Round(BmpRGBA[X].B / BmpRGBA[X].A * 255);
                PngRGB^.R := Round(BmpRGBA[X].R / BmpRGBA[X].A * 255);
                PngRGB^.G := Round(BmpRGBA[X].G / BmpRGBA[X].A * 255);
              end
              else
              begin
                PngRGB^.B := Round(BmpRGBA[X].B * 255);
                PngRGB^.R := Round(BmpRGBA[X].R * 255);
                PngRGB^.G := Round(BmpRGBA[X].G * 255);
              end;
            end;
            Inc(PngRGB);
          end;
        end;
      except
        Result.Free;
        raise;
      end;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    var
      LBitmap: TBitmap;
      MyPng: TPNGImage;
    begin
      if opendialog1.Execute then
      begin
        LBitmap := TBitmap.Create;
        try
          LBitmap.SetSize(1000, 1000);
          LBitmap.SkiaDraw(
            procedure (const ACanvas: ISKCanvas)
            var
              LSvgBrush: TSkSvgBrush;
            begin
              LSvgBrush := TSkSvgBrush.Create;
              try
                LSvgBrush.Source := TFile.ReadAllText(opendialog1.FileName);
                LSvgBrush.Render(ACanvas, RectF(0, 0, LBitmap.Width, LBitmap.Height), 1);
              finally
                LSvgBrush.Free;
              end;
            end);
    
            if savedialog1.Execute then
            begin
              MyPng := CreatePNGFromTransparentBitmap(LBitmap);
              try
                MyPng.SaveToFile(savedialog1.FileName);
              finally
                MyPng.Free;
              end;
            end;
        finally
          LBitmap.Free;
        end;
      end;
    end;