/* * CINELERRA * Copyright (C) 2008 Adam Williams * * This program 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 program 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "clip.h" #include "filexml.h" #include "maskauto.h" #include "maskautos.h" #include #include MaskPoint::MaskPoint() { x = 0; y = 0; control_x1 = 0; control_y1 = 0; control_x2 = 0; control_y2 = 0; } void MaskPoint::copy_from(MaskPoint &ptr) { this->x = ptr.x; this->y = ptr.y; this->control_x1 = ptr.control_x1; this->control_y1 = ptr.control_y1; this->control_x2 = ptr.control_x2; this->control_y2 = ptr.control_y2; } MaskPoint& MaskPoint::operator=(MaskPoint& ptr) { copy_from(ptr); return *this; } int MaskPoint::operator==(MaskPoint& ptr) { return EQUIV(x, ptr.x) && EQUIV(y, ptr.y) && EQUIV(control_x1, ptr.control_x1) && EQUIV(control_y1, ptr.control_y1) && EQUIV(control_x2, ptr.control_x2) && EQUIV(control_y2, ptr.control_y2); } SubMask::SubMask(MaskAuto *keyframe, int no) { this->keyframe = keyframe; memset(name, 0, sizeof(name)); sprintf(name, "%d", no); this->fader = 100; this->feather = 0; } SubMask::~SubMask() { points.remove_all_objects(); } int SubMask::equivalent(SubMask& ptr) { if( fader != ptr.fader ) return 0; if( feather != ptr.feather ) return 0; int n = points.size(); if( n != ptr.points.size() ) return 0; for( int i=0; iread_tag()) ) { if( file->tag.title_is("/MASK") ) break; if( file->tag.title_is("POINT") ) { XMLBuffer data; file->read_text_until("/POINT", &data); MaskPoint *point = new MaskPoint; char *cp = data.cstr(); if( cp ) point->x = strtof(cp, &cp); if( cp && *cp==',' ) point->y = strtof(cp+1, &cp); if( cp && *cp==',' ) point->control_x1 = strtof(cp+1, &cp); if( cp && *cp==',' ) point->control_y1 = strtof(cp+1, &cp); if( cp && *cp==',' ) point->control_x2 = strtof(cp+1, &cp); if( cp && *cp==',' ) point->control_y2 = strtof(cp+1, &cp); points.append(point); } } } void SubMask::copy(FileXML *file) { if(points.total) { file->tag.set_title("MASK"); file->tag.set_property("NUMBER", !keyframe ? -1 : keyframe->masks.number_of(this)); file->tag.set_property("NAME", name); file->tag.set_property("FADER", fader); file->tag.set_property("FEATHER", feather); file->append_tag(); file->append_newline(); for(int i = 0; i < points.total; i++) { file->append_newline(); file->tag.set_title("POINT"); file->append_tag(); char string[BCTEXTLEN]; //printf("SubMask::copy 1 %p %d %p\n", this, i, points.values[i]); sprintf(string, "%.7g, %.7g, %.7g, %.7g, %.7g, %.7g", points.values[i]->x, points.values[i]->y, points.values[i]->control_x1, points.values[i]->control_y1, points.values[i]->control_x2, points.values[i]->control_y2); //printf("SubMask::copy 2\n"); file->append_text(string); file->tag.set_title("/POINT"); file->append_tag(); } file->append_newline(); file->tag.set_title("/MASK"); file->append_tag(); file->append_newline(); } } void SubMask::dump(FILE *fp) { for( int i=0; ix, points.values[i]->y, points.values[i]->control_x1, points.values[i]->control_y1, points.values[i]->control_x2, points.values[i]->control_y2); } } MaskAuto::MaskAuto(EDL *edl, MaskAutos *autos) : Auto(edl, autos) { apply_before_plugins = 0; disable_opengl_masking = 0; // We define a fixed number of submasks so that interpolation for each // submask matches. for(int i = 0; i < SUBMASKS; i++) masks.append(new SubMask(this, i)); } MaskAuto::~MaskAuto() { masks.remove_all_objects(); } int MaskAuto::operator==(Auto &that) { return identical((MaskAuto*)&that); } int MaskAuto::operator==(MaskAuto &that) { return identical((MaskAuto*)&that); } int MaskAuto::identical(MaskAuto *src) { if( masks.size() != src->masks.size() || apply_before_plugins != src->apply_before_plugins || disable_opengl_masking != src->disable_opengl_masking ) return 0; for( int i=0; imasks.values[i])) return 0; } return 1; } void MaskAuto::update_parameter(MaskAuto *ref, MaskAuto *src) { if( src->apply_before_plugins != ref->apply_before_plugins ) this->apply_before_plugins = src->apply_before_plugins; if( src->disable_opengl_masking != ref->disable_opengl_masking ) this->disable_opengl_masking = src->disable_opengl_masking; for( int i=0; iget_submask(i)->equivalent(*ref->get_submask(i)) ) this->get_submask(i)->copy_from(*src->get_submask(i)); } } void MaskAuto::copy_from(Auto *src) { copy_from((MaskAuto*)src); } void MaskAuto::copy_from(MaskAuto *src) { Auto::copy_from(src); copy_data(src); } void MaskAuto::copy_data(MaskAuto *src) { apply_before_plugins = src->apply_before_plugins; disable_opengl_masking = src->disable_opengl_masking; masks.remove_all_objects(); for(int i = 0; i < src->masks.size(); i++) { masks.append(new SubMask(this, i)); masks.values[i]->copy_from(*src->masks.values[i]); } } int MaskAuto::interpolate_from(Auto *a1, Auto *a2, int64_t position, Auto *templ) { if(!a1) a1 = previous; if(!a2) a2 = next; MaskAuto *mask_auto1 = (MaskAuto *)a1; MaskAuto *mask_auto2 = (MaskAuto *)a2; if (!mask_auto2 || !mask_auto1 || mask_auto2->masks.total == 0) // can't interpolate, fall back to copying (using template if possible) { return Auto::interpolate_from(a1, a2, position, templ); } this->apply_before_plugins = mask_auto1->apply_before_plugins; this->disable_opengl_masking = mask_auto1->disable_opengl_masking; this->position = position; masks.remove_all_objects(); for( int i=0; imasks.total; ++i ) { SubMask *new_submask = new SubMask(this, i); masks.append(new_submask); SubMask *mask1 = mask_auto1->masks.values[i]; SubMask *mask2 = mask_auto2->masks.values[i]; double len = mask_auto2->position - mask_auto1->position; double weight = !len ? 0 : (position - mask_auto1->position) / len; new_submask->fader = mask1->fader*(1-weight) + mask2->fader*weight + 0.5; new_submask->feather = mask1->feather*(1-weight) + mask2->feather*weight + 0.5; // just in case, should never happen int total_points = MIN(mask1->points.total, mask2->points.total); for( int j=0; jpoints.values[j], mask2->points.values[j], position, mask_auto1->position, mask_auto2->position); new_submask->points.append(point); } } return 1; } SubMask* MaskAuto::get_submask(int number) { CLAMP(number, 0, masks.size() - 1); return masks.values[number]; } void MaskAuto::get_points(MaskPoints *points, int submask) { points->remove_all_objects(); SubMask *submask_ptr = get_submask(submask); for(int i = 0; i < submask_ptr->points.size(); i++) { MaskPoint *point = new MaskPoint; point->copy_from(*submask_ptr->points.get(i)); points->append(point); } } void MaskAuto::set_points(MaskPoints *points, int submask) { SubMask *submask_ptr = get_submask(submask); submask_ptr->points.remove_all_objects(); for(int i = 0; i < points->size(); i++) { MaskPoint *point = new MaskPoint; point->copy_from(*points->get(i)); submask_ptr->points.append(point); } } void MaskAuto::load(FileXML *file) { // legacy, moved to SubMask int old_mode = file->tag.get_property("MODE", -1); int old_value = file->tag.get_property("VALUE", 100); float old_feather = file->tag.get_property("FEATHER", 0); apply_before_plugins = file->tag.get_property("APPLY_BEFORE_PLUGINS", apply_before_plugins); disable_opengl_masking = file->tag.get_property("DISABLE_OPENGL_MASKING", disable_opengl_masking); for( int i=0; iread_tag()) ) { if( file->tag.title_is("/AUTO") ) break; if( file->tag.title_is("MASK") ) { int no = file->tag.get_property("NUMBER", 0); char name[BCTEXTLEN]; name[0] = 0; file->tag.get_property("NAME", name); if( !name[0] ) sprintf(name, "%d", no); SubMask *mask = masks.values[no]; memset(mask->name, 0, sizeof(mask->name)); strncpy(mask->name, name, sizeof(mask->name)); mask->feather = file->tag.get_property("FEATHER", old_feather); mask->fader = file->tag.get_property("FADER", old_value); if( old_mode == MASK_MULTIPLY_ALPHA ) mask->fader = -mask->fader; mask->load(file); } } } void MaskAuto::copy(int64_t start, int64_t end, FileXML *file, int default_auto) { file->tag.set_title("AUTO"); file->tag.set_property("APPLY_BEFORE_PLUGINS", apply_before_plugins); file->tag.set_property("DISABLE_OPENGL_MASKING", disable_opengl_masking); file->tag.set_property("POSITION", default_auto ? 0 : position - start); file->append_tag(); file->append_newline(); for( int i=0; icopy(file); file->tag.set_title("/AUTO"); file->append_tag(); file->append_newline(); } void MaskAuto::dump(FILE *fp) { fprintf(fp,"mask_auto: apply_before_plugins %d, disable_opengl_masking %d\n", apply_before_plugins, disable_opengl_masking); for( int i=0; idump(fp); } } void MaskAuto::translate_submasks(float translate_x, float translate_y) { for( int i=0; ipoints.total; ++j ) { mask->points.values[j]->x += translate_x; mask->points.values[j]->y += translate_y; } } } void MaskAuto::scale_submasks(int orig_scale, int new_scale) { for( int i=0; ipoints.total; ++j ) { float orig_x = mask->points.values[j]->x * orig_scale; float orig_y = mask->points.values[j]->y * orig_scale; mask->points.values[j]->x = orig_x / new_scale; mask->points.values[j]->y = orig_y / new_scale; orig_x = mask->points.values[j]->control_x1 * orig_scale; orig_y = mask->points.values[j]->control_y1 * orig_scale; mask->points.values[j]->control_x1 = orig_x / new_scale; mask->points.values[j]->control_y1 = orig_y / new_scale; orig_x = mask->points.values[j]->control_x2 * orig_scale; orig_y = mask->points.values[j]->control_y2 * orig_scale; mask->points.values[j]->control_x2 = orig_x / new_scale; mask->points.values[j]->control_y2 = orig_y / new_scale; } } } int MaskAuto::has_active_mask() { int total_points = 0; float min_fader = 100; for( int i=0; ipoints.size(); if( submask_points > 1 ) total_points += submask_points; int fader = mask->fader; if( fader < min_fader ) min_fader = fader; } return min_fader >= 0 && total_points < 2 ? 0 : 1; } static inline double line_dist(float cx,float cy, float tx,float ty) { double dx = tx-cx, dy = ty-cy; return sqrt(dx*dx + dy*dy); } void MaskEdge::load(MaskPoints &points, float ofs) { remove_all(); int first_point = 1; // Need to tabulate every vertex in persistent memory because // gluTessVertex doesn't copy them. for( int i=0; i= points.total-1) ? points.values[0] : points.values[i+1]; int segments = 0; if( !point1->control_x2 && !point1->control_y2 && !point2->control_x1 && !point2->control_y1 ) segments = 1; float x0 = point1->x, y0 = point1->y; float x1 = point1->x + point1->control_x2; float y1 = point1->y + point1->control_y2; float x2 = point2->x + point2->control_x1; float y2 = point2->y + point2->control_y1; float x3 = point2->x, y3 = point2->y; // forward differencing bezier curves implementation taken from GPL code at // http://cvs.sourceforge.net/viewcvs.py/guliverkli/guliverkli/src/subtitles/Rasterizer.cpp?rev=1.3 float cx3, cx2, cx1, cx0; float cy3, cy2, cy1, cy0; // [-1 +3 -3 +1] // [+3 -6 +3 0] // [-3 +3 0 0] // [+1 0 0 0] cx3 = - x0 + 3*x1 - 3*x2 + x3; cx2 = 3*x0 - 6*x1 + 3*x2; cx1 = -3*x0 + 3*x1; cx0 = x0; cy3 = - y0 + 3*y1 - 3*y2 + y3; cy2 = 3*y0 - 6*y1 + 3*y2; cy1 = -3*y0 + 3*y1; cy0 = y0; // This equation is from Graphics Gems I. // // The idea is that since we're approximating a cubic curve with lines, // any error we incur is due to the curvature of the line, which we can // estimate by calculating the maximum acceleration of the curve. For // a cubic, the acceleration (second derivative) is a line, meaning that // the absolute maximum acceleration must occur at either the beginning // (|c2|) or the end (|c2+c3|). Our bounds here are a little more // conservative than that, but that's okay. if( !segments ) { float maxaccel1 = fabs(2*cy2) + fabs(6*cy3); float maxaccel2 = fabs(2*cx2) + fabs(6*cx3); float maxaccel = maxaccel1 > maxaccel2 ? maxaccel1 : maxaccel2; segments = maxaccel > 1.0 ? sqrt(maxaccel) : 1 + line_dist(point1->x,point1->y, point2->x,point2->y); } for( int j=0; j<=segments; ++j ) { float t = (float)j / segments; float x = cx0 + t*(cx1 + t*(cx2 + t*cx3)); float y = cy0 + t*(cy1 + t*(cy2 + t*cy3)); if( j > 0 || first_point ) { append(x, y-ofs); first_point = 0; } } } }