Funkcja Canvas.Draw ze stopniem przeźroczystości bitmapy i obsługą przeźroczystego koloru

Piotrekdp

Oto uniwerslna funkcja rysująca grafikę o dowolnym procencie przeźroczystości z możliwością ustawienia przeźroczystego koloru

Dzięki tej procedurze możemy narysować grafikę prześwitującą w podanym procencie zachowując przy tym "normalny" przeźroczysty kolor.

Przykład

Dysponując takim oto rysunkiem :

user image

Po ustawieniu w btmapie koloru przeźroczystego na niebieski
można powyższy obrazek narysować (nawet na pulpicie) tak :

user image

  <b>Parametry Procedury DrawTransparency()</b>

*Canvas - Canvas (płótno)Na którym rysujemy bitmapę.
*X,Y - pozycja jak w zwykłym Canvas.Draw().

  • Transparency-stopień przeźroczystości -przyjmuje wartości od 0 do 100.

Aby bitmapa była narysowana z uwzględnieniem przeźroczystego koloru "tradycyjnie" ustawiamy :

Bitmapka.TransparentColor=clBlack;
Bitmapka.Transparent:=True;

i wywołujemy procedurę DrawTransparency() z parametrami :)

procedure DrawTransparency(Canvas:TCanvas;X,Y:Integer ;Bitmap:TBitmap;Transparency:Byte);
var
Temp:TBitmap;
ByteSrc,ByteDest:^Byte;
TripleSrc,TripleDest:^TRGBTriple;
TransparentColor:TRGBTriple;
H,V:Integer;
begin
Bitmap.PixelFormat:=pf24bit;
Temp:=TBitmap.Create;
Temp.Canvas.Brush.Color :=Bitmap.TransparentColor;  //ustawiam sobie aby bitmapa była odrazu w kolorze przeźroczystym ...
Temp.Width :=Bitmap.Width ;                        //...niezależnie od  tego czy przeżrczystość jest czy jej nie ma
Temp.Height :=Bitmap.Height ;
Temp.PixelFormat:=pf24bit;
Temp.Canvas.CopyRect(Rect(0,0,Bitmap.Width ,Bitmap.Height ),Canvas,Rect(X,Y,Bitmap.Width+X ,Bitmap.Height+Y ));

if Bitmap.Transparent then
//Jak Jest Przeźroczystośc to mamy dużo roboty bo musimy omijać w modyfikacji kolor przeżroczysty
  begin
  {Zamieniam Kolor Przeżroczysty TColor na TRGBTriple do porównań}


  TransparentColor.rgbtBlue :=(Bitmap.TransparentColor and $FF0000) shr 16 ;
  TransparentColor.rgbtGreen :=(Bitmap.TransparentColor and $00FF00) shr 8 ;
  TransparentColor.rgbtRed  :=Bitmap.TransparentColor and $0000FF;

  Temp.TransparentColor :=Bitmap.TransparentColor;
  Temp.Transparent :=True; //Jak bitmapka jest przeźroczysta to i po przeróbce też

for V:=0 to Bitmap.Height -1 do
  begin
    TripleSrc:=Bitmap.ScanLine[V];
     TripleDest:=Temp.ScanLine[V];
  for H:=0 to Bitmap.Width -1 do
      begin
     if(TransparentColor.rgbtBlue <> TripleSrc.rgbtBlue) or
       ( TransparentColor.rgbtGreen <> TripleSrc.rgbtGreen) or
       (TransparentColor.rgbtRed  <> TripleSrc.rgbtRed )  then

          begin
          TripleDest^.rgbtBlue :=Trunc((TripleDest^.rgbtBlue / 100)*Transparency+
          (TripleSrc^.rgbtBlue  / 100)*(100-Transparency));

          TripleDest^.rgbtGreen :=Trunc((TripleDest^.rgbtGreen / 100)*Transparency+
          (TripleSrc^.rgbtGreen  / 100)*(100-Transparency));

          TripleDest^.rgbtRed :=Trunc((TripleDest^.rgbtRed / 100)*Transparency+
          (TripleSrc^.rgbtRed  / 100)*(100-Transparency));
          end;

      inc(TripleSrc);
      inc(TripleDest);
     end ;
  end;
end
   else     // Gdy nie mamy przeźroczystosci koloru to lecimy po bajtach dlatego osobna wersja bo bez if-ów i szybciej
begin
for V:=0 to Bitmap.Height -1 do
  begin
    ByteSrc:=Bitmap.ScanLine[V];   // nasza bitmapka
    ByteDest:=Temp.ScanLine[V];    // bitmapka reprezetująca "Canvas"
  for H:=0 to Bitmap.Width*3 -1 do
    begin
    ByteDest^:=Trunc((ByteDest^ / 100)*Transparency+   // procent koloru tła
    (ByteSrc^ / 100)*(100-Transparency));            // procent koloru Bitmapy
    inc(ByteSrc);
    inc(ByteDest);
    end;
  end;
