Einzelnen Beitrag anzeigen

Michael II

Registriert seit: 1. Dez 2012
Ort: CH BE Eriswil
724 Beiträge
 
Delphi 11 Alexandria
 
#21

AW: Zwei transparente Bitmaps miteinader verrechnen

  Alt 7. Jun 2017, 19:27
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
Michael Gasser
  Mit Zitat antworten Zitat