Konwersja HBITMAP na przezroczysty HIBTMAP z wyznaczonym kolorem przezroczystości.

0

Cześć.

Na wstępie wybaczcie rozpisanie się. Dodam, że trochę googlowałem i pokombinowałem samodzielnie, ale niewiele mi to dało :/ Sam raczej sobie nie poradzę.

Piszę w Newbie, bo pewnie ktoś podsunie mi rozwiązanie równie proste jak wcześniej @kAzek z przesywaniem ScrollBara. Poza tym ten dział czyta nemalże każdy :) Rozpocznę od tego, że interesują mnie kody WinAPI pod Delphi.

Skończyłem już jakiś czas temu przepisywać swój plugin dla Total Commandera na WinAPI. Jednak okazało się, że rysowanie poprzez WM_DRAWITEM i/lub WM_PAINT ikonek dla staticów powoduje kaszanienie się wyglądu i działania pluginu po załadowaniu przez podgląd z Ctrl+Q pod Total Commanderem po kolei kilkuset plików i krótkiego odtworzenia około sekundy modułów muzycznych. Dlatego z moich testów wynika, że kiedy ustawimy bitmapę dla kontrolki poprzez STM_SETIMAGE sprawia, że plugin działa lepiej.

Button tworzę i ładuję grafikę, która jest plikiem *.ico - w taki sposób:

//...
const
  GFX_BASE = 500;
  Buttons_Icon_Size = 32;

var
  PlayIconH : HICOn;
  IconInfo : TIconInfo;
  PlayBtnHandle : HWND;

 PlayBtnHandle := CreateWindow('Static', '',
    WS_CHILD or WS_VISIBLE or SS_NOTIFY or SS_BITMAP,
    GetControlLeft(ModulePositionSBHandle),
    GetControlTop(ModulePositionSBHandle) + GetControlHeight(ModulePositionSBHandle) + Controls_Vert_Distance,
    Buttons_Icon_Size, Buttons_Icon_Size, ControlsGBHandle, IDC_PLAYBTN, HInstance, nil);
  PlayIconH := LoadImage(HInstance, MAKEINTRESOURCE(GFX_BASE + (1 mod 100)), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);
  GetIconInfo(PlayIconH, IconInfo);
  SendMessage(PlayBtnHandle, STM_SETIMAGE, IMAGE_BITMAP, IconInfo.hbmColor);

I teraz problem, ponieważ mam taką procedurę do rysowania przezroczystego jak poniżej i ona oczywiście działa. Ale chciałbym poznać kod, który pozowoli przekszałcić mi to co siedzi w IconInfo.hbmColor na przezroczystą bitmapę. Ponieważ ten kod rysuje, a nie operuje na bitmapie. I nie mając dużego doświadczenia z grafiką i WinAPI, nie wiem jak go poprawić aby powodował że mogę albo ustawić przezroczysty kolor (tło w moim pluginie można zmieniać z czarnego na te z ustawień tła dla Listera z Total Commander, więc po zmianie, widać pod buttonem domyślne czarne tło).

procedure DrawTransparentBitmap(DC : HDC; HBmp : HBITMAP; XStart, YStart : integer; TransparentColor : COLORREF);
var
  BM : BITMAP;
  PtSize : TPOINT;
  CColor : COLORREF;
  HdcMem, HdcBack, HdcObject, HdcTemp, HdcSave : HDC;
  BMAndBack, BMAndObject, BMAndMem, BMSave, BMBackOld, BMObjectOld, BMMemOld, BMSaveOld : HBITMAP;
