a360fd5ee1e1be275bd7fb81c79a9d087aefb665
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / shapewipe / shapewipe.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 "bcdisplayinfo.h"
23 #include "bchash.h"
24 #include "cstrdup.h"
25 #include "edl.inc"
26 #include "filesystem.h"
27 #include "filexml.h"
28 #include "language.h"
29 #include "overlayframe.h"
30 #include "theme.h"
31 #include "vframe.h"
32 #include "shapewipe.h"
33
34 #include <png.h>
35 #include <math.h>
36 #include <stdint.h>
37 #include <string.h>
38
39 #define SHAPE_SEARCHPATH "/shapes"
40 #define DEFAULT_SHAPE "circle"
41 // feather slider range log2 = -10 .. -1 == 9.8e-4 .. 0.5
42 #define SHAPE_FLOG_MIN -10.
43 #define SHAPE_FLOG_MAX -1.
44
45 REGISTER_PLUGIN(ShapeWipeMain)
46
47 ShapeWipeConfig::ShapeWipeConfig()
48 {
49         direction = 0;
50         feather = 0;
51         preserve_aspect = 0;
52         strcpy(shape_name, DEFAULT_SHAPE);
53 }
54 ShapeWipeConfig::~ShapeWipeConfig()
55 {
56 }
57
58 void ShapeWipeConfig::read_xml(KeyFrame *keyframe)
59 {
60         FileXML input;
61         input.set_shared_input(keyframe->xbuf);
62
63         while( !input.read_tag() ) {
64                 if( input.tag.title_is("SHAPEWIPE") ) {
65                         direction = input.tag.get_property("DIRECTION", direction);
66                         feather = input.tag.get_property("FEATHER", feather);
67                         preserve_aspect = input.tag.get_property("PRESERVE_ASPECT", preserve_aspect);
68                         input.tag.get_property("SHAPE_NAME", shape_name);
69                 }
70         }
71 }
72 void ShapeWipeConfig::save_xml(KeyFrame *keyframe)
73 {
74         FileXML output;
75         output.set_shared_output(keyframe->xbuf);
76         output.tag.set_title("SHAPEWIPE");
77         output.tag.set_property("DIRECTION", direction);
78         output.tag.set_property("FEATHER", feather);
79         output.tag.set_property("PRESERVE_ASPECT", preserve_aspect);
80         output.tag.set_property("SHAPE_NAME", shape_name);
81         output.append_tag();
82         output.tag.set_title("/SHAPEWIPE");
83         output.append_tag();
84         output.terminate_string();
85 }
86
87
88 ShapeWipeW2B::ShapeWipeW2B(ShapeWipeMain *plugin,
89         ShapeWipeWindow *window, int x, int y)
90  : BC_Radial(x, y, plugin->config.direction == 0, _("White to Black"))
91 {
92         this->plugin = plugin;
93         this->window = window;
94 }
95
96 int ShapeWipeW2B::handle_event()
97 {
98         update(1);
99         plugin->config.direction = 0;
100         window->right->update(0);
101         plugin->send_configure_change();
102         return 0;
103 }
104
105 ShapeWipeB2W::ShapeWipeB2W(ShapeWipeMain *plugin,
106         ShapeWipeWindow *window, int x, int y)
107  : BC_Radial(x, y, plugin->config.direction == 1, _("Black to White"))
108 {
109         this->plugin = plugin;
110         this->window = window;
111 }
112
113 int ShapeWipeB2W::handle_event()
114 {
115         update(1);
116         plugin->config.direction = 1;
117         window->left->update(0);
118         plugin->send_configure_change();
119         return 0;
120 }
121
122
123 ShapeWipePreserveAspectRatio::ShapeWipePreserveAspectRatio(ShapeWipeMain *plugin,
124         ShapeWipeWindow *window, int x, int y)
125  : BC_CheckBox (x, y, plugin->config.preserve_aspect, _("Preserve shape aspect ratio"))
126 {
127         this->plugin = plugin;
128         this->window = window;
129 }
130
131 int ShapeWipePreserveAspectRatio::handle_event()
132 {
133         plugin->config.preserve_aspect = get_value();
134         plugin->send_configure_change();
135         return 0;
136 }
137
138
139 ShapeWipeTumble::ShapeWipeTumble(ShapeWipeMain *client,
140         ShapeWipeWindow *window, int x, int y)
141  : BC_Tumbler(x, y)
142 {
143         this->client = client;
144         this->window = window;
145         set_increment(0.01);
146 }
147
148 int ShapeWipeTumble::handle_up_event()
149 {
150         window->prev_shape();
151         return 1;
152 }
153
154 int ShapeWipeTumble::handle_down_event()
155 {
156         window->next_shape();
157         return 0;
158 }
159
160
161 ShapeWipeFeather::ShapeWipeFeather(ShapeWipeMain *client,
162                 ShapeWipeWindow *window, int x, int y)
163  : BC_FSlider(x, y, 0, 150, 150, SHAPE_FLOG_MIN, SHAPE_FLOG_MAX,
164         !client->config.feather ? SHAPE_FLOG_MIN :
165                 log(client->config.feather)/M_LN2)
166 {
167         this->client = client;
168         this->window = window;
169         set_precision(0.001);
170         set_pagination(0.01, 0.1);
171 }
172
173 char *ShapeWipeFeather::get_caption()
174 {
175         double  v = get_value();
176         char *caption = BC_Slider::get_caption();
177         sprintf(caption, "%-5.3f", exp(v*M_LN2));
178         return caption;
179 }
180
181 int ShapeWipeFeather::handle_event()
182 {
183         float v = get_value();
184         client->config.feather =  exp(M_LN2*v);
185         client->send_configure_change();
186         return 1;
187 }
188
189
190 ShapeWipeShape::ShapeWipeShape(ShapeWipeMain *client,
191                 ShapeWipeWindow *window, int x, int y,
192                 int text_w, int list_h)
193  : BC_PopupTextBox(window, &window->shapes, client->config.shape_name,
194         x, y, text_w, list_h)
195 {
196         this->client = client;
197         this->window = window;
198 }
199
200 int ShapeWipeShape::handle_event()
201 {
202         strcpy(client->config.shape_name, get_text());
203         client->send_configure_change();
204         return 1;
205 }
206
207
208 ShapeWipeWindow::ShapeWipeWindow(ShapeWipeMain *plugin)
209  : PluginClientWindow(plugin, 450, 125, 450, 125, 0)
210 {
211         this->plugin = plugin;
212 }
213
214 ShapeWipeWindow::~ShapeWipeWindow()
215 {
216         shapes.remove_all_objects();
217 }
218
219
220 void ShapeWipeWindow::create_objects()
221 {
222         BC_Title *title = 0;
223         lock_window("ShapeWipeWindow::create_objects");
224         int widget_border = plugin->get_theme()->widget_border;
225         int window_border = plugin->get_theme()->window_border;
226         int x = window_border, y = window_border;
227
228         plugin->init_shapes();
229         for( int i=0; i<plugin->shape_titles.size(); ++i ) {
230                 shapes.append(new BC_ListBoxItem(plugin->shape_titles.get(i)));
231         }
232
233         add_subwindow(title = new BC_Title(x, y, _("Direction:")));
234         x += title->get_w() + widget_border;
235         add_subwindow(left = new ShapeWipeW2B(plugin,
236                 this, x, y));
237         x += left->get_w() + widget_border;
238         add_subwindow(right = new ShapeWipeB2W(plugin,
239                 this, x, y));
240         x = window_border;
241         y += right->get_h() + widget_border;
242
243
244         add_subwindow(title = new BC_Title(x, y, _("Shape:")));
245         x += title->get_w() + widget_border;
246
247         shape_text = new ShapeWipeShape(plugin,
248                 this, x, y, 150, 200);
249         shape_text->create_objects();
250         x += shape_text->get_w() + widget_border;
251         add_subwindow(new ShapeWipeTumble(plugin,
252                 this, x, y));
253         y += shape_text->get_h() + widget_border;
254
255         x = window_border;
256         add_subwindow(title = new BC_Title(x, y, _("Feather:")));
257         x += title->get_w() + widget_border;
258         add_subwindow(shape_feather = new ShapeWipeFeather(plugin, this, x, y));
259         y += shape_feather->get_h() + widget_border;
260
261         x = window_border;
262         ShapeWipePreserveAspectRatio *aspect_ratio;
263         add_subwindow(aspect_ratio = new ShapeWipePreserveAspectRatio(
264                 plugin, this, x, y));
265         y += aspect_ratio->get_h() + widget_border;
266
267         show_window();
268         unlock_window();
269 }
270
271 void ShapeWipeWindow::next_shape()
272 {
273         ShapeWipeConfig &config = plugin->config;
274         int k = plugin->shape_titles.size();
275         while( --k>=0 && strcmp(plugin->shape_titles.get(k),config.shape_name) );
276
277         if( k >= 0 ) {
278                 if( ++k >= plugin->shape_titles.size() ) k = 0;
279                 strcpy(config.shape_name, plugin->shape_titles.get(k));
280                 shape_text->update(config.shape_name);
281         }
282         client->send_configure_change();
283 }
284
285 void ShapeWipeWindow::prev_shape()
286 {
287         ShapeWipeConfig &config = plugin->config;
288         int k = plugin->shape_titles.size();
289         while( --k>=0 && strcmp(plugin->shape_titles.get(k),config.shape_name) );
290
291         if( k >= 0 ) {
292                 if( --k < 0 ) k = plugin->shape_titles.size()-1;
293                 strcpy(config.shape_name, plugin->shape_titles.get(k));
294                 shape_text->update(config.shape_name);
295         }
296         client->send_configure_change();
297 }
298
299
300 ShapeWipeMain::ShapeWipeMain(PluginServer *server)
301  : PluginVClient(server)
302 {
303         input = 0;
304         output = 0;
305         engine = 0;
306         current_filename[0] = '\0';
307         current_name[0] = 0;
308         pattern_image = 0;
309         min_value = 255;
310         max_value = 0;
311         last_preserve_aspect = 0;
312         shapes_initialized = 0;
313         shape_paths.set_array_delete();
314         shape_titles.set_array_delete();
315 }
316
317 ShapeWipeMain::~ShapeWipeMain()
318 {
319         reset_pattern_image();
320         shape_paths.remove_all_objects();
321         shape_titles.remove_all_objects();
322         delete engine;
323 }
324
325 const char* ShapeWipeMain::plugin_title() { return N_("Shape Wipe"); }
326 int ShapeWipeMain::is_transition() { return 1; }
327 int ShapeWipeMain::uses_gui() { return 1; }
328
329 NEW_WINDOW_MACRO(ShapeWipeMain, ShapeWipeWindow);
330
331 void ShapeWipeMain::read_data(KeyFrame *keyframe)
332 {
333         config.read_xml(keyframe);
334 }
335 void ShapeWipeMain::save_data(KeyFrame *keyframe)
336 {
337         config.save_xml(keyframe);
338 }
339
340 void ShapeWipeMain::init_shapes()
341 {
342         if( !shapes_initialized ) {
343                 FileSystem fs;
344                 fs.set_filter("*.png");
345                 char shape_path[BCTEXTLEN];
346                 sprintf(shape_path, "%s%s", get_plugin_dir(), SHAPE_SEARCHPATH);
347                 fs.update(shape_path);
348
349                 for( int i=0; i<fs.total_files(); ++i ) {
350                         FileItem *file_item = fs.get_entry(i);
351                         if( !file_item->get_is_dir() ) {
352                                 shape_paths.append(cstrdup(file_item->get_path()));
353                                 char *ptr = cstrdup(file_item->get_name());
354                                 char *ptr2 = strrchr(ptr, '.');
355                                 if(ptr2) *ptr2 = 0;
356                                 shape_titles.append(ptr);
357                         }
358                 }
359
360                 shapes_initialized = 1;
361         }
362 }
363
364
365 int ShapeWipeMain::load_configuration()
366 {
367         read_data(get_prev_keyframe(get_source_position()));
368         return 1;
369 }
370
371 int ShapeWipeMain::read_pattern_image(char *shape_name,
372                 int new_frame_width, int new_frame_height)
373 {
374         png_byte header[8];
375         int is_png;
376         int row, col;
377         int pixel_width;
378         unsigned char value;
379         png_uint_32 width;
380         png_uint_32 height;
381         png_byte color_type;
382         png_byte bit_depth;
383         png_structp png_ptr = 0;
384         png_infop info_ptr = 0;
385         png_infop end_info = 0;
386         png_bytep *image = 0;
387         FILE *fp = 0;
388         frame_width = new_frame_width;
389         frame_height = new_frame_height;
390         int ret = 0;
391
392 // Convert name to filename
393         int k = shape_paths.size();
394         while( --k>=0 && strcmp(shape_titles[k], shape_name) );
395         if( k < 0 ) ret = 1;
396         if( !ret ) {
397                 strcpy(current_filename, shape_paths[k]);
398                 fp = fopen(current_filename, "rb");
399                 if( !fp ) ret = 1;
400         }
401         if( !ret ) {
402                 fread(header, 1, 8, fp);
403                 is_png = !png_sig_cmp(header, 0, 8);
404                 if( !is_png ) ret = 1;
405         }
406         if( !ret ) {
407                 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
408                 if( !png_ptr ) ret = 1;
409         }
410         if( !ret ) {
411                 /* Tell libpng we already checked the first 8 bytes */
412                 png_set_sig_bytes(png_ptr, 8);
413                 info_ptr = png_create_info_struct(png_ptr);
414                 if( !info_ptr ) ret = 1;
415         }
416         if( !ret ) {
417                 end_info = png_create_info_struct(png_ptr);
418                 if( !end_info ) ret = 1;
419         }
420         if( !ret ) {
421                 png_init_io(png_ptr, fp);
422                 png_read_info(png_ptr, info_ptr);
423
424                 color_type = png_get_color_type(png_ptr, info_ptr);
425                 bit_depth = png_get_bit_depth(png_ptr, info_ptr);
426                 width  = png_get_image_width (png_ptr, info_ptr);
427                 height = png_get_image_height(png_ptr, info_ptr);
428
429                 /* Skip the alpha channel if present
430                 * stripping alpha currently doesn't work in conjunction with
431                 * converting to grayscale in libpng */
432                 pixel_width = color_type & PNG_COLOR_MASK_ALPHA ? 2 : 1;
433                 /* Convert 16 bit data to 8 bit */
434                 if( bit_depth == 16 ) png_set_strip_16(png_ptr);
435                 /* Expand to 1 pixel per byte if necessary */
436                 if( bit_depth < 8 ) png_set_packing(png_ptr);
437
438                 /* Convert to grayscale */
439                 if( color_type == PNG_COLOR_TYPE_RGB ||
440                     color_type == PNG_COLOR_TYPE_RGB_ALPHA )
441                         png_set_rgb_to_gray_fixed(png_ptr, 1, -1, -1);
442
443                 /* Allocate memory to hold the original png image */
444                 image = (png_bytep*)new png_bytep[height];
445                 for( row=0; row<(int)height; ++row )
446                         image[row] = new png_byte[width*pixel_width];
447
448                 /* Allocate memory for the pattern image that will actually be
449                 * used for the wipe */
450                 pattern_image = new  unsigned char*[frame_height];
451
452                 png_read_image(png_ptr, image);
453                 png_read_end(png_ptr, end_info);
454
455                 double row_factor, col_factor;
456                 double row_offset = 0.5, col_offset = 0.5;      // for rounding
457
458                 if( config.preserve_aspect && aspect_w && aspect_h ) {
459                         row_factor = (height-1)/aspect_h;
460                         col_factor = (width-1)/aspect_w;
461                         if( row_factor < col_factor )
462                                 col_factor = row_factor;
463                         else
464                                 row_factor = col_factor;
465                         row_factor *= aspect_h/(double)(frame_height-1);
466                         col_factor *= aspect_w/(double)(frame_width-1);
467
468                         // center the pattern over the frame
469                         row_offset += (height-1-(frame_height-1)*row_factor)/2;
470                         col_offset += (width-1-(frame_width-1)*col_factor)/2;
471                 }
472                 else {
473                         // Stretch (or shrink) the pattern image to fill the frame
474                         row_factor = (double)(height-1)/(double)(frame_height-1);
475                         col_factor = (double)(width-1)/(double)(frame_width-1);
476                 }
477                 // first, determine range min..max
478                 for( int y=0; y<frame_height; ++y ) {
479                         row = (int)(row_factor*y + row_offset);
480                         for( int x=0; x<frame_width; ++x ) {
481                                 col = (int)(col_factor*x + col_offset)*pixel_width;
482                                 value = image[row][col];
483                                 if( value < min_value ) min_value = value;
484                                 if( value > max_value ) max_value = value;
485                         }
486                 }
487                 int range = max_value - min_value;
488                 if( !range ) range = 1;
489                 // scale to fade normalized pattern_image
490                 for( int y=0; y<frame_height; ++y ) {
491                         row = (int)(row_factor*y + row_offset);
492                         pattern_image[y] = new unsigned char[frame_width];
493                         for( int x=0; x<frame_width; ++x ) {
494                                 col = (int)(col_factor*x + col_offset)*pixel_width;
495                                 value = image[row][col];
496                                 pattern_image[y][x] = 0xff*(value - min_value) / range;
497                         }
498                 }
499         }
500
501         if( png_ptr || info_ptr || end_info )
502                 png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
503         if( fp )
504                 fclose(fp);
505         if( image ) {
506                 for( row=0; row<(int)height; ++row )
507                         delete [] image[row];
508                 delete [] image;
509         }
510         return ret;
511 }
512
513 void ShapeWipeMain::reset_pattern_image()
514 {
515         if( pattern_image ) {
516                 for( int y=0; y<frame_height; ++y )
517                         delete [] pattern_image[y];
518                 delete [] pattern_image;  pattern_image = 0;
519                 min_value = 255;
520                 max_value = 0;  // updated in read_pattern_image
521         }
522 }
523
524 #define SHAPEBLEND(type, components, tmp_type) { \
525         float scale = feather ? 1/feather : 0xff; \
526         type  **in_rows = (type**)input->get_rows(); \
527         type **out_rows = (type**)output->get_rows(); \
528         for( int y=y1; y<y2; ++y ) { \
529                 type *in_row = (type*) in_rows[y]; \
530                 type *out_row = (type*)out_rows[y]; \
531                 unsigned char *pattern_row = pattern_image[y]; \
532                 for( int x=0; x<w; ++x ) { \
533                         tmp_type d = (pattern_row[x] - threshold) * scale; \
534                         if( d > 0xff ) d = 0xff; \
535                         else if( d < -0xff ) d = -0xff; \
536                         tmp_type a = (d + 0xff) / 2, b = 0xff - a; \
537                         for( int i=0; i<components; ++i ) { \
538                                 type ic = in_row[i], oc = out_row[i]; \
539                                 out_row[i] = (ic * a + oc * b) / 0xff; \
540                         } \
541                         in_row += components; out_row += components; \
542                 } \
543         } \
544 }
545
546 int ShapeWipeMain::process_realtime(VFrame *input, VFrame *output)
547 {
548         this->input = input;
549         this->output = output;
550         int w = input->get_w();
551         int h = input->get_h();
552         init_shapes();
553         load_configuration();
554
555         if( strncmp(config.shape_name, current_name, BCTEXTLEN) ||
556             config.preserve_aspect != last_preserve_aspect ) {
557                 reset_pattern_image();
558         }
559         if ( !pattern_image ) {
560                 if( read_pattern_image(config.shape_name, w, h) ) {
561                         fprintf(stderr, _("Shape Wipe: cannot load shape %s\n"),
562                                 current_filename);
563                         current_filename[0] = 0;
564                         return 0;
565                 }
566                 strncpy(current_name, config.shape_name, BCTEXTLEN);
567                 last_preserve_aspect = config.preserve_aspect;
568         }
569
570         float fade = (float)PluginClient::get_source_position() /
571                 (float)PluginClient::get_total_len();
572         if( !config.direction ) fade = 1 - fade;
573         threshold = fade * 0xff;
574
575         int slices = w*h/0x40000+1;
576         int max_slices = BC_Resources::machine_cpus/2;
577         if( slices > max_slices ) slices = max_slices;
578         if( slices < 1 ) slices = 1;
579         if( engine && engine->get_total_clients() != slices ) {
580                 delete engine;  engine = 0;
581         }
582         if( !engine )
583                 engine = new ShapeEngine(this, slices, slices);
584
585         engine->process_packages();
586         return 0;
587 }
588
589
590 ShapePackage::ShapePackage()
591  : LoadPackage()
592 {
593 }
594
595 ShapeUnit::ShapeUnit(ShapeEngine *server) : LoadClient(server)
596 {
597         this->server = server;
598 }
599
600 ShapeUnit::~ShapeUnit()
601 {
602 }
603
604 void ShapeUnit::process_package(LoadPackage *package)
605 {
606         VFrame *input = server->plugin->input;
607         VFrame *output = server->plugin->output;
608         int w = input->get_w();
609
610         unsigned char **pattern_image = server->plugin->pattern_image;
611         unsigned char threshold = server->plugin->threshold;
612         float feather = server->plugin->config.feather;
613         ShapePackage *pkg = (ShapePackage*)package;
614         int y1 = pkg->y1, y2 = pkg->y2;
615
616         switch(input->get_color_model()) {
617         case BC_RGB_FLOAT:
618                 SHAPEBLEND(float, 3, float)
619                 break;
620         case BC_RGB888:
621         case BC_YUV888:
622                 SHAPEBLEND(unsigned char, 3, int)
623                 break;
624         case BC_RGBA_FLOAT:
625                 SHAPEBLEND(float, 4, float)
626                 break;
627         case BC_RGBA8888:
628         case BC_YUVA8888:
629                 SHAPEBLEND(unsigned char, 4, int)
630                 break;
631         case BC_RGB161616:
632         case BC_YUV161616:
633                 SHAPEBLEND(uint16_t, 3, int64_t)
634                 break;
635         case BC_RGBA16161616:
636         case BC_YUVA16161616:
637                 SHAPEBLEND(uint16_t, 4, int64_t)
638                 break;
639         }
640 }
641
642
643 ShapeEngine::ShapeEngine(ShapeWipeMain *plugin,
644         int total_clients, int total_packages)
645  : LoadServer(total_clients, total_packages)
646 {
647         this->plugin = plugin;
648 }
649
650 ShapeEngine::~ShapeEngine()
651 {
652 }
653
654
655 void ShapeEngine::init_packages()
656 {
657         int y = 0, h1 = plugin->input->get_h()-1;
658         int total_packages = get_total_packages();
659         for(int i = 0; i<total_packages; ) {
660                 ShapePackage *pkg = (ShapePackage*)get_package(i++);
661                 pkg->y1 = y;
662                 y = h1 * i / total_packages;
663                 pkg->y2 = y;
664         }
665 }
666
667 LoadClient* ShapeEngine::new_client()
668 {
669         return new ShapeUnit(this);
670 }
671
672 LoadPackage* ShapeEngine::new_package()
673 {
674         return new ShapePackage;
675 }
676