Delphi-PRAXiS
Seite 2 von 2     12   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Geht das noch schneller? - Bitmap-Verrechnung (https://www.delphipraxis.net/182859-geht-das-noch-schneller-bitmap-verrechnung.html)

Harry Stahl 24. Nov 2014 23:24

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von jfheins (Beitrag 1280784)
4. Sagt wir "premultiplied alpha" etwas? Klingt erstmal komisch, ist aber eigentlich die sinnvollere Variante. Würde deinen Rechenaufwand auch hier reduzieren, aber deine Bilddaten müssten dafür natürlich angepasst werden.

Ja, sagt mir was. Wäre durchaus eine Überlegung (gewesen), aber dafür hätte man das dann von Anfang an im Programm (es dreht sich hier um ein Bildbearbeitungsprogramm) berücksichtigen müssen.

Die einzige Verwendung der hier diskutierten Funktion im Programm ist die Anzeige des fertig verrechneten Bitmap-Ebenen-Stapels auf einen Karo-Hintergrund (siehe anliegenden Screenshot).

Ich verwende übrigens dafür nicht die Windows Alphablend-Funktion, da die für bestimmte Bilder fehlerhafte Ergebnisse berechnet (eigentlich schade, denn die ist noch schneller, als die Funktion hier). Insofern stellt die hier diskutierte Funktion einen (Teil-)Ersatz dafür dar.

Harry Stahl 24. Nov 2014 23:28

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Jens01 (Beitrag 1280741)
Schon mal bei/mit Graphics32 versucht?!

Nein, muss gestehen, kannte ich bislang nicht (habe mit GraphicEX, ansonsten teilweise mit ImageEn, aber meistens mit eigenen Lösungen gearbeitet).

Werde ich mir mal ansehen. Gibt es dort so eine spezielle Verrechnungsfunktion wie hier benötigt?

Medium 25. Nov 2014 01:31

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Alpha-Blending würde ich nun nicht wirklich als speziell bezeichnen, daher ja: Die Graphics32 bietet eine Fülle an Möglichkeiten diesbezüglich. Sogar direkten Support für Layers in Bitmaps (mit den respektiven Kombinationsfunktionen, u.a. standard Alpha-Blending). Vom Gefühl her würde ich fast sagen, dass diese Lib dein Heiland sein könnte.

Blup 25. Nov 2014 08:15

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Harry Stahl (Beitrag 1280962)
Ich verwende übrigens dafür nicht die Windows Alphablend-Funktion, da die für bestimmte Bilder fehlerhafte Ergebnisse berechnet (eigentlich schade, denn die ist noch schneller, als die Funktion hier)

Eigentlich funktioniert die Windowsfunktion bei aktuellen Windowsversionen oder zumindest aktuellem Servicepack ganz gut. Allerdings müssen die transparenten Bitmaps dafür "premultiplied alpha" vorliegen. Das spart schon mal die halbe Rechenzeit.

Harry Stahl 25. Nov 2014 18:09

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Danke, das war ein wichtiger Hinweis, den ich übersehen hatte: Wenn die Bitmap Premultiplied ist, funktioniert die Alphablend-Funktion auch wie bei allen Bitmaps wie gewünscht!!:oops:

Insgesamt ist dann die Kombination temporäres Premultiplied-Bitmap erzeugen und mit Alphablend benutzen schon schneller als die hier bislang gefundene Lösung.

Das hier ist meine aktuelle PreMultiply-Funktion:

Kann man das auch noch irgendwie beschleunigen?

Delphi-Quellcode:
procedure PreMultiply (bm: TBitmap); inline;
var
  y, x: Integer;
  RGBA: pRGBALine;
begin
  for y := 0 to bm.Height-1 do begin
    RGBA := bm.Scanline[y];
    for x := 0 to bm.Width-1 do begin
      if (RGBA^[x].rgbReserved <> 0) and (RGBA^[x].rgbReserved <> 255) then begin
        if RGBA^[x].rgbRed <> 0 then RGBA^[x].rgbRed := round ((RGBA^[x].rgbReserved * RGBA^[x].rgbRed ) / 255);
        if RGBA^[x].rgbGreen <> 0 then RGBA^[x].rgbGreen := round ((RGBA^[x].rgbReserved * RGBA^[x].rgbGreen ) / 255);
        if RGBA^[x].rgbBlue <> 0 then RGBA^[x].rgbBlue := round ((RGBA^[x].rgbReserved * RGBA^[x].rgbBlue ) / 255);
      end;
    end;
  end;
end;

Dejan Vu 25. Nov 2014 18:36

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Ja, kann man. Einfach die Beiträge durchlesen (Array vs. Pointer, Integer vs. FP mathematik (Stichwort: 'MulDiv')

Du kannst die Ergebnisse der Rechnung auch vorher einmal in einer Matrix vornehmen und dann nur noch auslesen. Dann hast Du einmalig overhead aber bei vielen Berechnungen könnte sich das lohnen, zumal Du wohl auf FP-Mathematik bestehst. Warum auch immer.
Delphi-Quellcode:
var
  lkup : Array [0..255, 0..255] of Byte;

Begin
  for i:=0 to 255 do for j:=0 to 255 do lkup[i,j] := round(i*j/255);
End;

...
for y := 0 to bm.Height-1 do begin
  RGBA := bm.Scanline[y];
  for x := 0 to bm.Width-1 do begin
    if (RGBA^.rgbReserved <> 0) and (RGBA^.rgbReserved <> 255) then begin
      if RGBA^.rgbRed <> 0 then RGBA^.rgbRed := lk[RGBA^.rgbReserved , RGBA^.rgbRed];
      if RGBA^.rgbGreen <> 0 then RGBA^.rgbGreen := lk[RGBA^.rgbReserved, RGBA^.rgbGreen];
      if RGBA^.rgbBlue <> 0 then RGBA^.rgbBlue := lk[RGBA^.rgbReserved, RGBA^.rgbBlue];
    end;
   inc(RGBA);
  end;
end;

BUG 25. Nov 2014 18:50

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Die inneren Verzweigungen wegzulassen könnte auch noch was bringen:
Delphi-Quellcode:
var
  lkup : Array [0..255, 0..255] of Byte;

Begin
  for i:=0 to 255 do for j:=0 to 255 do
  begin
    if (j <> 0) and (j <> 255) then // ist j <> 0 überhaupt korrekt hier?
      lkup[i,j] := round(i*j/255)
    else
      lkup[i,j] := j;
  end;
End;

...
for y := 0 to bm.Height-1 do begin
  RGBA := bm.Scanline[y];
  for x := 0 to bm.Width-1 do begin
    RGBA^.rgbRed := lk[RGBA^.rgbReserved , RGBA^.rgbRed];
    RGBA^.rgbGreen := lk[RGBA^.rgbReserved, RGBA^.rgbGreen];
    RGBA^.rgbBlue := lk[RGBA^.rgbReserved, RGBA^.rgbBlue];
   inc(RGBA);
  end;
end;

Harry Stahl 25. Nov 2014 18:59

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Das hatte ich gerade auch zufälligerweise im Internet gefunden.

Doch Überraschung: Ist deutlich langsamer, als meine Funktion, die ich gepostet hatte. Wahrscheinlich kostet der doppelte Zugriff auf das byte-Array mehr Zeit als die direkte Berechnung der Werte.

Edit: Halt, diese Aussage muss ich evtl. zurückziehen. Wenn ich folgende logische Abfrage wie bei mir oben einbaue, ist es zumindest ähnlich schnell:

Delphi-Quellcode:
  if RGBA^[x].rgbReserved <> 255 then begin
Hängt dann eben davon ab, wieviel Pixel überhaupt Transparent sind, denn nur die müssen ja berechnet werden, die anderen können den Wert behalten.

Dejan Vu 26. Nov 2014 07:04

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Das Lookup-Array wird nur einmal berechnet, da ist es unerheblich, etwas zu optimieren. Daher würde ich diesen Vorgang nicht in die Performancebetrachtungen mit einbeziehen.
Beim Schreiben des Vorschlags ist mir aber auch aufgefallen, das es bei drei Operationen (*, /, round) nicht allzuviel zu optimieren gibt.
Aber wieso verwendest Du 'MulDiv' nicht? Das Ergebnis ist um höchstens 1 unterschiedlich (Rundungsverhalten).

Mavarik 26. Nov 2014 11:33

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Dejan Vu (Beitrag 1281096)
Aber wieso verwendest Du 'MulDiv' nicht?

MulDiv ist immer ein Far-Call und Jumps und damit auf jeden Fall langsamer. (Auch wenn ich es nicht getestet habe)

Aber ein

C := A * 5 div B;

ist:
Code:
xor eax,eax
mov al,[ebp-$5]
lea eax,[eax+eax*4]
xor edx,edx
mov dl,[ebp-$06]
mov ecx,edx
xor edx,edx
div ecx
mov edx,[ebp-$04]
mov [edx+$002d4],al
ein C := MULDIV(A,5,B) ist:
Code:
xor eax,eax
mov al,[ebp-$6]      
push eax
push $05
xor eax,eax
mov al,[ebp-$5)
Push eax            // bishier schon fast so viele Ticks wie oben
CALL MulDiv
// aus MulDiv
jmp dword ptr[$00896ae8]
mov eax,[esp+$04}
or eax,eax
js $75201b80
mov edx,[esp+$80]
or edx,edx
js $75201c06
mul edx
mov ecx,[eps+$0c]
or ecx,ecx
js $75201c48
sar ecx,1
add eax,ecx
adc edx.$00
cmp edx,[esp+$0c]
jnb $75201b7a
div dword ptr [esp+$0c]
or eax,eay
js $75201b7a
ret $000c
mov edx,[ebp-$04]  // gleich mit oben
mov [edx+$02d4],al // gleich mit oben
Ein C := A * B div 8 ist:
Code:
xor eax,eax        // gleich mit oben
mov al,[ebp-$05]   // gleich mit oben
xor edx,edx        // gleich mit oben
mov dl,[ebp-$06],
imul edx
shr eax, $03        // div 8 erkannt
mov edx,[ebp-$04]  // gleich mit oben
mov [edx+$02d4],al // gleich mit oben

Mavarik

Dejan Vu 26. Nov 2014 11:45

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
MulDiv basiert auf In64 und ist damit überlauf-sicher, was bei A*B div C imho nicht der Fall ist. Dafür ist es langsamer als der das einfache A*B DIV C.

Bezüglich des Performancevergleiches zielte ich eher auf 'MulDiv' vs. 'Round(A*B/C)' ab, also Floating Point Arithmetik.

Harry Stahl 26. Nov 2014 20:16

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Dejan Vu (Beitrag 1281096)
Aber wieso verwendest Du 'MulDiv' nicht? Das Ergebnis ist um höchstens 1 unterschiedlich (Rundungsverhalten).

Das habe ich schon in Betrag Nr. 10 gesagt, weil es dann doppelt so lange braucht.

Harry Stahl 26. Nov 2014 20:35

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Um die bisherigen Ergebnisse zusammenzufassen:

* Zugriff auf ein Lookup-Array dauert länger, als die Berechnung.
* MulDiv bringt eindeutig keine Beschleunigung
* Im vorliegenden Fall bringt auch eine Parallelisierung nichts, da der Verwaltungsaufwand
größer ist, als der Gewinn durch mehrere Prozessorkerne

Das bislang schnellste Ergebnis liefert der untenstehende Code, egal, ob per Pointer-Addition oder Array-Zugriff:

Delphi-Quellcode:
procedure Draw32BitToBitmapxxx(const BitOben: TBitmap; BitUnten: TBitmap); // Array-Zugriff
var
   h,w,i, r, g, b: Integer;
   RGBA_Unten, RGBA_Oben: pRGBALine;
begin
    for h := 0 to BitUnten.Height-1 do begin
     RGBA_Unten := BitUnten.ScanLine[h];
     RGBA_Oben := BitOben.ScanLine[h];

     For w:= 0 to BitUnten.Width-1 do begin
       if RGBA_Oben^[w].rgbReserved = 0 then begin
         // unten bleibt
       end else begin
         RGBA_Unten^[w].rgbred := (RGBA_Oben^[w].rgbReserved * (RGBA_Oben^[w].rgbred - RGBA_Unten^[w].rgbred) shr 8 + RGBA_Unten^[w].rgbred);
         RGBA_Unten^[w].rgbGreen := (RGBA_Oben^[w].rgbReserved * (RGBA_Oben^[w].rgbgreen - RGBA_Unten^[w].rgbGreen) shr 8 + RGBA_Unten^[w].rgbGreen);
         RGBA_Unten^[w].rgbBlue := (RGBA_Oben^[w].rgbReserved * (RGBA_Oben^[w].rgbBlue - RGBA_Unten^[w].rgbBlue) shr 8 + RGBA_Unten^[w].rgbBlue);

         RGBA_Unten^[w].rgbReserved := 255;
       end;
    end;
   end;
end;

procedure Draw32BitToBitmapnew(const BitOben: TBitmap; BitUnten: TBitmap); // Pointer-Addition
var
  h,w: Integer;
  RGBA_Unten, RGBA_Oben: ^TRGBQuad; // pRGBALine;
begin
  For h := 0 to BitUnten.Height-1 do begin
    RGBA_Unten := BitUnten.ScanLine[h];
    RGBA_Oben := BitOben.ScanLine[h];

    For w:= 0 to BitUnten.Width-1 do begin
      if RGBA_Oben^.rgbReserved = 0 then begin
        // unten bleibt
      end else begin
        RGBA_Unten^.rgbBlue := ((RGBA_Oben^.rgbReserved * (RGBA_Oben^.rgbBlue -  RGBA_Unten^.rgbBlue)) shr 8 + RGBA_Unten^.rgbBlue);
        RGBA_Unten^.rgbGreen := ((RGBA_Oben^.rgbReserved * (RGBA_Oben^.rgbgreen - RGBA_Unten^.rgbGreen)) shr 8 + RGBA_Unten^.rgbGreen);
        RGBA_Unten^.rgbred  := ((RGBA_Oben^.rgbReserved * (RGBA_Oben^.rgbred -   RGBA_Unten^.rgbred))  shr 8 + RGBA_Unten^.rgbred);

        RGBA_Unten^.rgbReserved := 255;
      end;
      inc (RGBA_Unten);
      inc (RGBA_oben);
   end;
  end;
end;

Mavarik 27. Nov 2014 11:20

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Harry Stahl (Beitrag 1281237)
* Im vorliegenden Fall bringt auch eine Parallelisierung nichts, da der Verwaltungsaufwand
größer ist, als der Gewinn durch mehrere Prozessorkerne

Das kann ich eigentlich kaum glauben.

Vielleicht über die Parallel FOR ok, kann ich nicht nachvollziehen. Aber einen optimierten thread mit entsprechendem vorbereiteten Prozessen müsste schneller sein.

Können wir am Sa. ja mit unserem Thread König, emm Kaiser, ich meine Kaisler diskutieren. :stupid:

Mavarik

manfred42 27. Nov 2014 21:08

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Hallo, ich bin hier neu hinzugestoßen und versuche mich am Beschleunigen.
Zuförderst ein paar Fragen zu den Details:
● Ist der Nenner in der Formel 255 oder 256? Bei 255 sind einige Klimmzüge nötig
● Ist Round vonnöten, oder dient es nur zur Float-Integer-Konvertierung?
● Von welcher WEB-Site kann ich 2 repräsentative 32-bittige Bitmaps absaugen?

Da ich schon einmal mit MMX-Befehlen Antialiasing veranstaltet habe,
würde ich mich gern an der Bitmap-Verrechnung versuchen.

MfG Manfred(19)42

Harry Stahl 27. Nov 2014 23:26

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Mavarik (Beitrag 1281300)
Zitat:

Zitat von Harry Stahl (Beitrag 1281237)
* Im vorliegenden Fall bringt auch eine Parallelisierung nichts, da der Verwaltungsaufwand
größer ist, als der Gewinn durch mehrere Prozessorkerne

Das kann ich eigentlich kaum glauben.

Vielleicht über die Parallel FOR ok, kann ich nicht nachvollziehen. Aber einen optimierten thread mit entsprechendem vorbereiteten Prozessen müsste schneller sein.

Können wir am Sa. ja mit unserem Thread König, emm Kaiser, ich meine Kaisler diskutieren. :stupid:

Mavarik

Hängt eben davon ab, was die Funktion macht. Wenn die nur wenige Befehle ausführen muss, sind die Aktionen, welche für die Threadverwaltung benötigt werden, zeitintensiver, als die Abarbeitung der wenigen Befehle. Das gilt sowohl für Parallel FOR und für Parallel Task.

Muss man quasi im Einzelfall testen, wo was schneller ist.

Harry Stahl 27. Nov 2014 23:31

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von manfred42 (Beitrag 1281403)
Hallo, ich bin hier neu hinzugestoßen und versuche mich am Beschleunigen.
Zuförderst ein paar Fragen zu den Details:
● Ist der Nenner in der Formel 255 oder 256? Bei 255 sind einige Klimmzüge nötig
● Ist Round vonnöten, oder dient es nur zur Float-Integer-Konvertierung?
● Von welcher WEB-Site kann ich 2 repräsentative 32-bittige Bitmaps absaugen?

Da ich schon einmal mit MMX-Befehlen Antialiasing veranstaltet habe,
würde ich mich gern an der Bitmap-Verrechnung versuchen.

MfG Manfred(19)42

256 wäre wohl richtig.
Round wäre gut, zur Not aber auch ohne.
Erstelle Dir doch einfach selber 2 passende Bitmaps, hier die Größe und Bedingungen, mit denen ich hier teste:

- 3548x2558 Pixel, 32 Bit-Format.
- Das untere Bitmap hat einen Verlaufsuntergrund, das obere Bitmap hat zwei rechteckige Bereiche (jeweils 340 x 2300), die zu 100% transparent sind.

manfred42 28. Nov 2014 00:52

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Danke für den Ratschlag (um diese Uhrzeit)!
Ich hatte mit 2 kleineren Bitmaps experimentiert und werde
dem Ratschlag folgen.
Jetzt habe die letzte (Pointer)-Version der Prozedur probiert.
Sie läuft sehr flott und schreit förmlich nach dem Einsatz von
MMX-Code.
An einer Thread-Version will ich mich später versuchen. Ich
habe da schon eine Idee. Bei Threads bin ich aber nicht sehr
sattelfest. Auf meinem DualCore-Notebook für Rentner wird es
nicht viel bringen
Wenn ich mit den technischen Rahmenbedingungen der DP
mehr vertraut bin, werde ich auch Codepassagen präsentieren.
Gute Nacht!

Amateurprofi 28. Nov 2014 01:10

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Delphi-Quellcode:
PROCEDURE Blend32(Source,Dest:pRGBQuad; Width,Height,OOffset,UOffset:Integer);
const
   RegSize=4;
   WOffs=6*RegSize; HOffs=12*RegSize; OOffs=11*RegSize; UOffs=10*RegSize;
asm
         pushad
         mov     ebp,ecx               // Width
         lea     esi,[eax+ebp*4]       // Source
         lea     edi,[edx+ebp*4]       // Dest
         neg     ebp
         mov     [esp+WOffs],ebp
@Loop:  mov     bl,[esi+ebp*4].TRgbQuad.rgbReserved // S.Reserved
         test    bl,bl
         jz      @Next
         // Red
         mov     al,[esi+ebp*4].TRgbQuad.rgbRed     // S.Red
         mov     cl,[edi+ebp*4].TRgbQuad.rgbRed     // D.Red
         sub     al,cl                              // S.Red-D.Red
         imul    bl                                 // (S.Red-D.Red)*S.Reserved
         add     ah,cl                              // ((S.Red-D.Red)*S.Reserved) shr 8 + D.Red
         mov     dx,ax
         // Green
         mov     al,[esi+ebp*4].TRgbQuad.rgbGreen   // S.Green
         mov     cl,[edi+ebp*4].TRgbQuad.rgbGreen   // D.Green
         sub     al,cl                              // S.Green-D.Green
         imul    bl                                 // (S.Green-D.Green)*S.Reserved
         mov     dl,ah                              // ((S.Green-D.Green)*S.Reserved) shr 8
         add     dl,cl                              // ((S.Green-D.Green)*S.Reserved) shr 8 + D.Green
         shl     edx,8
         // Blue
         mov     al,[esi+ebp*4].TRgbQuad.rgbBlue    // S.Blue
         mov     cl,[edi+ebp*4].TRgbQuad.rgbBlue    // D.Blue
         sub     al,cl                              // S.Blue-D.Blue
         imul    bl                                 // (S.Blue-D.Blue)*S.Reserved
         mov     dl,ah                              // ((S.Blue-D.Blue)*S.Reserved) shr 8
         add     dl,cl                              // ((S.Blue-D.Blue)*S.Reserved) shr 8 + D.Blue
         // Reserved
         or      edx,$FF000000
         mov     [edi+ebp*4],edx
         add     ebp,1
         jl      @Loop
         // Nächste Zeile
@Next:  add     esi,[esp+OOffs]
         add     edi,[esp+UOffs]
         mov     ebp,[esp+WOffs]
         sub     [esp+HOffs],1
         jnz     @Loop
@End:   popad
end;
Delphi-Quellcode:
PROCEDURE AsmDraw32BitToBitmap(Source,Dest:TBitmap);
resourcestring
   sWidthDifferent='Bitmaps haben unterschiedliche Breiten';
   sHeightDifferent='Bitmaps haben unterschiedliche Höhen';
   sZeroWidth='Breite der Bitmaps ist 0';
   sLessTwoLines='Höhe der Bitmaps ist < 2';
   sSourceNone32Bit='Source ist keine 32 Bit Bitmap';
   sDestNone32Bit='Dest ist keine 32 Bit Bitmap';
var PSource,PDest:pRGBQuad; W,H,SOffset,DOffset:Integer;
begin
   W:=Source.Width;
   H:=Source.Height;
   if Dest.Width<>W then raise Exception.Create(sWidthDifferent);
   if Dest.Height<>H then raise Exception.Create(sHeightDifferent);
   if W<1 then raise Exception.Create(sZeroWidth);
   if H<2 then raise Exception.Create(sLessTwoLines);
   if Source.PixelFormat<>pf32bit then raise Exception.Create(sSourceNone32Bit);
   if Dest.PixelFormat<>pf32bit then raise Exception.Create(sDestNone32Bit);
   PSource:=Source.ScanLine[0];
   SOffset:=NativeInt(Source.ScanLine[1])-NativeInt(PSource);
   PDest:=Dest.ScanLine[0];
   DOffset:=NativeInt(Dest.ScanLine[1])-NativeInt(PDest);
   Blend32(PSource,PDest,W,H,SOffset,DOffset);
end;

hanvas 28. Nov 2014 08:44

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Harry Stahl (Beitrag 1281237)
Um die bisherigen Ergebnisse zusammenzufassen:

* Im vorliegenden Fall bringt auch eine Parallelisierung nichts, da der Verwaltungsaufwand
größer ist, als der Gewinn durch mehrere Prozessorkerne

Dann müsste man sehen wo der Break-Even ist. Mann könnte z.B. das Bild in 2 Hälften unterteilen und je eine in einem Thread abarbeiten und so die Arbeit in größeren Brocken (und damit einem besseren Verhältnis zum Verwaltungsaufwand) auf zwei Threads auslagern.

Wie an anderer Stelle schon dargestellt sollte die Implementierung mit Hilfe von SIMD Befehlen mehr bringen. Wenn Du dabei lieber mit Pascal als mit Assembler arbeitest, dann schau Dir doch mal Vector-Pascal :

http://sourceforge.net/projects/vectorpascalcom/
http://www.dcs.gla.ac.uk/~wpc/report...index/x25.html

an. Ist zwar schon einige Zeit her das ich damit gespielt habe, aber damals konnte ich damit eine DLL erstellen und die Funktionen in Delphi einbinden und für solche Probleme wie Deines war das Teil ideal.


hth

HaJoe

Harry Stahl 29. Nov 2014 08:01

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:

Zitat von Amateurprofi (Beitrag 1281418)
Delphi-Quellcode:
PROCEDURE Blend32(Source,Dest:pRGBQuad; Width,Height,OOffset,UOffset:Integer);
....

Ja, das gute alte Assembler...

Wäre noch etwas schneller als meine letzte Variante (hier zuletzt ca. 62 MS, die ASM ca. 47 MS), allerdings ist das Ergebnis erkennbar falsch, siehe Screenshot).

Harry Stahl 29. Nov 2014 08:05

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
[QUOTE=hanvas;1281434]
Zitat:

Zitat von Harry Stahl (Beitrag 1281237)

Danke für den Tipp. Allerdings möchte ich erst mal nur 2-3 bestehende Grafik-Funktionen unter Weiterverwendung von TBitmap und meines bisherigen Source-Codes beschleunigen.

Die Konsequenz, das ganze Programm umzuschreiben, möchte ich derzeit noch ganz gerne vermeiden...

hanvas 29. Nov 2014 10:39

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Harry Stahl (Beitrag 1281543)

Danke für den Tipp. Allerdings möchte ich erst mal nur 2-3 bestehende Grafik-Funktionen unter Weiterverwendung von TBitmap und meines bisherigen Source-Codes beschleunigen.

Bearbeitest Du eigentlich nur ein Bild, oder wird deer Funktion eine Serie von Bildern, eines nach dem anderen übergeben? Im ersten Fall glaube ich das das Optimierungspotiental - abgesehen von den bereits erfolgten Hinweisen, ziemlisch ausgeschöpft.

cu HaJoe

Amateurprofi 29. Nov 2014 12:26

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Harry Stahl (Beitrag 1281542)
Zitat:

Zitat von Amateurprofi (Beitrag 1281418)
Delphi-Quellcode:
PROCEDURE Blend32(Source,Dest:pRGBQuad; Width,Height,OOffset,UOffset:Integer);
....

Ja, das gute alte Assembler...

Wäre noch etwas schneller als meine letzte Variante (hier zuletzt ca. 62 MS, die ASM ca. 47 MS), allerdings ist das Ergebnis erkennbar falsch, siehe Screenshot).