begin
  HdcTemp := CreateCompatibleDC(DC);
  SelectObject(HdcTemp, HBmp);
  GetObject(HBmp, SizeOf(BITMAP), @BM);
  PtSize.X := BM.bmWidth;
  PtSize.Y := BM.bmHeight;
  DPToLP(HdcTemp, PtSize, 1);
  HdcBack := CreateCompatibleDC(DC);
  HdcObject := CreateCompatibleDC(DC);
  HdcMem := CreateCompatibleDC(DC);
  HdcSave := CreateCompatibleDC(DC);
  BMAndBack := CreateBitmap(PtSize.X, PtSize.Y, 1, 1, nil);
  BMAndObject := CreateBitmap(PtSize.X, PtSize.Y, 1, 1, nil);
  BMAndMem := CreateCompatibleBitmap(DC, PtSize.X, PtSize.Y);
  BMSave := CreateCompatibleBitmap(DC, PtSize.X, PtSize.Y);
  BMBackOld := SelectObject(HdcBack, BMAndBack);
  BMObjectOld := SelectObject(HdcObject, BMAndObject);
  BMMemOld := SelectObject(HdcMem, BMAndMem);
  BMSaveOld := SelectObject(HdcSave, BMSave);
  SetMapMode(HdcTemp, GetMapMode(DC));
  BitBlt(HdcSave, 0, 0, PtSize.X, PtSize.Y, HdcTemp, 0, 0, SRCCOPY);
  CColor := SetBkColor(HdcTemp, TransparentColor);
  BitBlt(HdcObject, 0, 0, PtSize.X, PtSize.Y, HdcTemp, 0, 0, SRCCOPY);
  SetBkColor(HdcTemp, CColor);
  BitBlt(HdcBack, 0, 0, PtSize.X, PtSize.Y, HdcObject, 0, 0, NOTSRCCOPY);
  BitBlt(HdcMem, 0, 0, PtSize.X, PtSize.Y, DC, XStart, YStart, SRCCOPY);
  BitBlt(HdcMem, 0, 0, PtSize.X, PtSize.Y, HdcObject, 0, 0, SRCAND);
  BitBlt(HdcTemp, 0, 0, PtSize.X, PtSize.Y, HdcBack, 0, 0, SRCAND);
  BitBlt(HdcMem, 0, 0, PtSize.x, PtSize.y, HdcTemp, 0, 0, SRCPAINT);
  BitBlt(DC, XStart, YStart, PtSize.X, PtSize.Y, HdcMem, 0, 0, SRCCOPY);
  BitBlt(HdcTemp, 0, 0, PtSize.X, PtSize.Y, HdcSave, 0, 0, SRCCOPY);
  DeleteObject(SelectObject(HdcBack, BMBackOld));
  DeleteObject(SelectObject(HdcObject, BMObjectOld));
  DeleteObject(SelectObject(HdcMem, bMMemOld));
  DeleteObject(SelectObject(HdcSave, BMSaveOld));
  DeleteDC(HdcMem);
  DeleteDC(HdcBack);
  DeleteDC(HdcObject);
  DeleteDC(HdcSave);
  DeleteDC(HdcTemp);
end;

Probówałem też tłumaczyć kod z http://www.codeproject.com/Articles/2841/How-to-replace-a-color-in-a-HBITMAP na Delphi, ale po takich operacjach jak poniżej nie działa. Pewnie tę pętlę trzeba przetłumaczyć inaczej, ale wlaśnie nie wiem jak. A z tego co mi się zdaję to taki kod działający pod Delphi by mnie urządzał. Czekam na przykłady od Was i z góry dziekuję.

type
  PByteArray = ^TByteArray;
  TByteArray = array[0..32767] of Byte;

function COLORREF2RGB(Color : COLORREF) : COLORREF;
begin
  Result := (Color and $FF00) or ((Color shr 16) and $FF) or ((Color shl 16) and $FF0000);
end;

function ReplaceColor(hBmp : HBITMAP; cOldColor : COLORREF; cNewColor : COLORREF; hBmpDC : HDC) : HBITMAP;
var
  BM : BITMAP;
  I : integer;
  ptPixels : PUINT;
  BufferDC, DirectDC : HDC;
  RetBmp, hTmpBitmap, DirectBitmap : HBITMAP;
  PreviousBufferObject, PreviousObject : HGDIOBJ;
  RGB32BitsBITMAPINFO : TBitmapInfo;
