Delphi-PRAXiS
Seite 3 von 3     123   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Zwei transparente Bitmaps miteinader verrechnen (https://www.delphipraxis.net/192942-zwei-transparente-bitmaps-miteinader-verrechnen.html)

Michael II 7. Jun 2017 19:27

AW: Zwei transparente Bitmaps miteinader verrechnen
 
Hallo Harry

danke fürs Posten der angepassten Version. Ich weiss nun wieso du die Korrekturfunktion einbauen musst.

Du hast alphaA und alphaB vertauscht und auch bei der Berechnung der Farbwerte eine Vertauschung vorgenommen.

Wenn du noch einmal die Formel auf https://de.wikipedia.org/wiki/Alpha_Blending anschaust (oder den von mir geposteten Delphi Code) für das Überblenden A über B, dann findest du

alphaC := alphaA + (1-alphaA)*alphaB
und für die Berechnung des Farbwerts gilt
C=(alphaA*A + alphaB*B - alphaA*alphaB*B)/alphaC [0.]


Du hast aber berechnet:
alphaA = B/255 // in alphaA steht also bei dir der Alphakanal Wert von B (statt A)
alphaB = A/255 // und in alphaB steht der Alphakanal Wert von A (statt B)

Da die Funktion zur Berechnung von alphaC symmetrisch [f(x,y)=f(x,y)=x+y-xy) ist, spielt deine Vertauschung der alpha-Werte hier keine Rolle. Der von dir ermittelte Wert von alphaC ist korrekt.

Bei der Berechnung der Farbe rechnest du
C=(alphaA*B + (1-alphaA)*alphaB*A)/alphaC [1.]

Hier hast du nun auch noch A und B vertauscht (aber nicht so, dass sich die "AlphaVertauschung" und die "FarbVertauschung" aufheben) ;-). Du rechnest so, als wäre A unten und B oben.

Vor der Berechnung der Farbwerte korrigierst du die Farbwerte von B wie folgt:
B=alphaB*(A-B)+B [2.]

Wenn wir [2]. in [1]. einsetzen, erhalten wir:

C=(alphaA*(alphaB*(A-B)+B) + (1-alphaA)*alphaB*A)/alphaC
= alphaA*alphaB*A - alphaA*alphaB*B + alphaA*B + alphaB*A-alphaA*alphaB*A)/alphaC
= (alphaB*A + alphaA*B - alphaA*alphaB*B)/alphaC

Wenn du jetzt noch berücksichtigst, dass du alphaA und alphaB vertauscht hast, erhältst du exakt [0.]. Du hast also durch deine Korrektur [2.] die Vertauschungen korrigiert.


Deine Korrektur [2.] ist aber etwas heikel umgesetzt. Du rechnest in deinem Code
B := (ao * (bo - b) shr 8 + b); // hier ergänzt
B ist als Byte definiert und die rechte Seite kann grösser werden als 255. Die Korrektur könnte deshalb in gewissen Fällen misslingen.

Ich würde deshalb deine Vertauschunen zurücktauschen und stattdessen die Originalformel verwenden, also:

alphaC := alphaA + (1-alphaA)*alphaB
C=(alphaA*A + alphaB*B - alphaA*alphaB*B)/alphaC

also etwa so:

Delphi-Quellcode:
procedure AueberB( const a, b : TBitMap; c : TBitMap );

// 0 = "normal"
// 1 = Alpha Korrektur
// Berechne A über B - Resultat in C

var lineA, lineB, lineC : PRGB32Array;
    x, y : integer;
    alphaA, alphaB, alphaC : extended;
label weiter;

begin
  for y := 0 to a.Height-1 do
  begin
    lineA := a.ScanLine[y];
    lineB := b.ScanLine[y];
    lineC := c.ScanLine[y];

    for x := 0 to a.Width-1 do
    begin
      alphaA := lineA[x].A/255;
      alphaB := lineB[x].A/255;

      // 0 = Transparent -  1 = intransparent
      if ( alphaA + alphaB = 0 ) then
          lineC[x] := lineA[x]
      else
      if ( lineA[x].A = 255 ) then // A intransparent => A nehmen
          lineC[x] := lineA[x]
      else
      if ( lineA[x].A = 0 ) then // A transparent => B nehmen
          lineC[x] := lineB[x]
      else
      if ( lineB[x].B = 0 ) then // B transparent => A nehmen
          lineC[x] := lineA[x]
      else
      begin // A und B verrechnen:
           alphaC := alphaA + ( 1 - alphaA )*alphaB;
           lineC[x].A := round(alphaC*255);

           // falls die Farbwerte von A und von B "vormultipliziert mit Alpha" vorliegen:
          (* lineC[x].R := EnsureRange( round((lineA[x].R + (1-alphaA)*lineB[x].R )), 0, 255 );
           lineC[x].G := EnsureRange( round((lineA[x].G + (1-alphaA)*lineB[x].G )), 0, 255 );
           lineC[x].B := EnsureRange( round((lineA[x].B + (1-alphaA)*lineB[x].B )), 0, 255 );
            *)

           // falls Farbwerte nicht vormultipliziert mit Alpha vorliegen:
           lineC[x].R := round(1/alphaC*(alphaA*lineA[x].R + (1-alphaA)*alphaB*lineB[x].R ));
           lineC[x].G := round(1/alphaC*(alphaA*lineA[x].G + (1-alphaA)*alphaB*lineB[x].G ));
           lineC[x].B := round(1/alphaC*(alphaA*lineA[x].B + (1-alphaA)*alphaB*lineB[x].B ));
      end;
    end; {for x := 0 to a.Width-1 do}
  end;{for y := 0 to a.Height-1 do}
end;

Tipp: Du musst keine EnsureRange Funktion nutzen. Die Berechnungen von alpha und Farbwert sind ja genau so konstruiert, dass Sie im Intervall [0..1] (bzw. [0..255]) liegen.

Ich habe gesehen, dass du deine Bitmaps in PNGs umwandelst und dann erst die PNG ins Image lädst. Dies hat wahrscheinlich technische Gründe. TImage kann auch direkt Bitmaps mit AlphaKanal darstellen.

In deinem Code müsstest du ergänzen:
Delphi-Quellcode:
  bmres.PixelFormat := pf32bit; // bisher
  bmres.AlphaFormat := afDefined; // neu
..
..
  // Ausgabe bmRes:
  imgres.Transparent := false;  //neu
  imgres.Picture.Assign( bmres ); //neu

Gruss
Michael

Harry Stahl 9. Jun 2017 00:21

AW: Zwei transparente Bitmaps miteinader verrechnen
 
Danke für den Tipp mit dem TImage.

Ansonsten werde ich mir morgen mal in Ruhe ansehen, ob ich möglicherweise etwas vertauscht habe, könnte so sein. Ergebnis in meinem Programm stimmt jedenfalls (habe ich inzwischen mit dutzenden semitransparenten Überblendsituationen in Photoshop und bei mir verglichen). Die PSP-Datei lade ich mit der ImageEn-Komponente und übernehme daraus die Bitmaps und Alphawerte (letztere sind dort i.d.R. in einem 8-Bit-Bimtap abgelegt) für die Erstellung meiner 32-Alpha-Bitmap, mit der ich intern arbeite und die ganzen Berechnungen vornehme.


Alle Zeitangaben in WEZ +1. Es ist jetzt 02:24 Uhr.
Seite 3 von 3     123   

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