Hab ich geprüft.
Das Label "Next:" gehört 2 Code-Zeilen höher.
So wie es war, wurde bei rgbReserved=0 zum nächsten Zeilenanfang gesprungen statt zum nächsten Pixel.
Bei meiner Prüfung auf identische Ergebnisse hatte ich dummerweise immer alle rgbReserved<>0, so dass dieser Fehler nicht auftrat.

Delphi-Quellcode:
PROCEDURE Blend32(Source,Dest:pRGBQuad; Width,Height,OOffset,UOffset:Integer);
const
   RegSize=4;
   WOffs=6*RegSize; HOffs=12*RegSize; OOffs=11*RegSize; UOffs=10*RegSize;
asm
         pushad
         mov     ebp,ecx               // Width
         lea     esi,[eax+ebp*4]       // Source
         lea     edi,[edx+ebp*4]       // Dest
         neg     ebp
         mov     [esp+WOffs],ebp
@Loop:  mov     bl,[esi+ebp*4].TRgbQuad.rgbReserved // S.Reserved
         test    bl,bl
         jz      @Next
         // Red
         mov     al,[esi+ebp*4].TRgbQuad.rgbRed     // S.Red
         mov     cl,[edi+ebp*4].TRgbQuad.rgbRed     // D.Red
         sub     al,cl                              // S.Red-D.Red
         imul    bl                                 // (S.Red-D.Red)*S.Reserved
         add     ah,cl                              // ((S.Red-D.Red)*S.Reserved) shr 8 + D.Red
         mov     dx,ax
         // Green
         mov     al,[esi+ebp*4].TRgbQuad.rgbGreen   // S.Green
         mov     cl,[edi+ebp*4].TRgbQuad.rgbGreen   // D.Green
         sub     al,cl                              // S.Green-D.Green
         imul    bl                                 // (S.Green-D.Green)*S.Reserved
         mov     dl,ah                              // ((S.Green-D.Green)*S.Reserved) shr 8
         add     dl,cl                              // ((S.Green-D.Green)*S.Reserved) shr 8 + D.Green
         shl     edx,8
         // Blue
         mov     al,[esi+ebp*4].TRgbQuad.rgbBlue    // S.Blue
         mov     cl,[edi+ebp*4].TRgbQuad.rgbBlue    // D.Blue
         sub     al,cl                              // S.Blue-D.Blue
         imul    bl                                 // (S.Blue-D.Blue)*S.Reserved
         mov     dl,ah                              // ((S.Blue-D.Blue)*S.Reserved) shr 8
         add     dl,cl                              // ((S.Blue-D.Blue)*S.Reserved) shr 8 + D.Blue
         // Reserved
         or      edx,$FF000000
         mov     [edi+ebp*4],edx
