Implements PXEGeek's http://wiki.synfig.com/Wish_list entry: "Optionally display...
[synfig.git] / synfig-core / trunk / src / synfig / color.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file color.cpp
3 **      \brief Color Class
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **
10 **      This package is free software; you can redistribute it and/or
11 **      modify it under the terms of the GNU General Public License as
12 **      published by the Free Software Foundation; either version 2 of
13 **      the License, or (at your option) any later version.
14 **
15 **      This package is distributed in the hope that it will be useful,
16 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
17 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 **      General Public License for more details.
19 **      \endlegal
20 */
21 /* ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 #       include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #       include <config.h>
30 #endif
31
32 #include <ETL/angle>
33 #include "color.h"
34 #include <cstdio>
35 #include <sstream>
36 #include <iostream>
37
38 #endif
39
40 using namespace synfig;
41 using namespace etl;
42 using namespace std;
43
44 /* === M A C R O S ========================================================= */
45
46 #define COLOR_EPSILON   (0.000001f)
47
48 /* === G L O B A L S ======================================================= */
49
50 /* === P R O C E D U R E S ================================================= */
51
52 /* === M E T H O D S ======================================================= */
53
54
55
56 static ColorReal
57 Color::hex2real(String s)
58 {
59         std::istringstream i(s);
60         int n;
61         i.fill('0');
62         if (!(i >> hex >> n))
63                 throw String("bad conversion from hex string \"") + s + String("\"");
64         return n / 255.0f;
65 }
66
67 static const String
68 Color::real2hex(ColorReal c)
69 {
70         std::ostringstream o;
71         o.width(2);
72         o.fill('0');
73         if (c<0) c = 0;
74         if (c>1) c = 1;
75         o << hex << int(c*255.0f);
76         return o.str();
77 }
78
79 void
80 Color::set_hex(String& hex)
81 {
82         value_type r, g, b;
83         try
84         {
85                 if (hex.size() == 3)
86                 {
87                         r = hex2real(hex.substr(0,1)+hex.substr(0,1));
88                         g = hex2real(hex.substr(1,1)+hex.substr(1,1));
89                         b = hex2real(hex.substr(2,1)+hex.substr(2,1));
90                         r_ = r; g_ = g; b_ = b;
91                 }
92                 else if (hex.size() == 6)
93                 {
94                         r = hex2real(hex.substr(0,2));
95                         g = hex2real(hex.substr(2,2));
96                         b = hex2real(hex.substr(4,2));
97                         r_ = r; g_ = g; b_ = b;
98                 }
99         }
100         catch (string s)
101         {
102                 printf("caught <%s>\n", s.c_str());
103                 return;
104         }
105 }
106
107 #if 0
108 Color&
109 Color::rotate_uv(const Angle& theta)const
110 {
111 /*/
112         Color ret(*this);
113         ret.set_hue(ret.get_hue()+theta);
114         return ret;
115 /*/
116         const float
117                 a(angle::sin(theta).get()),
118                 b(angle::cos(theta).get());
119         const float
120                 u(get_u()),
121                 v(get_v());
122
123         return set_uv(b*u-a*v,a*u+b*v);
124         //return YUV(get_y(),b*u-a*v,a*u+b*v,get_a());
125 //*/
126 }
127 #endif
128
129 Color
130 Color::clamped_negative()const
131 {
132         Color ret=*this;
133
134         if(ret.a_==0)
135                 return alpha();
136
137         if(ret.a_<0)
138                 ret=-ret;
139
140         if(ret.r_<0)
141         {
142                 ret.g_-=ret.r_;
143                 ret.b_-=ret.r_;
144                 ret.r_=0.0f;
145         }
146         if(ret.g_<0)
147         {
148                 ret.r_-=ret.g_;
149                 ret.b_-=ret.g_;
150                 ret.g_=0.0f;
151         }
152         if(ret.b_<0)
153         {
154                 ret.r_-=ret.b_;
155                 ret.g_-=ret.b_;
156                 ret.b_=0.0f;
157         }
158
159         if(ret.r_>1) ret.r_=1;
160         if(ret.g_>1) ret.g_=1;
161         if(ret.b_>1) ret.b_=1;
162         if(ret.a_>1) ret.a_=1;
163
164         if(isnan(ret.get_r())) ret.r_=0.5;
165         if(isnan(ret.get_g())) ret.g_=0.5;
166         if(isnan(ret.get_b())) ret.b_=0.5;
167         if(isnan(ret.get_a())) ret.a_=1;
168
169 /*
170         if(ret.r_>1) { ret.g_/=ret.r_; ret.b_/=ret.r_; ret.r_=1; }
171         if(ret.g_>1) { ret.r_/=ret.g_; ret.b_/=ret.g_; ret.g_=1; }
172         if(ret.b_>1) { ret.g_/=ret.b_; ret.r_/=ret.b_; ret.b_=1; }
173         if(ret.a_>1) ret.a_=1;
174 */
175
176         return ret;
177 }
178
179 Color
180 Color::clamped()const
181 {
182         Color ret(*this);
183         if(ret.get_r()<0)
184                 ret.set_r(0);
185         if(ret.get_g()<0)
186                 ret.set_g(0);
187         if(ret.get_b()<0)
188                 ret.set_b(0);
189         if(ret.get_a()<0)
190                 ret.set_a(0);
191
192         if(ret.r_>1) ret.r_=1;
193         if(ret.g_>1) ret.g_=1;
194         if(ret.b_>1) ret.b_=1;
195         if(ret.a_>1) ret.a_=1;
196
197         if(isnan(ret.get_r())) ret.r_=0.5;
198         if(isnan(ret.get_g())) ret.g_=0.5;
199         if(isnan(ret.get_b())) ret.b_=0.5;
200         if(isnan(ret.get_a())) ret.a_=1;
201
202         return(ret);
203 }
204
205 typedef Color (*blendfunc)(Color &,Color &,float);
206
207 static Color
208 blendfunc_COMPOSITE(Color &src,Color &dest,float amount)
209 {
210         //c_dest'=c_src+(1.0-a_src)*c_dest
211         //a_dest'=a_src+(1.0-a_src)*a_dest
212
213         float a_src=src.get_a()*amount;
214         float a_dest=dest.get_a();
215
216         // if a_arc==0.0
217         //if(fabsf(a_src)<COLOR_EPSILON) return dest;
218
219         // Scale the source and destination by their alpha values
220         src*=a_src;
221         dest*=a_dest;
222
223         dest=src + dest*(1.0f-a_src);
224
225         a_dest=a_src + a_dest*(1.0f-a_src);
226
227         // if a_dest!=0.0
228         if(fabsf(a_dest)>COLOR_EPSILON)
229         {
230                 dest/=a_dest;
231                 dest.set_a(a_dest);
232         }
233         else
234         {
235                 dest=Color::alpha();
236         }
237         assert(dest.is_valid());
238         return dest;
239 }
240
241 static Color
242 blendfunc_STRAIGHT(Color &src,Color &bg,float amount)
243 {
244         //a_out'=(a_src-a_bg)*amount+a_bg
245         //c_out'=(((c_src*a_src)-(c_bg*a_bg))*amount+(c_bg*a_bg))/a_out'
246
247         // ie: if(amount==1.0)
248         //if(fabsf(amount-1.0f)<COLOR_EPSILON)return src;
249
250         Color out;
251
252         float a_out((src.get_a()-bg.get_a())*amount+bg.get_a());
253
254         // if a_out!=0.0
255         if(fabsf(a_out)>COLOR_EPSILON)
256 //      if(a_out>COLOR_EPSILON || a_out<-COLOR_EPSILON)
257         {
258                 out=((src*src.get_a()-bg*bg.get_a())*amount+bg*bg.get_a())/a_out;
259                 out.set_a(a_out);
260         }
261         else
262                 out=Color::alpha();
263
264         assert(out.is_valid());
265         return out;
266 }
267
268 static Color
269 blendfunc_ONTO(Color &a,Color &b,float amount)
270 {
271         float alpha(b.get_a());
272         return blendfunc_COMPOSITE(a,b.set_a(1.0f),amount).set_a(alpha);
273 }
274
275 static Color
276 blendfunc_STRAIGHT_ONTO(Color &a,Color &b,float amount)
277 {
278         a.set_a(a.get_a()*b.get_a());
279         return blendfunc_STRAIGHT(a,b,amount);
280 }
281
282 static Color
283 blendfunc_BRIGHTEN(Color &a,Color &b,float amount)
284 {
285         const float alpha(a.get_a()*amount);
286
287         if(b.get_r()<a.get_r()*alpha)
288                 b.set_r(a.get_r()*alpha);
289
290         if(b.get_g()<a.get_g()*alpha)
291                 b.set_g(a.get_g()*alpha);
292
293         if(b.get_b()<a.get_b()*alpha)
294                 b.set_b(a.get_b()*alpha);
295
296         return b;
297 }
298
299 static Color
300 blendfunc_DARKEN(Color &a,Color &b,float amount)
301 {
302         const float alpha(a.get_a()*amount);
303
304         if(b.get_r()>(a.get_r()-1.0f)*alpha+1.0f)
305                 b.set_r((a.get_r()-1.0f)*alpha+1.0f);
306
307         if(b.get_g()>(a.get_g()-1.0f)*alpha+1.0f)
308                 b.set_g((a.get_g()-1.0f)*alpha+1.0f);
309
310         if(b.get_b()>(a.get_b()-1.0f)*alpha+1.0f)
311                 b.set_b((a.get_b()-1.0f)*alpha+1.0f);
312
313
314         return b;
315 }
316
317 static Color
318 blendfunc_ADD(Color &a,Color &b,float amount)
319 {
320         const float alpha(a.get_a()*amount);
321
322         b.set_r(b.get_r()+a.get_r()*alpha);
323         b.set_g(b.get_g()+a.get_g()*alpha);
324         b.set_b(b.get_b()+a.get_b()*alpha);
325
326         return b;
327 }
328
329 static Color
330 blendfunc_SUBTRACT(Color &a,Color &b,float amount)
331 {
332         const float alpha(a.get_a()*amount);
333
334         b.set_r(b.get_r()-a.get_r()*alpha);
335         b.set_g(b.get_g()-a.get_g()*alpha);
336         b.set_b(b.get_b()-a.get_b()*alpha);
337
338         return b;
339 }
340
341 static Color
342 blendfunc_DIFFERENCE(Color &a,Color &b,float amount)
343 {
344         const float alpha(a.get_a()*amount);
345
346         b.set_r(abs(b.get_r()-a.get_r()*alpha));
347         b.set_g(abs(b.get_g()-a.get_g()*alpha));
348         b.set_b(abs(b.get_b()-a.get_b()*alpha));
349
350         return b;
351 }
352
353 static Color
354 blendfunc_MULTIPLY(Color &a,Color &b,float amount)
355 {
356         if(amount<0) a=~a, amount=-amount;
357
358         amount*=a.get_a();
359         b.set_r(((b.get_r()*a.get_r())-b.get_r())*(amount)+b.get_r());
360         b.set_g(((b.get_g()*a.get_g())-b.get_g())*(amount)+b.get_g());
361         b.set_b(((b.get_b()*a.get_b())-b.get_b())*(amount)+b.get_b());
362         return b;
363 }
364
365 static Color
366 blendfunc_DIVIDE(Color &a,Color &b,float amount)
367 {
368         amount*=a.get_a();
369
370         // We add COLOR_EPSILON in order to avoid a divide-by-zero condition.
371         // This causes DIVIDE to bias toward positive values, but the effect is
372         // really neglegable. There is a reason why we use COLOR_EPSILON--we
373         // want the change to be imperceptable.
374
375         b.set_r(((b.get_r()/(a.get_r()+COLOR_EPSILON))-b.get_r())*(amount)+b.get_r());
376         b.set_g(((b.get_g()/(a.get_g()+COLOR_EPSILON))-b.get_g())*(amount)+b.get_g());
377         b.set_b(((b.get_b()/(a.get_b()+COLOR_EPSILON))-b.get_b())*(amount)+b.get_b());
378
379         return b;
380 }
381
382 static Color
383 blendfunc_COLOR(Color &a,Color &b,float amount)
384 {
385         Color temp(b);
386         temp.set_uv(a.get_u(),a.get_v());
387         return (temp-b)*amount*a.get_a()+b;
388 }
389
390 static Color
391 blendfunc_HUE(Color &a,Color &b,float amount)
392 {
393         Color temp(b);
394         temp.set_hue(a.get_hue());
395         return (temp-b)*amount*a.get_a()+b;
396 }
397
398 static Color
399 blendfunc_SATURATION(Color &a,Color &b,float amount)
400 {
401         Color temp(b);
402         temp.set_s(a.get_s());
403         return (temp-b)*amount*a.get_a()+b;
404 }
405
406 static Color
407 blendfunc_LUMINANCE(Color &a,Color &b,float amount)
408 {
409         Color temp(b);
410         temp.set_y(a.get_y());
411         return (temp-b)*amount*a.get_a()+b;
412 }
413
414 static Color
415 blendfunc_BEHIND(Color &a,Color &b,float amount)
416 {
417         if(a.get_a()==0)a.set_a(COLOR_EPSILON);         //!< \hack
418         a.set_a(a.get_a()*amount);
419         return blendfunc_COMPOSITE(b,a,1.0);
420 }
421
422 static Color
423 blendfunc_ALPHA_BRIGHTEN(Color &a,Color &b,float amount)
424 {
425         if(a.get_a()<b.get_a()*amount)
426                 return a.set_a(a.get_a()*amount);
427         return b;
428 }
429
430 static Color
431 blendfunc_ALPHA_DARKEN(Color &a,Color &b,float amount)
432 {
433         if(a.get_a()*amount>b.get_a())
434                 return a.set_a(a.get_a()*amount);
435         return b;
436 }
437
438 static Color
439 blendfunc_SCREEN(Color &a,Color &b,float amount)
440 {
441         if(amount<0) a=~a, amount=-amount;
442
443         a.set_r(1.0-(1.0f-a.get_r())*(1.0f-b.get_r()));
444         a.set_g(1.0-(1.0f-a.get_g())*(1.0f-b.get_g()));
445         a.set_b(1.0-(1.0f-a.get_b())*(1.0f-b.get_b()));
446
447         return blendfunc_ONTO(a,b,amount);
448 }
449
450 static Color
451 blendfunc_OVERLAY(Color &a,Color &b,float amount)
452 {
453         if(amount<0) a=~a, amount=-amount;
454
455         Color rm;
456         rm.set_r(b.get_r()*a.get_r());
457         rm.set_g(b.get_g()*a.get_g());
458         rm.set_b(b.get_b()*a.get_b());
459
460         Color rs;
461         rs.set_r(1.0-(1.0f-a.get_r())*(1.0f-b.get_r()));
462         rs.set_g(1.0-(1.0f-a.get_g())*(1.0f-b.get_g()));
463         rs.set_b(1.0-(1.0f-a.get_b())*(1.0f-b.get_b()));
464
465         Color& ret(a);
466
467         ret.set_r(a.get_r()*rs.get_r() + (1.0-a.get_r())*rm.get_r());
468         ret.set_g(a.get_g()*rs.get_g() + (1.0-a.get_g())*rm.get_g());
469         ret.set_b(a.get_b()*rs.get_b() + (1.0-a.get_b())*rm.get_b());
470
471         return blendfunc_ONTO(ret,b,amount);
472 }
473
474 static Color
475 blendfunc_HARD_LIGHT(Color &a,Color &b,float amount)
476 {
477         if(amount<0) a=~a, amount=-amount;
478
479         if(a.get_r()>0.5f)      a.set_r(1.0-(1.0f-(a.get_r()*2.0f-1.0f))*(1.0f-b.get_r()));
480         else                            a.set_r(b.get_r()*(a.get_r()*2.0f));
481         if(a.get_g()>0.5f)      a.set_g(1.0-(1.0f-(a.get_g()*2.0f-1.0f))*(1.0f-b.get_g()));
482         else                            a.set_g(b.get_g()*(a.get_g()*2.0f));
483         if(a.get_b()>0.5f)      a.set_b(1.0-(1.0f-(a.get_b()*2.0f-1.0f))*(1.0f-b.get_b()));
484         else                            a.set_b(b.get_b()*(a.get_b()*2.0f));
485
486         return blendfunc_ONTO(a,b,amount);
487 }
488
489 static Color
490 blendfunc_ALPHA_OVER(Color &a,Color &b,float amount)
491 {
492         Color rm(b);
493
494         //multiply the inverse alpha channel with the one below us
495         rm.set_a((1-a.get_a())*b.get_a());
496
497         return blendfunc_STRAIGHT(rm,b,amount);
498 }
499
500
501 Color
502 Color::blend(Color a, Color b,float amount, Color::BlendMethod type)
503 {
504 #if 0
505         if(isnan(a.get_r()) || isnan(a.get_g()) || isnan(a.get_b()))
506         {
507 #ifdef _DEBUG
508                 a=magenta().set_a(a.get_a());
509 #else
510                 a=black().set_a(a.get_a());
511 #endif
512         }
513
514         if(isnan(b.get_r()) || isnan(b.get_g()) || isnan(b.get_b()))
515         {
516 #ifdef _DEBUG
517                 b=magenta().set_a(b.get_a());
518 #else
519                 b=black().set_a(b.get_a());
520 #endif
521         }
522 #endif
523
524 /*
525         if(!a.is_valid()&&b.is_valid())
526                 return b;
527
528         if(a.is_valid()&&!b.is_valid())
529                 return a;
530
531         if(!a.is_valid()||!b.is_valid())
532         {
533 #ifdef _DEBUG
534                 return magenta();
535 #else
536                 return black();
537 #endif
538         }
539 */
540
541         // No matter what blend method is being used,
542         // if the amount is equal to zero, then only B
543         // will shine through
544         if(fabsf(amount)<=COLOR_EPSILON)return b;
545
546         assert(type<BLEND_END);
547
548         const static blendfunc vtable[BLEND_END]=
549         {
550                 blendfunc_COMPOSITE,
551                 blendfunc_STRAIGHT,
552                 blendfunc_BRIGHTEN,
553                 blendfunc_DARKEN,
554                 blendfunc_ADD,
555                 blendfunc_SUBTRACT,
556                 blendfunc_MULTIPLY,
557                 blendfunc_DIVIDE,
558                 blendfunc_COLOR,
559                 blendfunc_HUE,
560                 blendfunc_SATURATION,
561                 blendfunc_LUMINANCE,
562                 blendfunc_BEHIND,
563                 blendfunc_ONTO,
564                 blendfunc_ALPHA_BRIGHTEN,
565                 blendfunc_ALPHA_DARKEN,
566                 blendfunc_SCREEN,
567                 blendfunc_HARD_LIGHT,
568                 blendfunc_DIFFERENCE,
569                 blendfunc_ALPHA_OVER,
570                 blendfunc_OVERLAY,
571                 blendfunc_STRAIGHT_ONTO,
572         };
573
574         return vtable[type](a,b,amount);
575 }