Łączenie TPngImage z zachowaniem przezroczystego tła

Łączenie TPngImage z zachowaniem przezroczystego tła
MA
  • Rejestracja: dni
  • Ostatnio: dni
0

Witajcie.
Mam problem z zachowaniem (utrzymaniem) przezroczystego tła obrazka png.
Wyjściowo mam obrazek png z przezroczystym tłem (fragment mapy). Następnie dzielę tę mapę na przykład na 2 obrazki png o wysokości równej połowie wysokości obrazka głównego (tnę obrazek w poziomie na 2 równe części). Kolejno uruchamiam 2 wątki, które wykonują operacje na tych 2 plikach. Ostatecznie muszę złożyć to z powrotem w całość - z zachowaniem przezroczystego tła.

I tu ma problem, z którym nie mogę sobie poradzić. Pomimo, że obie części, które chcę złożyć w całość posiadają przezroczyste tło to nie mogę zachować przezroczystości tła po ich złączeniu. A konkretnie:

Poniższy kod generuje pusty obrazek png. Obraz jest całkowicie przezroczysty i pomimo użycia polecenia Draw obrazek końcowy nie ma ani części dolnej ani górnej :(

Kopiuj

procedure MergePngImages;
var
  MergedPng, TopPartOfPng, BottomPartOfPng: TPngImage;

begin
  TopPartOfPng.LoadFromFile('d:\top.png');
  BottomPartOfPng.LoadFromFile('d:\bottom.png');
  MergedPng := TPngImage.CreateBlank(COLOR_RGBALPHA, 8, 1200, 1200);
  MergedPng.Canvas.Draw(0, 0, TopPartOfPng);
  MergedPng.Canvas.Draw(0, 600, BottomPartOfPng);
  MergedPng.SaveToFile('d:\map.png');
end;

Próbowałem polecenia MergedPng.RemoveTransparency. Poniższy kod z RemoveTransparency sprawia, że górna i dolna część są w końcu widoczne (mały sukces), ale przezroczyste tło obu części wypełniane jest kolorem czarnym.

Kopiuj

procedure MergePngImages;
var
  MergedPng, TopPartOfPng, BottomPartOfPng: TPngImage;

begin
  TopPartOfPng.LoadFromFile('d:\top.png');
  BottomPartOfPng.LoadFromFile('d:\bottom.png');
  MergedPng := TPngImage.CreateBlank(COLOR_RGBALPHA, 8, 1200, 1200);
  MergedPng.Canvas.Draw(0, 0, TopPartOfPng);
  MergedPng.Canvas.Draw(0, 600, BottomPartOfPng);
  MergedPng.RemoveTransparency; // usuń przezroczystość
  MergedPng.SaveToFile('d:\map.png');
end;

W kolejnym podejściu określiłem TransparentColor := clBlack. Poniższy kod działa już "prawie dobrze", bo otrzymuję przezroczyste tło mapy, ale niestety usunięty jest całowicie czarny kolor - czyli wszytskie napisy i oznaczenia na mape:

Kopiuj

procedure MergePngImages;
var
  MergedPng: TPngImage;

begin
  TopPartOfPng.LoadFromFile('d:\top.png');
  BottomPartOfPng.LoadFromFile('d:\bottom.png');
  MergedPng := TPngImage.CreateBlank(COLOR_RGBALPHA, 8, 1200, 1200);
  MergedPng.Canvas.Draw(0, 0, TopPartOfPng);
  MergedPng.Canvas.Draw(0, 600, BottomPartOfPng);
  MergedPng.RemoveTransparency; // usuń przezroczystość
  MergedPng.TransparentColor := clBlack; // ustaw czarny jako przezroczysty
  MergedPng.SaveToFile('d:\map.png');
end;

Bardzo proszę o podpowiedź jak mogę:

  1. stworzyć obrazek png z przezroczystym tłem
  2. następnie narysować na nim ("wstawić") inne obrazki png zachowując przezroczyste tło całości po zapisie
flowCRANE
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Tuchów
  • Postów: 12269
0

Miałem ten sam problem, tyle że w Lazarusie. Tworzenie 32-bitowego obrazu PNG za pomocą klasy TPNG okazało się niemożliwe, bo zawsze wychodził 24-bitowy, więc olałem kombinacje z TLazIntfImage, writerami i innych bullshitem — stworzyłem taki obraz w GIMP-ie, wsadziłem do zasobów i ładowałem jedną linijką kodu. Potem już mogłem bez problemu na nim malować, zarówno zwykłymi metodami, jak i manipulując danymi pikseli, uzyskując do nich dostęp za pomocą metody ScanLine. 😉

Jeśli wystarczy Ci takie obejście, to po prostu wrzuć sobie 32-bitowy oraz do zasobów, w rozmiarach 1x1 piksela, bo miejsca szkoda. Jak będziesz potrzebował stworzyć przezroczyste PNG na potrzeby jakiegoś tła, to stwórz instancję TPngImage, załaduij do niej obraz z zasobów (ten szablon 1x1 px), zmień rozmiar na docelowy, a następnie użyj ScanLine i wyzeruj wszystkie dane pikseli każdego wiersza (użyj FillChar, będzie najwygodniej). No i tyle — rezultatem powinien być przezroczysty obraz.

Spróbuj się w ten sposób pobawić.


Jeśli nie zależy Ci na zapisywaniu gotowego (pomalowanego dynamicznie) orazu na dysku, a chcesz go użyć tylko jako płótna do malowania, to równie dobrze możesz skorzystać z klasy TBitmap, bo format BMP wspiera obrazy 32-bitowe. PNG ma głównie tę przewagę, że jego zawartość jest kompresowana, więc mniej zajmuje. Naotmiast w pamięci i tak operuje się na nieskompresowanych bitmapach — nieważne czy to BMP, czy PNG, JPG czy jeszcze coś innego.

MA
  • Rejestracja: dni
  • Ostatnio: dni
0
flowCRANE napisał(a):

Miałem ten sam problem, tyle że w Lazarusie. Tworzenie 32-bitowego obrazu PNG za pomocą klasy TPNG okazało się niemożliwe, bo zawsze wychodził 24-bitowy, więc olałem kombinacje z TLazIntfImage, writerami i innych bullshitem — stworzyłem taki obraz w GIMP-ie, wsadziłem do zasobów i ładowałem jedną linijką kodu. Potem już mogłem bez problemu na nim malować, zarówno zwykłymi metodami, jak i manipulując danymi pikseli, uzyskując do nich dostęp za pomocą metody ScanLine. 😉

Jeśli wystarczy Ci takie obejście, to po prostu wrzuć sobie 32-bitowy oraz do zasobów, w rozmiarach 1x1 piksela, bo miejsca szkoda. Jak będziesz potrzebował stworzyć przezroczyste PNG na potrzeby jakiegoś tła, to stwórz instancję TPngImage, załaduij do niej obraz z zasobów (ten szablon 1x1 px), zmień rozmiar na docelowy, a następnie użyj ScanLine i wyzeruj wszystkie dane pikseli każdego wiersza (użyj FillChar, będzie najwygodniej). No i tyle — rezultatem powinien być przezroczysty obraz.

Spróbuj się w ten sposób pobawić.


Jeśli nie zależy Ci na zapisywaniu gotowego (pomalowanego dynamicznie) orazu na dysku, a chcesz go użyć tylko jako płótna do malowania, to równie dobrze możesz skorzystać z klasy TBitmap, bo format BMP wspiera obrazy 32-bitowe. PNG ma głównie tę przewagę, że jego zawartość jest kompresowana, więc mniej zajmuje. Naotmiast w pamięci i tak operuje się na nieskompresowanych bitmapach — nieważne czy to BMP, czy PNG, JPG czy jeszcze coś innego.

Wielkie dzięki @flowCRANE za obszerną odpowiedź!
Pomimo wielu prób niestety nie udało mi się „wkleić” na przezroczysty obrazek PNG innych obrazków i zapisać całości nie tracąc przezroczystości. Wyzerowanie wszystkich danych pikseli używając ScanLine i FillChar nie zagwarantowało, że finalny obrazek PNG miał przezroczyste tło. To samo działo się, gdy wczytałem "poprawny" obrazek PNG z przezroczystym tłem (1x1 piksel stworzony w Photoshopie).

Nie wiem, czy nie mam rację ale wychodzi mi na to, że TPngImage po zapisie zamiast przezroczystego tła nadaje mu czarny kolor. Wszystko „sypie” się w momencie gdy mamy coś na wzór typowego PDFa (czyli czarne napisy na białym tle). Wówczas ustawienie tła na czarne sprawia, że tracę wszystkie napisy bo zlewają się one z tłem, które chyba domyślnie jest czarne. Wówczas wywołanie MergedPNG.TransparentColor := clBlack sprawia, że owszem mam przezroczyste tło oraz przy okazji także w gratisie wycięte wszystkie napisy. Raczej lipa.

Ale… dzięki za sugestię z wykorzystaniem TBitmap jako klasy „roboczej”, na której wklejam moje przezroczyste fragmenty PNG, a potem finalnie zapisuję całość do PNG.

Kopiuj

procedure MergePngImages;
var
  MergedBMP: TBitmap;
  MergedPNG: TPngImage;
  RotatedPngImagePart: Array of TPngImage;
  PngPartHeight, PngMergedHeight, PngMergedWidth: Integer;
  i, n: Integer;

begin

PngPartHeight := RotatedPngImagePart[0].Height;
PngMergedWidth := RotatedPngImagePart[0].Width;
PngMergedHeight := Length(RotatedPngImagePart) * PngPartHeight;


  try

  // tworzę roboczy obiekt klasy TBitmap 
  MergedBMP := TBitmap.Create;
  MergedBMP.PixelFormat := pf32bit;
  MergedBMP.Transparent := True;
  MergedBMP.Width := PngMergedWidth;
  MergedBMP.Height := PngMergedHeight;

  // rusyję (wklejam) na roboczym obiekcie klasy TBitmap fragmenty obrazków png
  for i := 0 to Length(RotatedPngImagePart) - 1 do MergedBMP.Canvas.Draw(0, i*PngPartHeight, RotatedPngImagePart[i]);
    
  // tworzę docelowy obiekt klasy TPngImage, do którego przypisuję wcześniejszy obiekt TBitmap z "poprawną" przezroczystością
  MergedPNG := TPngImage.Create;
  MergedPNG.Assign(MergedBMP);
  MergedPNG.SaveToFile('D:\example_rot.png');

  finally
    MergedBMP.Free;
    MergedPNG.Free;
  end;

end;

Wówczas zachowane jest przezroczyste tło obrazka wyjściowego - niezależnie czy wyeksportuję go do BMP czy PNG. 👍

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.