@Next:  add     ebp,1
         jl      @Loop
         // Nächste Zeile
         add     esi,[esp+OOffs]
         add     edi,[esp+UOffs]
         mov     ebp,[esp+WOffs]
         sub     [esp+HOffs],1
         jnz     @Loop
@End:   popad
end;

Harry Stahl 29. Nov 2014 16:30

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
@Amateurprofi:

Danke, dass Du Dich der Sache noch mal angenommen hast. Allerdings stimmt das Ergebnis immer noch nicht.

Falls Du die Sache noch weiter verfolgen willst, kannst Du hier im Thread-Beitrag Nr. 26 das Demoprojekt laden, das ich gepostet habe.

Da brauchst Du nur im Button-Click Event folgendes zu machen, um Deine Procedure einzubinden:

Delphi-Quellcode:
  for L := 1 to count do begin
    AsmDraw32BitToBitmap (b, b3);
    //Draw32BitToBitmapnew (b, b3);
  end;
Dann Siehst Du direkt im Vergleich, ob beide Bilder den gleichen Inhalt haben.

Harry Stahl 29. Nov 2014 20:52

[Gelöst] AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Ich habe nun den Rat gefolgt und habe mir die Graphics32-Unit mal angehen.

Der Grafk-Typ TBitmap32 ist zwar ein eigener Klassen-Typ, aber die Lowlowel-Routinen, die dahinter liegen, kann man auch auf TBitmap anwenden.

