2012-10-03 10 views
5

Pracuję nad aplikacją, która tworzy obrazy, których barwa, nasycenie i wartość zmieniają się w zależności od różnych parametrów. Ze względów wydajności warto renderować elementy barwy, nasycenia i wartości oddzielnie, a następnie komponować je razem, stosując tryby mieszania w stylu programu Photoshop (mnożenie, nakładanie, ekran, odcień itp.).Odtwarzanie kolorów HSV przy użyciu trybów mieszania

Już wiem, jak to zrobić w przypadku obrazów RGB: podziel każdy kanał na własny czerwony, zielony lub niebieski obraz z wartościami od przezroczystości do koloru tego kanału. Warstwa je razem na wierzchu czarny i ustawić ich tryb mieszania na ekranie, a hey presto, masz kolorowy obraz:

RGB image made from components

jaki sposób mogę to zrobić z obrazem określonym przez wartości HSV? Moja aplikacja często zmienia jeden z tych kanałów bez zmiany dwóch pozostałych i przyspieszyłaby moje renderowanie, gdybym mógł połączyć istniejące obrazy na GPU zamiast renderować zupełnie nowy obraz za każdym razem, gdy coś się zmieni.

Oto przykład: an image generated with separate H, S, and V channels

W tym przykładzie, barwa zmienia się od pozycji 0 ° do 360 ° na obwodzie, nasycenie, od 0% do 100% w stosunku do środka do krawędzi, a jego jasność (V) zmienia się od 0% do 100% na obwodzie. Jest to typowy rodzaj obrazu, który tworzy moja aplikacja. Czy istnieje kombinacja typowych trybów mieszania, które mogłem wykorzystać do stworzenia tych kanałów oddzielnie i złożyć je w matematycznie doskonały sposób?

+0

Myślę, że to bardzo interesujące pytanie. Próbuję również znaleźć odpowiedź. Czy coś znalazłeś? – Alejandro

+0

Tak naprawdę grałem z tym niedawno. Nie znalazłem dokładnego rozwiązania, ale ekran jest blisko. Obraz, który stworzyłem w Photoshopie, rzeczywiście wyglądał lepiej (jaśniejsze, bardziej nasycone kolory) niż wersja generowana przez kod, ale oczywiście, jeśli idziesz o dokładność, to go nie wycinam. Być może mógłbyś użyć kombinacji trybów mieszania Hue i Lightness z duplikatami warstw? –

Odpowiedz

6

Moja aplikacja często zmienia jeden z tych kanałów bez zmiany pozostałe dwa, i to przyspieszyć mój rendering gdybym mógł kompozytowe istniejących obrazów na GPU zamiast renderowania zupełnie nowy obraz za każdym razem coś się zmienia. [OP @ZevEisenberg]

W odniesieniu do trwałości i graficznego, że właśnie rzut a conversion function w cieniującego fragment (e.g.). To odczytałoby HSV przechowywane w tekstury lub trzech różnych teksturach, wykonaj konwersję na piksel i wyjście RGB. Ładne i łatwe. Nie widzę żadnej korzyści, aby nie zmieniać innych warstw, ponieważ H, S lub V będą wpływać na wszystkie kanały RGB. Być może przechowywanie pośrednich wyników RGB, takich jak hue=hsv2rgb(H,1,1) i aktualizowanie z final=(hue*S+1-S)*V, buforowanie hue-to-rgb, ale nie sądzę, że warto.

Zresztą, każdy tryb mieszania ma prostą formułę, i można ciąg je razem dla HSV udziałem nadmiernie skomplikowany zestaw tekstur pośrednich, ale to będzie znacznie wolniej głównie z powodu niepotrzebnego tymczasowego magazynowania i przepustowość pamięci. Nie wspominając, próbując przepisać wzór na funkcje mieszania brzmi dość trudne, co z rozgałęzienia, podziałów, fract, zaciskania, absoluty etc ...

