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