X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=synfig-core%2Fsrc%2Fmodules%2Flyr_std%2Fsphere_distort.cpp;fp=synfig-core%2Fsrc%2Fmodules%2Flyr_std%2Fsphere_distort.cpp;h=9c0003e677e2c6141fbf216e8fb691f4fbca51db;hb=a095981e18cc37a8ecc7cd237cc22b9c10329264;hp=0000000000000000000000000000000000000000;hpb=9459638ad6797b8139f1e9f0715c96076dbf0890;p=synfig.git diff --git a/synfig-core/src/modules/lyr_std/sphere_distort.cpp b/synfig-core/src/modules/lyr_std/sphere_distort.cpp new file mode 100644 index 0000000..9c0003e --- /dev/null +++ b/synfig-core/src/modules/lyr_std/sphere_distort.cpp @@ -0,0 +1,578 @@ +/* === S Y N F I G ========================================================= */ +/*! \file sphere_distort.cpp +** \brief Implementation of the "Spherize" layer +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** Copyright (c) 2007, 2008 Chris Moore +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +/* === H E A D E R S ======================================================= */ + +#ifdef USING_PCH +# include "pch.h" +#else +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "sphere_distort.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#endif + +/* === U S I N G =========================================================== */ + +using namespace std; +using namespace etl; +using namespace synfig; + +/* === M A C R O S ========================================================= */ + +#ifndef PI +const double PI = 3.14159265; +#endif + +enum +{ + TYPE_NORMAL = 0, + TYPE_DISTH = 1, //axe the horizontal axis + TYPE_DISTV = 2, //axe the vertical axis + N_TYPES +}; + +/* === G L O B A L S ======================================================= */ + +SYNFIG_LAYER_INIT(Layer_SphereDistort); +SYNFIG_LAYER_SET_NAME(Layer_SphereDistort,"spherize"); +SYNFIG_LAYER_SET_LOCAL_NAME(Layer_SphereDistort,N_("Spherize")); +SYNFIG_LAYER_SET_CATEGORY(Layer_SphereDistort,N_("Distortions")); +SYNFIG_LAYER_SET_VERSION(Layer_SphereDistort,"0.2"); +SYNFIG_LAYER_SET_CVS_ID(Layer_SphereDistort,"$Id$"); + +/* === P R O C E D U R E S ================================================= */ + +/* === M E T H O D S ======================================================= */ + +/* === E N T R Y P O I N T ================================================= */ + +Layer_SphereDistort::Layer_SphereDistort() +:center(0,0), +radius(1), +percent(1.0), +type(TYPE_NORMAL), +clip(false) +{ +} + + +bool +Layer_SphereDistort::set_param(const String & param, const ValueBase &value) +{ + IMPORT_PLUS(center,sync()); + IMPORT_PLUS(radius,sync()); + IMPORT(type); + IMPORT_AS(percent,"amount"); + IMPORT(clip); + + if(param=="percent") + { + if(dynamic_param_list().count("percent")) + { + connect_dynamic_param("amount",dynamic_param_list().find("percent")->second); + disconnect_dynamic_param("percent"); + synfig::warning("Layer_SphereDistort::::set_param(): Updated valuenode connection to use the new \"amount\" parameter."); + } + else + synfig::warning("Layer_SphereDistort::::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead."); + } + + return false; +} + +ValueBase +Layer_SphereDistort::get_param(const String ¶m)const +{ + EXPORT(center); + EXPORT(radius); + EXPORT(type); + EXPORT_AS(percent,"amount"); + EXPORT(clip); + + EXPORT_NAME(); + EXPORT_VERSION(); + + return ValueBase(); +} + +void +Layer_SphereDistort::sync() +{ +} + +Layer::Vocab +Layer_SphereDistort::get_param_vocab()const +{ + Layer::Vocab ret; + + ret.push_back(ParamDesc("center") + .set_local_name(_("Position")) + ); + + ret.push_back(ParamDesc("radius") + .set_local_name(_("Radius")) + .set_origin("center") + .set_is_distance() + ); + + ret.push_back(ParamDesc("amount") + .set_local_name(_("Amount")) + .set_is_distance(false) + ); + + ret.push_back(ParamDesc("clip") + .set_local_name(_("Clip")) + ); + + ret.push_back(ParamDesc("type") + .set_local_name(_("Distort Type")) + .set_description(_("The direction of the distortion")) + .set_hint("enum") + .add_enum_value(TYPE_NORMAL,"normal",_("Spherize")) + .add_enum_value(TYPE_DISTH,"honly",_("Vertical Bar")) + .add_enum_value(TYPE_DISTV,"vonly",_("Horizontal Bar")) + ); + + return ret; +} + +/* + Spherical Distortion: maps an image onto a ellipsoid of some sort + + so the image coordinate (i.e. distance away from the center) + will determine how things get mapped + + so with the radius and position the mapping would go as follows + + r = (pos - center) / radius clamped to [-1,1] + + if it's outside of that range then it's not distorted + but if it's inside of that range then it goes as follows + + angle = r * pi/2 (-pi/2,pi/2) + + newr = cos(angle)*radius + + the inverse of this is (which is actually what we'd be transforming it from + + +*/ + +inline float spherify(float f) +{ + if(f > -1 && f < 1 && f!=0) + return sinf(f*(PI/2)); + else return f; +} + +inline float unspherify(float f) +{ + if(f > -1 && f < 1 && f!=0) + return asin(f)/(PI/2); + else return f; +} + +Point sphtrans(const Point &p, const Point ¢er, const float &radius, + const Real &percent, int type, bool& clipped) +{ + const Vector v = (p - center) / radius; + + Point newp = p; + const float t = percent; + + clipped=false; + + if(type == TYPE_NORMAL) + { + const float m = v.mag(); + float lerp(0); + + if(m <= -1 || m >= 1) + { + clipped=true; + return newp; + }else + if(m==0) + return newp; + else + if(t > 0) + { + lerp = (t*unspherify(m) + (1-t)*m); + }else if(t < 0) + { + lerp = ((1+t)*m - t*spherify(m)); + }else lerp = m; + + const float d = lerp*radius; + newp = center + v*(d/m); + } + + else if(type == TYPE_DISTH) + { + float lerp(0); + if(v[0] <= -1 || v[0] >= 1) + { + clipped=true; + return newp; + }else + if(v[0]==0) + return newp; + else + if(t > 0) + { + lerp = (t*unspherify(v[0]) + (1-t)*v[0]); + }else if(t < 0) + { + lerp = ((1+t)*v[0] - t*spherify(v[0])); + }else lerp = v[0]; + + newp[0] = center[0] + lerp*radius; + } + + else if(type == TYPE_DISTV) + { + float lerp(0); + if(v[1] <= -1 || v[1] >= 1) + { + clipped=true; + return newp; + } + else + if(v[1]==0) + return newp; + else + if(t > 0) + { + lerp = (t*unspherify(v[1]) + (1-t)*v[1]); + }else if(t < 0) + { + lerp = ((1+t)*v[1] - t*spherify(v[1])); + }else lerp = v[1]; + + newp[1] = center[1] + lerp*radius; + } + + return newp; +} + +inline Point sphtrans(const Point &p, const Point ¢er, const Real &radius, + const Real &percent, int type) +{ + bool tmp; + return sphtrans(p, center, radius, percent, type, tmp); +} + +synfig::Layer::Handle +Layer_SphereDistort::hit_check(synfig::Context context, const synfig::Point &pos)const +{ + bool clipped; + Point point(sphtrans(pos,center,radius,percent,type,clipped)); + if(clip && clipped) + return 0; + return context.hit_check(point); +} + +Color +Layer_SphereDistort::get_color(Context context, const Point &pos)const +{ + bool clipped; + Point point(sphtrans(pos,center,radius,percent,type,clipped)); + if(clip && clipped) + return Color::alpha(); + return context.get_color(point); +} + +#if 1 +bool Layer_SphereDistort::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const +{ + /* Things to consider: + 1) Block expansion for distortion (ouch... quality level??) + 2) Bounding box clipping + 3) Super sampling for better visual quality (based on the quality level?) + 4) Interpolation type for sampling (based on quality level?) + + //things to defer until after + super sampling, non-linear interpolation + */ + + //bounding box reject + { + Rect sphr; + + sphr.set_point(center[0]-radius,center[1]-radius); + sphr.expand(center[0]+radius,center[1]+radius); + + //get the bounding box of the transform + Rect windr; + + //and the bounding box of the rendering + windr.set_point(renddesc.get_tl()[0],renddesc.get_tl()[1]); + windr.expand(renddesc.get_br()[0],renddesc.get_br()[1]); + + //test bounding boxes for collision + if( (type == TYPE_NORMAL && !intersect(sphr,windr)) || + (type == TYPE_DISTH && (sphr.minx >= windr.maxx || windr.minx >= sphr.maxx)) || + (type == TYPE_DISTV && (sphr.miny >= windr.maxy || windr.miny >= sphr.maxy)) ) + { + //synfig::warning("Spherize: Bounding box reject"); + if (clip) + { + surface->set_wh(renddesc.get_w(), renddesc.get_h()); + surface->clear(); + return true; + } + else + return context.accelerated_render(surface,quality,renddesc,cb); + } + + //synfig::warning("Spherize: Bounding box accept"); + } + + //Ok, so we overlap some... now expand the window for rendering + RendDesc r = renddesc; + Surface background; + Real pw = renddesc.get_pw(),ph = renddesc.get_ph(); + + int nl=0,nt=0,nr=0,nb=0, nw=0,nh=0; + Point tl = renddesc.get_tl(), br = renddesc.get_br(); + + { + //must enlarge window by pixel coordinates so go! + + //need to figure out closest and farthest point and distort THOSE + + Point origin[4] = {tl,tl,br,br}; + Vector v[4] = {Vector(0,br[1]-tl[1]), + Vector(br[0]-tl[0],0), + Vector(0,tl[1]-br[1]), + Vector(tl[0]-br[0],0)}; + + Point close(0,0); + Real t = 0; + Rect expandr(tl,br); + + //expandr.set_point(tl[0],tl[1]); + //expandr.expand(br[0],br[1]); + + //synfig::warning("Spherize: Loop through lines and stuff"); + for(int i=0; i<4; ++i) + { + //synfig::warning("Spherize: %d", i); + Vector p_o = center-origin[i]; + + //project onto left line + t = (p_o*v[i])/v[i].mag_squared(); + + //clamp + if(t < 0) t = 0; if(t > 1) t = 1; + + close = origin[i] + v[i]*t; + + //now get transforms and expand the rectangle to accommodate + Point p = sphtrans(close,center,radius,percent,type); + expandr.expand(p[0],p[1]); + p = sphtrans(origin[i],center,radius,percent,type); + expandr.expand(p[0],p[1]); + p = sphtrans(origin[i]+v[i],center,radius,percent,type); + expandr.expand(p[0],p[1]); + } + + /*synfig::warning("Spherize: Bounding box (%f,%f)-(%f,%f)", + expandr.minx,expandr.miny,expandr.maxx,expandr.maxy);*/ + + //now that we have the bounding rectangle of ALL the pixels (should be...) + //order it so that it's in the same orientation as the tl,br pair + + //synfig::warning("Spherize: Organize like tl,br"); + Point ntl(0,0),nbr(0,0); + + //sort x + if(tl[0] < br[0]) + { + ntl[0] = expandr.minx; + nbr[0] = expandr.maxx; + } + else + { + ntl[0] = expandr.maxx; + nbr[0] = expandr.minx; + } + + //sort y + if(tl[1] < br[1]) + { + ntl[1] = expandr.miny; + nbr[1] = expandr.maxy; + } + else + { + ntl[1] = expandr.maxy; + nbr[1] = expandr.miny; + } + + //now expand the window as needed + Vector temp = ntl-tl; + + //pixel offset + nl = (int)(temp[0]/pw)-1; + nt = (int)(temp[1]/ph)-1; + + temp = nbr - br; + nr = (int)(temp[0]/pw)+1; + nb = (int)(temp[1]/ph)+1; + + nw = renddesc.get_w() + nr - nl; + nh = renddesc.get_h() + nb - nt; + + //synfig::warning("Spherize: Setting subwindow (%d,%d) (%d,%d) (%d,%d)",nl,nt,nr,nb,nw,nh); + r.set_subwindow(nl,nt,nw,nh); + + /*r = renddesc; + nw = r.get_w(), nh = r.get_h(); + nl = 0, nt = 0;*/ + } + + //synfig::warning("Spherize: render background"); + if(!context.accelerated_render(&background,quality,r,cb)) + { + synfig::warning("SphereDistort: Layer below failed"); + return false; + } + + //now distort and check to make sure we aren't overshooting our bounds here + int w = renddesc.get_w(), h = renddesc.get_h(); + surface->set_wh(w,h); + + Point sample = tl, sub = tl, trans(0,0); + float xs = 0,ys = 0; + int y=0,x=0; + Real invpw = 1/pw, invph = 1/ph; + Surface::pen p = surface->begin(); + + Point rtl = r.get_tl(); + + //synfig::warning("Spherize: About to transform"); + + for(y = 0; y < h; ++y, sample[1] += ph, p.inc_y()) + { + sub = sample; + for(x = 0; x < w; ++x, sub[0] += pw, p.inc_x()) + { + bool clipped; + trans=sphtrans(sub,center,radius,percent,type,clipped); + if(clip && clipped) + { + p.put_value(Color::alpha()); + continue; + } + + xs = (trans[0]-rtl[0])*invpw; + ys = (trans[1]-rtl[1])*invph; + + if(!(xs >= 0 && xs < nw && ys >= 0 && ys < nh)) + { + //synfig::warning("Spherize: we failed to account for %f,%f",xs,ys); + p.put_value(context.get_color(trans));//Color::alpha()); + continue; + } + + //sample at that pixel location based on the quality + if(quality <= 4) // cubic + p.put_value(background.cubic_sample(xs,ys)); + else if(quality <= 5) // cosine + p.put_value(background.cosine_sample(xs,ys)); + else if(quality <= 6) // linear + p.put_value(background.linear_sample(xs,ys)); + else // nearest + p.put_value(background[round_to_int(ys)][round_to_int(xs)]); + } + p.dec_x(w); + } + + return true; +} +#endif + +class synfig::Spherize_Trans : public synfig::Transform +{ + etl::handle layer; +public: + Spherize_Trans(const Layer_SphereDistort* x):Transform(x->get_guid()),layer(x) { } + + synfig::Vector perform(const synfig::Vector& x)const + { + return sphtrans(x,layer->center,layer->radius,-layer->percent,layer->type); + } + + synfig::Vector unperform(const synfig::Vector& x)const + { + return sphtrans(x,layer->center,layer->radius,layer->percent,layer->type); + } +}; + +etl::handle +Layer_SphereDistort::get_transform()const +{ + return new Spherize_Trans(this); +} + +Rect +Layer_SphereDistort::get_bounding_rect()const +{ + Rect bounds(Rect::full_plane()); + + if (clip) + return bounds; + + switch(type) + { + case TYPE_NORMAL: + bounds=Rect(center[0]+radius, center[1]+radius, + center[0]-radius, center[1]-radius); + break; + case TYPE_DISTH: + bounds = Rect::vertical_strip(center[0]-radius, center[0]+radius); + break; + case TYPE_DISTV: + bounds = Rect::horizontal_strip(center[1]-radius, center[1]+radius); + break; + default: + break; + } + + return bounds; +}