Skip to content

Instantly share code, notes, and snippets.

@stijnsanders
Last active June 17, 2021 21:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stijnsanders/9d421b64a6c56d80108360427368da8a to your computer and use it in GitHub Desktop.
Save stijnsanders/9d421b64a6c56d80108360427368da8a to your computer and use it in GitHub Desktop.
quick and dirty conversion of png to svg
program pxsvg;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
pxsvg1 in 'pxsvg1.pas';
begin
try
if ParamCount=0 then
Writeln('usage: pxsvg [png file]')
else
ConvertPNG(ParamStr(1));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
unit pxsvg1;
interface
procedure ConvertPNG(const fn:string);
implementation
uses System.SysUtils, System.Classes, Vcl.Graphics, Vcl.Imaging.PngImage;
function IntToStr8(c:cardinal):AnsiString;
var
d:array[0..11] of AnsiChar;
i,j:integer;
begin
i:=0;
repeat
d[i]:=AnsiChar($30+(c mod 10));
c:=c div 10;
inc(i);
until c=0;
SetLength(Result,i);
j:=0;
while i<>0 do
begin
dec(i);
inc(j);
Result[j]:=d[i];
end;
end;
procedure ConvertPNG(const fn:string);
type
TCArr=array[0..0] of cardinal;
PCArr=^TCArr;
var
p:TPngImage;
b:TBitmap;
d:PCArr;
x,y,ax,ay,i:cardinal;
c:cardinal;
cc:packed record
b,g,r,a:byte;//yes, really in this order!
end absolute c;
svg:AnsiString;
f:TFileStream;
paths:array of record
c:cardinal;
d:AnsiString;
end;
pathsSize,pathsIndex:cardinal;
begin
b:=TBitmap.Create;
try
p:=TPngImage.Create;
try
p.LoadFromFile(fn);
b.PixelFormat:=pf32bit;
b.Assign(p);
finally
p.Free;
end;
ax:=b.Width;
ay:=b.Height;
d:=b.ScanLine[ay-1];
pathsIndex:=0;
pathsSize:=0;
y:=0;
while y<ay do
begin
x:=0;
while x<ax do
begin
c:=d[y*ax+x];
//not all alpha?
if cc.a=0 then
inc(x)//skip
else
begin
//look-up color
i:=0;
while (i<pathsIndex) and (paths[i].c<>c) do inc(i);
if i=pathsIndex then
begin
//not found, add
if pathsIndex=pathsSize then
begin
inc(pathsSize,$100);//grow step
SetLength(paths,pathsSize);
end;
paths[pathsIndex].c:=c;
paths[pathsIndex].d:='';
inc(pathsIndex);
end;
//add line segment
paths[i].d:=paths[i].d+'M'+IntToStr8(x)+','+IntToStr8(ay-y);
//adjacent pixels with same color?
repeat
inc(x);
until (x=ax) or (d[y*ax+x]<>c);
paths[i].d:=paths[i].d+'H'+IntToStr8(x)+'Z';
end;
end;
inc(y);
end;
finally
b.Free;
end;
FormatSettings.DecimalSeparator:='.';
FormatSettings.ThousandSeparator:=',';
svg:='<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 '+
IntToStr8(ax)+' '+IntToStr8(ay)+'">'#13#10;
for i:=0 to pathsIndex-1 do
begin
c:=paths[i].c;
svg:=svg+'<path d="'+paths[i].d+'" stroke="#'+
AnsiString(Format('%.2x%.2x%.2x',[cc.r,cc.g,cc.b]));
if cc.a<>255 then
svg:=svg+'" stroke-opacity="'+AnsiString(Format('%.3f',[cc.a/255]));
//stroke-width="1"?
svg:=svg+'" fill="none" />'#13#10;
end;
svg:=svg+'</svg>'#13#10;
f:=TFileStream.Create(ChangeFileExt(fn,'.svg'),fmCreate);
try
f.Write(svg[1],Length(svg));
finally
f.Free;
end;
end;
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment