Improved geometry
[melted] / src / framework / mlt_geometry.c
1 /*
2 * mlt_geometry.h -- provides the geometry API
3 * Copyright (C) 2004-2005 Ushodaya Enterprises Limited
4 * Author: Charles Yates <charles.yates@pandora.be>
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 Foundation,
18 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 */
20
21 #include "mlt_geometry.h"
22 #include "mlt_tokeniser.h"
23 #include "mlt_factory.h"
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <math.h>
29
30 typedef struct geometry_item_s
31 {
32 struct mlt_geometry_item_s data;
33 struct geometry_item_s *next, *prev;
34 }
35 *geometry_item;
36
37 typedef struct
38 {
39 char *data;
40 int length;
41 int nw;
42 int nh;
43 geometry_item item;
44 }
45 geometry_s, *geometry;
46
47 // Create a new geometry structure
48 mlt_geometry mlt_geometry_init( )
49 {
50 mlt_geometry this = calloc( 1, sizeof( struct mlt_geometry_s ) );
51 if ( this != NULL )
52 {
53 this->local = calloc( 1, sizeof( geometry_s ) );
54 if ( this->local != NULL )
55 {
56 geometry self = this->local;
57 char *normalisation = mlt_environment( "MLT_NORMALISATION" );
58 self->nw = 720;
59 if ( normalisation == NULL || strcmp( normalisation, "NTSC" ) )
60 self->nh = 576;
61 else
62 self->nh = 480;
63 }
64 else
65 {
66 free( this );
67 this = NULL;
68 }
69 }
70 return this;
71 }
72
73 /** A linear step
74 */
75
76 static inline double linearstep( double start, double end, double position, int length )
77 {
78 double o = ( end - start ) / length;
79 return start + position * o;
80 }
81
82 static void mlt_geometry_virtual_refresh( mlt_geometry this )
83 {
84 geometry self = this->local;
85
86 // Parse of all items to ensure unspecified keys are calculated correctly
87 if ( self->item != NULL )
88 {
89 int i = 0;
90 for ( i = 0; i < 5; i ++ )
91 {
92 geometry_item current = self->item;
93 while( current != NULL )
94 {
95 int fixed = current->data.f[ i ];
96 if ( !fixed )
97 {
98 geometry_item prev = current->prev;
99 geometry_item next = current->next;
100
101 float prev_value = 0;
102 float next_value = 0;
103 float value = 0;
104
105 while( prev != NULL && !prev->data.f[ i ] ) prev = prev->prev;
106 while( next != NULL && !next->data.f[ i ] ) next = next->next;
107
108 switch( i )
109 {
110 case 0:
111 if ( prev ) prev_value = prev->data.x;
112 if ( next ) next_value = next->data.x;
113 break;
114 case 1:
115 if ( prev ) prev_value = prev->data.y;
116 if ( next ) next_value = next->data.y;
117 break;
118 case 2:
119 if ( prev ) prev_value = prev->data.w;
120 if ( next ) next_value = next->data.w;
121 break;
122 case 3:
123 if ( prev ) prev_value = prev->data.h;
124 if ( next ) next_value = next->data.h;
125 break;
126 case 4:
127 if ( prev ) prev_value = prev->data.mix;
128 if ( next ) next_value = next->data.mix;
129 break;
130 }
131
132 // This should never happen
133 if ( prev == NULL )
134 current->data.f[ i ] = 1;
135 else if ( next == NULL )
136 value = prev_value;
137 else
138 value = linearstep( prev_value, next_value, current->data.frame - prev->data.frame, next->data.frame - prev->data.frame );
139
140 switch( i )
141 {
142 case 0: current->data.x = value; break;
143 case 1: current->data.y = value; break;
144 case 2: current->data.w = value; break;
145 case 3: current->data.h = value; break;
146 case 4: current->data.mix = value; break;
147 }
148 }
149
150 // Move to the next item
151 current = current->next;
152 }
153 }
154 }
155 }
156
157 static int mlt_geometry_drop( mlt_geometry this, geometry_item item )
158 {
159 geometry self = this->local;
160
161 if ( item == self->item )
162 {
163 self->item = item->next;
164 if ( self->item != NULL )
165 self->item->prev = NULL;
166 // To ensure correct seeding, ensure all values are fixed
167 if ( self->item != NULL )
168 {
169 self->item->data.f[0] = 1;
170 self->item->data.f[1] = 1;
171 self->item->data.f[2] = 1;
172 self->item->data.f[3] = 1;
173 self->item->data.f[4] = 1;
174 }
175 }
176 else if ( item->next != NULL )
177 {
178 item->next->prev = item->prev;
179 }
180 else if ( item->prev != NULL )
181 {
182 item->prev->next = item->next;
183 item->next->prev = item->prev;
184 }
185
186 free( item );
187
188 return 0;
189 }
190
191 static void mlt_geometry_clean( mlt_geometry this )
192 {
193 geometry self = this->local;
194 free( self->data );
195 self->data = NULL;
196 while( self->item )
197 mlt_geometry_drop( this, self->item );
198 }
199
200 // Parse the geometry specification for a given length and normalised width/height (-1 for default)
201 // data is constructed as: [frame=]X,Y:WxH[:mix][;[frame=]X,Y:WxH[:mix]]*
202 // and X, Y, W and H can have trailing % chars to indicate percentage of normalised size
203 int mlt_geometry_parse( mlt_geometry this, char *data, int length, int nw, int nh )
204 {
205 int i = 0;
206
207 // Create a tokeniser
208 mlt_tokeniser tokens = mlt_tokeniser_init( );
209
210 // Get the local/private structure
211 geometry self = this->local;
212
213 // Clean the existing geometry
214 mlt_geometry_clean( this );
215
216 // Update the info on the data
217 if ( length != -1 )
218 self->length = length;
219 if ( nw != -1 )
220 self->nw = nw;
221 if ( nh != -1 )
222 self->nh = nh;
223 if ( data != NULL )
224 self->data = strdup( data );
225
226 // Tokenise
227 if ( data != NULL )
228 mlt_tokeniser_parse_new( tokens, data, ";" );
229
230 // Iterate through each token
231 for ( i = 0; i < mlt_tokeniser_count( tokens ); i ++ )
232 {
233 struct mlt_geometry_item_s item;
234 char *value = mlt_tokeniser_get_string( tokens, i );
235
236 // Set item to 0
237 memset( &item, 0, sizeof( struct mlt_geometry_item_s ) );
238
239 // Now parse the item
240 mlt_geometry_parse_item( this, &item, value );
241
242 // Now insert into place
243 mlt_geometry_insert( this, &item );
244 }
245
246 // Remove the tokeniser
247 mlt_tokeniser_close( tokens );
248
249 // ???
250 return 0;
251 }
252
253 // Conditionally refresh in case of a change
254 int mlt_geometry_refresh( mlt_geometry this, char *data, int length, int nw, int nh )
255 {
256 geometry self = this->local;
257 int changed = ( length != -1 && length != self->length );
258 changed = changed || ( nw != -1 && nw != self->nw );
259 changed = changed || ( nh != -1 && nh != self->nh );
260 changed = changed || ( data != NULL && ( self->data == NULL || strcmp( data, self->data ) ) );
261 if ( changed )
262 return mlt_geometry_parse( this, data, length, nw, nh );
263 return -1;
264 }
265
266 int mlt_geometry_get_length( mlt_geometry this )
267 {
268 // Get the local/private structure
269 geometry self = this->local;
270
271 // return the length
272 return self->length;
273 }
274
275 void mlt_geometry_set_length( mlt_geometry this, int length )
276 {
277 // Get the local/private structure
278 geometry self = this->local;
279
280 // return the length
281 self->length = length;
282 }
283
284 int mlt_geometry_parse_item( mlt_geometry this, mlt_geometry_item item, char *value )
285 {
286 int ret = 0;
287
288 // Get the local/private structure
289 geometry self = this->local;
290
291 if ( value != NULL && strcmp( value, "" ) )
292 {
293 char *p = strchr( value, '=' );
294 int count = 0;
295 double temp;
296
297 // Determine if a position has been specified
298 if ( p != NULL )
299 {
300 temp = atof( value );
301 if ( temp > -1 && temp < 1 )
302 item->frame = temp * self->length;
303 else
304 item->frame = temp;
305 value = p + 1;
306 }
307
308 // Special case - frame < 0
309 if ( item->frame < 0 )
310 item->frame += self->length;
311
312 // Obtain the current value at this position - this allows new
313 // frames to be created which don't specify all values
314 mlt_geometry_fetch( this, item, item->frame );
315
316 // Special case - when an empty string is specified, all values are fixed
317 // TODO: Check if this is logical - it's convenient, but it's also odd...
318 if ( !*value )
319 {
320 item->f[0] = 1;
321 item->f[1] = 1;
322 item->f[2] = 1;
323 item->f[3] = 1;
324 item->f[4] = 1;
325 }
326
327 // Iterate through the remainder of value
328 while( *value )
329 {
330 // Get the value
331 temp = strtod( value, &p );
332
333 // Check if a value was specified
334 if ( p != value )
335 {
336 // Handle the % case
337 if ( *p == '%' )
338 {
339 if ( count == 0 || count == 2 )
340 temp *= self->nw / 100.0;
341 else if ( count == 1 || count == 3 )
342 temp *= self->nh / 100.0;
343 p ++;
344 }
345
346 // Special case - distort token
347 if ( *p == '!' || *p == '*' )
348 {
349 p ++;
350 item->distort = 1;
351 }
352
353 // Actually, we don't care about the delimiter at all..
354 if ( *p ) p ++;
355
356 // Assign to the item
357 switch( count )
358 {
359 case 0: item->x = temp; item->f[0] = 1; break;
360 case 1: item->y = temp; item->f[1] = 1; break;
361 case 2: item->w = temp; item->f[2] = 1; break;
362 case 3: item->h = temp; item->f[3] = 1; break;
363 case 4: item->mix = temp; item->f[4] = 1; break;
364 }
365 }
366 else
367 {
368 p ++;
369 }
370
371 // Update the value pointer
372 value = p;
373 count ++;
374 }
375 }
376 else
377 {
378 ret = 1;
379 }
380
381 return ret;
382 }
383
384 // Fetch a geometry item for an absolute position
385 int mlt_geometry_fetch( mlt_geometry this, mlt_geometry_item item, float position )
386 {
387 // Get the local geometry
388 geometry self = this->local;
389
390 // Need to find the nearest key to the position specifed
391 geometry_item key = self->item;
392
393 // Iterate through the keys until we reach last or have
394 while( key != NULL && key->next != NULL && position >= key->next->data.frame )
395 key = key->next;
396
397 if ( key != NULL )
398 {
399 // Position is situated before the first key - all zeroes
400 if ( position < key->data.frame )
401 {
402 memset( item, 0, sizeof( struct mlt_geometry_item_s ) );
403 item->mix = 100;
404 }
405 // Position is a key itself - no iterpolation need
406 else if ( position == key->data.frame )
407 {
408 memcpy( item, &key->data, sizeof( struct mlt_geometry_item_s ) );
409 }
410 // Position is after the last key - no interpolation, but not a key frame
411 else if ( key->next == NULL )
412 {
413 memcpy( item, &key->data, sizeof( struct mlt_geometry_item_s ) );
414 item->key = 0;
415 item->f[ 0 ] = 0;
416 item->f[ 1 ] = 0;
417 item->f[ 2 ] = 0;
418 item->f[ 3 ] = 0;
419 item->f[ 4 ] = 0;
420 }
421 // Interpolation is needed - position > key and there is a following key
422 else
423 {
424 item->key = 0;
425 item->frame = position;
426 position -= key->data.frame;
427 item->x = linearstep( key->data.x, key->next->data.x, position, key->next->data.frame - key->data.frame );
428 item->y = linearstep( key->data.y, key->next->data.y, position, key->next->data.frame - key->data.frame );
429 item->w = linearstep( key->data.w, key->next->data.w, position, key->next->data.frame - key->data.frame );
430 item->h = linearstep( key->data.h, key->next->data.h, position, key->next->data.frame - key->data.frame );
431 item->mix = linearstep( key->data.mix, key->next->data.mix, position, key->next->data.frame - key->data.frame );
432 item->distort = key->data.distort;
433 position += key->data.frame;
434 }
435
436 item->frame = position;
437 }
438 else
439 {
440 memset( item, 0, sizeof( struct mlt_geometry_item_s ) );
441 item->mix = 100;
442 }
443
444 return key == NULL;
445 }
446
447 // Specify a geometry item at an absolute position
448 int mlt_geometry_insert( mlt_geometry this, mlt_geometry_item item )
449 {
450 // Get the local/private geometry structure
451 geometry self = this->local;
452
453 // Create a new local item (this may be removed if a key already exists at this position)
454 geometry_item new = calloc( 1, sizeof( struct geometry_item_s ) );
455 memcpy( &new->data, item, sizeof( struct mlt_geometry_item_s ) );
456 new->data.key = 1;
457
458 // Determine if we need to insert or append to the list, or if it's a new list
459 if ( self->item != NULL )
460 {
461 // Get the first item
462 geometry_item place = self->item;
463
464 // Locate an existing nearby item
465 while ( place->next != NULL && item->frame > place->data.frame )
466 place = place->next;
467
468 if ( item->frame < place->data.frame )
469 {
470 if ( place == self->item )
471 self->item = new;
472 if ( place->prev )
473 place->prev->next = new;
474 new->next = place;
475 new->prev = place->prev;
476 place->prev = new;
477 }
478 else if ( item->frame > place->data.frame )
479 {
480 if ( place->next )
481 place->next->prev = new;
482 new->next = place->next;
483 new->prev = place;
484 place->next = new;
485 }
486 else
487 {
488 memcpy( &place->data, &new->data, sizeof( struct mlt_geometry_item_s ) );
489 free( new );
490 }
491 }
492 else
493 {
494 // Set the first item
495 self->item = new;
496
497 // To ensure correct seeding, ensure all values are fixed
498 self->item->data.f[0] = 1;
499 self->item->data.f[1] = 1;
500 self->item->data.f[2] = 1;
501 self->item->data.f[3] = 1;
502 self->item->data.f[4] = 1;
503 }
504
505 // Refresh all geometries
506 mlt_geometry_virtual_refresh( this );
507
508 // TODO: Error checking
509 return 0;
510 }
511
512 // Remove the key at the specified position
513 int mlt_geometry_remove( mlt_geometry this, int position )
514 {
515 int ret = 1;
516
517 // Get the local/private geometry structure
518 geometry self = this->local;
519
520 // Get the first item
521 geometry_item place = self->item;
522
523 while( place != NULL && position < place->data.frame )
524 place = place->next;
525
526 if ( place != NULL && position == place->data.frame )
527 ret = mlt_geometry_drop( this, place );
528
529 // Refresh all geometries
530 mlt_geometry_virtual_refresh( this );
531
532 return ret;
533 }
534
535 // Get the key at the position or the next following
536 int mlt_geometry_key( mlt_geometry this, mlt_geometry_item item, int position )
537 {
538 // Get the local/private geometry structure
539 geometry self = this->local;
540
541 // Get the first item
542 geometry_item place = self->item;
543
544 while( place != NULL && position > place->data.frame )
545 place = place->next;
546
547 if ( place != NULL )
548 memcpy( item, &place->data, sizeof( struct mlt_geometry_item_s ) );
549
550 return place == NULL;
551 }
552
553 char *mlt_geometry_serialise_cut( mlt_geometry this, int in, int out )
554 {
555 struct mlt_geometry_item_s item;
556 char *ret = malloc( 1000 );
557 int used = 0;
558 int size = 1000;
559
560 if ( in == -1 )
561 in = 0;
562 if ( out == -1 )
563 out = mlt_geometry_get_length( this );
564
565 if ( ret != NULL )
566 {
567 char temp[ 100 ];
568
569 strcpy( ret, "" );
570
571 item.frame = in;
572
573 while( 1 )
574 {
575 strcpy( temp, "" );
576
577 // If it's the first frame, then it's not necessarily a key
578 if ( item.frame == in )
579 {
580 if ( mlt_geometry_fetch( this, &item, item.frame ) )
581 break;
582
583 // To ensure correct seeding, ensure all values are fixed
584 item.f[0] = 1;
585 item.f[1] = 1;
586 item.f[2] = 1;
587 item.f[3] = 1;
588 item.f[4] = 1;
589 }
590 // Typically, we move from key to key
591 else if ( item.frame < out )
592 {
593 if ( mlt_geometry_key( this, &item, item.frame ) )
594 break;
595
596 // Special case - crop at the out point
597 if ( item.frame > out )
598 mlt_geometry_fetch( this, &item, out );
599 }
600 // We've handled the last key
601 else
602 {
603 break;
604 }
605
606 if ( item.frame - in != 0 )
607 sprintf( temp, "%d=", item.frame - in );
608
609 if ( item.f[0] )
610 sprintf( temp + strlen( temp ), "%.0f", item.x );
611 strcat( temp, "," );
612 if ( item.f[1] )
613 sprintf( temp + strlen( temp ), "%.0f", item.y );
614 strcat( temp, ":" );
615 if ( item.f[2] )
616 sprintf( temp + strlen( temp ), "%.0f", item.w );
617 strcat( temp, "x" );
618 if ( item.f[3] )
619 sprintf( temp + strlen( temp ), "%.0f", item.h );
620 if ( item.f[4] )
621 sprintf( temp + strlen( temp ), ":%.0f", item.mix );
622
623 if ( used + strlen( temp ) > size )
624 {
625 size += 1000;
626 ret = realloc( ret, size );
627 }
628
629 if ( ret != NULL && used != 0 )
630 {
631 used ++;
632 strcat( ret, ";" );
633 }
634 if ( ret != NULL )
635 {
636 used += strlen( temp );
637 strcat( ret, temp );
638 }
639
640 item.frame ++;
641 }
642 }
643
644 return ret;
645 }
646
647 // Serialise the current geometry
648 char *mlt_geometry_serialise( mlt_geometry this )
649 {
650 geometry self = this->local;
651 char *ret = mlt_geometry_serialise_cut( this, 0, self->length );
652 if ( ret )
653 {
654 free( self->data );
655 self->data = ret;
656 }
657 return ret;
658 }
659
660 // Close the geometry
661 void mlt_geometry_close( mlt_geometry this )
662 {
663 if ( this != NULL )
664 {
665 mlt_geometry_clean( this );
666 free( this->local );
667 free( this );
668 }
669 }
670
671