1 /* === S Y N F I G ========================================================= */
2 /*! \file lineargradient.cpp
3 ** \brief Implementation of the "Linear Gradient" layer
4 **
5 ** $Id$
6 **
7 ** \legal
8 ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 ** Copyright (c) 2011-2013 Carlos López
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 ** === N O T E S ===========================================================
23 **
24 ** ========================================================================= */
25
26 /* === H E A D E R S ======================================================= */
27
28 #ifdef USING_PCH
29 # include "pch.h"
30 #else
31 #ifdef HAVE_CONFIG_H
32 # include <config.h>
33 #endif
34
35 #include "lineargradient.h"
36
37 #include <synfig/localization.h>
38 #include <synfig/general.h>
39
40 #include <synfig/string.h>
41 #include <synfig/time.h>
42 #include <synfig/context.h>
43 #include <synfig/paramdesc.h>
44 #include <synfig/renddesc.h>
45 #include <synfig/surface.h>
46 #include <synfig/value.h>
47 #include <synfig/valuenode.h>
48
49 #endif
50
51 /* === M A C R O S ========================================================= */
52
53 /* === G L O B A L S ======================================================= */
54
55 SYNFIG_LAYER_INIT(LinearGradient);
56 SYNFIG_LAYER_SET_NAME(LinearGradient,"linear_gradient");
57 SYNFIG_LAYER_SET_LOCAL_NAME(LinearGradient,N_("Linear Gradient"));
58 SYNFIG_LAYER_SET_CATEGORY(LinearGradient,N_("Gradients"));
59 SYNFIG_LAYER_SET_VERSION(LinearGradient,"0.0");
60 SYNFIG_LAYER_SET_CVS_ID(LinearGradient,"$Id$");
61
62 /* === P R O C E D U R E S ================================================= */
63
64 /* === M E T H O D S ======================================================= */
65
66 inline void
calc_diff()67 LinearGradient::Params::calc_diff()
68 {
69 diff=(p2-p1);
70 Real mag_squared = diff.mag_squared();
71 if (mag_squared > 0.0) diff /= mag_squared;
72 }
73
74
LinearGradient()75 LinearGradient::LinearGradient():
76 Layer_Composite(1.0,Color::BLEND_COMPOSITE),
77 param_p1(ValueBase(Point(1,1))),
78 param_p2(ValueBase(Point(-1,-1))),
79 param_gradient(ValueBase(Gradient(Color::black(), Color::white()))),
80 param_loop(ValueBase(false)),
81 param_zigzag(ValueBase(false))
82 {
83 SET_INTERPOLATION_DEFAULTS();
84 SET_STATIC_DEFAULTS();
85 }
86
87 inline void
fill_params(Params & params) const88 LinearGradient::fill_params(Params ¶ms)const
89 {
90 params.p1=param_p1.get(Point());
91 params.p2=param_p2.get(Point());
92 params.gradient=param_gradient.get(Gradient());
93 params.loop=param_loop.get(bool());
94 params.zigzag=param_zigzag.get(bool());
95 params.calc_diff();
96 }
97
98 inline Color
color_func(const Params & params,const Point & point,synfig::Real supersample) const99 LinearGradient::color_func(const Params ¶ms, const Point &point, synfig::Real supersample)const
100 {
101 Real dist(point*params.diff-params.p1*params.diff);
102
103 if(params.loop)
104 dist-=floor(dist);
105
106 if(params.zigzag)
107 {
108 dist*=2.0;
109 supersample*=2.0;
110 if(dist>1)dist=2.0-dist;
111 }
112
113 if(params.loop)
114 {
115 if(dist+supersample*0.5>1.0)
116 {
117 synfig::Real left(supersample*0.5-(dist-1.0));
118 synfig::Real right(supersample*0.5+(dist-1.0));
119 Color pool(params.gradient(1.0-(left*0.5),left).premult_alpha()*left/supersample);
120 if (params.zigzag) pool+=params.gradient(1.0-right*0.5,right).premult_alpha()*right/supersample;
121 else pool+=params.gradient(right*0.5,right).premult_alpha()*right/supersample;
122 return pool.demult_alpha();
123 }
124 if(dist-supersample*0.5<0.0)
125 {
126 synfig::Real left(supersample*0.5-dist);
127 synfig::Real right(supersample*0.5+dist);
128 Color pool(params.gradient(right*0.5,right).premult_alpha()*right/supersample);
129 if (params.zigzag) pool+=params.gradient(left*0.5,left).premult_alpha()*left/supersample;
130 else pool+=params.gradient(1.0-left*0.5,left).premult_alpha()*left/supersample;
131 return pool.demult_alpha();
132 }
133 }
134 return params.gradient(dist,supersample);
135 }
136
137 inline synfig::Real
calc_supersample(const Params & params,synfig::Real pw,synfig::Real) const138 LinearGradient::calc_supersample(const Params ¶ms, synfig::Real pw, synfig::Real /*ph*/)const
139 {
140 // it's copy of code
141 // see also other calc_supersample overload
142 return pw/(params.p2-params.p1).mag();
143 }
144
145 synfig::Layer::Handle
hit_check(synfig::Context context,const synfig::Point & point) const146 LinearGradient::hit_check(synfig::Context context, const synfig::Point &point)const
147 {
148 if(get_blend_method()==Color::BLEND_STRAIGHT && get_amount()>=0.5)
149 return const_cast<LinearGradient*>(this);
150 if(get_amount()==0.0)
151 return context.hit_check(point);
152
153 Params params;
154 fill_params(params);
155
156 if((get_blend_method()==Color::BLEND_STRAIGHT || get_blend_method()==Color::BLEND_COMPOSITE) && color_func(params, point).get_a()>0.5)
157 return const_cast<LinearGradient*>(this);
158 return context.hit_check(point);
159 }
160
161 bool
set_param(const String & param,const ValueBase & value)162 LinearGradient::set_param(const String & param, const ValueBase &value)
163 {
164 IMPORT_VALUE(param_p1);
165 IMPORT_VALUE(param_p2);
166 IMPORT_VALUE(param_gradient);
167 IMPORT_VALUE(param_loop);
168 IMPORT_VALUE(param_zigzag);
169 return Layer_Composite::set_param(param,value);
170 }
171
172 ValueBase
get_param(const String & param) const173 LinearGradient::get_param(const String & param)const
174 {
175 EXPORT_VALUE(param_p1);
176 EXPORT_VALUE(param_p2);
177 EXPORT_VALUE(param_gradient);
178 EXPORT_VALUE(param_loop);
179 EXPORT_VALUE(param_zigzag);
180
181 EXPORT_NAME();
182 EXPORT_VERSION();
183
184 return Layer_Composite::get_param(param);
185 }
186
187 Layer::Vocab
get_param_vocab() const188 LinearGradient::get_param_vocab()const
189 {
190 Layer::Vocab ret(Layer_Composite::get_param_vocab());
191
192 ret.push_back(ParamDesc("p1")
193 .set_local_name(_("Point 1"))
194 .set_connect("p2")
195 .set_description(_("Start point of the gradient"))
196 );
197 ret.push_back(ParamDesc("p2")
198 .set_local_name(_("Point 2"))
199 .set_description(_("End point of the gradient"))
200 );
201 ret.push_back(ParamDesc("gradient")
202 .set_local_name(_("Gradient"))
203 .set_description(_("Gradient to apply"))
204 );
205 ret.push_back(ParamDesc("loop")
206 .set_local_name(_("Loop"))
207 .set_description(_("When checked the gradient is looped"))
208 );
209 ret.push_back(ParamDesc("zigzag")
210 .set_local_name(_("ZigZag"))
211 .set_description(_("When checked the gradient is symmetrical at the center"))
212 );
213
214 return ret;
215 }
216
217 Color
get_color(Context context,const Point & point) const218 LinearGradient::get_color(Context context, const Point &point)const
219 {
220 Params params;
221 fill_params(params);
222
223 const Color color(color_func(params, point));
224
225 if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
226 return color;
227 else
228 return Color::blend(color,context.get_color(point),get_amount(),get_blend_method());
229 }
230
231 bool
accelerated_render(Context context,Surface * surface,int quality,const RendDesc & renddesc,ProgressCallback * cb) const232 LinearGradient::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
233 {
234 Params params;
235 fill_params(params);
236
237 if (!renddesc.get_transformation_matrix().is_identity())
238 {
239 Point origin = params.p1;
240 Point axis_x = params.p2 - origin;
241 Point axis_y = axis_x.perp();
242 origin = renddesc.get_transformation_matrix().get_transformed(origin);
243 axis_x = renddesc.get_transformation_matrix().get_transformed(axis_x, false);
244 axis_y = renddesc.get_transformation_matrix().get_transformed(axis_y, false);
245
246 Point valid_axis_x = -axis_y.perp();
247 Real mag_squared = valid_axis_x.mag_squared();
248 if (mag_squared > 0.0)
249 valid_axis_x *= (valid_axis_x * axis_x)/mag_squared;
250 else
251 valid_axis_x = axis_x;
252
253 params.p1 = origin;
254 params.p2 = origin + valid_axis_x;
255 params.calc_diff();
256 }
257
258 SuperCallback supercb(cb,0,9500,10000);
259
260 if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
261 {
262 surface->set_wh(renddesc.get_w(),renddesc.get_h());
263 }
264 else
265 {
266 if(!context.accelerated_render(surface,quality,renddesc,&supercb))
267 return false;
268 if(get_amount()==0)
269 return true;
270 }
271
272
273 int x,y;
274
275 Surface::pen pen(surface->begin());
276 const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
277 Point pos;
278 Point tl(renddesc.get_tl());
279 const int w(surface->get_w());
280 const int h(surface->get_h());
281 synfig::Real supersample = calc_supersample(params, pw, ph);
282
283 if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT)
284 {
285 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
286 for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
287 pen.put_value(color_func(params,pos,supersample));
288 }
289 else
290 {
291 for(y=0,pos[1]=tl[1];y<h;y++,pen.inc_y(),pen.dec_x(x),pos[1]+=ph)
292 for(x=0,pos[0]=tl[0];x<w;x++,pen.inc_x(),pos[0]+=pw)
293 pen.put_value(Color::blend(color_func(params,pos,supersample),pen.get_value(),get_amount(),get_blend_method()));
294 }
295
296 // Mark our progress as finished
297 if(cb && !cb->amount_complete(10000,10000))
298 return false;
299
300 return true;
301 }
302
303
304 bool
accelerated_cairorender(Context context,cairo_t * cr,int quality,const RendDesc & renddesc,ProgressCallback * cb) const305 LinearGradient::accelerated_cairorender(Context context, cairo_t *cr, int quality, const RendDesc &renddesc, ProgressCallback *cb)const
306 {
307 bool loop=param_loop.get(bool());
308 Point p1=param_p1.get(Point());
309 Point p2=param_p2.get(Point());
310 Gradient gradient=param_gradient.get(Gradient());
311
312 cairo_save(cr);
313 cairo_pattern_t* pattern=cairo_pattern_create_linear(p1[0], p1[1], p2[0], p2[1]);
314 bool cpoints_all_opaque=compile_gradient(pattern, gradient);
315 if(loop)
316 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
317 if(quality>8) cairo_pattern_set_filter(pattern, CAIRO_FILTER_FAST);
318 else if(quality>=4) cairo_pattern_set_filter(pattern, CAIRO_FILTER_GOOD);
319 else cairo_pattern_set_filter(pattern, CAIRO_FILTER_BEST);
320 if(
321 !
322 (is_solid_color() ||
323 (cpoints_all_opaque && get_blend_method()==Color::BLEND_COMPOSITE && get_amount()==1.f))
324 )
325 {
326 // Initially render what's behind us
327 if(!context.accelerated_cairorender(cr,quality,renddesc,cb))
328 {
329 if(cb)cb->error(strprintf(__FILE__"%d: Accelerated Cairo Renderer Failure",__LINE__));
330 return false;
331 }
332 }
333 cairo_set_source(cr, pattern);
334 cairo_paint_with_alpha_operator(cr, get_amount(), get_blend_method());
335
336 cairo_pattern_destroy(pattern); // Not needed more
337 cairo_restore(cr);
338 return true;
339 }
340
341
342
343 bool
compile_gradient(cairo_pattern_t * pattern,Gradient mygradient) const344 LinearGradient::compile_gradient(cairo_pattern_t* pattern, Gradient mygradient)const
345 {
346 bool loop=param_loop.get(bool());
347 bool zigzag=param_zigzag.get(bool());
348
349 bool cpoints_all_opaque=true;
350 synfig::Real a,r,g,b;
351 Gradient::CPoint cp;
352 Gradient::const_iterator iter;
353 mygradient.sort();
354 if(zigzag)
355 {
356 Gradient zgradient;
357 for(iter=mygradient.begin();iter!=mygradient.end(); iter++)
358 {
359 cp=*iter;
360 cp.pos=cp.pos/2;
361 zgradient.push_back(cp);
362 }
363 for(iter=mygradient.begin();iter!=mygradient.end(); iter++)
364 {
365 cp=*iter;
366 cp.pos=1.0-cp.pos/2;
367 zgradient.push_back(cp);
368 }
369 mygradient=zgradient;
370 }
371 mygradient.sort();
372 if(loop)
373 {
374 cp=*mygradient.begin();
375 a=cp.color.get_a();
376 r=cp.color.get_r();
377 g=cp.color.get_g();
378 b=cp.color.get_b();
379 cairo_pattern_add_color_stop_rgba(pattern, 0.0, r, g, b, a);
380 }
381 for(iter=mygradient.begin();iter!=mygradient.end(); iter++)
382 {
383 cp=*iter;
384 a=cp.color.get_a();
385 r=cp.color.get_r();
386 g=cp.color.get_g();
387 b=cp.color.get_b();
388 cairo_pattern_add_color_stop_rgba(pattern, cp.pos, r, g, b, a);
389 if(a!=1.0) cpoints_all_opaque=false;
390 }
391 if(loop)
392 {
393 cp=*(--mygradient.end());
394 a=cp.color.get_a();
395 r=cp.color.get_r();
396 g=cp.color.get_g();
397 b=cp.color.get_b();
398 cairo_pattern_add_color_stop_rgba(pattern, 1.0, r, g, b, a);
399 }
400 return cpoints_all_opaque;
401 }
402