Jestem bardzo zainteresowany w roztworze do dzielenia obrazu na jego komponentów HSV i odtworzyć oryginalny obraz przy użyciu trybów mieszania w programie Photoshop. [Bounty, @phisch]

Co do Photoshopa ... Nie jestem stworzony z pieniędzy. Tak więc w gimpie jest Colours -> Components -> Compose/Decompose który robi to za Ciebie. Byłbym nieco zaskoczony, gdyby to nie było w Photoshopie, ale potem też nie. Być może są skrypty/wtyczki Photoshopa, które mogą to zrobić, jeśli nie? Ale konkretnie powiedziałeś mieszanie. Twoje pytanie może uzyskać lepszą uwagę pod adresem https://graphicdesign.stackexchange.com/.Poniżej przedstawiłem pojęcie złożoności i wątpię, czy program Photoshop może to zrobić. Może istnieć wiele sposobów na wartości pikseli poza 0 do 1, ale wtedy możesz napotkać problemy z precyzją, po prostu nie powinno się tego robić.


W każdym razie wyzwanie stanowi wyzwanie, mimo że jest niepraktyczne. Poniższe są tylko dla zabawy.

Zacznę poniższej funkcji (od here) i trzech faktur HSV ...

vec3 hsv2rgb(vec3 c) 
{ 
    vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0); 
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); 
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 
} 

h h h

wiem tylko OpenGL i nie jestem jestem pewien, jak bym to zrobił bez tekstur zmiennoprzecinkowych lub niektórych rozszerzonych funkcji mieszania, więc używam ich. Ale mogę używać tylko mieszania (bez shaderów). Dla stałych zrobię tekstury za pomocą (1,1,1), (1,2/3,1/3), (3,3,3), (6,6,6) (1/255,1/255,1/255), (255,255,255), (1/2,1/2,1/2) i (0,0,0), ponieważ nie mogłem uzyskać skali GL_ZERO za pomocą GL_DIFFERENCE_NV.

  1. Rozpocznij barwy tekstury
  2. stosowanie dodatku mieszania dodać (1,2/3,1/3)
  3. znaleźć część ułamkowa

    1. z subtraktywnego mieszania, odejmowanie 0,5 (to jest dla floor(), ponieważ zakładam, że kolory są konwertowane na 8 bitów. Jeśli nie, pomiń to)
    2. skalowanie w dół o 1/255. można to zrobić za pomocą zwykłego mieszania alfa, ale zamiast tego przeskalowałem z kolorową teksturą.
    3. przechodzą przez nie zmiennoprzecinkowej tekstury zaokrąglić do skali najbliższej 1/255
    4. z powrotem przez 255 (z powrotem do zmiennoprzecinkowej tekstury)

      integer

    5. teraz mamy składowa całkowita. odjąć od tego, co zaczęliśmy

      fractional

  4. skalę przez 6

  5. z subtraktywnego mieszania, weź 3
  6. wziąć bezwzględne wartości

    jestem zamierzam po prostu użyć do tego celu GL_DIFFERENCE_NV, ale bez niego może istnieć sposób użycia dwóch oddzielnych zacisków do następnego kroku. ponieważ i tak negatywy będą zaciśnięte, coś w stylu clamp(p-K.xxx,0,1) + clamp(-p-K.xxx,0,1).

  7. odjąć 1

    enter image description here dobrze, to barwa zrobić

  8. mogła zacisnąć przechodząc przez nie-zmiennoprzecinkowej tekstury, ale tylko zamiar użyć GL_MIN

  9. teraz mogę używać alfa mieszanie dla mix(), ale nasycenie jest ładowane jako obraz czarno-biały bez kanału alfa. ponieważ jest mieszanie biały, robi to ręcznie jest rzeczywiście łatwiej ... skala

    przez nasycenie

  10. dodać 1
  11. odjąć nasycenie

    enter image description here i nasycenie zostało zastosowane

  12. Skala

    według wartości

    enter image description here i jest tam zdjęcie

  13. przerwa kawowa

