Added copyright lines for files I've edited this year.
[synfig.git] / synfig-core / trunk / src / synfig / canvas.cpp
index df200ba..c50b901 100644 (file)
@@ -2,19 +2,21 @@
 /*!    \file canvas.cpp
 **     \brief Canvas Class Member Definitions
 **
-**     $Id: canvas.cpp,v 1.1.1.1 2005/01/04 01:23:14 darco Exp $
+**     $Id$
 **
 **     \legal
-**     Copyright (c) 2002 Robert B. Quattlebaum Jr.
+**     Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
+**     Copyright (c) 2007, 2008 Chris Moore
 **
-**     This software and associated documentation
-**     are CONFIDENTIAL and PROPRIETARY property of
-**     the above-mentioned copyright holder.
+**     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.
 **
-**     You may not copy, print, publish, or in any
-**     other way distribute this software without
-**     a prior written agreement with
-**     the copyright holder.
+**     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
 */
 /* ========================================================================= */
@@ -67,8 +69,9 @@ int _CanvasCounter::counter(0);
 
 /* === M E T H O D S ======================================================= */
 
-Canvas::Canvas(const string &id):
+Canvas::Canvas(const String &id):
        id_                     (id),
+       version_        (CURRENT_CANVAS_VERSION),
        cur_time_       (0),
        is_inline_      (false),
        is_dirty_       (true),
@@ -87,9 +90,28 @@ Canvas::on_changed()
 
 Canvas::~Canvas()
 {
+       // we were having a crash where pastecanvas layers were still
+       // refering to a canvas after it had been destroyed;  this code
+       // will stop the pastecanvas layers from refering to the canvas
+       // before the canvas is destroyed
+
+       // the set_sub_canvas(0) ends up deleting the parent-child link,
+       // which deletes the current element from the set we're iterating
+       // through, so we have to make sure we've incremented the iterator
+       // before we mess with the pastecanvas
+       std::set<Node*>::iterator iter = parent_set.begin();
+       while (iter != parent_set.end())
+       {
+               Layer_PasteCanvas* paste_canvas = dynamic_cast<Layer_PasteCanvas*>(*iter);
+               iter++;
+               if(paste_canvas)
+                       paste_canvas->set_sub_canvas(0);
+               else
+                       warning("destroyed canvas has a parent that is not a pastecanvas - please report if repeatable");
+       }
+
        //if(is_inline() && parent_) assert(0);
        _CanvasCounter::counter--;
-       //DEBUGPOINT();
        clear();
        begin_delete();
 }
@@ -131,7 +153,7 @@ Canvas::clear()
        {
                Layer::Handle layer(front());
                //if(layer->count()>2)synfig::info("before layer->count()=%d",layer->count());
-                               
+
                erase(begin());
                //if(layer->count()>1)synfig::info("after layer->count()=%d",layer->count());
        }
@@ -143,7 +165,7 @@ Canvas::clear()
        // would just continue going when polled
        // for a color.
        CanvasBase::push_back(Layer::Handle());
-       
+
        changed();
 }
 
