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