Wszystko odbywa się za pomocą

  • glBlendEquation z GL_FUNC_REVERSE_SUBTRACT, GL_MIN i GL_DIFFERENCE_NV
  • glBlendFunc

Oto mój kod ...

//const tex init 
constTex[0] = makeTex() with 1, 1, 1... 
constTex[1] = makeTex() with 1, 2/3, 1/3... 
constTex[2] = makeTex() with 3, 3, 3... 
constTex[3] = makeTex() with 6, 6, 6... 
constTex[4] = makeTex() with 1/255, 1/255, 1/255... 
constTex[5] = makeTex() with 255, 255, 255... 
constTex[6] = makeTex() with 1/2, 1/2, 1/2... 
constTex[7] = makeTex() with 0, 0, 0... 

... 

fbo[0] = makeFBO() with GL_RGB 
fbo[1] = makeFBO() with GL_RGB32F 
fbo[2] = makeFBO() with GL_RGB32F 

... 


hsv[0] = loadTex() hue 
hsv[1] = loadTex() value 
hsv[2] = loadTex() saturation 

... 

fbo[1].bind(); 
glDisable(GL_BLEND); 
draw(hsv[0]); //start with hue 
glEnable(GL_BLEND); 
glBlendEquation(GL_FUNC_ADD); 
glBlendFunc(GL_ONE, GL_ONE); //add 
draw(constTex[1]); //(1, 2/3, 1/3) 
glBlendFunc(GL_ONE, GL_ONE); 
fbo[1].unbind(); 

//compute integer part 
fbo[2].bind(); 
glDisable(GL_BLEND); 
draw(*fbo[1].colour[0]); //copy the last bit 
glEnable(GL_BLEND); 
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); 
glBlendFunc(GL_ONE, GL_ONE); //subtract 
draw(constTex[6]); //0.5 
glBlendEquation(GL_FUNC_ADD); 
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale down 
draw(constTex[4]); //1/255 
fbo[2].unbind(); 

fbo[0].bind(); //floor to integer 
glDisable(GL_BLEND); 
draw(*fbo[2].colour[0]); 
fbo[0].unbind(); 

fbo[2].bind(); //scale back up 
glDisable(GL_BLEND); 
draw(*fbo[0].colour[0]); 
glEnable(GL_BLEND); 
glBlendEquation(GL_FUNC_ADD); 
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale up 
draw(constTex[5]); //255 
fbo[2].unbind(); 

//take integer part for fractional 
fbo[1].bind(); 
glEnable(GL_BLEND); 
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); 
glBlendFunc(GL_ONE, GL_ONE); //subtract 
draw(*fbo[2].colour[0]); //integer part 
glBlendEquation(GL_FUNC_ADD); 
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale 
draw(constTex[3]); //6 
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); 
glBlendFunc(GL_ONE, GL_ONE); //subtract 
draw(constTex[2]); //3 
glBlendEquation(GL_DIFFERENCE_NV); 
glBlendFunc(GL_ZERO, GL_ONE); //take the absolute 
draw(constTex[7]); //0 
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); 
glBlendFunc(GL_ONE, GL_ONE); //subtract 
draw(constTex[0]); //1 
glBlendEquation(GL_MIN); 
glBlendFunc(GL_ONE, GL_ONE); //clamp (<0 doesn't matter, >1 use min) 
draw(constTex[0]); //1 
glBlendEquation(GL_FUNC_ADD); 
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale 
draw(hsv[1]); //saturation 
glBlendEquation(GL_FUNC_ADD); 
glBlendFunc(GL_ONE, GL_ONE); //add 
draw(constTex[0]); //1 
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); 
glBlendFunc(GL_ONE, GL_ONE); //subtract 
draw(hsv[1]); //saturation 
glBlendEquation(GL_FUNC_ADD); 
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //scale 
draw(hsv[2]); //saturation 
fbo[1].unbind(); 

fbo[1].blit(); //check result 
Powiązane problemy