Fix 1795802: Fixed a crash in the animated gradient code I wrote yesterday. Added...
[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 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 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() == 1)
86                 {
87                         r = hex2real(hex.substr(0,1)+hex.substr(0,1));
88                         r_ = g_ = b_ = r;
89                 }
90                 else if (hex.size() == 3)
91                 {
92                         r = hex2real(hex.substr(0,1)+hex.substr(0,1));
93                         g = hex2real(hex.substr(1,1)+hex.substr(1,1));
94                         b = hex2real(hex.substr(2,1)+hex.substr(2,1));
95                         r_ = r; g_ = g; b_ = b;
96                 }
97                 else if (hex.size() == 6)
98                 {
99                         r = hex2real(hex.substr(0,2));
100                         g = hex2real(hex.substr(2,2));
101                         b = hex2real(hex.substr(4,2));
102                         r_ = r; g_ = g; b_ = b;
103                 }
104         }
105         catch (string s)
106         {
107                 printf("caught <%s>\n", s.c_str());
108                 return;
109         }
110 }
111
112 #if 0
113 Color&
114 Color::rotate_uv(const Angle& theta)const
115 {
116 /*/
117         Color ret(*this);
118         ret.set_hue(ret.get_hue()+theta);
119         return ret;
120 /*/
121         const float
122                 a(angle::sin(theta).get()),
123                 b(angle::cos(theta).get());
124         const float
125                 u(get_u()),
126                 v(get_v());
127
128         return set_uv(b*u-a*v,a*u+b*v);
129         //return YUV(get_y(),b*u-a*v,a*u+b*v,get_a());
130 //*/
131 }
132 #endif
133
134 Color
135 Color::clamped_negative()const
136 {
137         Color ret=*this;
138
139         if(ret.a_==0)
140                 return alpha();
141
142         if(ret.a_<0)
143                 ret=-ret;
144
145         if(ret.r_<0)
146         {
147                 ret.g_-=ret.r_;
148                 ret.b_-=ret.r_;
149                 ret.r_=0.0f;
150         }
151         if(ret.g_<0)
152         {
153                 ret.r_-=ret.g_;
154                 ret.b_-=ret.g_;
155                 ret.g_=0.0f;
156         }
157         if(ret.b_<0)
158         {
159                 ret.r_-=ret.b_;
160                 ret.g_-=ret.b_;
161                 ret.b_=0.0f;
162         }
163
164         if(ret.r_>1) ret.r_=1;
165         if(ret.g_>1) ret.g_=1;
166         if(ret.b_>1) ret.b_=1;
167         if(ret.a_>1) ret.a_=1;
168
169         if(isnan(ret.get_r())) ret.r_=0.5;
170         if(isnan(ret.get_g())) ret.g_=0.5;
171         if(isnan(ret.get_b())) ret.b_=0.5;
172         if(isnan(ret.get_a())) ret.a_=1;
173
174 /*
175         if(ret.r_>1) { ret.g_/=ret.r_; ret.b_/=ret.r_; ret.r_=1; }
176         if(ret.g_>1) { ret.r_/=ret.g_; ret.b_/=ret.g_; ret.g_=1; }
177         if(ret.b_>1) { ret.g_/=ret.b_; ret.r_/=ret.b_; ret.b_=1; }
178         if(ret.a_>1) ret.a_=1;
179 */
180
181         return ret;
182 }
183
184 Color
185 Color::clamped()const
186 {
187         Color ret(*this);
188         if(ret.get_r()<0)
189                 ret.set_r(0);
190         if(ret.get_g()<0)
191                 ret.set_g(0);
192         if(ret.get_b()<0)
193                 ret.set_b(0);
194         if(ret.get_a()<0)
195                 ret.set_a(0);
196
197         if(ret.r_>1) ret.r_=1;
198         if(ret.g_>1) ret.g_=1;
199         if(ret.b_>1) ret.b_=1;
200         if(ret.a_>1) ret.a_=1;
201
202         if(isnan(ret.get_r())) ret.r_=0.5;
203         if(isnan(ret.get_g())) ret.g_=0.5;
204         if(isnan(ret.get_b())) ret.b_=0.5;
205         if(isnan(ret.get_a())) ret.a_=1;
206
207         return(ret);
208 }
209
210 typedef Color (*blendfunc)(Color &,Color &,float);
211
212 static Color
213 blendfunc_COMPOSITE(Color &src,Color &dest,float amount)
214 {
215         //c_dest'=c_src+(1.0-a_src)*c_dest
216         //a_dest'=a_src+(1.0-a_src)*a_dest
217
218         float a_src=src.get_a()*amount;
219         float a_dest=dest.get_a();
220
221         // if a_arc==0.0
222         //if(fabsf(a_src)<COLOR_EPSILON) return dest;
223
224         // Scale the source and destination by their alpha values
225         src*=a_src;
226         dest*=a_dest;
227
228         dest=src + dest*(1.0f-a_src);
229
230         a_dest=a_src + a_dest*(1.0f-a_src);
231
232         // if a_dest!=0.0
233         if(fabsf(a_dest)>COLOR_EPSILON)
234         {
235                 dest/=a_dest;
236                 dest.set_a(a_dest);
237         }
238         else
239         {
240                 dest=Color::alpha();
241         }
242         assert(dest.is_valid());
243         return dest;
244 }
245
246 static Color
247 blendfunc_STRAIGHT(Color &src,Color &bg,float amount)
248 {
249         //a_out'=(a_src-a_bg)*amount+a_bg
250         //c_out'=(((c_src*a_src)-(c_bg*a_bg))*amount+(c_bg*a_bg))/a_out'
251
252         // ie: if(amount==1.0)
253         //if(fabsf(amount-1.0f)<COLOR_EPSILON)return src;
254
255         Color out;
256
257         float a_out((src.get_a()-bg.get_a())*amount+bg.get_a());
258
259         // if a_out!=0.0
260         if(fabsf(a_out)>COLOR_EPSILON)
261 //      if(a_out>COLOR_EPSILON || a_out<-COLOR_EPSILON)
262         {
263                 out=((src*src.get_a()-bg*bg.get_a())*amount+bg*bg.get_a())/a_out;
264                 out.set_a(a_out);
265         }
266         else
267                 out=Color::alpha();
268
269         assert(out.is_valid());
270         return out;
271 }
272
273 static Color
274 blendfunc_ONTO(Color &a,Color &b,float amount)
275 {
276         float alpha(b.get_a());
277         return blendfunc_COMPOSITE(a,b.set_a(1.0f),amount).set_a(alpha);
278 }
279
280 static Color
281 blendfunc_STRAIGHT_ONTO(Color &a,Color &b,float amount)
282 {
283         a.set_a(a.get_a()*b.get_a());
284         return blendfunc_STRAIGHT(a,b,amount);
285 }
286
287 static Color
288 blendfunc_BRIGHTEN(Color &a,Color &b,float amount)
289 {
290         const float alpha(a.get_a()*amount);
291
292         if(b.get_r()<a.get_r()*alpha)
293                 b.set_r(a.get_r()*alpha);
294
295         if(b.get_g()<a.get_g()*alpha)
296                 b.set_g(a.get_g()*alpha);
297
298         if(b.get_b()<a.get_b()*alpha)
299                 b.set_b(a.get_b()*alpha);
300
301         return b;
302 }
303
304 static Color
305 blendfunc_DARKEN(Color &a,Color &b,float amount)
306 {
307         const float alpha(a.get_a()*amount);
308
309         if(b.get_r()>(a.get_r()-1.0f)*alpha+1.0f)
310                 b.set_r((a.get_r()-1.0f)*alpha+1.0f);
311
312         if(b.get_g()>(a.get_g()-1.0f)*alpha+1.0f)
313                 b.set_g((a.get_g()-1.0f)*alpha+1.0f);
314
315         if(b.get_b()>(a.get_b()-1.0f)*alpha+1.0f)
316                 b.set_b((a.get_b()-1.0f)*alpha+1.0f);
317
318
319         return b;
320 }
321
322 static Color
323 blendfunc_ADD(Color &a,Color &b,float amount)
324 {
325         const float alpha(a.get_a()*amount);
326
327         b.set_r(b.get_r()+a.get_r()*alpha);
328         b.set_g(b.get_g()+a.get_g()*alpha);
329         b.set_b(b.get_b()+a.get_b()*alpha);
330
331         return b;
332 }
333
334 static Color
335 blendfunc_SUBTRACT(Color &a,Color &b,float amount)
336 {
337         const float alpha(a.get_a()*amount);
338
339         b.set_r(b.get_r()-a.get_r()*alpha);
340         b.set_g(b.get_g()-a.get_g()*alpha);
341         b.set_b(b.get_b()-a.get_b()*alpha);
342
343         return b;
344 }
345
346 static Color
347 blendfunc_DIFFERENCE(Color &a,Color &b,float amount)
348 {
349         const float alpha(a.get_a()*amount);
350
351         b.set_r(abs(b.get_r()-a.get_r()*alpha));
352         b.set_g(abs(b.get_g()-a.get_g()*alpha));
353         b.set_b(abs(b.get_b()-a.get_b()*alpha));
354
355         return b;
356 }
357
358 static Color
359 blendfunc_MULTIPLY(Color &a,Color &b,float amount)
360 {
361         if(amount<0) a=~a, amount=-amount;
362
363         amount*=a.get_a();
364         b.set_r(((b.get_r()*a.get_r())-b.get_r())*(amount)+b.get_r());
365         b.set_g(((b.get_g()*a.get_g())-b.get_g())*(amount)+b.get_g());
366         b.set_b(((b.get_b()*a.get_b())-b.get_b())*(amount)+b.get_b());
367         return b;
368 }
369
370 static Color
371 blendfunc_DIVIDE(Color &a,Color &b,float amount)
372 {
373         amount*=a.get_a();
374
375         // We add COLOR_EPSILON in order to avoid a divide-by-zero condition.
376         // This causes DIVIDE to bias toward positive values, but the effect is
377         // really negligible. There is a reason why we use COLOR_EPSILON--we
378         // want the change to be imperceptable.
379
380         b.set_r(((b.get_r()/(a.get_r()+COLOR_EPSILON))-b.get_r())*(amount)+b.get_r());
381         b.set_g(((b.get_g()/(a.get_g()+COLOR_EPSILON))-b.get_g())*(amount)+b.get_g());
382         b.set_b(((b.get_b()/(a.get_b()+COLOR_EPSILON))-b.get_b())*(amount)+b.get_b());
383
384         return b;
385 }
386
387 static Color
388 blendfunc_COLOR(Color &a,Color &b,float amount)
389 {
390         Color temp(b);
391         temp.set_uv(a.get_u(),a.get_v());
392         return (temp-b)*amount*a.get_a()+b;
393 }
394
395 static Color
396 blendfunc_HUE(Color &a,Color &b,float amount)
397 {
398         Color temp(b);
399         temp.set_hue(a.get_hue());
400         return (temp-b)*amount*a.get_a()+b;
401 }
402
403 static Color
404 blendfunc_SATURATION(Color &a,Color &b,float amount)
405 {
406         Color temp(b);
407         temp.set_s(a.get_s());
408         return (temp-b)*amount*a.get_a()+b;
409 }
410
411 static Color
412 blendfunc_LUMINANCE(Color &a,Color &b,float amount)
413 {
414         Color temp(b);
415         temp.set_y(a.get_y());
416         return (temp-b)*amount*a.get_a()+b;
417 }
418
419 static Color
420 blendfunc_BEHIND(Color &a,Color &b,float amount)
421 {
422         if(a.get_a()==0)a.set_a(COLOR_EPSILON);         //!< \hack
423         a.set_a(a.get_a()*amount);
424         return blendfunc_COMPOSITE(b,a,1.0);
425 }
426
427 static Color
428 blendfunc_ALPHA_BRIGHTEN(Color &a,Color &b,float amount)
429 {
430         if(a.get_a()<b.get_a()*amount)
431                 return a.set_a(a.get_a()*amount);
432         return b;
433 }
434
435 static Color
436 blendfunc_ALPHA_DARKEN(Color &a,Color &b,float amount)
437 {
438         if(a.get_a()*amount>b.get_a())
439                 return a.set_a(a.get_a()*amount);
440         return b;
441 }
442
443 static Color
444 blendfunc_SCREEN(Color &a,Color &b,float amount)
445 {
446         if(amount<0) a=~a, amount=-amount;
447
448         a.set_r(1.0-(1.0f-a.get_r())*(1.0f-b.get_r()));
449         a.set_g(1.0-(1.0f-a.get_g())*(1.0f-b.get_g()));
450         a.set_b(1.0-(1.0f-a.get_b())*(1.0f-b.get_b()));
451
452         return blendfunc_ONTO(a,b,amount);
453 }
454
455 static Color
456 blendfunc_OVERLAY(Color &a,Color &b,float amount)
457 {
458         if(amount<0) a=~a, amount=-amount;
459
460         Color rm;
461         rm.set_r(b.get_r()*a.get_r());
462         rm.set_g(b.get_g()*a.get_g());
463         rm.set_b(b.get_b()*a.get_b());
464
465         Color rs;
466         rs.set_r(1.0-(1.0f-a.get_r())*(1.0f-b.get_r()));
467         rs.set_g(1.0-(1.0f-a.get_g())*(1.0f-b.get_g()));
468         rs.set_b(1.0-(1.0f-a.get_b())*(1.0f-b.get_b()));
469
470         Color& ret(a);
471
472         ret.set_r(a.get_r()*rs.get_r() + (1.0-a.get_r())*rm.get_r());
473         ret.set_g(a.get_g()*rs.get_g() + (1.0-a.get_g())*rm.get_g());
474         ret.set_b(a.get_b()*rs.get_b() + (1.0-a.get_b())*rm.get_b());
475
476         return blendfunc_ONTO(ret,b,amount);
477 }
478
479 static Color
480 blendfunc_HARD_LIGHT(Color &a,Color &b,float amount)
481 {
482         if(amount<0) a=~a, amount=-amount;
483
484         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()));
485         else                            a.set_r(b.get_r()*(a.get_r()*2.0f));
486         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()));
487         else                            a.set_g(b.get_g()*(a.get_g()*2.0f));
488         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()));
489         else                            a.set_b(b.get_b()*(a.get_b()*2.0f));
490
491         return blendfunc_ONTO(a,b,amount);
492 }
493
494 static Color
495 blendfunc_ALPHA_OVER(Color &a,Color &b,float amount)
496 {
497         Color rm(b);
498
499         //multiply the inverse alpha channel with the one below us
500         rm.set_a((1-a.get_a())*b.get_a());
501
502         return blendfunc_STRAIGHT(rm,b,amount);
503 }
504
505
506 Color
507 Color::blend(Color a, Color b,float amount, Color::BlendMethod type)
508 {
509 #if 0
510         if(isnan(a.get_r()) || isnan(a.get_g()) || isnan(a.get_b()))
511         {
512 #ifdef _DEBUG
513                 a=magenta().set_a(a.get_a());
514 #else
515                 a=black().set_a(a.get_a());
516 #endif
517         }
518
519         if(isnan(b.get_r()) || isnan(b.get_g()) || isnan(b.get_b()))
520         {
521 #ifdef _DEBUG
522                 b=magenta().set_a(b.get_a());
523 #else
524                 b=black().set_a(b.get_a());
525 #endif
526         }
527 #endif
528
529 /*
530         if(!a.is_valid()&&b.is_valid())
531                 return b;
532
533         if(a.is_valid()&&!b.is_valid())
534                 return a;
535
536         if(!a.is_valid()||!b.is_valid())
537         {
538 #ifdef _DEBUG
539                 return magenta();
540 #else
541                 return black();
542 #endif
543         }
544 */
545
546         // No matter what blend method is being used,
547         // if the amount is equal to zero, then only B
548         // will shine through
549         if(fabsf(amount)<=COLOR_EPSILON)return b;
550
551         assert(type<BLEND_END);
552
553         const static blendfunc vtable[BLEND_END]=
554         {
555                 blendfunc_COMPOSITE,
556                 blendfunc_STRAIGHT,
557                 blendfunc_BRIGHTEN,
558                 blendfunc_DARKEN,
559                 blendfunc_ADD,
560                 blendfunc_SUBTRACT,
561                 blendfunc_MULTIPLY,
562                 blendfunc_DIVIDE,
563                 blendfunc_COLOR,
564                 blendfunc_HUE,
565                 blendfunc_SATURATION,
566                 blendfunc_LUMINANCE,
567                 blendfunc_BEHIND,
568                 blendfunc_ONTO,
569                 blendfunc_ALPHA_BRIGHTEN,
570                 blendfunc_ALPHA_DARKEN,
571                 blendfunc_SCREEN,
572                 blendfunc_HARD_LIGHT,
573                 blendfunc_DIFFERENCE,
574                 blendfunc_ALPHA_OVER,
575                 blendfunc_OVERLAY,
576                 blendfunc_STRAIGHT_ONTO,
577         };
578
579         return vtable[type](a,b,amount);
580 }