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