X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=synfig-core%2Fsrc%2Fmodules%2Flyr_std%2Fcurvewarp.cpp;fp=synfig-core%2Fsrc%2Fmodules%2Flyr_std%2Fcurvewarp.cpp;h=82c213d97f55a721f1717d2df0a09cdafb0fcac0;hb=a095981e18cc37a8ecc7cd237cc22b9c10329264;hp=0000000000000000000000000000000000000000;hpb=9459638ad6797b8139f1e9f0715c96076dbf0890;p=synfig.git diff --git a/synfig-core/src/modules/lyr_std/curvewarp.cpp b/synfig-core/src/modules/lyr_std/curvewarp.cpp new file mode 100644 index 0000000..82c213d --- /dev/null +++ b/synfig-core/src/modules/lyr_std/curvewarp.cpp @@ -0,0 +1,591 @@ +/* === S Y N F I G ========================================================= */ +/*! \file curvewarp.cpp +** \brief Implementation of the "Curve Warp" 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 +** +** === 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 "curvewarp.h" + +#include +#include +#include +#include +#include + +#endif + +/* === M A C R O S ========================================================= */ + +#define FAKE_TANGENT_STEP 0.000001 +#define TOO_THIN 0.01 + +/* === G L O B A L S ======================================================= */ + +SYNFIG_LAYER_INIT(CurveWarp); +SYNFIG_LAYER_SET_NAME(CurveWarp,"curve_warp"); +SYNFIG_LAYER_SET_LOCAL_NAME(CurveWarp,N_("Curve Warp")); +SYNFIG_LAYER_SET_CATEGORY(CurveWarp,N_("Distortions")); +SYNFIG_LAYER_SET_VERSION(CurveWarp,"0.0"); +SYNFIG_LAYER_SET_CVS_ID(CurveWarp,"$Id$"); + +/* === P R O C E D U R E S ================================================= */ + +inline float calculate_distance(const std::vector& bline) +{ + 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(); + 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+=curve.length(); + } + + return dist; +} + +std::vector::const_iterator +find_closest_to_bline(bool fast, const std::vector& bline,const Point& p,float& t, float& len, bool& extreme) +{ + 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_pos(0), best_len(0); + etl::hermite best_curve; + iter=next++; + Point bp; + float total_len(0); + bool first = true, last = false; + extreme = false; + + for(;next!=end;iter=next++) + { + // Setup the curve + etl::hermite curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1()); + float thisdist(0); + last = false; + + if (fast) + { +#define POINT_CHECK(x) bp=curve(x); thisdist=(bp-p).mag_squared(); if(thisdist .99) extreme = true; + } + else + { + len = best_len + best_curve.find_distance(0,best_pos); + if (last && t == 1) extreme = true; + } + return ret; +} + +/* === M E T H O D S ======================================================= */ + +inline void +CurveWarp::sync() +{ + curve_length_=calculate_distance(bline); + perp_ = (end_point - start_point).perp().norm(); +} + +CurveWarp::CurveWarp(): + origin(0,0), + perp_width(1), + start_point(-2.5,-0.5), + end_point(2.5,-0.3), + fast(true) +{ + bline.push_back(BLinePoint()); + bline.push_back(BLinePoint()); + bline[0].set_vertex(Point(-2.5,0)); + bline[1].set_vertex(Point( 2.5,0)); + bline[0].set_tangent(Point(1, 0.1)); + bline[1].set_tangent(Point(1, -0.1)); + bline[0].set_width(1.0f); + bline[1].set_width(1.0f); + + sync(); +} + +inline Point +CurveWarp::transform(const Point &point_, Real *dist, Real *along, int quality)const +{ + Vector tangent; + Vector diff; + Point p1; + Real thickness; + bool edge_case = false; + float len(0); + bool extreme; + float t; + + if(bline.size()==0) + return Point(); + else if(bline.size()==1) + { + tangent=bline.front().get_tangent1(); + p1=bline.front().get_vertex(); + thickness=bline.front().get_width(); + t = 0.5; + extreme = false; + } + else + { + Point point(point_-origin); + + std::vector::const_iterator iter,next; + + // Figure out the BLinePoint we will be using, + next=find_closest_to_bline(fast,bline,point,t,len,extreme); + + 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<=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 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()) + 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(); + + // the width of the bline at the closest point on the curve + thickness=(next->get_width()-iter->get_width())*t+iter->get_width(); + } + + if (thickness < TOO_THIN && thickness > -TOO_THIN) + { + if (thickness > 0) thickness = TOO_THIN; + else thickness = -TOO_THIN; + } + + if (extreme) + { + Vector tangent; + + if (t < 0.5) + { + std::vector::const_iterator iter(bline.begin()); + tangent = iter->get_tangent1().norm(); + len = 0; + } + else + { + std::vector::const_iterator iter(--bline.end()); + tangent = iter->get_tangent2().norm(); + len = curve_length_; + } + len += (point_-origin - p1)*tangent; + diff = tangent.perp(); + } + else if (edge_case) + { + diff=(p1-(point_-origin)); + if(diff*tangent.perp()<0) diff=-diff; + diff=diff.norm(); + } + else + diff=tangent.perp(); + + // diff is a unit vector perpendicular to the bline + const Real unscaled_distance((point_-origin - p1)*diff); + if (dist) *dist = unscaled_distance; + if (along) *along = len; + return ((start_point + (end_point - start_point) * len / curve_length_) + + perp_ * unscaled_distance/(thickness*perp_width)); +} + +synfig::Layer::Handle +CurveWarp::hit_check(synfig::Context context, const synfig::Point &point)const +{ + return context.hit_check(transform(point)); +} + +bool +CurveWarp::set_param(const String & param, const ValueBase &value) +{ + IMPORT(origin); + IMPORT(start_point); + IMPORT(end_point); + IMPORT(fast); + IMPORT(perp_width); + + if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST) + { + bline=value; + sync(); + + return true; + } + + IMPORT_AS(origin,"offset"); + + return false; +} + +ValueBase +CurveWarp::get_param(const String & param)const +{ + EXPORT(origin); + EXPORT(start_point); + EXPORT(end_point); + EXPORT(bline); + EXPORT(fast); + EXPORT(perp_width); + + EXPORT_NAME(); + EXPORT_VERSION(); + + return ValueBase(); +} + +Layer::Vocab +CurveWarp::get_param_vocab()const +{ + Layer::Vocab ret; + + ret.push_back(ParamDesc("origin") + .set_local_name(_("Origin"))); + + ret.push_back(ParamDesc("perp_width") + .set_local_name(_("Width")) + .set_origin("start_point")); + + ret.push_back(ParamDesc("start_point") + .set_local_name(_("Start Point")) + .set_connect("end_point")); + + ret.push_back(ParamDesc("end_point") + .set_local_name(_("End Point"))); + + 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("fast") + .set_local_name(_("Fast"))); + + return ret; +} + +Color +CurveWarp::get_color(Context context, const Point &point)const +{ + return context.get_color(transform(point)); +} + +bool +CurveWarp::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const +{ + SuperCallback stageone(cb,0,9000,10000); + SuperCallback stagetwo(cb,9000,10000,10000); + + int x,y; + + const Real pw(renddesc.get_pw()),ph(renddesc.get_ph()); + Point tl(renddesc.get_tl()); + Point br(renddesc.get_br()); + const int w(renddesc.get_w()); + const int h(renddesc.get_h()); + + // find a bounding rectangle for the context we need to render + // todo: find a better way of doing this - this way doesn't work + Rect src_rect(transform(tl)); + Point pos1, pos2; + Real dist, along; + Real min_dist(999999), max_dist(-999999), min_along(999999), max_along(-999999); + +#define UPDATE_DIST \ + if (dist < min_dist) min_dist = dist; \ + if (dist > max_dist) max_dist = dist; \ + if (along < min_along) min_along = along; \ + if (along > max_along) max_along = along + + // look along the top and bottom edges + pos1[0] = pos2[0] = tl[0]; pos1[1] = tl[1]; pos2[1] = br[1]; + for (x = 0; x < w; x++, pos1[0] += pw, pos2[0] += pw) + { + src_rect.expand(transform(pos1, &dist, &along)); UPDATE_DIST; + src_rect.expand(transform(pos2, &dist, &along)); UPDATE_DIST; + } + + // look along the left and right edges + pos1[0] = tl[0]; pos2[0] = br[0]; pos1[1] = pos2[1] = tl[1]; + for (y = 0; y < h; y++, pos1[1] += ph, pos2[1] += ph) + { + src_rect.expand(transform(pos1, &dist, &along)); UPDATE_DIST; + src_rect.expand(transform(pos2, &dist, &along)); UPDATE_DIST; + } + + // look along the diagonals + const int max_wh(std::max(w,h)); + const Real inc_x((br[0]-tl[0])/max_wh),inc_y((br[1]-tl[1])/max_wh); + pos1[0] = pos2[0] = tl[0]; pos1[1] = tl[1]; pos2[1] = br[1]; + for (x = 0; x < max_wh; x++, pos1[0] += inc_x, pos2[0] = pos1[0], pos1[1]+=inc_y, pos2[1]-=inc_y) + { + src_rect.expand(transform(pos1, &dist, &along)); UPDATE_DIST; + src_rect.expand(transform(pos2, &dist, &along)); UPDATE_DIST; + } + +#if 0 + // look at each blinepoint + std::vector::const_iterator iter; + for (iter=bline.begin(); iter!=bline.end(); iter++) + src_rect.expand(transform(iter->get_vertex()+origin, &dist, &along)); UPDATE_DIST; +#endif + + Point src_tl(src_rect.get_min()); + Point src_br(src_rect.get_max()); + + Vector ab((end_point - start_point).norm()); + Angle::tan ab_angle(ab[1], ab[0]); + + Real used_length = max_along - min_along; + Real render_width = max_dist - min_dist; + + int src_w = (abs(used_length*Angle::cos(ab_angle).get()) + + abs(render_width*Angle::sin(ab_angle).get())) / abs(pw); + int src_h = (abs(used_length*Angle::sin(ab_angle).get()) + + abs(render_width*Angle::cos(ab_angle).get())) / abs(ph); + + Real src_pw((src_br[0] - src_tl[0]) / src_w); + Real src_ph((src_br[1] - src_tl[1]) / src_h); + + if (src_pw > abs(pw)) + { + src_w = int((src_br[0] - src_tl[0]) / abs(pw)); + src_pw = (src_br[0] - src_tl[0]) / src_w; + } + + if (src_ph > abs(ph)) + { + src_h = int((src_br[1] - src_tl[1]) / abs(ph)); + src_ph = (src_br[1] - src_tl[1]) / src_h; + } + +#define MAXPIX 10000 + if (src_w > MAXPIX) src_w = MAXPIX; + if (src_h > MAXPIX) src_h = MAXPIX; + + // this is an attempt to remove artifacts around tile edges - the + // cubic interpolation uses at most 2 pixels either side of the + // target pixel, so add an extra 2 pixels around the tile on all + // sides + src_tl -= (Point(src_pw,src_ph)*2); + src_br += (Point(src_pw,src_ph)*2); + src_w += 4; + src_h += 4; + src_pw = (src_br[0] - src_tl[0]) / src_w; + src_ph = (src_br[1] - src_tl[1]) / src_h; + + // set up a renddesc for the context to render + RendDesc src_desc(renddesc); + src_desc.clear_flags(); + src_desc.set_tl(src_tl); + src_desc.set_br(src_br); + src_desc.set_wh(src_w, src_h); + + // render the context onto a new surface + Surface source; + source.set_wh(src_w,src_h); + if(!context.accelerated_render(&source,quality,src_desc,&stageone)) + return false; + + float u,v; + Point pos, tmp; + + surface->set_wh(w,h); + surface->clear(); + + if(quality<=4) // CUBIC + for(y=0,pos[1]=tl[1];y=src_w || v>=src_h || isnan(u) || isnan(v)) + (*surface)[y][x]=context.get_color(tmp); + else + (*surface)[y][x]=source.cubic_sample(u,v); + } + if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false; + } + else if (quality<=6) // INTERPOLATION_LINEAR + for(y=0,pos[1]=tl[1];y=src_w || v>=src_h || isnan(u) || isnan(v)) + (*surface)[y][x]=context.get_color(tmp); + else + (*surface)[y][x]=source.linear_sample(u,v); + } + if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false; + } + else // NEAREST_NEIGHBOR + for(y=0,pos[1]=tl[1];y=src_w || v>=src_h || isnan(u) || isnan(v)) + (*surface)[y][x]=context.get_color(tmp); + else + (*surface)[y][x]=source[floor_to_int(v)][floor_to_int(u)]; + } + if((y&31)==0 && cb && !stagetwo.amount_complete(y,h)) return false; + } + + // Mark our progress as finished + if(cb && !cb->amount_complete(10000,10000)) + return false; + + return true; +}