1 /* === S Y N F I G ========================================================= */
2 /*!	\file sphere_distort.cpp
3 **	\brief Implementation of the "Spherize" layer
4 **
5 **	$Id$
6 **
7 **	\legal
8 **	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **	Copyright (c) 2007, 2008 Chris Moore
10 **	Copyright (c) 2011 Carlos López
11 **
12 **	This package is free software; you can redistribute it and/or
13 **	modify it under the terms of the GNU General Public License as
14 **	published by the Free Software Foundation; either version 2 of
15 **	the License, or (at your option) any later version.
16 **
17 **	This package is distributed in the hope that it will be useful,
18 **	but WITHOUT ANY WARRANTY; without even the implied warranty of
19 **	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 **	General Public License for more details.
21 **	\endlegal
22 */
23 /* ========================================================================= */
24 
25 /* === H E A D E R S ======================================================= */
26 
27 #ifdef USING_PCH
28 #	include "pch.h"
29 #else
30 #ifdef HAVE_CONFIG_H
31 #	include <config.h>
32 #endif
33 
34 #include "sphere_distort.h"
35 
36 #include <synfig/localization.h>
37 #include <synfig/general.h>
38 
39 #include <synfig/string.h>
40 #include <synfig/time.h>
41 #include <synfig/context.h>
42 #include <synfig/paramdesc.h>
43 #include <synfig/renddesc.h>
44 #include <synfig/surface.h>
45 #include <synfig/value.h>
46 #include <synfig/valuenode.h>
47 #include <synfig/transform.h>
48 #include <synfig/cairo_renddesc.h>
49 
50 #include <synfig/curve_helper.h>
51 
52 #endif
53 
54 /* === U S I N G =========================================================== */
55 
56 using namespace std;
57 using namespace etl;
58 using namespace synfig;
59 using namespace modules;
60 using namespace lyr_std;
61 
62 /* === M A C R O S ========================================================= */
63 
64 #ifndef PI
65 const double PI = 3.14159265;
66 #endif
67 
68 enum
69 {
70 	TYPE_NORMAL = 0,
71 	TYPE_DISTH = 1, //axe the horizontal axis
72 	TYPE_DISTV = 2, //axe the vertical axis
73 	N_TYPES
74 };
75 
76 /* === G L O B A L S ======================================================= */
77 
78 SYNFIG_LAYER_INIT(Layer_SphereDistort);
79 SYNFIG_LAYER_SET_NAME(Layer_SphereDistort,"spherize");
80 SYNFIG_LAYER_SET_LOCAL_NAME(Layer_SphereDistort,N_("Spherize"));
81 SYNFIG_LAYER_SET_CATEGORY(Layer_SphereDistort,N_("Distortions"));
82 SYNFIG_LAYER_SET_VERSION(Layer_SphereDistort,"0.2");
83 SYNFIG_LAYER_SET_CVS_ID(Layer_SphereDistort,"$Id$");
84 
85 /* === P R O C E D U R E S ================================================= */
86 
87 /* === M E T H O D S ======================================================= */
88 
89 /* === E N T R Y P O I N T ================================================= */
90 
Layer_SphereDistort()91 Layer_SphereDistort::Layer_SphereDistort():
92 param_center(ValueBase(Vector(0,0))),
93 param_radius(ValueBase(double(1))),
94 param_amount(ValueBase(double(1))),
95 param_type(ValueBase(int(TYPE_NORMAL))),
96 param_clip(ValueBase(false))
97 {
98 	SET_INTERPOLATION_DEFAULTS();
99 	SET_STATIC_DEFAULTS();
100 }
101 
102 
103 bool
set_param(const String & param,const ValueBase & value)104 Layer_SphereDistort::set_param(const String & param, const ValueBase &value)
105 {
106 	IMPORT_VALUE_PLUS(param_center,sync());
107 	IMPORT_VALUE_PLUS(param_radius,sync());
108 	IMPORT_VALUE(param_type);
109 	IMPORT_VALUE(param_amount);
110 	IMPORT_VALUE(param_clip);
111 
112 	if(param=="percent" && param_amount.get_type()==value.get_type())
113 		return set_param("amount", value);
114 
115 	return Layer::set_param(param,value);
116 }
117 
118 ValueBase
get_param(const String & param) const119 Layer_SphereDistort::get_param(const String &param)const
120 {
121 	EXPORT_VALUE(param_center);
122 	EXPORT_VALUE(param_radius);
123 	EXPORT_VALUE(param_type);
124 	EXPORT_VALUE(param_amount);
125 	EXPORT_VALUE(param_clip);
126 	if(param=="percent")
127 		return get_param("amount");
128 
129 	EXPORT_NAME();
130 	EXPORT_VERSION();
131 
132 	return Layer::get_param(param);
133 }
134 
135 void
sync()136 Layer_SphereDistort::sync()
137 {
138 // Remove me?
139 }
140 
141 Layer::Vocab
get_param_vocab() const142 Layer_SphereDistort::get_param_vocab()const
143 {
144 	Layer::Vocab ret;
145 
146 	ret.push_back(ParamDesc("center")
147 		.set_local_name(_("Position"))
148 		.set_description(_("Where the sphere distortion is centered"))
149 	);
150 
151 	ret.push_back(ParamDesc("radius")
152 		.set_local_name(_("Radius"))
153 		.set_origin("center")
154 		.set_is_distance()
155 		.set_description(_("The size of the sphere distortion"))
156 	);
157 
158 	ret.push_back(ParamDesc("amount")
159 		.set_local_name(_("Amount"))
160 		.set_is_distance(false)
161 		.set_description(_("The distortion intensity (negative values inverts effect)"))
162 	);
163 
164 	ret.push_back(ParamDesc("clip")
165 		.set_local_name(_("Clip"))
166 		.set_description(_("When cheked, the area outside the Radius are not distorted"))
167 	);
168 
169 	ret.push_back(ParamDesc("type")
170 		.set_local_name(_("Distort Type"))
171 		.set_description(_("The direction of the distortion"))
172 		.set_hint("enum")
173 		.add_enum_value(TYPE_NORMAL,"normal",_("Spherize"))
174 		.add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar"))
175 		.add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar"))
176 	);
177 
178 	return ret;
179 }
180 
181 /*
182 	Spherical Distortion: maps an image onto a ellipsoid of some sort
183 
184 	so the image coordinate (i.e. distance away from the center)
185 	will determine how things get mapped
186 
187 	so with the radius and position the mapping would go as follows
188 
189 	r = (pos - center) / radius	clamped to [-1,1]
190 
191 	if it's outside of that range then it's not distorted
192 	but if it's inside of that range then it goes as follows
193 
194 	angle = r * pi/2 (-pi/2,pi/2)
195 
196 	newr = cos(angle)*radius
197 
198 	the inverse of this is (which is actually what we'd be transforming it from
199 
200 
201 */
202 
spherify(float f)203 inline float spherify(float f)
204 {
205 	if(f > -1 && f < 1 && f!=0)
206 		return sinf(f*(PI/2));
207 	else return f;
208 }
209 
unspherify(float f)210 inline float unspherify(float f)
211 {
212 	if(f > -1 && f < 1 && f!=0)
213 		return asin(f)/(PI/2);
214 	else return f;
215 }
216 
sphtrans(const Point & p,const Point & center,const float & radius,const Real & percent,int type,bool & clipped)217 Point sphtrans(const Point &p, const Point &center, const float &radius,
218 											const Real &percent, int type, bool& clipped)
219 {
220 	const Vector v = (p - center) / radius;
221 
222 	Point newp = p;
223 	const float t = percent;
224 
225 	clipped=false;
226 
227 	if(type == TYPE_NORMAL)
228 	{
229 		const float m = v.mag();
230 		float lerp(0);
231 
232 		if(m <= -1 || m >= 1)
233 		{
234 			clipped=true;
235 			return newp;
236 		}else
237 		if(m==0)
238 			return newp;
239 		else
240 		if(t > 0)
241 		{
242 			lerp = (t*unspherify(m) + (1-t)*m);
243 		}else if(t < 0)
244 		{
245 			lerp = ((1+t)*m - t*spherify(m));
246 		}else lerp = m;
247 
248 		const float d = lerp*radius;
249 		newp = center + v*(d/m);
250 	}
251 
252 	else if(type == TYPE_DISTH)
253 	{
254 		float lerp(0);
255 		if(v[0] <= -1 || v[0] >= 1)
256 		{
257 			clipped=true;
258 			return newp;
259 		}else
260 		if(v[0]==0)
261 			return newp;
262 		else
263 		if(t > 0)
264 		{
265 			lerp = (t*unspherify(v[0]) + (1-t)*v[0]);
266 		}else if(t < 0)
267 		{
268 			lerp = ((1+t)*v[0] - t*spherify(v[0]));
269 		}else lerp = v[0];
270 
271 		newp[0] = center[0] + lerp*radius;
272 	}
273 
274 	else if(type == TYPE_DISTV)
275 	{
276 		float lerp(0);
277 		if(v[1] <= -1 || v[1] >= 1)
278 		{
279 			clipped=true;
280 			return newp;
281 		}
282 		else
283 		if(v[1]==0)
284 			return newp;
285 		else
286 		if(t > 0)
287 		{
288 			lerp = (t*unspherify(v[1]) + (1-t)*v[1]);
289 		}else if(t < 0)
290 		{
291 			lerp = ((1+t)*v[1] - t*spherify(v[1]));
292 		}else lerp = v[1];
293 
294 		newp[1] = center[1] + lerp*radius;
295 	}
296 
297 	return newp;
298 }
299 
sphtrans(const Point & p,const Point & center,const Real & radius,const Real & percent,int type)300 inline Point sphtrans(const Point &p, const Point &center, const Real &radius,
301 											const Real &percent, int type)
302 {
303 	bool tmp;
304 	return sphtrans(p, center, radius, percent, type, tmp);
305 }
306 
307 Layer::Handle
hit_check(Context context,const Point & pos) const308 Layer_SphereDistort::hit_check(Context context, const Point &pos)const
309 {
310 	Vector center=param_center.get(Vector());
311 	double radius=param_radius.get(double());
312 	double percent=param_amount.get(double());
313 	int type=param_type.get(int());
314 	bool clip=param_clip.get(bool());
315 
316 	bool clipped;
317 	Point point(sphtrans(pos,center,radius,percent,type,clipped));
318 	if(clip && clipped)
319 		return 0;
320 	return context.hit_check(point);
321 }
322 
323 Color
get_color(Context context,const Point & pos) const324 Layer_SphereDistort::get_color(Context context, const Point &pos)const
325 {
326 	Vector center=param_center.get(Vector());
327 	double radius=param_radius.get(double());
328 	double percent=param_amount.get(double());
329 	int type=param_type.get(int());
330 	bool clip=param_clip.get(bool());
331 
332 	bool clipped;
333 	Point point(sphtrans(pos,center,radius,percent,type,clipped));
334 	if(clip && clipped)
335 		return Color::alpha();
336 	return context.get_color(point);
337 }
338 
339 RendDesc
get_sub_renddesc_vfunc(const RendDesc & renddesc) const340 Layer_SphereDistort::get_sub_renddesc_vfunc(const RendDesc &renddesc) const
341 {
342 	RendDesc desc(renddesc);
343 	Real pw = desc.get_pw();
344 	Real ph = desc.get_ph();
345 	desc.set_tl(Vector(-10.0, -10.0));
346 	desc.set_br(Vector( 10.0,  10.0));
347 	desc.set_wh(
348 		(int)approximate_ceil(fabs((desc.get_br()[0] - desc.get_tl()[0])/pw)),
349 		(int)approximate_ceil(fabs((desc.get_br()[1] - desc.get_tl()[1])/ph)) );
350 	return desc;
351 }
352 
353 #if 1
354 bool
accelerated_render(Context context,Surface * surface,int quality,const RendDesc & renddesc,ProgressCallback * cb) const355 Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
356 {
357 	RENDER_TRANSFORMED_IF_NEED(__FILE__, __LINE__)
358 
359 	/*	Things to consider:
360 		1) Block expansion for distortion (ouch... quality level??)
361 		2) Bounding box clipping
362 		3) Super sampling for better visual quality (based on the quality level?)
363 		4) Interpolation type for sampling (based on quality level?)
364 
365 		//things to defer until after
366 		super sampling, non-linear interpolation
367 	*/
368 
369 	//bounding box reject
370 	Vector center=param_center.get(Vector());
371 	double radius=param_radius.get(double());
372 	double percent=param_amount.get(double());
373 	int type=param_type.get(int());
374 	bool clip=param_clip.get(bool());
375 	{
376 		Rect	sphr;
377 
378 		sphr.set_point(center[0]-radius,center[1]-radius);
379 		sphr.expand(center[0]+radius,center[1]+radius);
380 
381 		//get the bounding box of the transform
382 		Rect	windr;
383 
384 		//and the bounding box of the rendering
385 		windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
386 		windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
387 
388 		//test bounding boxes for collision
389 		if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
390 			(type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
391 			(type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
392 		{
393 			//warning("Spherize: Bounding box reject");
394 			if (clip)
395 			{
396 				surface->set_wh(renddesc.get_w(), renddesc.get_h());
397 				surface->clear();
398 				return true;
399 			}
400 			else
401 				return context.accelerated_render(surface,quality,renddesc,cb);
402 		}
403 
404 		//warning("Spherize: Bounding box accept");
405 	}
406 
407 	//Ok, so we overlap some... now expand the window for rendering
408 	RendDesc r = renddesc;
409 	Surface background;
410 	Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
411 
412 	int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
413 	Point tl = renddesc.get_tl(), br = renddesc.get_br();
414 
415 	{
416 		//must enlarge window by pixel coordinates so go!
417 
418 		//need to figure out closest and farthest point and distort THOSE
419 
420 		Point origin[4] = {tl,tl,br,br};
421 		Vector v[4] = {Vector(0,br[1]-tl[1]),
422 					   Vector(br[0]-tl[0],0),
423 					   Vector(0,tl[1]-br[1]),
424 					   Vector(tl[0]-br[0],0)};
425 
426 		Point close(0,0);
427 		Real t = 0;
428 		Rect	expandr(tl,br);
429 
430 		//expandr.set_point(tl[0],tl[1]);
431 		//expandr.expand(br[0],br[1]);
432 
433 		//warning("Spherize: Loop through lines and stuff");
434 		for(int i=0; i<4; ++i)
435 		{
436 			//warning("Spherize: 	%d", i);
437 			Vector p_o = center-origin[i];
438 
439 			//project onto left line
440 			t = (p_o*v[i])/v[i].mag_squared();
441 
442 			//clamp
443 			if(t < 0) t = 0; if(t > 1) t = 1;
444 
445 			close = origin[i] + v[i]*t;
446 
447 			//now get transforms and expand the rectangle to accommodate
448 			Point p = sphtrans(close,center,radius,percent,type);
449 			expandr.expand(p[0],p[1]);
450 			p = sphtrans(origin[i],center,radius,percent,type);
451 			expandr.expand(p[0],p[1]);
452 			p = sphtrans(origin[i]+v[i],center,radius,percent,type);
453 			expandr.expand(p[0],p[1]);
454 		}
455 
456 		/*warning("Spherize: Bounding box (%f,%f)-(%f,%f)",
457 							expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/
458 
459 		//now that we have the bounding rectangle of ALL the pixels (should be...)
460 		//order it so that it's in the same orientation as the tl,br pair
461 
462 		//warning("Spherize: Organize like tl,br");
463 		Point ntl(0,0),nbr(0,0);
464 
465 		//sort x
466 		if(tl[0] < br[0])
467 		{
468 			ntl[0] = expandr.minx;
469 			nbr[0] = expandr.maxx;
470 		}
471 		else
472 		{
473 			ntl[0] = expandr.maxx;
474 			nbr[0] = expandr.minx;
475 		}
476 
477 		//sort y
478 		if(tl[1] < br[1])
479 		{
480 			ntl[1] = expandr.miny;
481 			nbr[1] = expandr.maxy;
482 		}
483 		else
484 		{
485 			ntl[1] = expandr.maxy;
486 			nbr[1] = expandr.miny;
487 		}
488 
489 		//now expand the window as needed
490 		Vector temp = ntl-tl;
491 
492 		//pixel offset
493 		nl = (int)(temp[0]/pw)-1;
494 		nt = (int)(temp[1]/ph)-1;
495 
496 		temp = nbr - br;
497 		nr = (int)(temp[0]/pw)+1;
498 		nb = (int)(temp[1]/ph)+1;
499 
500 		nw = renddesc.get_w() + nr - nl;
501 		nh = renddesc.get_h() + nb - nt;
502 
503 		//warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh);
504 		r.set_subwindow(nl,nt,nw,nh);
505 
506 		/*r = renddesc;
507 		nw = r.get_w(), nh = r.get_h();
508 		nl = 0, nt = 0;*/
509 	}
510 
511 	//warning("Spherize: render background");
512 	if(!context.accelerated_render(&background,quality,r,cb))
513 	{
514 		warning("SphereDistort: Layer below failed");
515 		return false;
516 	}
517 
518 	//now distort and check to make sure we aren't overshooting our bounds here
519 	int w = renddesc.get_w(), h = renddesc.get_h();
520 	surface->set_wh(w,h);
521 
522 	Point sample = tl, sub = tl, trans(0,0);
523 	float xs = 0,ys = 0;
524 	int y=0,x=0;
525 	Real invpw = 1/pw, invph = 1/ph;
526 	Surface::pen	p = surface->begin();
527 
528 	Point rtl = r.get_tl();
529 
530 	//warning("Spherize: About to transform");
531 
532 	for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y())
533 	{
534 		sub = sample;
535 		for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x())
536 		{
537 			bool clipped;
538 			trans=sphtrans(sub,center,radius,percent,type,clipped);
539 			if(clip && clipped)
540 			{
541 				p.put_value(Color::alpha());
542 				continue;
543 			}
544 
545 			xs = (trans[0]-rtl[0])*invpw;
546 			ys = (trans[1]-rtl[1])*invph;
547 
548 			if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
549 			{
550 				//warning("Spherize: we failed to account for %f,%f",xs,ys);
551 				p.put_value(context.get_color(trans));//Color::alpha());
552 				continue;
553 			}
554 
555 			//sample at that pixel location based on the quality
556 			if(quality <= 4)	// cubic
557 				p.put_value(background.cubic_sample(xs,ys));
558 			else if(quality <= 5) // cosine
559 				p.put_value(background.cosine_sample(xs,ys));
560 			else if(quality <= 6) // linear
561 				p.put_value(background.linear_sample(xs,ys));
562 			else				// nearest
563 				p.put_value(background[round_to_int(ys)][round_to_int(xs)]);
564 		}
565 		p.dec_x(w);
566 	}
567 
568 	return true;
569 }
570 
571 ////////
572 bool
accelerated_cairorender(Context context,cairo_t * cr,int quality,const RendDesc & renddesc_,ProgressCallback * cb) const573 Layer_SphereDistort::accelerated_cairorender(Context context, cairo_t *cr, int quality, const RendDesc &renddesc_, ProgressCallback *cb)const
574 {
575 	/*	Things to consider:
576 	 1) Block expansion for distortion (ouch... quality level??)
577 	 2) Bounding box clipping
578 	 3) Super sampling for better visual quality (based on the quality level?)
579 	 4) Interpolation type for sampling (based on quality level?)
580 
581 	 //things to defer until after
582 	 super sampling, non-linear interpolation
583 	 */
584 
585 	Vector center=param_center.get(Vector());
586 	double radius=param_radius.get(double());
587 	double percent=param_amount.get(double());
588 	int type=param_type.get(int());
589 	bool clip=param_clip.get(bool());
590 
591 	RendDesc	renddesc(renddesc_);
592 
593 	// Untransform the render desc
594 	if(!cairo_renddesc_untransform(cr, renddesc))
595 		return false;
596 
597 	//bounding box reject
598 	{
599 		Rect	sphr;
600 
601 		sphr.set_point(center[0]-radius,center[1]-radius);
602 		sphr.expand(center[0]+radius,center[1]+radius);
603 
604 		//get the bounding box of the transform
605 		Rect	windr;
606 
607 		//and the bounding box of the rendering
608 		windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]);
609 		windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]);
610 
611 		//test bounding boxes for collision
612 		if( (type == TYPE_NORMAL && !intersect(sphr,windr)) ||
613 		   (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) ||
614 		   (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) )
615 		{
616 			if (clip)
617 			{
618 				cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
619 				cairo_paint(cr);
620 				return true;
621 			}
622 			else
623 				return context.accelerated_cairorender(cr,quality,renddesc,cb);
624 		}
625 	}
626 
627 	//Ok, so we overlap some... now expand the window for rendering
628 	RendDesc r = renddesc;
629 	cairo_surface_t* background, *result;
630 	int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0;
631 
632 	// grab the current renddesc interesting values
633 	const Real pw = renddesc.get_pw(),ph = renddesc.get_ph();
634 	const Point tl = renddesc.get_tl(), br = renddesc.get_br();
635 	const int w = renddesc.get_w(), h = renddesc.get_h();
636 
637 	{
638 		//must enlarge window by pixel coordinates so go!
639 
640 		//need to figure out closest and farthest point and distort THOSE
641 
642 		Point origin[4] = {tl,tl,br,br};
643 		Vector v[4] = {Vector(0,br[1]-tl[1]),
644 			Vector(br[0]-tl[0],0),
645 			Vector(0,tl[1]-br[1]),
646 			Vector(tl[0]-br[0],0)};
647 
648 		Point close(0,0);
649 		Real t = 0;
650 		Rect	expandr(tl,br);
651 
652 		//expandr.set_point(tl[0],tl[1]);
653 		//expandr.expand(br[0],br[1]);
654 
655 		for(int i=0; i<4; ++i)
656 		{
657 			Vector p_o = center-origin[i];
658 
659 			//project onto left line
660 			t = (p_o*v[i])/v[i].mag_squared();
661 
662 			//clamp
663 			if(t < 0) t = 0; if(t > 1) t = 1;
664 
665 			close = origin[i] + v[i]*t;
666 
667 			//now get transforms and expand the rectangle to accommodate
668 			Point p = sphtrans(close,center,radius,percent,type);
669 			expandr.expand(p[0],p[1]);
670 			p = sphtrans(origin[i],center,radius,percent,type);
671 			expandr.expand(p[0],p[1]);
672 			p = sphtrans(origin[i]+v[i],center,radius,percent,type);
673 			expandr.expand(p[0],p[1]);
674 		}
675 
676 		//now that we have the bounding rectangle of ALL the pixels (should be...)
677 		//order it so that it's in the same orientation as the tl,br pair
678 
679 		Point ntl(0,0),nbr(0,0);
680 
681 		//sort x
682 		if(tl[0] < br[0])
683 		{
684 			ntl[0] = expandr.minx;
685 			nbr[0] = expandr.maxx;
686 		}
687 		else
688 		{
689 			ntl[0] = expandr.maxx;
690 			nbr[0] = expandr.minx;
691 		}
692 
693 		//sort y
694 		if(tl[1] < br[1])
695 		{
696 			ntl[1] = expandr.miny;
697 			nbr[1] = expandr.maxy;
698 		}
699 		else
700 		{
701 			ntl[1] = expandr.maxy;
702 			nbr[1] = expandr.miny;
703 		}
704 
705 		//now expand the window as needed
706 		Vector temp = ntl-tl;
707 
708 		//pixel offset
709 		nl = (int)(temp[0]/pw)-1;
710 		nt = (int)(temp[1]/ph)-1;
711 
712 		temp = nbr - br;
713 		nr = (int)(temp[0]/pw)+1;
714 		nb = (int)(temp[1]/ph)+1;
715 
716 		nw = renddesc.get_w() + nr - nl;
717 		nh = renddesc.get_h() + nb - nt;
718 
719 		r.set_subwindow(nl,nt,nw,nh);
720 	}
721 	// New values for expanded
722 	const double wpw =r.get_pw();
723 	const double wph =r.get_ph();
724 	const double wtlx=r.get_tl()[0];
725 	const double wtly=r.get_tl()[1];
726 
727 	// now we know the sice of the needed background, create the cairo surface
728 	background=cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, nw, nh);
729 	result=cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, w, h);
730 	// render the background
731 	cairo_t* subcr=cairo_create(background);
732 	cairo_scale(subcr, 1/wpw, 1/wph);
733 	cairo_translate(subcr, -wtlx, -wtly);
734 	if(!context.accelerated_cairorender(subcr,quality,r,cb))
735 	{
736 		warning("Cairo SphereDistort: Layer below failed");
737 		return false;
738 	}
739 	cairo_destroy(subcr);
740 	//now distort and check to make sure we aren't overshooting our bounds here
741 
742 	Point sample = tl, sub = tl, trans(0,0);
743 	float xs = 0,ys = 0;
744 	int y=0,x=0;
745 	Real invpw = 1/pw, invph = 1/ph;
746 	Point rtl = r.get_tl();
747 
748 	CairoSurface cresult(result);
749 	if(!cresult.map_cairo_image())
750 	{
751 		warning("Sphere Distort: map cairo surface failed");
752 		return false;
753 	}
754 	CairoSurface cbackground(background);
755 	if(!cbackground.map_cairo_image())
756 	{
757 		warning("Sphere Distort: map cairo surface failed");
758 		return false;
759 	}
760 
761 	for(y = 0; y < h; ++y, sample[1] += ph)
762 	{
763 		sub = sample;
764 		for(x = 0; x < w; ++x, sub[0] += pw)
765 		{
766 			bool clipped;
767 			trans=sphtrans(sub,center,radius,percent,type,clipped);
768 			if(clip && clipped)
769 			{
770 				cresult[y][x]=CairoColor::alpha();
771 				continue;
772 			}
773 
774 			xs = (trans[0]-rtl[0])*invpw;
775 			ys = (trans[1]-rtl[1])*invph;
776 
777 			if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh))
778 			{
779 				cresult[y][x]=context.get_cairocolor(trans).premult_alpha();
780 				continue;
781 			}
782 
783 			//sample at that pixel location based on the quality
784 			if(quality <= 4)	// cubic
785 				cresult[y][x]=cbackground.cubic_sample_cooked(xs, ys).premult_alpha();
786 			else if(quality <= 5) // cosine
787 				cresult[y][x]=cbackground.cosine_sample_cooked(xs, ys).premult_alpha();
788 			else if(quality <= 6) // linear
789 				cresult[y][x]=cbackground.linear_sample_cooked(xs, ys).premult_alpha();
790 			else				// nearest
791 				cresult[y][x]=cbackground[round_to_int(ys)][round_to_int(xs)].premult_alpha();
792 		}
793 	}
794 	cresult.unmap_cairo_image();
795 	cbackground.unmap_cairo_image();
796 
797 	cairo_surface_destroy(background);
798 
799 	cairo_save(cr);
800 
801 	cairo_translate(cr, tl[0], tl[1]);
802 	cairo_scale(cr, pw, ph);
803 	cairo_set_source_surface(cr, result, 0, 0);
804 	cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
805 	cairo_paint(cr);
806 
807 	cairo_restore(cr);
808 	cairo_surface_destroy(result);
809 
810 	return true;
811 }
812 
813 #endif
814 
815 class lyr_std::Spherize_Trans : public Transform
816 {
817 	etl::handle<const Layer_SphereDistort> layer;
818 public:
Spherize_Trans(const Layer_SphereDistort * x)819 	Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { }
820 
perform(const Vector & x) const821 	Vector perform(const Vector& x)const
822 	{
823 		return sphtrans(x,layer->param_center.get(Vector()),layer->param_radius.get(double()),-layer->param_amount.get(double()),layer->param_type.get(int()));
824 	}
825 
unperform(const Vector & x) const826 	Vector unperform(const Vector& x)const
827 	{
828 		return sphtrans(x,layer->param_center.get(Vector()),layer->param_radius.get(double()),-layer->param_amount.get(double()),layer->param_type.get(int()));
829 	}
830 
get_string() const831 	String get_string()const
832 	{
833 		return "spheredistort";
834 	}
835 };
836 
837 etl::handle<Transform>
get_transform() const838 Layer_SphereDistort::get_transform()const
839 {
840 	return new Spherize_Trans(this);
841 }
842 
843 Rect
get_bounding_rect() const844 Layer_SphereDistort::get_bounding_rect()const
845 {
846 	Vector center=param_center.get(Vector());
847 	double radius=param_radius.get(double());
848 	int type=param_type.get(int());
849 	bool clip=param_clip.get(bool());
850 
851 	Rect bounds(Rect::full_plane());
852 
853 	if (clip)
854 		return bounds;
855 
856 	switch(type)
857 	{
858 		case TYPE_NORMAL:
859 			bounds=Rect(center[0]+radius, center[1]+radius,
860 						center[0]-radius, center[1]-radius);
861 			break;
862 		case TYPE_DISTH:
863 			bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius);
864 			break;
865 		case TYPE_DISTV:
866 			bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius);
867 			break;
868 		default:
869 			break;
870 	}
871 
872 	return bounds;
873 }
874