add nested proxy path, rework perpetual session load/save strategy, build cleanups
[goodguy/cinelerra.git] / cinelerra-5.1 / cinelerra / maskauto.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include "clip.h"
23 #include "filexml.h"
24 #include "maskauto.h"
25 #include "maskautos.h"
26
27 #include <stdlib.h>
28 #include <string.h>
29
30
31 MaskPoint::MaskPoint()
32 {
33         x = 0;
34         y = 0;
35         control_x1 = 0;
36         control_y1 = 0;
37         control_x2 = 0;
38         control_y2 = 0;
39 }
40
41 void MaskPoint::copy_from(MaskPoint &ptr)
42 {
43         this->x = ptr.x;
44         this->y = ptr.y;
45         this->control_x1 = ptr.control_x1;
46         this->control_y1 = ptr.control_y1;
47         this->control_x2 = ptr.control_x2;
48         this->control_y2 = ptr.control_y2;
49 }
50
51 MaskPoint& MaskPoint::operator=(MaskPoint& ptr)
52 {
53         copy_from(ptr);
54         return *this;
55 }
56
57 int MaskPoint::operator==(MaskPoint& ptr)
58 {
59         return EQUIV(x, ptr.x) &&
60                 EQUIV(y, ptr.y) &&
61                 EQUIV(control_x1, ptr.control_x1) &&
62                 EQUIV(control_y1, ptr.control_y1) &&
63                 EQUIV(control_x2, ptr.control_x2) &&
64                 EQUIV(control_y2, ptr.control_y2);
65 }
66
67 SubMask::SubMask(MaskAuto *keyframe, int no)
68 {
69         this->keyframe = keyframe;
70         memset(name, 0, sizeof(name));
71         sprintf(name, "%d", no);
72         this->fader = 100;
73         this->feather = 0;
74 }
75
76 SubMask::~SubMask()
77 {
78         points.remove_all_objects();
79 }
80
81 int SubMask::equivalent(SubMask& ptr)
82 {
83         if( fader != ptr.fader ) return 0;
84         if( feather != ptr.feather ) return 0;
85         int n = points.size();
86         if( n != ptr.points.size() ) return 0;
87         for( int i=0; i<n; ++i ) {
88                 if(!(*points.get(i) == *ptr.points.get(i)))
89                         return 0;
90         }
91         return 1;
92 }
93
94 int SubMask::operator==(SubMask& ptr)
95 {
96         return equivalent(ptr);
97 }
98
99 void SubMask::copy_from(SubMask& ptr, int do_name)
100 {
101         if( do_name ) {
102                 memset(name, 0, sizeof(name));
103                 strncpy(name, ptr.name, sizeof(name)-1);
104         }
105         fader = ptr.fader;
106         feather = ptr.feather;
107         points.remove_all_objects();
108 //printf("SubMask::copy_from 1 %p %d\n", this, ptr.points.total);
109         for(int i = 0; i < ptr.points.total; i++)
110         {
111                 MaskPoint *point = new MaskPoint;
112                 *point = *ptr.points.values[i];
113                 points.append(point);
114         }
115 }
116
117 void SubMask::load(FileXML *file)
118 {
119         points.remove_all_objects();
120
121         int result = 0;
122         while( !(result = file->read_tag()) ) {
123                 if( file->tag.title_is("/MASK") ) break;
124                 if( file->tag.title_is("POINT") ) {
125                         XMLBuffer data;
126                         file->read_text_until("/POINT", &data);
127                         MaskPoint *point = new MaskPoint;
128                         char *cp = data.cstr();
129                         if( cp ) point->x = strtof(cp, &cp);
130                         if( cp && *cp==',' ) point->y = strtof(cp+1, &cp);
131                         if( cp && *cp==',' ) point->control_x1 = strtof(cp+1, &cp);
132                         if( cp && *cp==',' ) point->control_y1 = strtof(cp+1, &cp);
133                         if( cp && *cp==',' ) point->control_x2 = strtof(cp+1, &cp);
134                         if( cp && *cp==',' ) point->control_y2 = strtof(cp+1, &cp);
135                         points.append(point);
136                 }
137         }
138 }
139
140 void SubMask::copy(FileXML *file)
141 {
142         if(points.total)
143         {
144                 file->tag.set_title("MASK");
145                 file->tag.set_property("NUMBER",
146                         !keyframe ? -1 : keyframe->masks.number_of(this));
147                 file->tag.set_property("NAME", name);
148                 file->tag.set_property("FADER", fader);
149                 file->tag.set_property("FEATHER", feather);
150                 file->append_tag();
151                 file->append_newline();
152
153                 for(int i = 0; i < points.total; i++)
154                 {
155                         file->append_newline();
156                         file->tag.set_title("POINT");
157                         file->append_tag();
158                         char string[BCTEXTLEN];
159 //printf("SubMask::copy 1 %p %d %p\n", this, i, points.values[i]);
160                         sprintf(string, "%.7g, %.7g, %.7g, %.7g, %.7g, %.7g",
161                                 points.values[i]->x,
162                                 points.values[i]->y,
163                                 points.values[i]->control_x1,
164                                 points.values[i]->control_y1,
165                                 points.values[i]->control_x2,
166                                 points.values[i]->control_y2);
167 //printf("SubMask::copy 2\n");
168                         file->append_text(string);
169                         file->tag.set_title("/POINT");
170                         file->append_tag();
171                 }
172                 file->append_newline();
173
174                 file->tag.set_title("/MASK");
175                 file->append_tag();
176                 file->append_newline();
177         }
178 }
179
180 void SubMask::dump(FILE *fp)
181 {
182         for( int i=0; i<points.size(); ++i ) {
183                 fprintf(fp, "   mask: %d, name: %s, fader: %f, feather %f\n", i,
184                         name, fader, feather);
185                 fprintf(fp, "     point=%d x=%.2f y=%.2f in_x=%.2f in_y=%.2f out_x=%.2f out_y=%.2f\n",
186                         i, points.values[i]->x, points.values[i]->y,
187                         points.values[i]->control_x1, points.values[i]->control_y1,
188                         points.values[i]->control_x2, points.values[i]->control_y2);
189         }
190 }
191
192
193 MaskAuto::MaskAuto(EDL *edl, MaskAutos *autos)
194  : Auto(edl, autos)
195 {
196         apply_before_plugins = 0;
197         disable_opengl_masking = 0;
198
199 // We define a fixed number of submasks so that interpolation for each
200 // submask matches.
201
202         for(int i = 0; i < SUBMASKS; i++)
203                 masks.append(new SubMask(this, i));
204 }
205
206 MaskAuto::~MaskAuto()
207 {
208         masks.remove_all_objects();
209 }
210
211 int MaskAuto::operator==(Auto &that)
212 {
213         return identical((MaskAuto*)&that);
214 }
215
216
217
218 int MaskAuto::operator==(MaskAuto &that)
219 {
220         return identical((MaskAuto*)&that);
221 }
222
223
224 int MaskAuto::identical(MaskAuto *src)
225 {
226         if( masks.size() != src->masks.size() ||
227             apply_before_plugins != src->apply_before_plugins ||
228             disable_opengl_masking != src->disable_opengl_masking ) return 0;
229
230         for( int i=0; i<masks.size(); ++i ) {
231                 if(!(*masks.values[i] == *src->masks.values[i])) return 0;
232         }
233         return 1;
234 }
235
236 void MaskAuto::update_parameter(MaskAuto *ref, MaskAuto *src)
237 {
238         if( src->apply_before_plugins != ref->apply_before_plugins )
239                 this->apply_before_plugins = src->apply_before_plugins;
240         if( src->disable_opengl_masking != ref->disable_opengl_masking )
241                 this->disable_opengl_masking = src->disable_opengl_masking;
242
243         for( int i=0; i<masks.size(); ++i ) {
244                 if( !src->get_submask(i)->equivalent(*ref->get_submask(i)) )
245                         this->get_submask(i)->copy_from(*src->get_submask(i));
246         }
247 }
248
249 void MaskAuto::copy_from(Auto *src)
250 {
251         copy_from((MaskAuto*)src);
252 }
253
254 void MaskAuto::copy_from(MaskAuto *src)
255 {
256         Auto::copy_from(src);
257         copy_data(src);
258 }
259
260 void MaskAuto::copy_data(MaskAuto *src)
261 {
262         apply_before_plugins = src->apply_before_plugins;
263         disable_opengl_masking = src->disable_opengl_masking;
264
265         masks.remove_all_objects();
266         for(int i = 0; i < src->masks.size(); i++)
267         {
268                 masks.append(new SubMask(this, i));
269                 masks.values[i]->copy_from(*src->masks.values[i]);
270         }
271 }
272
273 int MaskAuto::interpolate_from(Auto *a1, Auto *a2, int64_t position, Auto *templ) {
274         if(!a1) a1 = previous;
275         if(!a2) a2 = next;
276         MaskAuto  *mask_auto1 = (MaskAuto *)a1;
277         MaskAuto  *mask_auto2 = (MaskAuto *)a2;
278
279         if (!mask_auto2 || !mask_auto1 || mask_auto2->masks.total == 0)
280         // can't interpolate, fall back to copying (using template if possible)
281         {
282                 return Auto::interpolate_from(a1, a2, position, templ);
283         }
284         this->apply_before_plugins = mask_auto1->apply_before_plugins;
285         this->disable_opengl_masking = mask_auto1->disable_opengl_masking;
286         this->position = position;
287         masks.remove_all_objects();
288
289         for( int i=0; i<mask_auto1->masks.total; ++i ) {
290                 SubMask *new_submask = new SubMask(this, i);
291                 masks.append(new_submask);
292                 SubMask *mask1 = mask_auto1->masks.values[i];
293                 SubMask *mask2 = mask_auto2->masks.values[i];
294                 double len = mask_auto2->position - mask_auto1->position;
295                 double weight = !len ? 0 : (position - mask_auto1->position) / len;
296                 new_submask->fader = mask1->fader*(1-weight) + mask2->fader*weight + 0.5;
297                 new_submask->feather = mask1->feather*(1-weight) + mask2->feather*weight + 0.5;
298
299                 // just in case, should never happen
300                 int total_points = MIN(mask1->points.total, mask2->points.total);
301                 for( int j=0; j<total_points; ++j ) {
302                         MaskPoint *point = new MaskPoint;
303                         MaskAutos::avg_points(point,
304                                 mask1->points.values[j], mask2->points.values[j],
305                                 position, mask_auto1->position, mask_auto2->position);
306                         new_submask->points.append(point);
307                 }
308         }
309         return 1;
310
311
312 }
313
314
315 SubMask* MaskAuto::get_submask(int number)
316 {
317         CLAMP(number, 0, masks.size() - 1);
318         return masks.values[number];
319 }
320
321 void MaskAuto::get_points(MaskPoints *points,
322         int submask)
323 {
324         points->remove_all_objects();
325         SubMask *submask_ptr = get_submask(submask);
326         for(int i = 0; i < submask_ptr->points.size(); i++)
327         {
328                 MaskPoint *point = new MaskPoint;
329                 point->copy_from(*submask_ptr->points.get(i));
330                 points->append(point);
331         }
332 }
333
334 void MaskAuto::set_points(MaskPoints *points,
335         int submask)
336 {
337         SubMask *submask_ptr = get_submask(submask);
338         submask_ptr->points.remove_all_objects();
339         for(int i = 0; i < points->size(); i++)
340         {
341                 MaskPoint *point = new MaskPoint;
342                 point->copy_from(*points->get(i));
343                 submask_ptr->points.append(point);
344         }
345 }
346
347
348 void MaskAuto::load(FileXML *file)
349 {
350 // legacy, moved to SubMask
351         int old_mode = file->tag.get_property("MODE", -1);
352         int old_value = file->tag.get_property("VALUE", 100);
353         float old_feather = file->tag.get_property("FEATHER", 0);
354         apply_before_plugins = file->tag.get_property("APPLY_BEFORE_PLUGINS", apply_before_plugins);
355         disable_opengl_masking = file->tag.get_property("DISABLE_OPENGL_MASKING", disable_opengl_masking);
356         for( int i=0; i<masks.size(); ++i ) {
357                 delete masks.values[i];
358                 masks.values[i] = new SubMask(this, i);
359         }
360
361         int result = 0;
362         while( !(result = file->read_tag()) ) {
363                 if( file->tag.title_is("/AUTO") ) break;
364                 if( file->tag.title_is("MASK") ) {
365                         int no = file->tag.get_property("NUMBER", 0);
366                         char name[BCTEXTLEN];  name[0] = 0;
367                         file->tag.get_property("NAME", name);
368                         if( !name[0] ) sprintf(name, "%d", no);
369                         SubMask *mask = masks.values[no];
370                         memset(mask->name, 0, sizeof(mask->name));
371                         strncpy(mask->name, name, sizeof(mask->name));
372                         mask->feather = file->tag.get_property("FEATHER", old_feather);
373                         mask->fader = file->tag.get_property("FADER", old_value);
374                         if( old_mode == MASK_MULTIPLY_ALPHA )
375                                 mask->fader = -mask->fader;
376                         mask->load(file);
377                 }
378         }
379 }
380
381 void MaskAuto::copy(int64_t start, int64_t end, FileXML *file, int default_auto)
382 {
383         file->tag.set_title("AUTO");
384         file->tag.set_property("APPLY_BEFORE_PLUGINS", apply_before_plugins);
385         file->tag.set_property("DISABLE_OPENGL_MASKING", disable_opengl_masking);
386         file->tag.set_property("POSITION", default_auto ? 0 : position - start);
387         file->append_tag();
388         file->append_newline();
389
390         for( int i=0; i<masks.size(); ++i )
391                 masks.values[i]->copy(file);
392
393         file->tag.set_title("/AUTO");
394         file->append_tag();
395         file->append_newline();
396 }
397
398 void MaskAuto::dump(FILE *fp)
399 {
400         fprintf(fp,"mask_auto:  apply_before_plugins %d, disable_opengl_masking %d\n",
401                 apply_before_plugins, disable_opengl_masking);
402         for( int i=0; i<masks.size(); ++i ) {
403                 fprintf(fp,"    submask %d:\n", i);
404                 masks.values[i]->dump(fp);
405         }
406 }
407
408 void MaskAuto::translate_submasks(float translate_x, float translate_y)
409 {
410         for( int i=0; i<masks.size(); ++i ) {
411                 SubMask *mask = get_submask(i);
412                 for( int j=0; j<mask->points.total; ++j ) {
413                         mask->points.values[j]->x += translate_x;
414                         mask->points.values[j]->y += translate_y;
415                 }
416         }
417 }
418
419 void MaskAuto::scale_submasks(int orig_scale, int new_scale)
420 {
421         for( int i=0; i<masks.size(); ++i ) {
422                 SubMask *mask = get_submask(i);
423                 for( int j=0; j<mask->points.total; ++j ) {
424                         float orig_x = mask->points.values[j]->x * orig_scale;
425                         float orig_y = mask->points.values[j]->y * orig_scale;
426                         mask->points.values[j]->x = orig_x / new_scale;
427                         mask->points.values[j]->y = orig_y / new_scale;
428
429                         orig_x = mask->points.values[j]->control_x1 * orig_scale;
430                         orig_y = mask->points.values[j]->control_y1 * orig_scale;
431                         mask->points.values[j]->control_x1 = orig_x / new_scale;
432                         mask->points.values[j]->control_y1 = orig_y / new_scale;
433
434                         orig_x = mask->points.values[j]->control_x2 * orig_scale;
435                         orig_y = mask->points.values[j]->control_y2 * orig_scale;
436                         mask->points.values[j]->control_x2 = orig_x / new_scale;
437                         mask->points.values[j]->control_y2 = orig_y / new_scale;
438                 }
439         }
440 }
441
442 int MaskAuto::has_active_mask()
443 {
444         int total_points = 0;
445         float min_fader = 100;
446         for( int i=0; i<masks.size(); ++i ) {
447                 SubMask *mask = get_submask(i);
448                 int submask_points = mask->points.size();
449                 if( submask_points > 1 ) total_points += submask_points;
450                 int fader = mask->fader;
451                 if( fader < min_fader ) min_fader = fader;
452         }
453         return min_fader >= 0 && total_points < 2 ? 0 : 1;
454 }
455
456 static inline double line_dist(float cx,float cy, float tx,float ty)
457 {
458         double dx = tx-cx, dy = ty-cy;
459         return sqrt(dx*dx + dy*dy);
460 }
461
462 void MaskEdge::load(MaskPoints &points, float ofs)
463 {
464         remove_all();
465         int first_point = 1;
466 // Need to tabulate every vertex in persistent memory because
467 // gluTessVertex doesn't copy them.
468         for( int i=0; i<points.total; ++i ) {
469                 MaskPoint *point1 = points.values[i];
470                 MaskPoint *point2 = (i >= points.total-1) ?
471                         points.values[0] : points.values[i+1];
472
473                 int segments = 0;
474                 if( !point1->control_x2 && !point1->control_y2 &&
475                     !point2->control_x1 && !point2->control_y1 )
476                         segments = 1;
477
478                 float x0 = point1->x, y0 = point1->y;
479                 float x1 = point1->x + point1->control_x2;
480                 float y1 = point1->y + point1->control_y2;
481                 float x2 = point2->x + point2->control_x1;
482                 float y2 = point2->y + point2->control_y1;
483                 float x3 = point2->x, y3 = point2->y;
484
485                 // forward differencing bezier curves implementation taken from GPL code at
486                 // http://cvs.sourceforge.net/viewcvs.py/guliverkli/guliverkli/src/subtitles/Rasterizer.cpp?rev=1.3
487
488                 float cx3, cx2, cx1, cx0;
489                 float cy3, cy2, cy1, cy0;
490
491                 // [-1 +3 -3 +1]
492                 // [+3 -6 +3  0]
493                 // [-3 +3  0  0]
494                 // [+1  0  0  0]
495
496                 cx3 = -  x0 + 3*x1 - 3*x2 + x3;
497                 cx2 =  3*x0 - 6*x1 + 3*x2;
498                 cx1 = -3*x0 + 3*x1;
499                 cx0 =    x0;
500
501                 cy3 = -  y0 + 3*y1 - 3*y2 + y3;
502                 cy2 =  3*y0 - 6*y1 + 3*y2;
503                 cy1 = -3*y0 + 3*y1;
504                 cy0 =    y0;
505
506                 // This equation is from Graphics Gems I.
507                 //
508                 // The idea is that since we're approximating a cubic curve with lines,
509                 // any error we incur is due to the curvature of the line, which we can
510                 // estimate by calculating the maximum acceleration of the curve.  For
511                 // a cubic, the acceleration (second derivative) is a line, meaning that
512                 // the absolute maximum acceleration must occur at either the beginning
513                 // (|c2|) or the end (|c2+c3|).  Our bounds here are a little more
514                 // conservative than that, but that's okay.
515                 if( !segments ) {
516                         float maxaccel1 = fabs(2*cy2) + fabs(6*cy3);
517                         float maxaccel2 = fabs(2*cx2) + fabs(6*cx3);
518                         float maxaccel = maxaccel1 > maxaccel2 ? maxaccel1 : maxaccel2;
519                         segments =  maxaccel > 1.0 ? sqrt(maxaccel) :
520                                 1 + line_dist(point1->x,point1->y, point2->x,point2->y);
521                 }
522
523                 for( int j=0; j<=segments; ++j ) {
524                         float t = (float)j / segments;
525                         float x = cx0 + t*(cx1 + t*(cx2 + t*cx3));
526                         float y = cy0 + t*(cy1 + t*(cy2 + t*cy3));
527
528                         if( j > 0 || first_point ) {
529                                 append(x, y-ofs);
530                                 first_point = 0;
531                         }
532                 }
533         }
534 }
535