begin
  RetBmp := 0;
  if hBmp > 0 then
  begin
    BufferDC := CreateCompatibleDC(0); // DC for Source Bitmap
    if BufferDC > 0 then
    begin
      if hBmpDC > 0 then
        if hBmp = HBITMAP(GetCurrentObject(hBmpDC, OBJ_BITMAP)) then
        begin
          hTmpBitmap := CreateBitmap(1, 1, 1, 1, nil);
          SelectObject(hBmpDC, hTmpBitmap);
        end;
      PreviousBufferObject := SelectObject(BufferDC, hBmp);
      // here BufferDC contains the bitmap

      DirectDC := CreateCompatibleDC(0); // DC for working
      if DirectDC > 0 then
      begin
        // Get bitmap size
        GetObject(hBmp, SizeOf(BM), @BM);

        // create a BITMAPINFO with minimal initilisation
        // for the CreateDIBSection
        ZeroMemory(@RGB32BitsBITMAPINFO, Sizeof(TBitmapInfo));
        RGB32BitsBITMAPINFO.bmiHeader.biSize := Sizeof(BITMAPINFOHEADER);
        RGB32BitsBITMAPINFO.bmiHeader.biWidth := bm.bmWidth;
        RGB32BitsBITMAPINFO.bmiHeader.biHeight := bm.bmHeight;
        RGB32BitsBITMAPINFO.bmiHeader.biPlanes := 1;
        RGB32BitsBITMAPINFO.bmiHeader.biBitCount := 32;

        // pointer used for direct Bitmap pixels access

        DirectBitmap := CreateDIBSection(DirectDC,
          TBitmapInfo(RGB32BitsBITMAPINFO),
          DIB_RGB_COLORS,
          Pointer(ptPixels),
          0, 0);
        if DirectBitmap > 0 then
        begin
          // here DirectBitmap!=NULL so ptPixels!=NULL no need to test
          PreviousObject := SelectObject(DirectDC, DirectBitmap);
          BitBlt(DirectDC, 0, 0,
            bm.bmWidth, bm.bmHeight,
            BufferDC, 0, 0, SRCCOPY);

          // here the DirectDC contains the bitmap

       // Convert COLORREF to RGB (Invert RED and BLUE)
          cOldColor := COLORREF2RGB(cOldColor);
          cNewColor := COLORREF2RGB(cNewColor);

          // After all the inits we can do the job : Replace Color
          for I := (bm.bmWidth * bm.bmHeight) - 1 downto 0 do
          begin
            if PByteArray(ptPixels)^[I] = cOldColor then
            begin
              PByteArray(ptPixels)^[I] := cNewColor;
            end;
          end;
          // little clean up
          // Don't delete the result of SelectObject because it's
          // our modified bitmap (DirectBitmap)
          SelectObject(DirectDC, PreviousObject);

          // finish
          RetBmp := DirectBitmap;
        end;
        // clean up
        DeleteDC(DirectDC);
      end;
      if hTmpBitmap > 0 then
      begin
        SelectObject(hBmpDC, hBmp);
        DeleteObject(hTmpBitmap);
      end;
      SelectObject(BufferDC, PreviousBufferObject);
      // BufferDC is now useless
      DeleteDC(BufferDC);
    end;
  end;
  Result := RetBmp;
end;
1

Ok, pogooglowałem jeszcze i udało mi się znaleźć dobry kod na:
http://www.dreamincode.net/forums/topic/281612-how-to-make-bitmaps-on-menus-transparent-in-c-win32/
Po przetłumaczeniu i poprawieniu wielkości znaków nazewnictwa wygląda on tak:

function ReplaceTransparentColor(SourceBitmap : HBITMAP; ReplaceWithColor : COLORREF) : HBITMAP;
var
  BM : BITMAP;
  ClrTP : COLORREF;
  NRow, NCol : integer;
  HDCSrc, HDCDst : HDC;
