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