Die Lösung besteht also nun darin, GR32 und GR32_Blend aus der Graphics32 einzubinden und dann kann man die Funktion mit einer Zeile (1) benutzen, die dann auch nur noch 31 MS benötigt, um das Bild zu verrechnen:

Delphi-Quellcode:
procedure Draw32BitToBitmap(const BitOben: TBitmap; BitUnten: TBitmap);
begin
  BLEND_LINE[cmBlend]^(pColor32(BitOben.ScanLine[BitUnten.Height-1]),
    pColor32(BitUnten.ScanLine[BitUnten.Height-1]), BitUnten.Width* BitUnten.Height);
end;

Amateurprofi 29. Nov 2014 23:39

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von Harry Stahl (Beitrag 1281595)
Allerdings stimmt das Ergebnis immer noch nicht.
Falls Du die Sache noch weiter verfolgen willst, kannst Du hier im Thread-Beitrag Nr. 26 das Demoprojekt laden, das ich gepostet habe.

@Harry:
Habe ich geprüft.
Ich habe eine 8Bit-Integer-Multipikation benutzt, bei der Byte-Werte > $7F als negativ betrachtet werden.
Im Debugger sah ich dann, dass bei Deiner Prozedur eine 32Bit-Integer Multiplikation verwendet wird.
Bei den Tests fiel das nicht ins Gewicht, weil ich einfach den gesamten Screen nach BitOben und BitUnten kopiert hatte.
Somit waren die Rgb-Werte in Oben und Unten gleich und die Subtraktion von z.B. Oben.rgbRed - Unten.rgbRed ergab immer 0.
Nachdem ich die Bitmaps mit Zufallswerten gefüllt hatte konnte den Grund der Abweichungen finden.
Ich habe die Prozedur Blend32 umgeschrieben. Für mich sehr überraschend, ist sie dadurch schneller geworden; ich hatte das Gegenteil erwartet.

