TProgressBar - Pobranie koloru Paska postępu i jego tła

0

Nowy tydzień, nowe problemy.
Jak już pisałem wcześniej, z grafiką jestem na bakier... a interfejs musi wyglądać...
Mam taki problem. Potrzebuję pobrać kolor paska postępu TProgressBar (tpBar oraz tpChunk). Kolor, który kontrolka przyjmuje z systemu.
Jeśli system ma włączone Motywy (Windows Theme) to można to zrobić tak:

uses Vcl.Themes;

var
   LStyles  : TCustomStyleServices;
   Details  : TThemedElementDetails;
   LRect    : TRect;
begin
   if StyleServices.Enabled then
      begin
         LRect := PAINTBOX.ClientRect;
         Details := StyleServices.GetElementDetails(tpBar);
         //Details := StyleServices.GetElementDetails(tpChunk);

         StyleServices.DrawElement(PAINTBOX.Canvas.Handle, Details, LRect);
      end
end;

Powyższy kod pobiera szczegóły dla danej kontrolki i maluje je na płótnie TPaintBox... Teoretycznie mam interesujący mnie kolor wymalowany gdzie chcę.
ALE, ja chcę pobrać sam Kolor (TColor) - żeby móc stworzyć wykres w odpowiednich kolorach (czyli pasek postępu na danym tle).

Jak to się robi? Czy wystarczy pobrać kolor z danego piksela naszej kontrolki TPaintBox (na której właśnie namalowałem ten kolor) - przy czym nie wiem czy on jest jednolity.
Zdaję się Canvas.Pixels(x,y) pozwala na pobranie koloru... ale czy to jest dobra metoda?

Ten kod pobiera odpowiednie kolory:

var
   LStyles  : TCustomStyleServices;
   Details     : TThemedElementDetails;
   LRect       : TRect;
   iUsedColor  : TColor;
   iBkgColor   : TColor;
   BMP         : TBitMap;
begin
   if StyleServices.Enabled then
      begin
         BMP := TBitmap.Create;
         try
            BMP.Width  := 20;
            BMP.Height := 20;
            LRect := Rect(0, 0, BMP.Width, BMP.Height);

            // GET BACKGROUND COLOR (tpBar)
            Details := StyleServices.GetElementDetails(tpBar);
            StyleServices.DrawElement(BMP.Canvas.Handle, Details, LRect);
            iBkgColor := BMP.Canvas.Pixels[BMP.Width div 2, BMP.Height div 2];

            // GET PROGRESS COLOR (tpChunk)
            Details := StyleServices.GetElementDetails(tpChunk);
            StyleServices.DrawElement(BMP.Canvas.Handle, Details, LRect);
            iUsedColor := BMP.Canvas.Pixels[BMP.Width div 2, BMP.Height div 2];

            ShowMessage('Progress: ' + '$' + IntToHex(iUsedColor) + #10#13 +
                        'Background: ' + '$' + IntToHex(iBkgColor) + #10#13 +
                       '');

         finally
            BMP.Free;
         end;
      end
   else
      begin

      end;

Ale - jak widać, pobieram kolor piksela ze środka bitmapy - a pasek postępu lubi być gradientem, więc ta metoda daje "uśredniony wynik"...

Może piszę niezrozumiale - chodzi o to, że system rysuje kontrolkę postępu TProgressBar na swój sposób (pasek postępu i tła mają przypisane odpowiednie kolory, w zależności od motywu) - chcę pobrać te kolory i znając je mogę stworzyć własny wykres z kolorami zgodnymi z systemem.

Ps: A co jeśli system ma wyłączone motywy? Jak wtedy pobrać te kolory?

-Pawel

0

I tu jest właśnie problem, bo pasek postępu może być czymkolwiek — może być jednokolorowym prostokątem, może być prostokątem gradientowym, ale może też mieć nieregularny kształt (np. prostokąt o zaokrąglonych rogach) czy być zbudowany z kostek (jak w WinXP). A jakby jeszcze tego było mało, w dodatku może być animowany, czyli nie tylko kolory mogą się zmieniać w każdej klatce animacji, ale też kształt.

