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