Dein Beispiel Projekt läßt sich bei mir (XE2) nicht kompilieren.

Delphi-Quellcode:
PROCEDURE Blend32(Source,Dest:pRGBQuad; Width,Height,OOffset,UOffset:Integer);
const
   RegSize=4;
   WOffs=6*RegSize; HOffs=12*RegSize; OOffs=11*RegSize; UOffs=10*RegSize;
asm
         pushad
         lea     esi,[eax+ecx*4]       // Source
         lea     edi,[edx+ecx*4]       // Dest
         neg     ecx
         mov     [esp+WOffs],ecx
         mov     ebp,[esp+HOffs]
@Loop:  movzx   ebx,[esi+ecx*4].TRgbQuad.rgbReserved // S.Reserved
         test    ebx,ebx
         jz      @Next
         // Blue
         movzx   eax,[esi+ecx*4].TRgbQuad.rgbBlue    // S.Blue
         movzx   edx,[edi+ecx*4].TRgbQuad.rgbBlue    // D.Blue
         sub     eax,edx                             // S.Blue-D.Blue
         imul    ebx                                 // (S.Blue-D.Blue)*S.Reserved
         add     [edi+ecx*4].TRgbQuad.rgbBlue,ah
         // Green
         movzx   eax,[esi+ecx*4].TRgbQuad.rgbGreen   // S.Green
         movzx   edx,[edi+ecx*4].TRgbQuad.rgbGreen   // D.Green
         sub     eax,edx                             // S.Green-D.Green
         imul    ebx                                 // (S.Green-D.Green)*S.Reserved
         add     [edi+ecx*4].TRgbQuad.rgbGreen,ah
         // Red
         movzx   eax,[esi+ecx*4].TRgbQuad.rgbRed     // S.Red
         movzx   edx,[edi+ecx*4].TRgbQuad.rgbRed     // D.Red
         sub     eax,edx                             // S.Red-D.Red
         imul    ebx                                 // (S.Red-D.Red)*S.Reserved
         add     [edi+ecx*4].TRgbQuad.rgbRed,ah
         // Reserved
         mov     [edi+ecx*4].TRgbQuad.rgbReserved,$FF
