Bitmap zeichen (gehts noch schneller?)
Mit den in der untenstehenden Test-Prozedur enthaltenen Prozeduren DrawBitmap und DrawGPBitmap zeichne ich eine Bitmap auf den Canvas einer maximierten Form.
Größe der Bitmap : 4000 x 3000 Pixel Größe des Canvass : 1920 x 1000 Pixel Meine Erwartung war, das das mit der GPBitmap (TGPGraphics.DrawImage) schneller geht als mit DrawBitmap (StretchBlt). Ich habe folgende Zeiten gemessen, jeweils für 100 maliges Zeichnen: DrawBitmap : 234 ms. DrawGPBitmap: 31809 ms, also knapp 136 mal so lange. Da ich mir kaum vorstellen kann, dass das DrawImage (immerhin GDI+) so viel langsamer ist, als das StretchBlt, vermute ich, dass ich irgend etwas verkehrt mache. Weiß jemand, wie man eine GPBitmap schnell ausgibt? Das Bild kann hier [URL="https://commons.wikimedia.org/w/index.php?curid=6558322"] heruntergeladen werden.
Delphi-Quellcode:
PROCEDURE TMain.Test;
//------------------------------------------------------------------------------ FUNCTION DrawBitmap(const Dsn:String; Count:Integer):Cardinal; var I,W,H:Integer; Image:TGPImage; Graphics:TGPGraphics; Bmp:TBitmap; begin Bmp:=TBitmap.Create; Image:=TGPImage.Create(Dsn); W:=Image.GetWidth; H:=Image.GetHeight; Bmp.SetSize(W,H); Graphics:=TGPGraphics.Create(Bmp.Canvas.Handle); Graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); Graphics.DrawImage(Image,MakeRect(0,0,W,H)); Graphics.Free; Image.Free; Result:=GetTickCount; for I:=1 to Count do begin SetStretchBltMode(Canvas.Handle,COLORONCOLOR); SetBrushOrgEx(Canvas.Handle,0,0,nil); StretchBlt(Canvas.Handle,0,0,ClientWidth,ClientHeight, Bmp.Canvas.Handle,0,0,Bmp.Width,Bmp.Height,SrcCopy); end; Result:=GetTickCount-Result; Bmp.Free end; //------------------------------------------------------------------------------ FUNCTION DrawGPBitmap(const Dsn:String; Count:Integer):Cardinal; var I:Integer; Image:TGPBitmap; Graphics:TGPGraphics; begin Image:=TGPBitmap.Create(Dsn); Result:=GetTickCount; for I:=1 to Count do begin Graphics:=TGPGraphics.Create(Canvas.Handle); Graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); Graphics.DrawImage(Image,MakeRect(0,0,ClientWidth,ClientHeight)); Graphics.Free; end; Result:=GetTickCount-Result; Image.Free; end; //------------------------------------------------------------------------------ // https://commons.wikimedia.org/w/index.php?curid=6558322 const Dsn='E:\Daten\DownLoads\PfauVonVorne.jpg'; var T1,T2:Cardinal; begin T1:=DrawBitmap(Dsn,100); T2:=DrawGPBitmap(Dsn,100); ShowMessage(IntToStr(T1)+#13+IntToStr(T2)); // Ausgabe // DrawBitmap 234 ms bei 100 Durchläufen // DrawGPBitmap 31809 ms bei 100 Durchläufen end; |
AW: Bitmap zeichen (gehts noch schneller?)
Mach doch mal das erzeugen und freigeben des Bildes außerhalb der Schleife. Und der Rückgabewert von makerect kann man auch vorher holen und zwischenspeichern.
|
AW: Bitmap zeichen (gehts noch schneller?)
Canvas verfügt doch selbst über die Methoden Draw, StretchDraw u.a.
Warum nicht gleich direkt zeichnen ohne den Umweg über das Bitmap? |
AW: Bitmap zeichen (gehts noch schneller?)
BitBlt, StretchBlt und andere Funktionen der GDI-Lib sind Hardwarebeschleunigt(XP, Vista Nicht, ab W7 wieder). GDI+ ist es nicht.
GDIP hat außerdem bessere interpolationen, die brauchen aber auch mehr cpu. Wobei zugegeben 136 mal langsamer schon komisch aussieht. Konnte nur noch diesen Artikel wiederfinden. Den wo GDI+ als nicht HW-beschleunigt genannt wird, nicht :( https://msdn.microsoft.com/en-us/lib...=vs.85%29.aspx PS: Wäre natürlich gut zu wissen unter welchem OS du bist. Ab W8 wurde dass ja schonwieder geändert. |
AW: Bitmap zeichen (gehts noch schneller?)
Zitat:
Zudem geht es ja nicht darum 100 Mal das gleiche hintereinander zu zeichnen sondern darum entweder ein TBitmap oder ein TGPBitmap zu zeichnen. Zu diesem Zeichnen gehört nun mal auch das MakeRect. Dass ich hie das Zeichnen 100 mal wiederhole ist nur, damit die Messung per GetTickCount verwertbar ist. Unter diesem Gesichtspunkt wäre ein separates MakeRect also eher schädlich, allerdings auch nur unwesentlich. Und was meinst du mit "erzeugen und freigeben des Bildes außerhalb der Schleife" ? Die Bitmaps werden doch außerhalb der Schleife erzeugt und freigegeben. Falls du damit das TGPGraphics.Create / und Free meinst, gilt ähnliches wie für MakeRect. Trotzdem Danke für Deinen Input. |
AW: Bitmap zeichen (gehts noch schneller?)
Zitat:
Canvas.StretchDraw braucht doch eine Info, WAS gezeichnet werden soll, in diesem Fall eben das Bitmap. Oder meinst du die TGPGraphic mit Canvas.StretchDraw zu zeichnen? Das geht leider nicht. |
AW: Bitmap zeichen (gehts noch schneller?)
Zitat:
Das wusste ich bisher nicht. Aber einen offensichtlichen Fehler in meinem Code, der das langsame Zeichnen verursacht siehst du auch nicht, oder? Siehst du denn eine Möglichkeit aus einer TGPBitmap SCHNELL eine TBitmap zu machen? Wie ich das bisher mache siehst du in der Prozedur DrawBitmap. Mein OS ist übrigens Win 7 Prof. |
AW: Bitmap zeichen (gehts noch schneller?)
Um überhaupt vergleichen zu können, würde ich erstmal die interpolationen in TGPGraphic abstellen. Dann die Frage warum überhaupt GDI+? Wenn du ein so hochauflösendes Bild hast, kannst du nicht auch SetStretchBLTMode HalfTone zum runterskalieren nehmen?
Benutz ich z.B. in meinem AsciiImage: TGDIRenderContext.EndScene(Zeile 135) Und so schauts aus: AsciiImage for Delphi: GDI-Downsampling, FireMonkey and more! EDIT: Vergiss nicht, dass du da bei GDI+ mitunter unsäglich viel konvertierst(Kann dir auch bei GDI passieren). Wenn die Pixel-Formate zwischen Source und Target nicht gleich sind, wird die source immer konvertiert beim Zeichnen! IIRC solltest du am besten mit 32bit Grafiken arbeiten. |
AW: Bitmap zeichen (gehts noch schneller?)
Zitat:
ich hab alle Modi probiert: InterpolationModeInvalid 270 ms InterpolationModeDefault 270 ms InterpolationModeLowQuality 270 ms InterpolationModeHighQuality 325 ms InterpolationModeBilinear 270 ms InterpolationModeBicubic 620 ms InterpolationModeNearestNeighbor 180 ms InterpolationModeHighQualityBilinear 370 ms InterpolationModeHighQualityBicubic 320 MS Alle Zeiten für einmal zeichnen. Dagegen StretchBlt 2 ms Bringt also die benötigte Zeit nicht einmal ansatzweise in die Zeit für StretchBlt. Und warum bin ich so spitz auf TGPBitmap bin, statt TBitmap? Das Bild liegt idR als .jpg vor, kann aber auch als .png oder .tif oder .bmp sein. Bisher habe ich z.B. .jpg entsprechend der untenstehenden "LoadBmpViaJPEG" geladen. Zur Zeit benutze ich die untenstehende "LoadBmpViaGPBitmap" Und dann hab ich die untenstehende "LoadGPBitmap" getestet. Die jeweils benötigten Zeiten: "LoadBmpViaJPEG" : 970 ms "LoadBmpViaGPBitmap" 270 ms "LoadGPBitmap" 319 µs Da ich das Bild nur ausgeben kann, wenn ich es vorher lade, drängt sich das laden in eine TGPBitmap förmlich auf. Deshalb möchte auch bei allem was folgt am liebsten mit TGPBitmap weitermachen.
Delphi-Quellcode:
FUNCTION LoadBmpViaJPEG(const Dsn:String):Int64;
var Jpg:TJpegImage; Bmp:TBitmap; begin Result:=TimeStamp; Jpg:=TJPEGImage.Create; Bmp:=TBitmap.Create; Jpg.LoadFromFile(Dsn); Bmp.Assign(Jpg); Jpg.Free; Result:=TimeStamp-Result; Bmp.Free; end;
Delphi-Quellcode:
FUNCTION LoadBmpViaGPBitmap(const Dsn:String):Int64;
var W,H:Integer; Image:TGPBitmap; Graphics:TGPGraphics; Bmp:TBitmap; begin Result:=TimeStamp; Bmp:=TBitmap.Create; Image:=TGPBitmap.Create(Dsn); W:=Image.GetWidth; H:=Image.GetHeight; Bmp.SetSize(W,H); Graphics:=TGPGraphics.Create(Bmp.Canvas.Handle); Graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); Graphics.DrawImage(Image,MakeRect(0,0,W,H)); Graphics.Free; Image.Free; Result:=TimeStamp-Result; Bmp.Free; end;
Delphi-Quellcode:
Zur Vollständigkeit:
FUNCTION LoadGPBitmap(const Dsn:String):Int64;
var I:Integer; Image:TGPBitmap; begin Result:=TimeStamp; Image:=TGPBitmap.Create(Dsn); Result:=TimeStamp-Result; Image.Free; end;
Delphi-Quellcode:
FUNCTION TimeStamp:Int64;
asm rdtsc {$IFDEF CPUX64} shl rdx,32 or rax,rdx {$ENDIF} end; |
AW: Bitmap zeichen (gehts noch schneller?)
Zitat:
Siehe hier: http://www.tek-tips.com/faqs.cfm?fid=7568 |
AW: Bitmap zeichen (gehts noch schneller?)
TPicture.LoadFromFile wäre auch noch da ;)
|
AW: Bitmap zeichen (gehts noch schneller?)
Zitat:
kannte ich auch noch nicht. Ist aber keine Alternative, weil recht bummelig. Im nächsten Beitrag werde ich das mal alles zusammenfassen. |
AW: Bitmap zeichen (gehts noch schneller?)
Zitat:
TPicture.LoadFromFile arbeitet zwar recht flott, aber Canvas.StretchDraw(ClientRect,Picture.Graphic) ist dafür umso langsamer. Ich hab mal alle Varianten durchprobiert. Die beste Alternative scheint zu sein: 1) GPImage oder GPBitmap laden 2) In TBitmap kopieren 3) TBitmap mit StretchBlt ausgeben. Letztendlich reduziert sich alles auf die Ausgangsfrage: Weiß jemand, wie man eine GPBitmap schnell ausgibt? Und so sind die Zeiten, die ich ermittelt habe. Bei mehreren Testen hintereinander weichen die Zeiten zwar ab, aber die Relation der Zeiten für die verschiedenen Abläufe ändert sich nicht wesentlich. Parameter : .jpg Datei, 4000x3000 Pixel, Größe des Canvas : 1920 x 1000 Pixel LoadGPImage 0.731 ms LoadGPBitmap 0.558 ms LoadWicImage 141.564 ms LoadPicture 2.989 ms LoadBmpViaJPEG 966.952 ms LoadBmpViaGPImage 243.285 ms LoadBmpViaGPBitmap 243.314 ms LoadBmpViaWicImage 387.731 ms LoadBmpViaPicture 984.076 ms DrawBitmap 2.729 ms DrawGPImage 174.647 ms DrawGPBitmap 174.511 ms DrawWicImage 262.219 ms DrawPicture 990.640 MS Zur Vollständigkeit die komplette (aktualisierte) Test Prozedur. Vielleicht macht Ihre Euch die Mühe und schaut die einmal kritisch durch, denn seine eigenend kleinen Flüchtigkeitsfehler übersieht man ja oft.
Delphi-Quellcode:
PROCEDURE TMain.Test;
//------------------------------------------------------------------------------ FUNCTION TimeStamp:Int64; asm rdtsc {$IFDEF CPUX64} shl rdx,32 or rax,rdx {$ENDIF} end; //------------------------------------------------------------------------------ FUNCTION FixCPU(var PaMask:NativeUInt):Boolean; var SaMask,TaMask:NativeUInt; begin GetProcessAffinityMask(GetCurrentProcess,PaMask,SaMask); if PaMask<>0 then begin TaMask:=1; while (TaMask<>0) and ((TaMask and PaMask)=0) do TaMask:=TaMask shl 1; if TaMask<>0 then SetThreadAffinityMask(GetCurrentThread,TaMask); Result:=TaMask<>0; end else begin Result:=False; end; end; //------------------------------------------------------------------------------ FUNCTION GetTimeStampFrequency:Int64; var OldPriority:Word; Q0,Q1,Qf,Ts0,Ts1:Int64; Seconds:Extended; PaMask:NativeUInt; Fixed:Boolean; begin // Thread nur auf einer CPU ausführen Fixed:=FixCPU(PaMask); // Priorität des Threads hochsetzen OldPriority:=GetThreadPriority(GetCurrentThread); SetThreadPriority(GetCurrentThread,THREAD_PRIORITY_TIME_CRITICAL); // QPC- und TimeStamp-Ticks für 10 ms holen QueryPerformanceCounter(Q0); Ts0:=TimeStamp; Sleep(10); Ts1:=TimeStamp; QueryPerformanceCounter(Q1); // Anhand QPF die TimeStamp-Frequenz ermitteln QueryPerformanceFrequency(Qf); Seconds:=(Q1-Q0)/Qf; Result:=Trunc((Ts1-Ts0)/Seconds+0.5); // Ticks/s // Priorität des Threads auf alten Wert stellen und Thread für alle // CPUs freigeben SetThreadPriority(GetCurrentThread,OldPriority); if Fixed and (PaMask<>0) then SetThreadAffinityMask(GetCurrentThread,PaMask); end; //------------------------------------------------------------------------------ FUNCTION LoadGPImage(const Dsn:String):Int64; var I:Integer; Image:TGPImage; begin Result:=TimeStamp; Image:=TGPImage.Create(Dsn); Result:=TimeStamp-Result; Image.Free; end; //------------------------------------------------------------------------------ FUNCTION LoadGPBitmap(const Dsn:String):Int64; var Image:TGPBitmap; begin Result:=TimeStamp; Image:=TGPBitmap.Create(Dsn); Result:=TimeStamp-Result; Image.Free; end; //------------------------------------------------------------------------------ FUNCTION LoadWicImage(const Dsn:String):Int64; var WicImage:TWicImage; begin WicImage:=TWicImage.Create; Result:=TimeStamp; WicImage.LoadFromFile(Dsn); Result:=TimeStamp-Result; WicImage.Free; end; //------------------------------------------------------------------------------ FUNCTION LoadBmpViaJPEG(const Dsn:String):Int64; var Jpg:TJpegImage; Bmp:TBitmap; begin Bmp:=TBitmap.Create; Result:=TimeStamp; Jpg:=TJPEGImage.Create; Jpg.LoadFromFile(Dsn); Bmp.Assign(Jpg); Jpg.Free; Result:=TimeStamp-Result; Bmp.Free; end; //------------------------------------------------------------------------------ FUNCTION LoadBmpViaGPImage(const Dsn:String):Int64; var W,H:Integer; Image:TGPImage; Graphics:TGPGraphics; Bmp:TBitmap; begin Bmp:=TBitmap.Create; Result:=TimeStamp; Image:=TGPImage.Create(Dsn); W:=Image.GetWidth; H:=Image.GetHeight; Bmp.SetSize(W,H); Graphics:=TGPGraphics.Create(Bmp.Canvas.Handle); Graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); Graphics.DrawImage(Image,MakeRect(0,0,W,H)); Graphics.Free; Image.Free; Result:=TimeStamp-Result; Bmp.Free; end; //------------------------------------------------------------------------------ FUNCTION LoadBmpViaGPBitmap(const Dsn:String):Int64; var W,H:Integer; Image:TGPBitmap; Graphics:TGPGraphics; Bmp:TBitmap; begin Bmp:=TBitmap.Create; Result:=TimeStamp; Image:=TGPBitmap.Create(Dsn); W:=Image.GetWidth; H:=Image.GetHeight; Bmp.SetSize(W,H); Graphics:=TGPGraphics.Create(Bmp.Canvas.Handle); Graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); Graphics.DrawImage(Image,MakeRect(0,0,W,H)); Graphics.Free; Image.Free; Result:=TimeStamp-Result; Bmp.Free; end; //------------------------------------------------------------------------------ FUNCTION LoadBmpViaWicImage(const Dsn:String):Int64; var WicImage:TWicImage; Bmp:TBitmap; begin Bmp:=TBitmap.Create; Result:=TimeStamp; WicImage:=TWicImage.Create; WicImage.LoadFromFile(Dsn); Bmp.Assign(WicImage); WicImage.Free; Result:=TimeStamp-Result; Bmp.Free; end; //------------------------------------------------------------------------------ FUNCTION LoadPicture(const Dsn:String):Int64; var Picture:TPicture; begin Picture:=TPicture.Create; Result:=TimeStamp; Picture.LoadFromFile(Dsn); Result:=TimeStamp-Result; Picture.Free; end; //------------------------------------------------------------------------------ FUNCTION LoadBmpViaPicture(const Dsn:String):Int64; var Picture:TPicture; Bmp:TBitmap; begin Bmp:=TBitmap.Create; Result:=TimeStamp; Picture:=TPicture.Create; Picture.LoadFromFile(Dsn); Bmp.Assign(Picture.Graphic); Picture.Free; Result:=TimeStamp-Result; Bmp.Free; end; //------------------------------------------------------------------------------ FUNCTION DrawBitmap(const Dsn:String):Int64; var W,H:Integer; Image:TGPImage; Graphics:TGPGraphics; Bmp:TBitmap; begin Bmp:=TBitmap.Create; Image:=TGPImage.Create(Dsn); W:=Image.GetWidth; H:=Image.GetHeight; Bmp.SetSize(W,H); Graphics:=TGPGraphics.Create(Bmp.Canvas.Handle); Graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); Graphics.DrawImage(Image,MakeRect(0,0,W,H)); Graphics.Free; Image.Free; Result:=TimeStamp; SetStretchBltMode(Canvas.Handle,COLORONCOLOR); SetBrushOrgEx(Canvas.Handle,0,0,nil); StretchBlt(Canvas.Handle,0,0,ClientWidth,ClientHeight, Bmp.Canvas.Handle,0,0,Bmp.Width,Bmp.Height,SrcCopy); Result:=TimeStamp-Result; Bmp.Free end; //------------------------------------------------------------------------------ FUNCTION DrawGPImage(const Dsn:String):Int64; var I:Integer; Image:TGPImage; Graphics:TGPGraphics; begin Image:=TGPImage.Create(Dsn,True); Result:=TimeStamp; Graphics:=TGPGraphics.Create(Canvas.Handle); Graphics.SetInterpolationMode(InterpolationModeNearestNeighbor); Graphics.DrawImage(Image,MakeRect(0,0,ClientWidth,ClientHeight)); Graphics.Free; Result:=TimeStamp-Result; Image.Free; end; //------------------------------------------------------------------------------ FUNCTION DrawGPBitmap(const Dsn:String):Int64; var I:Integer; Image:TGPBitmap; Graphics:TGPGraphics; begin Image:=TGPBitmap.Create(Dsn,True); Result:=TimeStamp; Graphics:=TGPGraphics.Create(Canvas.Handle); Graphics.SetInterpolationMode(InterpolationModeNearestNeighbor); Graphics.DrawImage(Image,MakeRect(0,0,ClientWidth,ClientHeight)); Graphics.Free; Result:=TimeStamp-Result; Image.Free; // InterpolationModeInvalid 270 // InterpolationModeDefault 270 // InterpolationModeLowQuality 270 // InterpolationModeHighQuality 325 // InterpolationModeBilinear 270 // InterpolationModeBicubic 620 // InterpolationModeNearestNeighbor 180 // InterpolationModeHighQualityBilinear 370 // InterpolationModeHighQualityBicubic 320 end; //------------------------------------------------------------------------------ FUNCTION DrawWicImage(const Dsn:String):Int64; var WicImage:TWicImage; begin WicImage:=TWicImage.Create; WicImage.LoadFromFile(Dsn); Result:=TimeStamp; Canvas.StretchDraw(ClientRect,WicImage); Result:=TimeStamp-Result; WicImage.Free; end; //------------------------------------------------------------------------------ FUNCTION DrawPicture(const Dsn:String):Int64; var Picture:TPicture; begin Picture:=TPicture.Create; Picture.LoadFromFile(Dsn); Result:=TimeStamp; Canvas.StretchDraw(ClientRect,Picture.Graphic); Result:=TimeStamp-Result; Picture.Free; end; //------------------------------------------------------------------------------ var Results:String; PROCEDURE Add(TSC,TSF:Int64; const FuncName:String); var S:String; begin if Results<>'' then Results:=Results+#13#10; if TSF=0 then begin S:=IntToStr(TSC)+' CPU-Ticks' end else begin Str(TSC/TSF*1000:0:3,S); S:=S+' ms'; end; Results:=Results+FuncName+' '+S end; //------------------------------------------------------------------------------ // https://commons.wikimedia.org/w/index.php?curid=6558322 const Dsn='E:\Daten\DownLoads\PfauVonVorne.jpg'; var TSF:Int64; PaMask:NativeUInt; CPUFixed:Boolean; begin TSF:=GetTimeStampFrequency; CPUFixed:=FixCPU(PaMask); Add(LoadGPImage(Dsn),TSF,'LoadGPImage'); Add(LoadGPBitmap(Dsn),TSF,'LoadGPBitmap'); Add(LoadWicImage(Dsn),TSF,'LoadWicImage'); Add(LoadPicture(Dsn),TSF,'LoadPicture'); Results:=Results+#13#10; Add(LoadBmpViaJPEG(Dsn),TSF,'LoadBmpViaJPEG'); Add(LoadBmpViaGPImage(Dsn),TSF,'LoadBmpViaGPImage'); Add(LoadBmpViaGPBitmap(Dsn),TSF,'LoadBmpViaGPBitmap'); Add(LoadBmpViaWicImage(Dsn),TSF,'LoadBmpViaWicImage'); Add(LoadBmpViaPicture(Dsn),TSF,'LoadBmpViaPicture'); Results:=Results+#13#10; Add(DrawBitmap(Dsn),TSF,'DrawBitmap'); Add(DrawGPImage(Dsn),TSF,'DrawGPImage'); Add(DrawGPBitmap(Dsn),TSF,'DrawGPBitmap'); Add(DrawWicImage(Dsn),TSF,'DrawWicImage'); Add(DrawPicture(Dsn),TSF,'DrawPicture'); if CPUFixed and (PaMask<>0) then SetThreadAffinityMask(GetCurrentThread,PaMask); Clipboard.AsText:=Results; ShowMessage(Results); end; |
AW: Bitmap zeichen (gehts noch schneller?)
Eine TBitmap mit Pixelformat32 (IIRC) welches du direkt auf den Canvas zeichnest (warum gehst du zum zeichnen der TBitmap über TGPGraphic?) sollte das schnellste sein
|
AW: Bitmap zeichen (gehts noch schneller?)
Zitat:
Zu "warum gehst du zum zeichnen der TBitmap über TGPGraphic?" Mache ich nicht! Entweder habe ich mich undeutlich ausgedrückt oder Du hast falsch gelesen. Ich schrieb: 1) GPImage oder GPBitmap laden 2) In TBitmap kopieren 3) TBitmap mit StretchBlt ausgeben. Ich lade also die Datei in ein TGPImage oder TGBBitmap, kopiere diese in eine TBitmap und zeichne die TBitmap mit StretchBlt. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 16:20 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz