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 &params)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 &params, 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 &params, 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