1%%
2%%  wings_color.erl --
3%%
4%%     Color utilities.
5%%
6%%  Copyright (c) 2001-2011 Bjorn Gustavsson
7%%
8%%  See the file "license.terms" for information on usage and redistribution
9%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10%%
11%%     $Id$
12%%
13
14-module(wings_color).
15-export([init/0,choose/1,choose/2,choose/3,
16	 share/1,store/1,average/1,average/2,mix/3,white/0,
17	 rgb_to_hsv/1,rgb_to_hsv/3,hsv_to_rgb/1,hsv_to_rgb/3,
18	 rgba_to_rgb/1, rgb3bv/1, rgb4bv/1, rgb3fv/1, rgb4fv/1,
19	 def_palette/0
20	]).
21
22-include("wings.hrl").
23-import(lists, [foreach/2,keyfind/3]).
24
25-define(BLACK, {0.0,0.0,0.0}).
26-define(WHITE, {1.0,1.0,1.0}).
27
28init() ->
29    Black = ?BLACK,
30    White = ?WHITE,
31    Standard = [Black,White,average([Black,White])],
32    ?SET(wings_color_chosen, Black),
33    wings_pref:set_default(color_dialog_native, true),
34    wings_pref:set_default(color_ctrl_palette,
35			   [begin G = float(G0), {G,G,G} end
36			    || G0 <- lists:seq(0, 255, 255 div 15)]),
37    foreach(fun(C0) ->
38		    C = wings_util:share(C0),
39		    put(C, C)
40	    end, Standard).
41
42def_palette() ->
43    {fun() -> wings_pref:get_value(color_ctrl_palette) end,
44     fun(Palette) when is_list(Palette) ->
45	     wings_pref:set_value(color_ctrl_palette, Palette)
46     end}.
47
48choose(Done0) ->
49    Color0 = ?GET(wings_color_chosen),
50    Done = fun(Color) ->
51		   ?SET(wings_color_chosen, Color),
52		   Done0(Color)
53	   end,
54    choose(Color0, Done).
55
56choose(Color, Done) ->
57    UseNative = wings_pref:get_value(color_dialog_native)
58	andalso tuple_size(Color) =:= 3,
59    choose(Color, Done, UseNative).
60
61choose(Color, Done, UseNative) ->
62    NoOverride = tuple_size(Color) =/= 4,
63    choose_1(Color, Done, UseNative andalso NoOverride).
64
65share({Same,Same}) -> {Same,Same};
66share({_,_}=UV) -> UV;
67share({_,_,_}=RGB) -> share_1(RGB);
68share({_,_,_,_}=RGBA) -> share_1(RGBA).
69
70share_1(Color) ->
71    case get(Color) of
72	undefined -> wings_util:share(Color);
73	SharedColor -> SharedColor
74    end.
75
76store({_,_,_}=RGB) -> store_1(RGB);
77store({_,_,_,_}=RGBA) -> store_1(RGBA).
78
79store_1(Color) ->
80    case get(Color) of
81	undefined ->
82	    C = wings_util:share(Color),
83	    put(C, C),
84	    C;
85	SharedColor -> SharedColor
86    end.
87
88mix(_W, Same, Same) -> Same;
89mix(Wa, {Ua,Va}, {Ub,Vb}) when is_float(Wa) ->
90    Wb = 1.0 - Wa,
91    share({Wa*Ua+Wb*Ub,Wa*Va+Wb*Vb});
92mix(Wa, {Ra,Ga,Ba}, {Rb,Gb,Bb}) when is_float(Wa) ->
93    Wb = 1.0 - Wa,
94    share({Wa*Ra+Wb*Rb,Wa*Ga+Wb*Gb,Wa*Ba+Wb*Bb});
95mix(Wa, {Ra,Ga,Ba}, {_,_,_,Ab}=B) ->
96    mix(Wa, {Ra,Ga,Ba,Ab}, B);
97mix(Wa, {_,_,_,Aa}=A, {Rb,Gb,Bb}) ->
98    mix(Wa, A, {Rb,Gb,Bb,Aa});
99mix(Wa, {Ra,Ga,Ba,Aa}, {Rb,Gb,Bb,Ab}) when is_float(Wa) ->
100    Wb = 1.0 - Wa,
101    share({Wa*Ra+Wb*Rb,Wa*Ga+Wb*Gb,Wa*Ba+Wb*Bb,Wa*Aa+Wb*Ab});
102mix(_, _, _) -> none.
103
104white() ->
105    get(?WHITE).
106
107average([none|_]) -> none;
108average([H|T]=L) ->
109    case classify(T, H) of
110	same -> H;
111	colors -> share(e3d_vec:average(L));
112	uvs -> average_uvs(T, H);
113	none -> none
114    end.
115
116average(Same, Same) -> Same;
117average({U0,V0}, {U1,V1}) when is_float(U0), is_float(V0) ->
118    share({(U0+U1)/2,(V0+V1)/2});
119average({R0,G0,B0}, {R1,G1,B1}) when is_float(R0), is_float(G0), is_float(B0) ->
120    share({(R0+R1)/2,(G0+G1)/2,(B0+B1)/2});
121average(_, _) -> none.
122
123classify([], _) -> same;
124classify([none|_], _) -> none;
125classify([H|T], H) -> classify(T, H);
126classify(List, {_,_}) -> classify_uvs(List);
127classify(List, {_,_,_}) -> classify_colors(List).
128
129classify_uvs([{_,_}|T]) -> classify_uvs(T);
130classify_uvs([_|_]) -> none;
131classify_uvs([]) -> uvs.
132
133classify_colors([{_,_,_}|T]) -> classify_colors(T);
134classify_colors([_|_]) -> none;
135classify_colors([]) -> colors.
136
137average_uvs(T, {V10,V11}) ->
138    average_uvs_1(T, V10, V11, length(T)+1).
139
140average_uvs_1([{V10,V11}|T], A0, A1, L)
141  when is_float(V10), is_float(V11), is_float(A0), is_float(A1) ->
142    average_uvs_1(T, A0+V10, A1+V11, L);
143average_uvs_1([], A0, A1, L0) ->
144    L = float(L0),
145    {A0/L,A1/L}.
146
147
148rgb3bv({R,G,B}) -> {round(R*255),round(G*255),round(B*255)};
149rgb3bv({R,G,B,_}) -> {round(R*255),round(G*255),round(B*255)}.
150
151rgb4bv({R,G,B}) -> {round(R*255),round(G*255), round(B*255), 255};
152rgb4bv({R,G,B,A}) -> {round(R*255),round(G*255),round(B*255), round(A*255)}.
153
154rgb3fv({R,G,B}) -> {R/255,G/255,B/255};
155rgb3fv({R,G,B,_}) -> {R/255,G/255,B/255}.
156
157rgb4fv({R,G,B}) -> {R/255,G/255,B/255, 1.0};
158rgb4fv({R,G,B,A}) -> {R/255,G/255,B/255,A/255}.
159
160rgb_to_hsv({R,G,B}) ->
161    rgb_to_hsv(R,G,B).
162rgb_to_hsv(R, G, B) ->
163    case internal_rgb_to_hsv(R, G, B) of
164	{undefined,S,V} -> {0.0,S,V};
165	HSV -> HSV
166    end.
167
168%% rgb_to_hsv(R, G, B, OldHue) ->
169%%     case internal_rgb_to_hsv(R, G, B) of
170%% 	{undefined,S,V} -> {OldHue,S,V};
171%% 	HSV -> HSV
172%%     end.
173
174internal_rgb_to_hsv(R, G, B) ->
175    Max = lists:max([R,G,B]),
176    Min = lists:min([R,G,B]),
177    V = Max,
178    {Hue,Sat} = try
179		    {if
180			 Min == B -> (G-Min)/(R+G-2.0*Min);
181			 Min == R -> (1.0+(B-Min)/(B+G-2.0*Min));
182			 Min == G -> (2.0+(R-Min)/(B+R-2.0*Min))
183		     end*120,(Max-Min)/Max}
184		catch
185		    error:badarith ->
186			{undefined,0.0}
187		end,
188    {Hue,Sat,V}.
189
190hsv_to_rgb({H,S,V}) ->
191    hsv_to_rgb(H,S,V).
192hsv_to_rgb(H, S, V) ->
193    Min = V*(1-S),
194    if
195	V < 1.0E-5 ->
196	    {0.0,0.0,0.0};
197	H =< 120.0 ->
198	    convert_hsv(H,V,Min);
199	H =< 240.0 ->
200	    {G,B,R} = convert_hsv(H-120.0,V,Min),
201	    {R,G,B};
202	true ->
203	    {B,R,G} = convert_hsv(H-240.0,V,Min),
204	    {R,G,B}
205    end.
206
207rgba_to_rgb({R,G,B,_}) -> {R,G,B};
208rgba_to_rgb({_,_,_}=RGB) -> RGB.
209
210convert_hsv(H,V,Min) when H =< 60.0 ->
211    Mean = Min + H*(V-Min)/(120.0-H),
212    {V, Mean, Min};
213convert_hsv(H,V,Min) ->
214    Mean = Min+(120-H)*(V-Min)/H,
215    {Mean,V,Min}.
216
217%%%
218%%% Local functions for color chooser.
219%%%
220
221choose_1(RGB0, Done, true) ->
222    CData = wxColourData:new(),
223    wxColourData:setColour(CData, rgb3bv(RGB0)),
224    wxColourData:setChooseFull(CData, true),
225    {GetPalette,SetPalette} = def_palette(),
226    set_palette(GetPalette, CData),
227    Dlg = wxColourDialog:new(wings_dialog:get_dialog_parent(), [{data, CData}]),
228    wxColourData:destroy(CData),
229    case wxDialog:showModal(Dlg) of
230	?wxID_CANCEL ->
231	    wxColourDialog:destroy(Dlg),
232	    keep;
233	?wxID_OK ->
234	    NewData = wxColourDialog:getColourData(Dlg),
235	    SetPalette(get_palette(NewData)),
236	    RGBCol = wxColourData:getColour(NewData),
237	    wxColourDialog:destroy(Dlg),
238	    wings_dialog:return_result(Done, rgb3fv(RGBCol), wings_wm:this())
239    end;
240
241choose_1(RGB0, Done, false) ->
242    {R1,G1,B1,A1} =
243	case RGB0 of
244	    {R0,G0,B0}    -> {R0,G0,B0,none};
245	    {R0,G0,B0,A0} -> {R0,G0,B0,A0}
246	end,
247    RGBRange = {0.0,1.0},
248    HRange   = {0.0,360.0},
249    SIRange  = {0.0,1.0},
250    {H1,S1,V1} = rgb_to_hsv(R1, G1, B1),
251    Aslider = case A1 of
252		  none -> [];
253		  _ ->
254		      [separator,
255		       {hframe,[{label,"A"},
256				{slider,
257				 {text,A1,[{key,alpha},{range,RGBRange}]}}]}]
258	      end,
259    CC = [red,green,blue, hue,sat,val],
260    Hook = fun({Type, Key}, Value, Fields) ->
261		   RGB = case Type of
262			     slider -> Value;
263			     text ->
264				 OLD = wings_dialog:get_value({slider, color}, Fields),
265				 update_element(Key, Value, OLD)
266			 end,
267		   [wings_dialog:set_value({slider, K}, RGB, Fields)
268		    || K <- [color|CC], K =/= Key orelse Type =/= slider ],
269		   [wings_dialog:set_value({text, K}, get_element(K, RGB), Fields)
270		    || K <- CC, K =/= Key orelse Type =/= text],
271		   ok
272	   end,
273    TextOpt = fun(Key, Range) ->
274		      [{key,{text,Key}},
275		       {range, Range},
276		       {hook, Hook},
277		       {width, 6}]
278	      end,
279
280    SliderOpt = fun(Col) ->
281			[{value, {R0,G0,B0}},
282			 {color, Col}, {key, {slider, Col}},
283			 {hook, Hook}]
284		end,
285    W = [{width,2}],
286    Qs = [{hframe,
287	   [{vframe,[panel,
288		     {color, {R0,G0,B0}, [{key, {slider, color}},
289					  {hook, Hook},
290					  {native_dialog, true}]},
291		     panel,
292		     {color, {R0,G0,B0}, [{static, true}]}]},
293	  {vframe,
294	   [{hframe,
295	     [{label,"R:", W}, {text,R1,TextOpt(red, RGBRange)},
296	      {slider, SliderOpt(red)}]},
297	    {hframe,
298	     [{label,"G:", W}, {text,G1,TextOpt(green, RGBRange)},
299	      {slider, SliderOpt(green)}
300	     ]},
301	    {hframe,
302	     [{label,"B:", W}, {text,B1,TextOpt(blue, RGBRange)},
303	      {slider, SliderOpt(blue)}
304	     ]},
305	    separator,
306	    {hframe,
307	     [{label,"H:", W}, {text,H1,TextOpt(hue, HRange)},
308	      {slider, SliderOpt(hue)}]},
309	    {hframe,
310	     [{label,"S:", W}, {text,S1,TextOpt(sat, SIRange)},
311	      {slider, SliderOpt(sat)}
312	     ]},
313	    {hframe,
314	     [{label,"V:", W},{text,V1,TextOpt(val, SIRange)},
315	      {slider, SliderOpt(val)}
316	     ]}
317	    |Aslider], [{proportion, 1}]}]}],
318
319    Fun = fun([{{slider,color},{R,G,B}}|More]) ->
320		  RGB = case keyfind(alpha, 1, More) of
321			    false -> {R,G,B};
322			    {alpha,A} -> {R,G,B,A}
323			end,
324		  Done(RGB)
325	  end,
326
327    wings_dialog:dialog(?STR(choose_1,1,"Choose Color"), Qs, Fun).
328
329element_no(red)   -> {rgb, 1};
330element_no(green) -> {rgb, 2};
331element_no(blue)  -> {rgb, 3};
332element_no(hue)   -> {hsv, 1};
333element_no(sat)   -> {hsv, 2};
334element_no(val)   -> {hsv, 3}.
335
336update_element(Col, V, RGB) ->
337    case element_no(Col) of
338	{rgb, Index} -> setelement(Index, RGB, V);
339	{hsv, Index} -> hsv_to_rgb(setelement(Index, rgb_to_hsv(RGB), V))
340    end.
341
342get_element(Col, RGB) ->
343    case element_no(Col) of
344	{rgb, Index} -> element(Index, RGB);
345	{hsv, Index} -> element(Index, rgb_to_hsv(RGB))
346    end.
347
348set_palette(Get, Data) when is_function(Get) ->
349    set_palette(Get(), Data, 0).
350
351set_palette([Col|Pal], Data, I) when I < 16 ->
352    wxColourData:setCustomColour(Data, I, wings_color:rgb3bv(Col)),
353    set_palette(Pal, Data, I+1);
354set_palette(_, _, _) -> ok.
355
356get_palette(Data) ->
357    get_palette(Data, 0).
358
359get_palette(Data, I) when I < 16 ->
360    [rgb3fv(wxColourData:getCustomColour(Data,I))|get_palette(Data, I+1)];
361get_palette(_, _) -> [].
362
363%% eyepicker_update(update, {Var,_I,{R,G,B}=Col,Sto0}) ->
364%%     Sto1 = gb_trees:update(Var, Col, Sto0),
365%%     Sto2 = gb_trees:update(red, R, Sto1),
366%%     Sto3 = gb_trees:update(green, G, Sto2),
367%%     Sto4 = gb_trees:update(blue, B, Sto3),
368%%     {H,S,V} = rgb_to_hsv(R, G, B),
369%%     Sto5 = gb_trees:update(hue, H, Sto4),
370%%     Sto6 = gb_trees:update(sat, S, Sto5),
371%%     Sto = gb_trees:update(val, V, Sto6),
372%%     {store,Sto};
373%% eyepicker_update(_, _) -> void.
374