/*! \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 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
*/
/* ========================================================================= */
/* === 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),
{
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());
}
// would just continue going when polled
// for a color.
CanvasBase::push_back(Layer::Handle());
-
+
changed();
}
{
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;
{
if(is_inline() && parent_)
throw runtime_error("Inline Canvas cannot have an ID");
-
+
if(!valid_id(x))
throw runtime_error("Invalid ID");
id_=x;
void
Canvas::set_time(Time t)const
-{
+{
if(is_dirty_ || !get_time().is_equal(t))
- {
+ {
#if 0
if(is_root())
{
// ...questionable
const_cast<Canvas&>(*this).cur_time_=t;
-
+
is_dirty_=false;
get_context().set_time(t);
}
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;
}
{
if(is_inline() && parent_)
return parent_->find_value_node(id);
-
+
if(id.empty())
throw Exception::IDNotFound("Empty 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);
}
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);
}
{
//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;
}
}
}
catch(Exception::IDNotFound)
{
- x->set_id(id);
+ x->set_id(id);
return;
}
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("");
}
{
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];
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));
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];
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));
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())
}
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
// 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();
}
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());
}
{
if(is_inline_ && parent_)
{
-
+
}
-
+
id_="inline";
is_inline_=true;
parent_=parent;
{
parent->group_db_[iter->first].insert(iter->second.begin(),iter->second.end());
}
-
+
rend_desc()=parent->rend_desc();
}
assert(parent);
//if(parent->is_inline())
// return create_inline(parent->parent());
-
+
Handle canvas(new Canvas("inline"));
canvas->set_inline(parent);
return canvas;
// 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();
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);
children().push_back(child_canvas);
child_canvas->parent_=this;
}
-
+
return 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;
return dirname(file_name_);
}
-
+
String
Canvas::get_meta_data(const String& key)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(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;
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")
{
+ // 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);
+ Canvas::Handle paste_sub_canvas = paste_canvas->get_sub_canvas();
+ if(paste_sub_canvas)
+ optimize_layers(paste_sub_canvas->get_context(),sub_canvas,motion_blurred);
//#define SYNFIG_OPTIMIZE_PASTE_CANVAS 1
-#ifdef SYNFIG_OPTIMIZE_PASTE_CANVAS
+#ifdef SYNFIG_OPTIMIZE_PASTE_CANVAS
Canvas::iterator sub_iter;
// Determine if we can just remove the paste canvas
- // altogether
+ // 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))
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
}catch(int) { }
#endif
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;
}
-
+
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;
{
const_iterator i = begin(),
iend = end();
-
+
for(; i != iend; ++i)
{
const Node::time_set &tset = (*i)->get_times();
return group_db_.size();
}
-
+
void
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_)
}
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);
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);