**
** \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
#include "time.h"
#include "context.h"
#include "layer_pastecanvas.h"
+#include "loadcanvas.h"
#include <sigc++/bind.h>
#endif
using namespace etl;
using namespace std;
-namespace synfig { extern Canvas::Handle open_canvas(const String &filename); };
+namespace synfig { extern Canvas::Handle open_canvas(const String &filename, String &errors, String &warnings); };
/* === M A C R O S ========================================================= */
+#define ALLOW_CLONE_NON_INLINE_CANVASES
+
struct _CanvasCounter
{
static int counter;
/* === 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),
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();
}
return id;
}
-
ValueNode::Handle
Canvas::find_value_node(const String &id)
{
//synfig::warning("constfind:value_node_id: "+value_node_id);
//synfig::warning("constfind:canvas_id: "+canvas_id);
- return find_canvas(canvas_id)->value_node_list_.find(value_node_id);
+ String warnings;
+ return find_canvas(canvas_id, warnings)->value_node_list_.find(value_node_id);
}
ValueNode::Handle
if(canvas_id.empty())
canvas_id=':';
- return surefind_canvas(canvas_id)->value_node_list_.surefind(value_node_id);
+ String warnings;
+ return surefind_canvas(canvas_id,warnings)->value_node_list_.surefind(value_node_id);
}
void
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");
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);
synfig::error("Unable to add ValueNode");
throw std::runtime_error("Unable to add ValueNode");
}
- //DEBUGPOINT();
return;
}
x->set_id("");
}
-
-etl::handle<Canvas>
-Canvas::surefind_canvas(const String &id)
+Canvas::Handle
+Canvas::surefind_canvas(const String &id, String &warnings)
{
if(is_inline() && parent_)
- return parent_->surefind_canvas(id);
+ return parent_->surefind_canvas(id,warnings);
if(id.empty())
return this;
// If '#' is the first character, remove it
// and attempt to parse the ID again.
if(id[0]=='#')
- return surefind_canvas(String(id,1));
+ return surefind_canvas(String(id,1),warnings);
//! \todo This needs a lot more optimization
String file_name(id,0,id.find_first_of('#'));
external_canvas=externals_[file_name];
else
{
+ String errors;
if(is_absolute_path(file_name))
- external_canvas=open_canvas(file_name);
+ external_canvas=open_canvas(file_name, errors, warnings);
else
- external_canvas=open_canvas(get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name);
+ external_canvas=open_canvas(get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name, errors, warnings);
if(!external_canvas)
- throw Exception::FileNotFound(file_name);
+ throw runtime_error(errors);
externals_[file_name]=external_canvas;
}
- return Handle::cast_const(external_canvas.constant()->find_canvas(external_id));
+ return Handle::cast_const(external_canvas.constant()->find_canvas(external_id, warnings));
}
// If we do not have any resolution, then we assume that the
// If the first character is the separator, then
// this references the root canvas.
if(id[0]==':')
- return get_root()->surefind_canvas(string(id,1));
+ return get_root()->surefind_canvas(string(id,1),warnings);
// Now we know that the requested Canvas is in a child
// of this canvas. We have to find that canvas and
String canvas_name=string(id,0,id.find_first_of(':'));
- Canvas::Handle child_canvas=surefind_canvas(canvas_name);
+ Canvas::Handle child_canvas=surefind_canvas(canvas_name,warnings);
- return child_canvas->surefind_canvas(string(id,id.find_first_of(':')+1));
+ return child_canvas->surefind_canvas(string(id,id.find_first_of(':')+1),warnings);
}
Canvas::Handle
-Canvas::find_canvas(const String &id)
+Canvas::find_canvas(const String &id, String &warnings)
{
return
Canvas::Handle::cast_const(
- const_cast<const Canvas*>(this)->find_canvas(id)
+ const_cast<const Canvas*>(this)->find_canvas(id, warnings)
);
}
Canvas::ConstHandle
-Canvas::find_canvas(const String &id)const
+Canvas::find_canvas(const String &id, String &warnings)const
{
- if(is_inline() && parent_)return parent_->find_canvas(id);
+ if(is_inline() && parent_)
+ return parent_->find_canvas(id, warnings);
if(id.empty())
return this;
// If '#' is the first character, remove it
// and attempt to parse the ID again.
if(id[0]=='#')
- return find_canvas(String(id,1));
+ return find_canvas(String(id,1), warnings);
//! \todo This needs a lot more optimization
String file_name(id,0,id.find_first_of('#'));
external_canvas=externals_[file_name];
else
{
+ String errors, warnings;
if(is_absolute_path(file_name))
- external_canvas=open_canvas(file_name);
+ external_canvas=open_canvas(file_name, errors, warnings);
else
- external_canvas=open_canvas(get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name);
+ external_canvas=open_canvas(get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name, errors, warnings);
if(!external_canvas)
- throw Exception::FileNotFound(file_name);
+ throw runtime_error(errors);
externals_[file_name]=external_canvas;
}
- return Handle::cast_const(external_canvas.constant()->find_canvas(external_id));
+ return Handle::cast_const(external_canvas.constant()->find_canvas(external_id, warnings));
}
// If we do not have any resolution, then we assume that the
// 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));
+ if(id[0]==':')
+ return get_root()->find_canvas(string(id,1), warnings);
// Now we know that the requested Canvas is in a child
// of this canvas. We have to find that canvas and
String canvas_name=string(id,0,id.find_first_of(':'));
- Canvas::ConstHandle child_canvas=find_canvas(canvas_name);
+ Canvas::ConstHandle child_canvas=find_canvas(canvas_name, warnings);
- return child_canvas->find_canvas(string(id,id.find_first_of(':')+1));
+ return child_canvas->find_canvas(string(id,id.find_first_of(':')+1), warnings);
}
-
Canvas::Handle
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());
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());
x->set_canvas(this);
-
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())
add_group_pair(x->get_group(),x);
-
changed();
}
}
void
-Canvas::erase(Canvas::iterator iter)
+Canvas::erase(iterator iter)
{
if(!(*iter)->get_group().empty())
remove_group_pair((*iter)->get_group(),(*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());
{
name=get_id()+"_CLONE";
- throw runtime_error("Cloning of non-inline canvases is not yet suported");
+#ifndef ALLOW_CLONE_NON_INLINE_CANVASES
+ throw runtime_error("Cloning of non-inline canvases is not yet supported");
+#endif // ALLOW_CLONE_NON_INLINE_CANVASES
}
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->rend_desc() = rend_desc();
//canvas->set_inline(parent());
}
try
{
- find_canvas(id);
+ String warnings;
+ find_canvas(id, warnings);
throw Exception::IDAlreadyExists(id);
}
catch(Exception::IDNotFound)
parent()->set_file_name(file_name);
else
{
+ String old_name(file_name_);
file_name_=file_name;
- signal_file_name_changed_();
+
+ // when a canvas is made, its name is ""
+ // then, before it's saved or even edited, it gets a name like "Synfig Animation 23", in the local language
+ // we don't want to register the canvas' filename in the canvas map until it gets a real filename
+ if (old_name != "")
+ {
+ file_name_=file_name;
+ std::map<synfig::String, etl::loose_handle<Canvas> >::iterator iter;
+ for(iter=get_open_canvas_map().begin();iter!=get_open_canvas_map().end();++iter)
+ if(iter->second==this)
+ break;
+ if (iter == get_open_canvas_map().end())
+ CanvasParser::register_canvas_in_map(this, file_name);
+ else
+ signal_file_name_changed_();
+ }
}
}
Canvas::signal_file_name_changed()
{
if(parent())
- return signal_file_name_changed();
+ return parent()->signal_file_name_changed();
else
return signal_file_name_changed_;
}
return dirname(file_name_);
}
-
String
Canvas::get_meta_data(const String& key)const
{
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++)
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()=="paste_canvas" && 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));
Canvas::Handle paste_sub_canvas = paste_canvas->get_sub_canvas();
if(paste_sub_canvas)
- optimize_layers(paste_sub_canvas->get_context(),sub_canvas);
-//#define SYNFIG_OPTIMIZE_PASTE_CANVAS 1
+ optimize_layers(time, paste_sub_canvas->get_context(),sub_canvas,motion_blurred);
+// \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();
+ // 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;
}
+ catch(int)
+ { }
+#endif // SYNFIG_OPTIMIZE_PASTE_CANVAS
- // 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;
- }catch(int) { }
-#endif
- Layer::Handle new_layer(Layer::create("paste_canvas"));
+ Layer::Handle new_layer(Layer::create("PasteCanvas"));
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_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));
+ // don't use clone() because it re-randomizes the seeds of any random valuenodes
+ sub_canvas->push_back(composite = composite->simple_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);
}
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_)