@@ -206,10 +228,10 @@ valid_id(const String &x)
 {
        static const char bad_chars[]=" :#@$^&()*";
        unsigned int i;
-       
+
        if(!x.empty() && x[0]>='0' && x[0]<='9')
                return false;
-       
+
        for(i=0;i<sizeof(bad_chars);i++)
                if(x.find_first_of(bad_chars[i])!=string::npos)
                        return false;
@@ -222,7 +244,7 @@ Canvas::set_id(const String &x)
 {
        if(is_inline() && parent_)
                throw runtime_error("Inline Canvas cannot have an ID");
-       
+
        if(!valid_id(x))
                throw runtime_error("Invalid ID");
        id_=x;
@@ -255,9 +277,9 @@ Canvas::set_description(const String &x)
 
 void
 Canvas::set_time(Time t)const
-{      
+{
        if(is_dirty_ || !get_time().is_equal(t))
-       {       
+       {
 #if 0
                if(is_root())
                {
@@ -269,7 +291,7 @@ Canvas::set_time(Time t)const
 
                // ...questionable
                const_cast<Canvas&>(*this).cur_time_=t;
-               
+
                is_dirty_=false;
                get_context().set_time(t);
        }
@@ -313,34 +335,34 @@ Canvas::_get_relative_id(etl::loose_handle<const Canvas> x)const
 
        if(x.get()==this)
                return String();
-       
+
        if(parent()==x.get())
                return get_id();
-       
+
        String id;
-       
+
        const Canvas* canvas=this;
-       
+
        for(;!canvas->is_root();canvas=canvas->parent().get())
                id=':'+canvas->get_id()+id;
-       
+
        if(x && get_root()!=x->get_root())
        {
                //String file_name=get_file_name();
                //String file_path=x->get_file_path();
-               
+
                String file_name;
                if(is_absolute_path(get_file_name()))
                        file_name=etl::relative_path(x->get_file_path(),get_file_name());
                else
                        file_name=get_file_name();
-               
+
                // If the path of X is inside of file_name,
                // then remove it.
                //if(file_name.size()>file_path.size())
                //      if(file_path==String(file_name,0,file_path.size()))
                //              file_name.erase(0,file_path.size()+1);
-                       
+
                id=file_name+'#'+id;
        }
 
@@ -362,7 +384,7 @@ Canvas::find_value_node(const String &id)const
 {
        if(is_inline() && parent_)
                return parent_->find_value_node(id);
-               
+
        if(id.empty())
                throw Exception::IDNotFound("Empty ID");
 
@@ -399,7 +421,7 @@ Canvas::surefind_value_node(const String &id)
        String value_node_id(id,id.rfind(':')+1);
        if(canvas_id.empty())
                canvas_id=':';
-               
+
        return surefind_canvas(canvas_id)->value_node_list_.surefind(value_node_id);
 }
 
@@ -410,7 +432,6 @@ Canvas::add_value_node(ValueNode::Handle x, const String &id)
                return parent_->add_value_node(x,id);
 //             throw runtime_error("You cannot add a ValueNode to an inline Canvas");
 
-       //DEBUGPOINT();
        if(x->is_exported())
                throw runtime_error("ValueNode is already exported");
 
@@ -419,30 +440,26 @@ Canvas::add_value_node(ValueNode::Handle x, const String &id)
 
        if(id.find_first_of(':',0)!=string::npos)
                throw Exception::BadLinkName("Bad character");
-       
+
        try
        {
-               //DEBUGPOINT();
                if(PlaceholderValueNode::Handle::cast_dynamic(value_node_list_.find(id)))
                        throw Exception::IDNotFound("add_value_node()");
-               
-               //DEBUGPOINT();
+
                throw Exception::IDAlreadyExists(id);
        }
        catch(Exception::IDNotFound)
        {
-               //DEBUGPOINT();
                x->set_id(id);
-       
+
                x->set_parent_canvas(this);
-       
+
                if(!value_node_list_.add(x))
                {
                        synfig::error("Unable to add ValueNode");
                        throw std::runtime_error("Unable to add ValueNode");
                }
-               //DEBUGPOINT();
-               
+
                return;
        }
 }
@@ -465,7 +482,7 @@ Canvas::rename_value_node(ValueNode::Handle x, const String &id)
        }
        catch(Exception::IDNotFound)
        {
-               x->set_id(id);  
+               x->set_id(id);
 
                return;
        }
@@ -478,15 +495,15 @@ Canvas::remove_value_node(ValueNode::Handle x)
        if(is_inline() && parent_)
                return parent_->remove_value_node(x);
 //             throw Exception::IDNotFound("Canvas::remove_value_node() was called from an inline canvas");
-               
+
        if(!x)
                throw Exception::IDNotFound("Canvas::remove_value_node() was passed empty handle");
-       
+
        if(!value_node_list_.erase(x))
                throw Exception::IDNotFound("Canvas::remove_value_node(): ValueNode was not found inside of this canvas");
 
        //x->set_parent_canvas(0);
-       
+
        x->set_id("");
 }
 
@@ -496,25 +513,27 @@ Canvas::surefind_canvas(const String &id)
 {
        if(is_inline() && parent_)
                return parent_->surefind_canvas(id);
-       
+
        if(id.empty())
                return this;
-       
+
        // If the ID contains a "#" character, then a filename is
-       // expected on the left side. 
+       // expected on the left side.
        if(id.find_first_of('#')!=string::npos)
        {
                // If '#' is the first character, remove it
                // and attempt to parse the ID again.
                if(id[0]=='#')
                        return surefind_canvas(String(id,1));
-               
-               //! \todo This needs alot more optimization
+
+               //! \todo This needs a lot more optimization
                String file_name(id,0,id.find_first_of('#'));
                String external_id(id,id.find_first_of('#')+1);
-               
+
+               file_name=unix_to_local_path(file_name);
+
                Canvas::Handle external_canvas;
-               
+
                // If the composition is already open, then use it.
                if(externals_.count(file_name))
                        external_canvas=externals_[file_name];
@@ -523,44 +542,44 @@ Canvas::surefind_canvas(const String &id)
                        if(is_absolute_path(file_name))
                                external_canvas=open_canvas(file_name);
                        else
-                               external_canvas=open_canvas(get_file_path()+'/'+file_name);
+                               external_canvas=open_canvas(get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name);
 
                        if(!external_canvas)
                                throw Exception::FileNotFound(file_name);
                        externals_[file_name]=external_canvas;
                }
-               
+
                return Handle::cast_const(external_canvas.constant()->find_canvas(external_id));
        }
-       
+
        // If we do not have any resolution, then we assume that the
        // request is for this immediate canvas
        if(id.find_first_of(':')==string::npos)
        {
                Children::iterator iter;
-       
+
                // Search for the image in the image list,
                // and return it if it is found
                for(iter=children().begin();iter!=children().end();iter++)
                        if(id==(*iter)->get_id())
                                return *iter;
-                       
+
                // Create a new canvas and return it
                //synfig::warning("Implicitly creating canvas named "+id);
                return new_child_canvas(id);
        }
-       
-       // If the first character is the seperator, then 
+
+       // If the first character is the separator, then
        // this references the root canvas.
        if(id[0]==':')
                return get_root()->surefind_canvas(string(id,1));
 
        // Now we know that the requested Canvas is in a child
-       // of this canvas. We have to find that canvas and 
+       // of this canvas. We have to find that canvas and
        // call "find_canvas" on it, and return the result.
-       
+
        String canvas_name=string(id,0,id.find_first_of(':'));
-       
+
        Canvas::Handle child_canvas=surefind_canvas(canvas_name);
 
        return child_canvas->surefind_canvas(string(id,id.find_first_of(':')+1));
@@ -584,20 +603,22 @@ Canvas::find_canvas(const String &id)const
                return this;
 
        // If the ID contains a "#" character, then a filename is
-       // expected on the left side. 
+       // expected on the left side.
        if(id.find_first_of('#')!=string::npos)
        {
                // If '#' is the first character, remove it
                // and attempt to parse the ID again.
                if(id[0]=='#')
                        return find_canvas(String(id,1));
-               
-               //! \todo This needs alot more optimization
+
+               //! \todo This needs a lot more optimization
                String file_name(id,0,id.find_first_of('#'));
                String external_id(id,id.find_first_of('#')+1);
-               
+
+               file_name=unix_to_local_path(file_name);
+
                Canvas::Handle external_canvas;
-               
+
                // If the composition is already open, then use it.
                if(externals_.count(file_name))
                        external_canvas=externals_[file_name];
@@ -606,42 +627,42 @@ Canvas::find_canvas(const String &id)const
                        if(is_absolute_path(file_name))
                                external_canvas=open_canvas(file_name);
                        else
-                               external_canvas=open_canvas(get_file_path()+'/'+file_name);
+                               external_canvas=open_canvas(get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name);
 
                        if(!external_canvas)
                                throw Exception::FileNotFound(file_name);
                        externals_[file_name]=external_canvas;
                }
-               
+
                return Handle::cast_const(external_canvas.constant()->find_canvas(external_id));
        }
-       
+
        // If we do not have any resolution, then we assume that the
        // request is for this immediate canvas
        if(id.find_first_of(':')==string::npos)
        {
                Children::const_iterator iter;
-       
+
                // Search for the image in the image list,
                // and return it if it is found
                for(iter=children().begin();iter!=children().end();iter++)
                        if(id==(*iter)->get_id())
                                return *iter;
-                       
+
                throw Exception::IDNotFound("Child Canvas in Parent Canvas: (child)"+id);
        }
-       
-       // If the first character is the seperator, then 
+
+       // If the first character is the separator, then
        // this references the root canvas.
        if(id.find_first_of(':')==0)
                return get_root()->find_canvas(string(id,1));
 
        // Now we know that the requested Canvas is in a child
-       // of this canvas. We have to find that canvas and 
+       // of this canvas. We have to find that canvas and
        // call "find_canvas" on it, and return the result.
-       
+
        String canvas_name=string(id,0,id.find_first_of(':'));
-       
+
        Canvas::ConstHandle child_canvas=find_canvas(canvas_name);
 
        return child_canvas->find_canvas(string(id,id.find_first_of(':')+1));
@@ -657,7 +678,6 @@ Canvas::create()
 void
 Canvas::push_back(etl::handle<Layer> x)
 {
-//     DEBUGPOINT();
 //     int i(x->count());
        insert(end(),x);
        //if(x->count()!=i+1)synfig::info("push_back before %d, after %d",i,x->count());
@@ -666,7 +686,6 @@ Canvas::push_back(etl::handle<Layer> x)
 void
 Canvas::push_front(etl::handle<Layer> x)
 {
-//     DEBUGPOINT();
 //     int i(x->count());
        insert(begin(),x);
        //if(x->count()!=i+1)synfig::error("push_front before %d, after %d",i,x->count());
@@ -690,29 +709,27 @@ Canvas::insert(iterator iter,etl::handle<Layer> x)
 
        add_child(x.get());
 
-       
+
        LooseHandle correct_canvas(this);
        //while(correct_canvas->is_inline())correct_canvas=correct_canvas->parent();
        Layer::LooseHandle loose_layer(x);
-       
-       x->signal_added_to_group().connect(
-               sigc::bind(
-                       sigc::mem_fun(
-                               *correct_canvas,
-                               &Canvas::add_group_pair
-                       ),
-                       loose_layer
-               )
-       );
-       x->signal_removed_from_group().connect(
-               sigc::bind(
-                       sigc::mem_fun(
-                               *correct_canvas,
-                               &Canvas::remove_group_pair
-                       ),
-                       loose_layer
-               )
-       );
+
+       add_connection(loose_layer,
+                                  sigc::connection::connection(
+                                          x->signal_added_to_group().connect(
+                                                  sigc::bind(
+                                                          sigc::mem_fun(
+                                                                  *correct_canvas,
+                                                                  &Canvas::add_group_pair),
+                                                          loose_layer))));
+       add_connection(loose_layer,
+                                  sigc::connection::connection(
+                                          x->signal_removed_from_group().connect(
+                                                  sigc::bind(
+                                                          sigc::mem_fun(
+                                                                  *correct_canvas,
+                                                                  &Canvas::remove_group_pair),
+                                                          loose_layer))));
 
 
        if(!x->get_group().empty())
@@ -730,11 +747,11 @@ Canvas::push_back_simple(etl::handle<Layer> x)
 }
 
 void
-Canvas::erase(Canvas::iterator iter)
+Canvas::erase(iterator iter)
 {
        if(!(*iter)->get_group().empty())
                remove_group_pair((*iter)->get_group(),(*iter));
-       
+
        // HACK: We really shouldn't be wiping
        // out these signals entirely. We should
        // only be removing the specific connections
@@ -744,11 +761,13 @@ Canvas::erase(Canvas::iterator iter)
        // is using these signals, so I'll just
        // leave these next two lines like they
        // are for now - darco 07-30-2004
-       (*iter)->signal_added_to_group().clear();
-       (*iter)->signal_removed_from_group().clear();
+
+       // so don't wipe them out entirely
+       // - dooglus 09-21-2007
+       disconnect_connections(*iter);
 
        if(!op_flag_)remove_child(iter->get());
-               
+
        CanvasBase::erase(iter);
        if(!op_flag_)changed();
 }
@@ -762,16 +781,20 @@ Canvas::clone(const GUID& deriv_guid)const
        else
        {
                name=get_id()+"_CLONE";
-               
-               throw runtime_error("Cloning of non-inline canvases is not yet suported");
+
+               throw runtime_error("Cloning of non-inline canvases is not yet supported");
        }
-       
+
        Handle canvas(new Canvas(name));
-       
+
        if(is_inline())
        {
                canvas->is_inline_=true;
-               canvas->parent_=0;
+               // \todo this was setting parent_=0 - is there a reason for that?
+               // this was causing bug 1838132, where cloning an inline canvas that contains an imported image fails
+               // it was failing to ascertain the absolute pathname of the imported image, since it needs the pathname
+               // of the canvas to get that, which is stored in the parent canvas
+               canvas->parent_=parent();
                //canvas->set_inline(parent());
        }
 
@@ -813,9 +836,9 @@ Canvas::set_inline(LooseHandle parent)
 {
        if(is_inline_ && parent_)
        {
-               
+
        }
-       
+
        id_="inline";
        is_inline_=true;
        parent_=parent;
@@ -828,7 +851,7 @@ Canvas::set_inline(LooseHandle parent)
        {
                parent->group_db_[iter->first].insert(iter->second.begin(),iter->second.end());
        }
-       
+
        rend_desc()=parent->rend_desc();
 }
 
@@ -838,7 +861,7 @@ Canvas::create_inline(Handle parent)
        assert(parent);
        //if(parent->is_inline())
        //      return create_inline(parent->parent());
-       
+
        Handle canvas(new Canvas("inline"));
        canvas->set_inline(parent);
        return canvas;
@@ -872,7 +895,7 @@ Canvas::new_child_canvas(const String &id)
        // Create a new canvas
        children().push_back(create());
        Canvas::Handle canvas(children().back());
-       
+
        canvas->set_id(id);
        canvas->parent_=this;
        canvas->rend_desc()=rend_desc();
@@ -888,10 +911,10 @@ Canvas::add_child_canvas(Canvas::Handle child_canvas, const synfig::String& id)
 
        if(child_canvas->parent() && !child_canvas->is_inline())
                throw std::runtime_error("Cannot add child canvas because it belongs to someone else!");
-       
+
        if(!valid_id(id))
                throw runtime_error("Invalid ID");
-       
+
        try
        {
                find_canvas(id);
@@ -905,7 +928,7 @@ Canvas::add_child_canvas(Canvas::Handle child_canvas, const synfig::String& id)
                children().push_back(child_canvas);
                child_canvas->parent_=this;
        }
-       
+
        return child_canvas;
 }
 
@@ -914,13 +937,13 @@ Canvas::remove_child_canvas(Canvas::Handle child_canvas)
 {
        if(is_inline() && parent_)
                return parent_->remove_child_canvas(child_canvas);
-       
+
        if(child_canvas->parent_!=this)
                throw runtime_error("Given child does not belong to me");
-       
+
        if(find(children().begin(),children().end(),child_canvas)==children().end())
                throw Exception::IDNotFound(child_canvas->get_id());
-       
+
        children().remove(child_canvas);
 
        child_canvas->parent_=0;
@@ -963,7 +986,7 @@ Canvas::get_file_path()const
        return dirname(file_name_);
 }
 
-       
+
 String
 Canvas::get_meta_data(const String& key)const
 {
@@ -1003,24 +1026,73 @@ Canvas::get_meta_data_keys()const
 
        for(iter=meta_data_.begin();!(iter==meta_data_.end());++iter)
                ret.push_back(iter->first);
-       
+
        return ret;
 }
 
+/* note - the "Motion Blur" and "Duplicate" layers need the dynamic
+                 parameters of any PasteCanvas layers they loop over to be
+                 maintained.  When the variables in the following function
+                 refer to "motion blur", they mean either of these two
+                 layers. */
 void
-synfig::optimize_layers(Context context, Canvas::Handle op_canvas)
+synfig::optimize_layers(Time time, Context context, Canvas::Handle op_canvas, bool seen_motion_blur_in_parent)
 {
        Context iter;
 
        std::vector< std::pair<float,Layer::Handle> > sort_list;
-       int i;
-       
+       int i, motion_blur_i=0; // motion_blur_i is for resolving which layer comes first in the event of a z_depth tie
+       float motion_blur_z_depth=0; // the z_depth of the least deep motion blur layer in this context
+       bool seen_motion_blur_locally = false;
+       bool motion_blurred; // the final result - is this layer blurred or not?
+
+       // If the parent didn't cause us to already be motion blurred,
+       // check whether there's a motion blur in this context,
+       // and if so, calculate its z_depth.
+       if (!seen_motion_blur_in_parent)
+               for(iter=context,i=0;*iter;iter++,i++)
+               {
+                       Layer::Handle layer=*iter;
+
+                       // If the layer isn't active, don't worry about it
+                       if(!layer->active())
+                               continue;
+
+                       // Any layer with an amount of zero is implicitly disabled.
+                       ValueBase value(layer->get_param("amount"));
+                       if(value.get_type()==ValueBase::TYPE_REAL && value.get(Real())==0)
+                               continue;
+
+                       if(layer->get_name()=="MotionBlur" || layer->get_name()=="duplicate")
+                       {
+                               float z_depth(layer->get_z_depth()*1.0001+i);
+
+                               // If we've seen a motion blur before in this context...
+                               if (seen_motion_blur_locally)
+                               {
+                                       // ... then we're only interested in this one if it's less deep...
+                                       if (z_depth < motion_blur_z_depth)
+                                       {
+                                               motion_blur_z_depth = z_depth;
+                                               motion_blur_i = i;
+                                       }
+                               }
+                               // ... otherwise we're always interested in it.
+                               else
+                               {
+                                       motion_blur_z_depth = z_depth;
+                                       motion_blur_i = i;
+                                       seen_motion_blur_locally = true;
+                               }
+                       }
+               }
+
        // Go ahead and start romping through the canvas to paste
        for(iter=context,i=0;*iter;iter++,i++)
        {
                Layer::Handle layer=*iter;
                float z_depth(layer->get_z_depth()*1.0001+i);
-               
+
                // If the layer isn't active, don't worry about it
                if(!layer->active())
                        continue;
@@ -1030,59 +1102,138 @@ synfig::optimize_layers(Context context, Canvas::Handle op_canvas)
                if(value.get_type()==ValueBase::TYPE_REAL && value.get(Real())==0)
                        continue;
 
-               Layer_PasteCanvas* paste_canvas(static_cast<Layer_PasteCanvas*>(layer.get()));
-               if(layer->get_name()=="PasteCanvas" && paste_canvas->get_time_offset()==0)
+               // note: this used to include "&& paste_canvas->get_time_offset()==0", but then
+               //               time-shifted layers weren't being sorted by z-depth (bug #1806852)
+               if(layer->get_name()=="PasteCanvas")
                {
+                       Layer_PasteCanvas* paste_canvas(static_cast<Layer_PasteCanvas*>(layer.get()));
+
+                       // we need to blur the sub canvas if:
+                       // our parent is blurred,
+                       // or the child is lower than a local blur,
+                       // or the child is at the same z_depth as a local blur, but later in the context
+
+#if 0 // DEBUG
+                       if (seen_motion_blur_in_parent)                                 synfig::info("seen BLUR in parent\n");
+                       else if (seen_motion_blur_locally)
+                               if (z_depth > motion_blur_z_depth)                      synfig::info("paste is deeper than BLUR\n");
+                               else if (z_depth == motion_blur_z_depth) {      synfig::info("paste is same depth as BLUR\n");
+                                       if (i > motion_blur_i)                                  synfig::info("paste is physically deeper than BLUR\n");
+                                       else                                                                    synfig::info("paste is less physically deep than BLUR\n");
+                               } else                                                                          synfig::info("paste is less deep than BLUR\n");
+                       else                                                                                    synfig::info("no BLUR at all\n");
+#endif // DEBUG
+
+                       motion_blurred = (seen_motion_blur_in_parent ||
+                                                         (seen_motion_blur_locally &&
+                                                          (z_depth > motion_blur_z_depth ||
+                                                               (z_depth == motion_blur_z_depth && i > motion_blur_i))));
+
                        Canvas::Handle sub_canvas(Canvas::create_inline(op_canvas));
-                       optimize_layers(paste_canvas->get_sub_canvas()->get_context(),sub_canvas);
-//#define SYNFIG_OPTIMIZE_PASTE_CANVAS 1
+                       Canvas::Handle paste_sub_canvas = paste_canvas->get_sub_canvas();
+                       if(paste_sub_canvas)
+                               optimize_layers(time, paste_sub_canvas->get_context(),sub_canvas,motion_blurred);
 
-#ifdef SYNFIG_OPTIMIZE_PASTE_CANVAS                    
+// \todo: uncommenting the following breaks the rendering of at least examples/backdrop.sifz quite severely
+// #define SYNFIG_OPTIMIZE_PASTE_CANVAS
+#ifdef SYNFIG_OPTIMIZE_PASTE_CANVAS
                        Canvas::iterator sub_iter;
-                       // Determine if we can just remove the paste canvas
-                       // altogether                   
-                       if(paste_canvas->get_blend_method()==Color::BLEND_COMPOSITE && paste_canvas->get_amount()==1.0f && paste_canvas->get_zoom()==0 && paste_canvas->get_time_offset()==0 && paste_canvas->get_origin()==Point(0,0))
-                       try{
-                               for(sub_iter=sub_canvas->begin();sub_iter!=sub_canvas->end();++sub_iter)
-                               {
-                                       Layer* layer=sub_iter->get();
-                                       
-                                       // any layers that deform end up breaking things
-                                       // so do things the old way if we run into anything like this
-                                       if(!dynamic_cast<Layer_NoDeform*>(layer))
-                                               throw int();
-
-                                       ValueBase value(layer->get_param("blend_method"));
-                                       if(value.get_type()!=ValueBase::TYPE_INTEGER || value.get(int())!=(int)Color::BLEND_COMPOSITE)
-                                               throw int();
-                               }
-                               
-                               // It has turned out that we don't need a paste canvas
-                               // layer, so just go ahead and add all the layers onto
-                               // the current stack and be done with it
-                               while(sub_canvas->size())
-                               {
-                                       sort_list.push_back(std::pair<float,Layer::Handle>(z_depth,sub_canvas->front()));
-                                       //op_canvas->push_back_simple(sub_canvas->front());
-                                       sub_canvas->pop_front();
+
+                       // Determine if we can just remove the paste canvas altogether
+                       if (paste_canvas->get_blend_method()    == Color::BLEND_COMPOSITE       &&
+                               paste_canvas->get_amount()                      == 1.0f                                         &&
+                               paste_canvas->get_zoom()                        == 0                                            &&
+                               paste_canvas->get_time_offset()         == 0                                            &&
+                               paste_canvas->get_origin()                      == Point(0,0)                           )
+                               try {
+                                       for(sub_iter=sub_canvas->begin();sub_iter!=sub_canvas->end();++sub_iter)
+                                       {
+                                               Layer* layer=sub_iter->get();
+
+                                               // any layers that deform end up breaking things
+                                               // so do things the old way if we run into anything like this
+                                               if(!dynamic_cast<Layer_NoDeform*>(layer))
+                                                       throw int();
+
+                                               ValueBase value(layer->get_param("blend_method"));
+                                               if(value.get_type()!=ValueBase::TYPE_INTEGER || value.get(int())!=(int)Color::BLEND_COMPOSITE)
+                                                       throw int();
+                                       }
+
+                                       // It has turned out that we don't need a paste canvas
+                                       // layer, so just go ahead and add all the layers onto
+                                       // the current stack and be done with it
+                                       while(sub_canvas->size())
+                                       {
+                                               sort_list.push_back(std::pair<float,Layer::Handle>(z_depth,sub_canvas->front()));
+                                               //op_canvas->push_back_simple(sub_canvas->front());
+                                               sub_canvas->pop_front();
+                                       }
+                                       continue;
                                }
-                               continue;
-                       }catch(int) { }
-#endif
+                               catch(int)
+                               { }
+#endif // SYNFIG_OPTIMIZE_PASTE_CANVAS
+
                        Layer::Handle new_layer(Layer::create("PasteCanvas"));
-                       dynamic_cast<Layer_PasteCanvas*>(new_layer.get())->set_do_not_muck_with_time(true);
+                       dynamic_cast<Layer_PasteCanvas*>(new_layer.get())->set_muck_with_time(false);
+                       if (motion_blurred)
+                       {
+                               Layer::DynamicParamList dynamic_param_list(paste_canvas->dynamic_param_list());
+                               for(Layer::DynamicParamList::const_iterator iter(dynamic_param_list.begin()); iter != dynamic_param_list.end(); ++iter)
+                                       new_layer->connect_dynamic_param(iter->first, iter->second);
+                       }
                        Layer::ParamList param_list(paste_canvas->get_param_list());
                        //param_list.erase("canvas");
                        new_layer->set_param_list(param_list);
                        dynamic_cast<Layer_PasteCanvas*>(new_layer.get())->set_sub_canvas(sub_canvas);
-                       dynamic_cast<Layer_PasteCanvas*>(new_layer.get())->set_do_not_muck_with_time(false);
+                       dynamic_cast<Layer_PasteCanvas*>(new_layer.get())->set_muck_with_time(true);
                        layer=new_layer;
                }
-                                                               
+               else                                    // not a PasteCanvas - does it use blend method 'Straight'?
+               {
+                       /* when we use the 'straight' blend method, every pixel on the layer affects the layers underneath,
+                        * not just the non-transparent pixels; the following workarea wraps non-pastecanvas layers in a
+                        * new pastecanvas to ensure that the straight blend affects the full plane, not just the area
+                        * within the layer's bounding box
+                        */
+
+                       // \todo: this code probably needs modification to work properly with motionblur and duplicate
+                       etl::handle<Layer_Composite> composite = etl::handle<Layer_Composite>::cast_dynamic(layer);
+
+                       /* some layers (such as circle) don't touch pixels that aren't
+                        * part of the circle, so they don't get blended correctly when
+                        * using a straight blend.  so we encapsulate the circle, and the
+                        * encapsulation layer takes care of the transparent pixels
+                        * for us.  if we do that for all layers, however, then the
+                        * distortion layers no longer work, since they have no
+                        * context to work on.  the Layer::reads_context() method
+                        * returns true for layers which need to be able to see
+                        * their context.  we can't encapsulate those.
+                        */
+                       if (composite &&
+                               Color::is_straight(composite->get_blend_method()) &&
+                               !composite->reads_context())
+                       {
+                               Canvas::Handle sub_canvas(Canvas::create_inline(op_canvas));
+                               sub_canvas->push_back(composite = composite->clone());
+                               layer = Layer::create("PasteCanvas");
+                               composite->set_description(strprintf("Wrapped clone of '%s'", composite->get_non_empty_description().c_str()));
+                               layer->set_description(strprintf("PasteCanvas wrapper for '%s'", composite->get_non_empty_description().c_str()));
+                               Layer_PasteCanvas* paste_canvas(static_cast<Layer_PasteCanvas*>(layer.get()));
+                               paste_canvas->set_blend_method(composite->get_blend_method());
+                               paste_canvas->set_amount(composite->get_amount());
+                               sub_canvas->set_time(time); // region and outline don't calculate their bounding rects until their time is set
+                               composite->set_blend_method(Color::BLEND_STRAIGHT); // do this before calling set_sub_canvas(), but after set_time()
+                               composite->set_amount(1.0f); // after set_time()
+                               paste_canvas->set_sub_canvas(sub_canvas);
+                       }
+               }
+
                sort_list.push_back(std::pair<float,Layer::Handle>(z_depth,layer));
-               //op_canvas->push_back_simple(layer);   
+               //op_canvas->push_back_simple(layer);
        }
-       
+
        //sort_list.sort();
        stable_sort(sort_list.begin(),sort_list.end());
        std::vector< std::pair<float,Layer::Handle> >::iterator iter2;
@@ -1096,7 +1247,7 @@ Canvas::get_times_vfunc(Node::time_set &set) const
 {
        const_iterator  i = begin(),
                                iend = end();
-       
+
        for(; i != iend; ++i)
        {
                const Node::time_set &tset = (*i)->get_times();
@@ -1136,7 +1287,7 @@ Canvas::get_group_count()const
 
        return group_db_.size();
 }
-       
+
 void
 Canvas::add_group_pair(String group, etl::handle<Layer> layer)
 {
@@ -1145,7 +1296,7 @@ Canvas::add_group_pair(String group, etl::handle<Layer> layer)
                signal_group_added()(group);
        else
                signal_group_changed()(group);
-       
+
        signal_group_pair_added()(group,layer);
 
        if(is_inline()  && parent_)
@@ -1172,11 +1323,26 @@ Canvas::remove_group_pair(String group, etl::handle<Layer> layer)
 }
 
 void
+Canvas::add_connection(etl::loose_handle<Layer> layer, sigc::connection connection)
+{
+       connections_[layer].push_back(connection);
+}
+
+void
+Canvas::disconnect_connections(etl::loose_handle<Layer> layer)
+{
+       std::vector<sigc::connection>::iterator iter;
+       for(iter=connections_[layer].begin();iter!=connections_[layer].end();++iter)
+               iter->disconnect();
+       connections_[layer].clear();
+}
+
+void
 Canvas::rename_group(const String&old_name,const String&new_name)
 {
        if(is_inline() && parent_)
                return parent_->rename_group(old_name,new_name);
-       
+
        {
                std::map<String,std::set<etl::handle<Layer> > >::iterator iter;
                iter=group_db_.find(old_name);
@@ -1188,10 +1354,10 @@ Canvas::rename_group(const String&old_name,const String&new_name)
                        rename_group(iter->first,name);
                }
        }
-       
+
        std::set<etl::handle<Layer> > layers(get_layers_in_group(old_name));
        std::set<etl::handle<Layer> >::iterator iter;
-       
+
        for(iter=layers.begin();iter!=layers.end();++iter)
        {
                (*iter)->remove_from_group(old_name);