/* === S Y N F I G ========================================================= */ /*! \file curvegradient.cpp ** \brief Implementation of the "Curve Gradient" layer ** ** $Id$ ** ** \legal ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley ** Copyright (c) 2007-2008 Chris Moore ** Copyright (c) 2011 Carlos López ** ** 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 ** ** === N O T E S =========================================================== ** ** ========================================================================= */ /* === H E A D E R S ======================================================= */ #ifdef USING_PCH # include "pch.h" #else #ifdef HAVE_CONFIG_H # include #endif #include "curvegradient.h" #include #include #include #include #include #include #include #include #include #include #include #endif /* === M A C R O S ========================================================= */ #define FAKE_TANGENT_STEP 0.000001 /* === G L O B A L S ======================================================= */ SYNFIG_LAYER_INIT(CurveGradient); SYNFIG_LAYER_SET_NAME(CurveGradient,"curve_gradient"); SYNFIG_LAYER_SET_LOCAL_NAME(CurveGradient,N_("Curve Gradient")); SYNFIG_LAYER_SET_CATEGORY(CurveGradient,N_("Gradients")); SYNFIG_LAYER_SET_VERSION(CurveGradient,"0.0"); SYNFIG_LAYER_SET_CVS_ID(CurveGradient,"$Id$"); /* === P R O C E D U R E S ================================================= */ inline float calculate_distance(const synfig::BLinePoint& a,const synfig::BLinePoint& b) { #if 1 const Point& c1(a.get_vertex()); const Point c2(a.get_vertex()+a.get_tangent2()/3); const Point c3(b.get_vertex()-b.get_tangent1()/3); const Point& c4(b.get_vertex()); return (c1-c2).mag()+(c2-c3).mag()+(c3-c4).mag(); #else #endif } inline float calculate_distance(const std::vector& bline, bool bline_loop) { std::vector::const_iterator iter,next,ret; std::vector::const_iterator end(bline.end()); float dist(0); if (bline.empty()) return dist; next=bline.begin(); if(bline_loop) iter=--bline.end(); else iter=next++; for(;next!=end;iter=next++) { // Setup the curve etl::hermite curve( iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1()); // dist+=calculate_distance(*iter,*next); dist+=curve.length(); } return dist; } std::vector::const_iterator find_closest(bool fast, const std::vector& bline,const Point& p,float& t,bool loop=false,float *bline_dist_ret=0) { std::vector::const_iterator iter,next,ret; std::vector::const_iterator end(bline.end()); ret=bline.end(); float dist(100000000000.0); next=bline.begin(); float best_bline_dist(0); float best_bline_len(0); float total_bline_dist(0); float best_pos(0); etl::hermite best_curve; if(loop) iter=--bline.end(); else iter=next++; Point bp; for(;next!=end;iter=next++) { // Setup the curve etl::hermite curve( iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1()); /* const float t(curve.find_closest(p,6,0.01,0.99)); bp=curve(t);if((bp-p).mag_squared()::const_iterator iter,next; // Figure out the BLinePoints we will be using, // Taking into account looping. if(perpendicular) { next=find_closest(fast,bline,point,t,bline_loop,&perp_dist); perp_dist/=curve_length_; } else // not perpendicular { next=find_closest(fast,bline,point,t,bline_loop); } iter=next++; if(next==bline.end()) next=bline.begin(); // Setup the curve etl::hermite curve( iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1() ); // Setup the derivative function etl::derivative > deriv(curve); int search_iterations(7); /*if(quality==0)search_iterations=8; else if(quality<=2)search_iterations=10; else if(quality<=4)search_iterations=8; */ if(perpendicular) { if(quality>7) search_iterations=4; } else // not perpendicular { if(quality<=6)search_iterations=7; else if(quality<=7)search_iterations=6; else if(quality<=8)search_iterations=5; else search_iterations=4; } // Figure out the closest point on the curve if (fast) t = curve.find_closest(fast, point,search_iterations); // Calculate our values p1=curve(t); // the closest point on the curve tangent=deriv(t); // the tangent at that point // if the point we're nearest to is at either end of the // bline, our distance from the curve is the distance from the // point on the curve. we need to know which side of the // curve we're on, so find the average of the two tangents at // this point if (t<0.00001 || t>0.99999) { bool zero_tangent = (tangent[0] == 0 && tangent[1] == 0); if (t<0.5) { if (iter->get_split_tangent_flag() || zero_tangent) { // fake the current tangent if we need to if (zero_tangent) tangent = curve(FAKE_TANGENT_STEP) - curve(0); // calculate the other tangent Vector other_tangent(iter->get_tangent1()); if (other_tangent[0] == 0 && other_tangent[1] == 0) { // find the previous blinepoint std::vector::const_iterator prev; if (iter != bline.begin()) (prev = iter)--; else if (loop) (prev = bline.end())--; else prev = iter; etl::hermite other_curve(prev->get_vertex(), iter->get_vertex(), prev->get_tangent2(), iter->get_tangent1()); other_tangent = other_curve(1) - other_curve(1-FAKE_TANGENT_STEP); } // normalise and sum the two tangents tangent=(other_tangent.norm()+tangent.norm()); edge_case=true; } } else { if (next->get_split_tangent_flag() || zero_tangent) { // fake the current tangent if we need to if (zero_tangent) tangent = curve(1) - curve(1-FAKE_TANGENT_STEP); // calculate the other tangent Vector other_tangent(next->get_tangent2()); if (other_tangent[0] == 0 && other_tangent[1] == 0) { // find the next blinepoint std::vector::const_iterator next2(next); if (++next2 == bline.end()) { if (loop) next2 = bline.begin(); else next2 = next; } etl::hermite other_curve(next->get_vertex(), next2->get_vertex(), next->get_tangent2(), next2->get_tangent1()); other_tangent = other_curve(FAKE_TANGENT_STEP) - other_curve(0); } // normalise and sum the two tangents tangent=(other_tangent.norm()+tangent.norm()); edge_case=true; } } } tangent = tangent.norm(); if(perpendicular) { tangent*=curve_length_; p1-=tangent*perp_dist; tangent=-tangent.perp(); } else // not perpendicular // the width of the bline at the closest point on the curve thickness=(next->get_width()-iter->get_width())*t+iter->get_width(); } if(perpendicular) { if(quality>7) { dist=perp_dist; /* diff=tangent.perp(); const Real mag(diff.inv_mag()); supersample=supersample*mag; */ supersample=0; } else { diff=tangent.perp(); //p1-=diff*0.5; const Real mag(diff.inv_mag()); supersample=supersample*mag; diff*=mag*mag; dist=(point_-origin - p1)*diff; } } else // not perpendicular { if (edge_case) { diff=(p1-(point_-origin)); if(diff*tangent.perp()<0) diff=-diff; diff=diff.norm()*thickness*width; } else diff=tangent.perp()*thickness*width; p1-=diff*0.5; const Real mag(diff.inv_mag()); supersample=supersample*mag; diff*=mag*mag; dist=(point_-origin - p1)*diff; } if(loop) dist-=floor(dist); if(zigzag) { dist*=2.0; supersample*=2.0; if(dist>1)dist=2.0-dist; } if(loop) { if(dist+supersample*0.5>1.0) { float left(supersample*0.5-(dist-1.0)); float right(supersample*0.5+(dist-1.0)); Color pool(gradient(1.0-(left*0.5),left).premult_alpha()*left/supersample); if (zigzag) pool+=gradient(1.0-right*0.5,right).premult_alpha()*right/supersample; else pool+=gradient(right*0.5,right).premult_alpha()*right/supersample; return pool.demult_alpha(); } if(dist-supersample*0.5<0.0) { float left(supersample*0.5-dist); float right(supersample*0.5+dist); Color pool(gradient(right*0.5,right).premult_alpha()*right/supersample); if (zigzag) pool+=gradient(left*0.5,left).premult_alpha()*left/supersample; else pool+=gradient(1.0-left*0.5,left).premult_alpha()*left/supersample; return pool.demult_alpha(); } } return gradient(dist,supersample); } float CurveGradient::calc_supersample(const synfig::Point &/*x*/, float pw,float /*ph*/)const { return pw; } synfig::Layer::Handle CurveGradient::hit_check(synfig::Context context, const synfig::Point &point)const { if(get_blend_method()==Color::BLEND_STRAIGHT && get_amount()>=0.5) return const_cast(this); if(get_amount()==0.0) return context.hit_check(point); if((get_blend_method()==Color::BLEND_STRAIGHT || get_blend_method()==Color::BLEND_COMPOSITE|| get_blend_method()==Color::BLEND_ONTO) && color_func(point).get_a()>0.5) return const_cast(this); return context.hit_check(point); } bool CurveGradient::set_param(const String & param, const ValueBase &value) { IMPORT(origin); IMPORT(perpendicular); IMPORT(fast); if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST) { bline=value; bline_loop=value.get_loop(); sync(); return true; } IMPORT(width); IMPORT(gradient); IMPORT(loop); IMPORT(zigzag); IMPORT_AS(origin,"offset"); return Layer_Composite::set_param(param,value); } ValueBase CurveGradient::get_param(const String & param)const { EXPORT(origin); EXPORT(bline); EXPORT(gradient); EXPORT(loop); EXPORT(zigzag); EXPORT(width); EXPORT(perpendicular); EXPORT(fast); EXPORT_NAME(); EXPORT_VERSION(); return Layer_Composite::get_param(param); } Layer::Vocab CurveGradient::get_param_vocab()const { Layer::Vocab ret(Layer_Composite::get_param_vocab()); ret.push_back(ParamDesc("origin") .set_local_name(_("Origin")) .set_description(_("Offset for the Vertices List")) ); ret.push_back(ParamDesc("width") .set_is_distance() .set_local_name(_("Width")) .set_description(_("Global width of the gradient")) ); ret.push_back(ParamDesc("bline") .set_local_name(_("Vertices")) .set_origin("origin") .set_hint("width") .set_description(_("A list of BLine Points")) ); ret.push_back(ParamDesc("gradient") .set_local_name(_("Gradient")) .set_description(_("Gradient to apply")) ); ret.push_back(ParamDesc("loop") .set_local_name(_("Loop")) .set_description(_("When checked the gradient is looped")) ); ret.push_back(ParamDesc("zigzag") .set_local_name(_("ZigZag")) .set_description(_("When checked the gradient is summetrical at the center")) ); ret.push_back(ParamDesc("perpendicular") .set_local_name(_("Perpendicular")) ); ret.push_back(ParamDesc("fast") .set_local_name(_("Fast")) .set_description(_("When checked, renders quickly but with artifacts")) ); return ret; } Color CurveGradient::get_color(Context context, const Point &point)const { const Color color(color_func(point,0)); if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT) return color; else return Color::blend(color,context.get_color(point),get_amount(),get_blend_method()); } bool CurveGradient::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const { SuperCallback supercb(cb,0,9500,10000); if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT) { surface->set_wh(renddesc.get_w(),renddesc.get_h()); } else { if(!context.accelerated_render(surface,quality,renddesc,&supercb)) return false; if(get_amount()==0) return true; } int x,y; Surface::pen pen(surface->begin()); const Real pw(renddesc.get_pw()),ph(renddesc.get_ph()); Point pos; Point tl(renddesc.get_tl()); const int w(surface->get_w()); const int h(surface->get_h()); if(get_amount()==1.0 && get_blend_method()==Color::BLEND_STRAIGHT) { for(y=0,pos[1]=tl[1];yamount_complete(10000,10000)) return false; return true; }