end;
  Canvas.Draw(X,Y,Temp); //Rysujemy Bitmapke
  Temp.Free;            
end;

5 komentarzy

A czy da się to jakoś przerobić by stworzyć funkcję do kopiowania fragmentu obrazu PNG z przezroczystościami do innej zmiennej z obrazem PNG??

Załóżmy miałbym dwie zmienne przechowujące obrazy: a i b.
Do a wczytuję duży obrazek, a do b mam skopiować tylko jego fragment z zachowaniem przezroczystości.
Coś ala CopyRect() z Canvasu.

Może ktoś podać przykład wywołania tej funkcji z artykułu?

Jeśli obie bitmapy mogą być 32 bitowe i już zawierać informacje o przezroczystości (na przykład dla LayeredWindow), to proponuję taki kod:

////////////////////////////////////////////////////////
// X,Y - współrzędne rysowania bitmapy źródłowej na docelowej
// Source, Destination - bitmapy źródłowa i docelowa
// Alpha - wartość przezroczystości dla 24 bitowej bitmapy źródłowej
//
// Jeśli obie są 32 bit, wylicza przezroczystości
// Jeśli źródłowa jest 24 bit, uznaje ją za jej przezroczystość uznaje parametr Alpha
// Jeśli docelowa nie jest 32 bit, wywołuje standardową funkcję Draw
////////////////////////////////////////////////////////

type
  TLinia32 = array[WORD] of TRGBQuad;
  PLinia32 = ^TLinia32;
  TLinia24 = array[WORD] of TRGBTriple;
  PLinia24 = ^TLinia24;

procedure DrawTransparency(X,Y:integer; Source:TBitmap; Destination:TBitmap; Alpha:byte = 255);
var dx,dy:integer;
    S,D:TRGBQuad;
    S24:TRGBTriple;
    SLinia, DLinia:PLinia32;
    SLinia24:PLinia24;
begin
if (Source.PixelFormat=pf32bit) and (Destination.PixelFormat=pf32bit) then
  begin
  for dy:=0 to Source.Height-1 do
    begin
    SLinia:=Source.ScanLine[dy];
    DLinia:=Destination.ScanLine[Y+dy];
    for dx:=0 to Source.Width-1 do
      begin
      S:=SLinia[dx];
      D:=DLinia[X+dx];
      DLinia[X+dx].rgbRed:=MulDiv(S.rgbRed,S.rgbReserved,255)+MulDiv(D.rgbRed,255-S.rgbReserved,255);
      DLinia[X+dx].rgbGreen:=MulDiv(S.rgbGreen,S.rgbReserved,255)+MulDiv(D.rgbGreen,255-S.rgbReserved,255);
      DLinia[X+dx].rgbBlue:=MulDiv(S.rgbBlue,S.rgbReserved,255)+MulDiv(D.rgbBlue,255-S.rgbReserved,255);
      DLinia[X+dx].rgbReserved:=D.rgbReserved+MulDiv(255-D.rgbReserved, S.rgbReserved, 255);
      end;
    end;
  end
else if (Source.PixelFormat=pf24bit) and (Destination.PixelFormat=pf32bit) then
  begin
  for dy:=0 to Source.Height-1 do
    begin
    SLinia24:=Source.ScanLine[dy];
    DLinia:=Destination.ScanLine[Y+dy];
    for dx:=0 to Source.Width-1 do
      begin
      S24:=SLinia24[dx];
      D:=DLinia[X+dx];
      DLinia[X+dx].rgbRed:=S24.rgbtRed;
      DLinia[X+dx].rgbGreen:=S24.rgbtGreen;
      DLinia[X+dx].rgbBlue:=S24.rgbtBlue;
      DLinia[X+dx].rgbReserved:=Alpha;
      end;
    end;
  end
else
  Destination.Canvas.Draw(X,Y,Source);
end;

Malutkie sprostowanie ww przykładzie napisałem sobie kolor z głowy -clBlack , natomiast wiadomo że jak rysowałem "duszka" ustawiłem clBlue ; :)

a co do samej zasady dzałania kodu jest bardzo prosta ,
Kopiuje kawałeczek tła spod spodu bitmapki i biorę kazdy pixel wyliczając procentowo kolory
jeśli np przeźroczystośc to 20% to wyświetlamy kolor składający się w 80% z koloru "wieszchniego"
i 20% z pod spodu - proste :)

Przydatny art!