Ogólnie, pasek może być wszystkim, dlatego bazowanie na kolorze nie zapewni kompatybilności ze wszystkimi używanymi motywami (szczególnie z tymi firm trzecich). Przy czym systemowe motywy co najmniej od WinXP obsługują graficzne wodotryski, więc bazowanie na kolorze w ogóle nie wchodzi w grę.



Jeśli chcesz wyrenderować pasek postepu w swoim kołowym wykresie, to jedynym zawsze dobrze działającym rozwiązaniem jest wyrenderowania paska w wysokiej rozdzielczości na bitmapie pomocniczej (w formie długiego paska o odpowiednim postępie), a następnie skorzystanie z algorytmu transformacji, który taki pasek wygnie w obręcz. Wyższa rozdzielczość paska w bitmapie pomocniczej pozwoli uniknąć pikselozy w wynikowym obrazie.

Niestety nie mam bladego pojęcia o transformacji obrazów, o ich warpowaniu, wyginaniu itd., dlatego nic sensownego nie jestem w stanie podpowiedzieć, jeśli o taki algorytm chodzi. Jednak nie ma się co łamać — da się coś takiego napisać samemu, wykorzystując dość proste operacje matematyczne, operujące na okręgach. W końcu jedyne co potrzeba, to wyznaczenie okręgu o odpowiedniej grubości oraz punktu środka, a następnie próbkowanie grafiki źródłowej i przepisywanie pikseli do bitmapy wynikowej. Samo próbkowanie możesz na początek przeprowadzić wykorzystując skalowanie nearest, czyli po prostu przepisywać kolor piksela z jednego obrazu do drugiego. Grafika docelowa będzie rozpikselowana, ale jak już znajdziesz działające rozwiązanie, to będzie można wymienić nearest zaimplementować jakąś interpolację.

Ważne jest tutaj, aby ten algorytm napisać w formie optymalnej, czyli pętla powinna jechać po każdym pikselu okręgu, a nie po każdym pikselu całego obrazu z tym okręgiem. W tym drugim przypadku, wydajność będzie dziesiątki razy gorsza, bo 90% pikseli w wynikowym obrazie będzie przecież kolorem tła, więc próbkowanie nie będzie miało sensu.

0

@Paweł Dmitruk: Dzięki!
Te funkcje działają jednak tylko w trybie klasycznym
""This function only affects the classic mode, not any visual style."

Ale, w tym trybie przypisywałem domyślne kolory - spróbuje użyć tych komunikatów (będzie zgodnie z system).
EDIT: Nie działa wysyłanie komunikatu. Robię to tak (do testu używam istniejącego paska z postępem na 50%):
result := SendMessage(PROGRESSBAR.Handle, PBM_GETBARCOLOR, 0, 0);
result := SendMessage(PROGRESSBAR.Handle, PBM_GETBKCOLOR, 0, 0);
Zawsze zwraca kolor czarny ($FF000000).

@furious programming: Tak, samo pobranie koloru udało mi się zrealizować, ale jest to rozwiązanie nieoptymalne, z uwagi na fakt, że pasek może być różny. Moja metoda zadziała tylko dla prostych pasków z jednolitym kolorem. Ale coś muszę wyświetlić, więc dobre i to (w ostateczności nie musi być zgodne z systemem, ale powinno).

Co do pomysłu z transformacją... grubo! Nie dość, że nie umiem :P, to chyba jednak zbyt złożone (prościej użyć jednolitego koloru i już).

0

Nie jest to znowu takie trudne — gdyby trzeba było prostokąt powyginać w węża, to byłoby to bardzo skomplikowane matematycznie, ale w przypadku okręgu jest to znacznie, znacznie łatwiejsze. Zaleta jest taka, że w ten sposób możesz obsłużyć dowolny pasek o dowolnym kształcie i zawartości, przyrelatywnie niskim stopniu skomplikowania kodu oraz w miarę dużej wydajności — wyrenderowanie obrazu wynikowego nie powinno zająć więcej niż kilkadziesiąt milisekund.

No ale rób jak chcesz — samo skorzystanie z systemowego koloru zapewni zgodność ze schematem kolorów aktywnego motywu, jednak niestety nie zapewni zgodności z komponentami klasy TProgressBar. Tak więc tutaj albo trzeba iść na graficzny kompromis, albo skusić się na napisanie transformacji. ;)

0

Tym kombajnem zrobisz wszystko https://github.com/skia4delphi/skia4delphi/

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