@Next:  add     ecx,1
         jl      @Loop
         // Nächste Zeile
         add     esi,[esp+OOffs]
         add     edi,[esp+UOffs]
         mov     ecx,[esp+WOffs]
         sub     ebp,1
         jnz     @Loop
@End:   popad
end;

Harry Stahl 30. Nov 2014 10:41

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Um das jetzt noch zu vervollständigen: Jetzt funktioniert auch Deine Lösung mit ca. 32 MS recht schnell und richtig.:thumb:

Wenn man die Graphics32 also nicht verwenden möchte, wäre Deine Lösung auch noch eine Variante.

OK, mit XE2 kannst Du das Demo nicht kompilieren, da ja dort noch nicht die TParallel-Library zur Verfügung stand.

Wenn Du aber die "System.Threading" Unit rausnimmst und "Draw32BitToBitmappara" auskommentierst sollte es gehen.

Codix32 30. Nov 2014 12:43

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Ich bitte um Entschuldigung, wenn ich eine Frage habe, die nicht ganz zum Thread passt, aber:

Frage:
Gibt es eine Möglichkeit, ein Bitmap mit geringer Auflösung (72dpi), das sich beim vergrößern total verpixelt, irgendwie so zu schärfen, dass die Details sichtbar werden?

also in Krimisendungen habe ich das schon gesehen, dass dort Aufnahmen vergrößert werden, dann verpixelt sind, dann mit einem Programm dennoch geschärft werden. Mir geht es dabei nicht, um irgendwelche verpixelten Gesichter zu erkennen, sondern um andere Objekte.