begin
  Result := 0;
  HDCSrc := CreateCompatibleDC(0);
  if HDCSrc > 0 then
  begin
    HDCDst := CreateCompatibleDC(0);
    if HDCDst > 0 then
    begin
      GetObject(SourceBitmap, SizeOf(BM), @BM);
      SelectObject(HDCSrc, SourceBitmap);
      Result := CreateBitmap(BM.bmWidth, BM.bmHeight, BM.bmPlanes, BM.bmBitsPixel, nil);
      SelectObject(HDCDst, Result);
      BitBlt(HDCDst, 0, 0, BM.bmWidth, BM.bmHeight, HDCSrc, 0, 0, SRCCOPY);
      ClrTP := GetPixel(HDCDst, 0, 0);
      for NRow := 0 to BM.bmHeight - 1 do
        for NCol := 0 to BM.bmWidth - 1 do
        begin
          if GetPixel(HDCDst, NCol, NRow) = ClrTP then
          begin
            SetPixel(HDCDst, NCol, NRow, ReplaceWithColor);
          end;
        end;
      DeleteDC(HDCDst);
    end;
    DeleteDC(HDCSrc);
  end;
end;

I robi dokładnie to co chciałem. Niestety mimo tego, że nie używałem do rysowania kontrolek zdarzeń z WM_DRAWITEM to i tak mój plugin się krzaczy. Być może na oficjalnym forum supportu Total Commandera uzyskam jakieś podpowiedzi dlaczego. Bo kod jest ok, jeżeli chodzi o obsługę i wygląd pluginy. Jednakże coś przeszkadza mu żeby działać nadal kiedy załaduje kilkaset modułów muzycznych. Nawet jeśli za każdym razem zwalniam i tworzę na nowo caly obiekt - okno pluginu.

0

Jednakże coś przeszkadza mu żeby działać nadal kiedy załaduje kilkaset modułów muzycznych.
Wyciek? A może te kilkaset to po prostu za dużo, bo alokujesz przy każdej pozycji jakieś uchwyty?

0

SetPixel i GetPixel - są okropnie powolne. Obejrzyj jak to się robi w VCL przy TBitBtn. Trzy normalnych rysowania załatwiają całość.

1

Trochę to zoptymalizowałem bo jak pisze @_13th_Dragon to GetPixel i SetPixel przy większych bitmapach to jakaś masakra.

function ReplaceTransparentColor(SourceBitmap : HBITMAP; ReplaceWithColor : COLORREF) : HBITMAP;
var
  BM: BITMAP;
  BMI: BITMAPINFO;
  ClrTP: COLORREF;
  HDCSrc, HDCDst: HDC;
  Bits: array of COLORREF;
  i: Integer;
begin
  Result := 0;
  HDCSrc := CreateCompatibleDC(0);
  if HDCSrc > 0 then
  begin
    HDCDst := CreateCompatibleDC(0);
    if HDCDst > 0 then
    begin
      GetObject(SourceBitmap, SizeOf(BM), @BM);
      SetLength(Bits, BM.bmWidth * BM.bmHeight);
      SelectObject(HDCSrc, SourceBitmap);
      ZeroMemory(@BMI, SizeOf(BITMAPINFO));
      BMI.bmiHeader.biSize:= SizeOf(BMI.bmiHeader);
      BMI.bmiHeader.biWidth:= BM.bmWidth;
      BMI.bmiHeader.biHeight:= BM.bmHeight;
      BMI.bmiHeader.biPlanes:= BM.bmPlanes;
      BMI.bmiHeader.biBitCount:= BM.bmBitsPixel;
      BMI.bmiHeader.biCompression:= BM.bmType;
      GetDIBits(HDCSrc, SourceBitmap, 0, BM.bmHeight, Bits, BMI, DIB_RGB_COLORS);
      Result:= CreateCompatibleBitmap(HDCSrc, BM.bmWidth, BM.bmHeight);
      SelectObject(HDCDst, Result);
      ClrTP:= Bits[High(Bits) - BM.bmWidth + 1]; //pixel 0,0
      for i:= Low(Bits) to High(Bits) do
      begin
        if Bits[i] = ClrTP then
          Bits[I]:= ReplaceWithColor shr 16 or ReplaceWithColor and $FF00 or ReplaceWithColor and $FF shl 16;
      end;
      SetDIBits(HDCDst, Result, 0, BM.bmHeight, Bits, BMI, DIB_RGB_COLORS);
      DeleteDC(HDCDst);
    end;
    DeleteDC(HDCSrc);
  end;