Enthält also eine kleine Grafik genug Infos, um die beim Vergrößern entstehenden Quadrate mit einem Algorythmus so zu bearbeiten, dass daraus ein größeres scharfes Bild wird?
Enthalten die Blöcke oder Quadrate überhaupt genug Farbinfos dafür?

Oder geht da wohl nur was mit Vectoren...?

Valle 30. Nov 2014 12:57

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Dann eröffne doch bitte einen neuen Thread für eine andere Frage.

Dann musst du doch nicht den Thread vom armen Harry durcheinander bringen. :(

Kurze Antwort: Nein, das geht nicht zufriedenstellend automatisch. Reine Fiktion.

manfred42 30. Nov 2014 17:47

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Nach flüchtigem Verfolgen der Diskussion meine ich:
Es geht hier, wie schon anklang, um Alphablending.
Da gab es doch 2005 das Thema
Alphablending mit MMX / SSE Befehle

Aber zur jetzigen Diskussion
Wenn man anstatt 255 durch 256 per Shift dividiert,
dann geht das nur bei nichnegativem Nenner.

Für den Datentyp Int32 kann man den Trick mit der
magischen Zahl $8081 zur Division durch 255 verwenden.

const
Magic: UInt16 = $8081;
var
n: Integer;

Dann ist für -Sqr(255) <= n<=Sqr(255)
( n * Magic) div (1 shl 23) = n div 255
Der Compiler erzeugt für den ersten Ausdruck
einen arithmetische Rechtsshift um 23.
Müsste mal mit RDTSC timen.

Manfred

arnof 30. Nov 2014 19:34

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
nur mit einem Auge das hier verfolgt, aber ist folgender Blog nicht genau das Thema:

http://www.delphifeeds.com/go/f/1217...hiFeeds.com%29


Die haben hier ein VCL Beispiel und hinweise für die Firemonkey Umsetzung

Harry Stahl 30. Nov 2014 22:05

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Zitat:

Zitat von arnof (Beitrag 1281724)
nur mit einem Auge das hier verfolgt, aber ist folgender Blog nicht genau das Thema:

http://www.delphifeeds.com/go/f/1217...hiFeeds.com%29


Die haben hier ein VCL Beispiel und hinweise für die Firemonkey Umsetzung

Nun ja, TParallel hatten wir hier ja auch schon. Der Link zeigt zwar die Parallel-For-Verwendung aber mit canvas.pixel, das ist ja so ziemlich das Langsamste, was man machen kann.

Dejan Vu 1. Dez 2014 07:00

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Man muss ja nicht gleich TParallel mit Canvas.Pixel verbinden. Vielleicht muss man auch manuell skalieren, d.h. durch Ausprobieren herausfinden, wo der break even zwischen Bildgröße und Anzahl der Threads ist.

manfred42 1. Dez 2014 20:45

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Ein Versuch in Vorbereitung einer MMX-Version
Delphi-Quellcode:
procedure Draw32BitToBitmapPreMMX(const BitOben: TBitmap; BitUnten: TBitmap);
const
  Magic: UInt16 = $8081;
var
  W, x, y:     Integer;
  Stride, ORes: Integer;
  RowO,     RowU,
  RGBA_Oben, RGBA_Unten: ^TRGBQuad;
begin
  RowU := BitUnten.ScanLine[0];
  RowO := BitOben.ScanLine[0];

  W := BitUnten.Width;
  Stride := 4 * W;

  for y := 0 to BitUnten.Height - 1 do
  begin
    RGBA_Unten := RowU;
    RGBA_Oben := RowO;
    for x := 0 to W - 1 do
    begin
      ORes := RGBA_Oben^.rgbReserved;
      with RGBA_Unten^ do
      begin
        rgbBlue := (ORes * (RGBA_Oben^.rgbBlue - rgbBlue)) *
          Magic div (1 shl 23) + rgbBlue;
        rgbGreen := (ORes * (RGBA_Oben^.rgbGreen - rgbGreen)) *
          Magic div (1 shl 23) + rgbGreen;
        rgbRed := (ORes * (RGBA_Oben^.rgbRed - rgbRed)) *
          Magic div (1 shl 23) + rgbRed;
        rgbReserved := 255;
      end;
      Inc(RGBA_Unten);  Inc(RGBA_Oben);
    end;
    Dec(Cardinal(RowU), Stride);
    Dec(Cardinal(RowO), Stride);
  end;
end;

manfred42 1. Jan 2015 21:22

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Hier mein letztes Angebot
Delphi-Quellcode:
unit UAlphaSSE;
{
   SSE-Version von Harry Stahls

       procedure Draw32BitToBitmap(const BitOben: TBitmap; BitUnten: TBitmap);
}
interface

uses
  Winapi.Windows, Vcl.Graphics;

procedure Draw32BitToBitmapSSE(const BitOben: TBitmap; BitUnten: TBitmap);

implementation

procedure Draw32BitToBitmapSSE(const BitOben: TBitmap; BitUnten: TBitmap);
const
  //                                 bbggrr
  RGBFF: array[0..1] of UInt64 = ($FF000000, 0);
  // src bytes                   ......01......00   ......03......02
  UMsk: array[0..1] of UInt64 = ($8080800180808000, $8080800380808002);
  // res bytes                   .......... 8 4 0   ................
  PMsk: array[0..1] of UInt64 = ($8080808080080400, $8080808080808080);
  C255: array[0..3] of Single = (1/255, 1/255, 1/255, 0);
var
  BmpO, BmpU: PRGBQuad;
  N: Integer;
begin
  N   := BitOben.Height;
  BmpO := BitOben.ScanLine[N - 1];
  BmpU := BitUnten.ScanLine[N - 1];
  N   := N * BitOben.Width - 1;     // size of bitmap - 1
  asm
    PUSH    EBX
    MOV     EAX, BmpO
    MOV     EDX, BmpU
    MOV     ECX, N
//           XMM7  free
    LDDQU   XMM6, UMsk
    LDDQU   XMM5, PMsk
    LDDQU   XMM4, C255
    LDDQU   XMM3, RGBFF
{$IFOPT O+}
    DB      $66,$90          // 2-Byte-NOP
{$ELSE}
    DB      $66,$0F,$1F,$44,0,0 // 6-Byte-NOP
{$ENDIF}
@Loop:
    MOVD    XMM0, [EAX+4*ECX]// XMM0 = | 0 |&#945;|B|G|R
    PEXTRW  EBX, XMM0, 1     // EBX = &#945; | B
    SHR     EBX, 8           // EBX = 0 | &#945;
    JZ      @LoopEnd        // test &#945; &#8801; RGBA_O.rgbReserved = 0 ?

    PSHUFB  XMM0, XMM6       // unpack to Int32
    CVTDQ2PS XMM0, XMM0       // convert RGB_O to single FP

//  SHUFPS  XMM1, XMM0, $FF // !!! useless result
    MOVAPD  XMM1, XMM0       // copy RGB_O  (necessary !)
    SHUFPS  XMM1, XMM1, $FF // XMM1 = &#945; |  &#945;   |  &#945;   |  &#945;
    MULPS   XMM1, XMM4       // XMM1 = 0 | &#945;/255 | &#945;/255 | &#945;/255

    MOVD    XMM2,[EDX+4*ECX] // XMM2 = | 0 |&#945;|B|G|R
    PSHUFB  XMM2, XMM6       // unpack to Int32
    CVTDQ2PS XMM2, XMM2       // convert RGB_U to single FP

    SUBPS   XMM0, XMM2       // RGB_O - RGB_U
    MULPS   XMM0, XMM1       // &#945; * (RGB_O - RGB_U) / 255
    ADDPS   XMM0, XMM2       // &#945; * (RGB_O - RGB_U) / 255 + RGB_U

    CVTPS2DQ XMM0, XMM0       // convert FP to Int32 with rounding
    PSHUFB  XMM0, XMM5       // pack into TRGBQuad
    POR     XMM0, XMM3       // RGB_U.rgbReserved = $FF
    MOVD    [EDX+4*ECX],XMM0 // restore RGB_U
@LoopEnd:
    SUB     ECX, 1
    JNS     @Loop
    POP     EBX
  end;
end;
end.
QUOTE=Harry Stahl;1281742]
Zitat:

Zitat von arnof (Beitrag 1281724)
nur mit einem Auge das hier verfolgt, aber ist folgender Blog nicht genau das Thema:

http://www.delphifeeds.com/go/f/1217...hiFeeds.com%29
Die haben hier ein VCL Beispiel und hinweise für die Firemonkey Umsetzung

Nun ja, TParallel hatten wir hier ja auch schon. Der Link zeigt zwar die Parallel-For-Verwendung aber mit canvas.pixel, das ist ja so ziemlich das Langsamste, was man machen kann.[/QUOTE]

Mit anonymen Threads habe ich hier nichts machen können. Sie laufen schön langsam
nacheinander auf dem selben Prozessorkern. Könnte vor Wut das CPU-Gatget pulversisieren

Manfred

Ursa 5. Jan 2015 06:46

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Da kann man "gleichzeitig", in einem Thread.




samsung galaxy A3 schutzhülle

Dejan Vu 5. Jan 2015 06:56

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Bitte antworte in vollständigen Sätzen, also: Subjekt, Prädikat, Objekt. Es ist ein wenig dadaistisch, was Du da von Dir gibst und von einfachen Menschen wie mir so nicht zu verstehen.

Thomasl 5. Jan 2015 21:30

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Soll das nur übereinander Angezeigt werden?
Da hätte ich auch ein Beispiel mit GDI+
da rechnet das ja die GPU zusammen?

Thomasl 5. Jan 2015 23:26

AW: Geht das noch schneller? - Bitmap-Verrechnung
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hier ein Beispiel mit GDI+
Die ganze Unit ist im Anhang incl. drehen und interpolation
einfach in ein Package einbinden und ausprobieren
Die GDI+ Units sind im XE4 bei mir schon enthalten

Einen Geschwindigkeitsvergleich habe ich noch nicht gemacht

Mit Transparenten PNG´s klappt das wohl, aber 32Bit BMP´s nicht

Kann ich noch Optimieren zb. Ram sparen, wenn ich den Stream Plattmache funktionieren JPG´s nicht mehr

Delphi-Quellcode:
uses GDIPOBJ, GDIPAPI;
...
  TTestImage = class(TGraphicControl)
  private
    { Private declarations }
    fImageA1: TGPImage;
    fImageB1: TPicture;
    fStream1: TMemoryStream;
    fImageA2: TGPImage;
    fImageB2: TPicture;
    fStream2: TMemoryStream;
...

procedure TTestImage.Paint;
var
  Graphics: TGPGraphics;
begin
  Graphics := TGPGraphics.Create(Canvas.Handle);
  try
    Graphics.DrawImage(fImageA1, 0, 0, Width, Height);
    Graphics.DrawImage(fImageA2, 0, 0, Width, Height);
  finally
    Graphics.Free;
  end;
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:32 Uhr.
Seite 2 von 2     12   

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