end;

EDIT//Poprawka odwróciłem składowe kolorów z R z G

0

Dziękuję @kAzek, poprawię jutro kiedy będę w domu, bo docelowo bitmapa, na której operowałem miała 32x32 pikseli, ale przyszłościowo mogło by to trwać za długo. @Azarien co do wycieku to możliwe, a co do zwalniania zasobów to poprzedni moduł przed odtworzeniem jest zwalniany. Jednak pluginy .wlx dla Total Commandera mają też możliwość obsługi funkcji LoadNext(W). Dzięki niej okno nie jest tworzone na nowo, a można jedynie załadować kolejny wybrany plik. Jednak kiedy zakomentowałem export tej funkcji i za każdym razem plugin tworzył okno i kontrolki na nowo oraz zwalniał przy przejściu do kolejnego pliku wraz z biblioteką bass w zasobach, to "kaszanienie" nadal miało miejsce. A docelowo plugin ma być używany kiedy mamy na przykład pobranie wiele modułów i chcemy sobie zostawić tylko te, które nam się spodobają po krotkiej chwili przesłuchania każdego z nich.

0

@kAzek: jednak musiałem poprawić Twoją funkcję, bo nie zamieniała jak trzeba kolorów. Poprawka jest mała objętościowo, ale dla prawidłowości działania dość znacząca. Inaczej tło było złego koloru (odwrotnego niż podano) Anyway, jeszcze raz dziękuję za szybkie rozwiązanie. Dla 20 pomiarów w pętli zmierzonych GetTickCount dla większej bitmapy (482x306 pikseli) wyszło za każdą iteracją zero. Natomiast poprzednia wersja kodu jaką ja podałem oscylowała już w okolicach kilkudziesięciu milisekund.

function ReplaceTransparentColor(SourceBitmap : HBITMAP; ReplaceWithColor : COLORREF) : HBITMAP;
var
  I : integer;
  BM : BITMAP;
  R, G, B : Byte;
  BMI : BITMAPINFO;
  ClrTP : COLORREF;
  HDCSrc, HDCDst : HDC;
  Bits : array of COLORREF;
begin
  Result := 0;
  HDCSrc := CreateCompatibleDC(0);
  if HDCSrc > 0 then
  begin
    HDCDst := CreateCompatibleDC(0);
    if HDCDst > 0 then
    begin
      GetObject(SourceBitmap, SizeOf(BM), @BM);
      SetLength(Bits, BM.bmWidth * BM.bmHeight);
      SelectObject(HDCSrc, SourceBitmap);
      ZeroMemory(@BMI, SizeOf(BITMAPINFO));
      BMI.bmiHeader.biSize := SizeOf(BMI.bmiHeader);
      BMI.bmiHeader.biWidth := BM.bmWidth;
      BMI.bmiHeader.biHeight := BM.bmHeight;
      BMI.bmiHeader.biPlanes := BM.bmPlanes;
      BMI.bmiHeader.biCompression := BM.bmType;
      BMI.bmiHeader.biBitCount := BM.bmBitsPixel;
      GetDIBits(HDCSrc, SourceBitmap, 0, BM.bmHeight, Bits, BMI, DIB_RGB_COLORS);
      Result := CreateCompatibleBitmap(HDCSrc, BM.bmWidth, BM.bmHeight);
      SelectObject(HDCDst, Result);
      ClrTP := Bits[High(Bits) - BM.bmWidth + 1];
      for i := Low(Bits) to High(Bits) do
      begin
        if Bits[I] = ClrTP then
        begin
          R := GetRValue(ReplaceWithColor);
          G := GetGValue(ReplaceWithColor);
          B := GetBValue(ReplaceWithColor);
          Bits[I] := RGB(B, G, R);
        end;
      end;
      SetDIBits(HDCDst, Result, 0, BM.bmHeight, Bits, BMI, DIB_RGB_COLORS);
      DeleteDC(HDCDst);
    end;
    DeleteDC(HDCSrc);
  end;
end;

1 użytkowników online, w tym zalogowanych: 0, gości: 1