dynamic keyframes, textbox rework, andrea ffmpeg.opts, perpetual chkpt undo, lv2...
[goodguy/history.git] / cinelerra-5.1 / plugins / whirl / whirl.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 "clip.h"
24 #include "bchash.h"
25 #include "filexml.h"
26 #include "guicast.h"
27 #include "keyframe.h"
28 #include "language.h"
29 #include "loadbalance.h"
30 #include "pluginvclient.h"
31 #include "vframe.h"
32
33
34
35 #include <stdint.h>
36 #include <string.h>
37
38 class WhirlEffect;
39 class WhirlWindow;
40 class WhirlEngine;
41
42 #define MAXRADIUS 100
43 #define MAXPINCH 100
44
45
46
47
48
49
50 class WhirlConfig
51 {
52 public:
53         WhirlConfig();
54
55         void copy_from(WhirlConfig &src);
56         int equivalent(WhirlConfig &src);
57         void interpolate(WhirlConfig &prev,
58                 WhirlConfig &next,
59                 long prev_frame,
60                 long next_frame,
61                 long current_frame);
62
63         float angle;
64         float pinch;
65         float radius;
66 };
67
68
69 class WhirlAngle : public BC_FSlider
70 {
71 public:
72         WhirlAngle(WhirlEffect *plugin, int x, int y);
73         int handle_event();
74         WhirlEffect *plugin;
75 };
76
77
78
79 class WhirlPinch : public BC_FSlider
80 {
81 public:
82         WhirlPinch(WhirlEffect *plugin, int x, int y);
83         int handle_event();
84         WhirlEffect *plugin;
85 };
86
87
88
89 class WhirlRadius : public BC_FSlider
90 {
91 public:
92         WhirlRadius(WhirlEffect *plugin, int x, int y);
93         int handle_event();
94         WhirlEffect *plugin;
95 };
96
97 class WhirlWindow : public PluginClientWindow
98 {
99 public:
100         WhirlWindow(WhirlEffect *plugin);
101         void create_objects();
102         WhirlEffect *plugin;
103         WhirlRadius *radius;
104         WhirlPinch *pinch;
105         WhirlAngle *angle;
106 };
107
108
109
110
111
112 class WhirlPackage : public LoadPackage
113 {
114 public:
115         WhirlPackage();
116         int row1, row2;
117 };
118
119 class WhirlUnit : public LoadClient
120 {
121 public:
122         WhirlUnit(WhirlEffect *plugin, WhirlEngine *server);
123         void process_package(LoadPackage *package);
124         WhirlEngine *server;
125         WhirlEffect *plugin;
126
127 };
128
129
130 class WhirlEngine : public LoadServer
131 {
132 public:
133         WhirlEngine(WhirlEffect *plugin, int cpus);
134         void init_packages();
135         LoadClient* new_client();
136         LoadPackage* new_package();
137         WhirlEffect *plugin;
138 };
139
140
141
142 class WhirlEffect : public PluginVClient
143 {
144 public:
145         WhirlEffect(PluginServer *server);
146         ~WhirlEffect();
147
148         PLUGIN_CLASS_MEMBERS(WhirlConfig)
149         int process_realtime(VFrame *input, VFrame *output);
150         int is_realtime();
151         void update_gui();
152         void save_data(KeyFrame *keyframe);
153         void read_data(KeyFrame *keyframe);
154
155         WhirlEngine *engine;
156         VFrame *temp_frame;
157         VFrame *input, *output;
158         int need_reconfigure;
159 };
160
161
162
163
164
165
166 REGISTER_PLUGIN(WhirlEffect)
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 WhirlConfig::WhirlConfig()
184 {
185         angle = 0.0;
186         pinch = 0.0;
187         radius = 0.0;
188 }
189
190 void WhirlConfig::copy_from(WhirlConfig &src)
191 {
192         this->angle = src.angle;
193         this->pinch = src.pinch;
194         this->radius = src.radius;
195 }
196
197 int WhirlConfig::equivalent(WhirlConfig &src)
198 {
199         return EQUIV(this->angle, src.angle) &&
200                 EQUIV(this->pinch, src.pinch) &&
201                 EQUIV(this->radius, src.radius);
202 }
203
204 void WhirlConfig::interpolate(WhirlConfig &prev,
205         WhirlConfig &next,
206         long prev_frame,
207         long next_frame,
208         long current_frame)
209 {
210         double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
211         double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
212
213         this->angle = prev.angle * prev_scale + next.angle * next_scale;
214         this->pinch = prev.pinch * prev_scale + next.pinch * next_scale;
215         this->radius = prev.radius * prev_scale + next.radius * next_scale;
216 }
217
218
219
220
221
222
223
224
225
226
227 WhirlWindow::WhirlWindow(WhirlEffect *plugin)
228  : PluginClientWindow(plugin,
229         220,
230         200,
231         220,
232         200,
233         0)
234 {
235         this->plugin = plugin;
236 }
237
238
239
240 void WhirlWindow::create_objects()
241 {
242         int x = 10, y = 10;
243         add_subwindow(new BC_Title(x, y, _("Radius")));
244         y += 20;
245         add_subwindow(radius = new WhirlRadius(plugin, x, y));
246         y += 40;
247         add_subwindow(new BC_Title(x, y, _("Pinch")));
248         y += 20;
249         add_subwindow(pinch = new WhirlPinch(plugin, x, y));
250         y += 40;
251         add_subwindow(new BC_Title(x, y, _("Angle")));
252         y += 20;
253         add_subwindow(angle = new WhirlAngle(plugin, x, y));
254
255         show_window();
256         flush();
257 }
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272 WhirlAngle::WhirlAngle(WhirlEffect *plugin, int x, int y)
273  : BC_FSlider(x,
274                 y,
275                 0,
276                 200,
277                 200,
278                 (float)0,
279                 (float)360,
280                 plugin->config.angle)
281 {
282         this->plugin = plugin;
283 }
284 int WhirlAngle::handle_event()
285 {
286         plugin->config.angle = get_value();
287         plugin->send_configure_change();
288         return 1;
289 }
290
291
292
293
294 WhirlPinch::WhirlPinch(WhirlEffect *plugin, int x, int y)
295  : BC_FSlider(x,
296                 y,
297                 0,
298                 200,
299                 200,
300                 (float)0,
301                 (float)MAXPINCH,
302                 plugin->config.pinch)
303 {
304         this->plugin = plugin;
305 }
306 int WhirlPinch::handle_event()
307 {
308         plugin->config.pinch = get_value();
309         plugin->send_configure_change();
310         return 1;
311 }
312
313
314
315
316 WhirlRadius::WhirlRadius(WhirlEffect *plugin, int x, int y)
317  : BC_FSlider(x,
318                 y,
319                 0,
320                 200,
321                 200,
322                 (float)0,
323                 (float)MAXRADIUS,
324                 plugin->config.radius)
325 {
326         this->plugin = plugin;
327 }
328 int WhirlRadius::handle_event()
329 {
330         plugin->config.radius = get_value();
331         plugin->send_configure_change();
332         return 1;
333 }
334
335
336
337
338
339
340
341
342
343
344
345 WhirlEffect::WhirlEffect(PluginServer *server)
346  : PluginVClient(server)
347 {
348         need_reconfigure = 1;
349         engine = 0;
350         temp_frame = 0;
351
352 }
353
354 WhirlEffect::~WhirlEffect()
355 {
356
357         if(engine) delete engine;
358         if(temp_frame) delete temp_frame;
359 }
360
361
362
363
364
365
366 const char* WhirlEffect::plugin_title() { return N_("Whirl"); }
367 int WhirlEffect::is_realtime() { return 1; }
368
369
370
371 NEW_WINDOW_MACRO(WhirlEffect, WhirlWindow)
372
373
374 void WhirlEffect::update_gui()
375 {
376         if(thread)
377         {
378                 load_configuration();
379                 thread->window->lock_window();
380                 ((WhirlWindow*)thread->window)->angle->update(config.angle);
381                 ((WhirlWindow*)thread->window)->pinch->update(config.pinch);
382                 ((WhirlWindow*)thread->window)->radius->update(config.radius);
383                 thread->window->unlock_window();
384         }
385 }
386
387 LOAD_CONFIGURATION_MACRO(WhirlEffect, WhirlConfig)
388
389
390
391
392 void WhirlEffect::save_data(KeyFrame *keyframe)
393 {
394         FileXML output;
395
396 // cause data to be stored directly in text
397         output.set_shared_output(keyframe->xbuf);
398
399         output.tag.set_title("WHIRL");
400         output.tag.set_property("ANGLE", config.angle);
401         output.tag.set_property("PINCH", config.pinch);
402         output.tag.set_property("RADIUS", config.radius);
403         output.append_tag();
404         output.tag.set_title("/WHIRL");
405         output.append_tag();
406         output.append_newline();
407         output.terminate_string();
408 // data is now in *text
409 }
410
411 void WhirlEffect::read_data(KeyFrame *keyframe)
412 {
413         FileXML input;
414
415         input.set_shared_input(keyframe->xbuf);
416
417         int result = 0;
418
419         while(!result)
420         {
421                 result = input.read_tag();
422
423                 if(!result)
424                 {
425                         if(input.tag.title_is("WHIRL"))
426                         {
427                                 config.angle = input.tag.get_property("ANGLE", config.angle);
428                                 config.pinch = input.tag.get_property("PINCH", config.pinch);
429                                 config.radius = input.tag.get_property("RADIUS", config.radius);
430                         }
431                 }
432         }
433 }
434
435 int WhirlEffect::process_realtime(VFrame *input, VFrame *output)
436 {
437         need_reconfigure |= load_configuration();
438         this->input = input;
439         this->output = output;
440
441         if(EQUIV(config.angle, 0) ||
442                 (EQUIV(config.radius, 0) && EQUIV(config.pinch, 0)))
443         {
444                 if(input->get_rows()[0] != output->get_rows()[0])
445                         output->copy_from(input);
446         }
447         else
448         {
449                 if(input->get_rows()[0] == output->get_rows()[0])
450                 {
451                         if(!temp_frame) temp_frame = new VFrame(input->get_w(), input->get_h(),
452                                 input->get_color_model(), 0);
453                         temp_frame->copy_from(input);
454                         this->input = temp_frame;
455                 }
456
457                 if(!engine) engine = new WhirlEngine(this, PluginClient::smp + 1);
458
459                 engine->process_packages();
460         }
461         return 0;
462 }
463
464
465
466
467
468
469
470
471 WhirlPackage::WhirlPackage()
472  : LoadPackage()
473 {
474 }
475
476
477
478 WhirlUnit::WhirlUnit(WhirlEffect *plugin, WhirlEngine *server)
479  : LoadClient(server)
480 {
481         this->plugin = plugin;
482 }
483
484
485
486 static int calc_undistorted_coords(double cen_x,
487                         double cen_y,
488                         double scale_x,
489                         double scale_y,
490                         double radius,
491                         double radius2,
492                         double radius3,
493                         double pinch,
494                         double wx,
495                         double wy,
496                         double &whirl,
497                         double &x,
498                         double &y)
499 {
500         double dx, dy;
501         double d, factor;
502         double dist;
503         double ang, sina, cosa;
504         int inside;
505
506 /* Distances to center, scaled */
507
508         dx = (wx - cen_x) * scale_x;
509         dy = (wy - cen_y) * scale_y;
510
511 /* Distance^2 to center of *circle* (scaled ellipse) */
512
513         d = dx * dx + dy * dy;
514
515 /*  If we are inside circle, then distort.
516  *  Else, just return the same position
517  */
518
519         inside = (d < radius2);
520
521         if(inside)
522     {
523         dist = sqrt(d / radius3) / radius;
524
525 /* Pinch */
526
527         factor = pow(sin(M_PI / 2 * dist), -pinch);
528
529         dx *= factor;
530         dy *= factor;
531
532 /* Whirl */
533
534         factor = 1.0 - dist;
535
536         ang = whirl * factor * factor;
537
538         sina = sin(ang);
539         cosa = cos(ang);
540
541         x = (cosa * dx - sina * dy) / scale_x + cen_x;
542         y = (sina * dx + cosa * dy) / scale_y + cen_y;
543     }
544
545         return inside;
546 }
547
548
549
550 #define GET_PIXEL(components, x, y, input_rows) \
551         input_rows[CLIP(y, 0, (h - 1))] + components * CLIP(x, 0, (w - 1))
552
553
554
555
556
557
558 static float bilinear(double x, double y, double *values)
559 {
560         double m0, m1;
561         x = fmod(x, 1.0);
562         y = fmod(y, 1.0);
563
564         if(x < 0.0) x += 1.0;
565         if(y < 0.0) y += 1.0;
566
567         m0 = (double)values[0] + x * ((double)values[1] - values[0]);
568         m1 = (double)values[2] + x * ((double)values[3] - values[2]);
569         return m0 + y * (m1 - m0);
570 }
571
572
573
574
575
576 #define WHIRL_MACRO(type, max, components) \
577 { \
578         type **input_rows = (type**)plugin->input->get_rows(); \
579 /* Compiler error requires separate arrays */ \
580         double top_values[4], bot_values[4]; \
581         for( int i=0; i<4; ++i ) top_values[i] = bot_values[i] = 0; \
582         for(int row = pkg->row1 / 2; row <= (pkg->row2 + pkg->row1) / 2; row++) \
583         { \
584                 type *top_row = (type*)plugin->output->get_rows()[row]; \
585                 type *bot_row = (type*)plugin->output->get_rows()[h - row - 1]; \
586                 type *top_p = top_row; \
587                 type *bot_p = bot_row + components * w - components; \
588                 type *pixel1; \
589                 type *pixel2; \
590                 type *pixel3; \
591                 type *pixel4; \
592  \
593                 for(int col = 0; col < w; col++) \
594                 { \
595                         if(calc_undistorted_coords(cen_x, \
596                                 cen_y, \
597                                 scale_x, \
598                                 scale_y, \
599                                 radius, \
600                                 radius2, \
601                                 radius3, \
602                                 pinch, \
603                                 col, \
604                                 row, \
605                                 whirl, \
606                                 cx, \
607                                 cy)) \
608                         { \
609 /* Inside distortion area */ \
610 /* Do top */ \
611                                 if(cx >= 0.0) \
612                                         ix = (int)cx; \
613                                 else \
614                                         ix = -((int)-cx + 1); \
615  \
616                                 if(cy >= 0.0) \
617                                         iy = (int)cy; \
618                                 else \
619                                         iy = -((int)-cy + 1); \
620  \
621                                 pixel1 = GET_PIXEL(components, ix,     iy,     input_rows); \
622                                 pixel2 = GET_PIXEL(components, ix + 1, iy,     input_rows); \
623                                 pixel3 = GET_PIXEL(components, ix,     iy + 1, input_rows); \
624                                 pixel4 = GET_PIXEL(components, ix + 1, iy + 1, input_rows); \
625  \
626                                 top_values[0] = pixel1[0]; \
627                                 top_values[1] = pixel2[0]; \
628                                 top_values[2] = pixel3[0]; \
629                                 top_values[3] = pixel4[0]; \
630                                 top_p[0] = (type)bilinear(cx, cy, top_values); \
631  \
632                                 top_values[0] = pixel1[1]; \
633                                 top_values[1] = pixel2[1]; \
634                                 top_values[2] = pixel3[1]; \
635                                 top_values[3] = pixel4[1]; \
636                                 top_p[1] = (type)bilinear(cx, cy, top_values); \
637  \
638                                 top_values[0] = pixel1[2]; \
639                                 top_values[1] = pixel2[2]; \
640                                 top_values[2] = pixel3[2]; \
641                                 top_values[3] = pixel4[2]; \
642                                 top_p[2] = (type)bilinear(cx, cy, top_values); \
643  \
644                                 if(components == 4) \
645                                 { \
646                                         top_values[0] = pixel1[3]; \
647                                         top_values[1] = pixel2[3]; \
648                                         top_values[2] = pixel3[3]; \
649                                         top_values[3] = pixel4[3]; \
650                                         top_p[3] = (type)bilinear(cx, cy, top_values); \
651                                 } \
652  \
653                                 top_p += components; \
654  \
655 /* Do bottom */ \
656                         cx = cen_x + (cen_x - cx); \
657                         cy = cen_y + (cen_y - cy); \
658  \
659                         if(cx >= 0.0) \
660                                         ix = (int)cx; \
661                         else \
662                                         ix = -((int)-cx + 1); \
663  \
664                         if(cy >= 0.0) \
665                                         iy = (int)cy; \
666                         else \
667                                         iy = -((int)-cy + 1); \
668  \
669                                 pixel1 = GET_PIXEL(components, ix,     iy,     input_rows); \
670                                 pixel2 = GET_PIXEL(components, ix + 1, iy,     input_rows); \
671                                 pixel3 = GET_PIXEL(components, ix,     iy + 1, input_rows); \
672                                 pixel4 = GET_PIXEL(components, ix + 1, iy + 1, input_rows); \
673  \
674  \
675  \
676                                 bot_values[0] = pixel1[0]; \
677                                 bot_values[1] = pixel2[0]; \
678                                 bot_values[2] = pixel3[0]; \
679                                 bot_values[3] = pixel4[0]; \
680                                 bot_p[0] = (type)bilinear(cx, cy, bot_values); \
681  \
682                                 bot_values[0] = pixel1[1]; \
683                                 bot_values[1] = pixel2[1]; \
684                                 bot_values[2] = pixel3[1]; \
685                                 bot_values[3] = pixel4[1]; \
686                                 bot_p[1] = (type)bilinear(cx, cy, bot_values); \
687  \
688                                 bot_values[0] = pixel1[2]; \
689                                 bot_values[1] = pixel2[2]; \
690                                 bot_values[2] = pixel3[2]; \
691                                 bot_values[3] = pixel4[2]; \
692                                 bot_p[2] = (type)bilinear(cx, cy, bot_values); \
693  \
694                                 if(components == 4) \
695                                 { \
696                                         bot_values[0] = pixel1[3]; \
697                                         bot_values[1] = pixel2[3]; \
698                                         bot_values[2] = pixel3[3]; \
699                                         bot_values[3] = pixel4[3]; \
700                                         bot_p[3] = (type)bilinear(cx, cy, bot_values); \
701                                 } \
702  \
703                                 bot_p -= components; \
704  \
705  \
706                         } \
707                         else \
708                         { \
709 /* Outside distortion area */ \
710 /* Do top */ \
711                                 top_p[0] = input_rows[row][components * col + 0]; \
712                                 top_p[1] = input_rows[row][components * col + 1]; \
713                                 top_p[2] = input_rows[row][components * col + 2]; \
714                                 if(components == 4) top_p[3] = input_rows[row][components * col + 3]; \
715  \
716  \
717                                 top_p += components; \
718  \
719 /* Do bottom */ \
720                                 int bot_offset = w * components - col * components - components; \
721                                 int bot_row = h - 1 - row; \
722                                 bot_p[0] = input_rows[bot_row][bot_offset + 0]; \
723                                 bot_p[1] = input_rows[bot_row][bot_offset + 1]; \
724                                 bot_p[2] = input_rows[bot_row][bot_offset + 2]; \
725                                 if(components == 4) bot_p[3] = input_rows[bot_row][bot_offset + 3]; \
726                                 bot_p -= components; \
727                         } \
728                 } \
729         } \
730 }
731
732 void WhirlUnit::process_package(LoadPackage *package)
733 {
734         WhirlPackage *pkg = (WhirlPackage*)package;
735         int w = plugin->input->get_w();
736         int h = plugin->input->get_h();
737         double whirl = plugin->config.angle * M_PI / 180;
738         double pinch = plugin->config.pinch / MAXPINCH;
739         double cx, cy;
740     int ix, iy;
741         double cen_x = (double)(w - 1) / 2.0;
742         double cen_y = (double)(h - 1) / 2.0;
743         double radius = MAX(w, h);
744         double radius3 = plugin->config.radius / MAXRADIUS;
745         double radius2 = radius * radius * radius3;
746         double scale_x;
747         double scale_y;
748
749
750 //printf("WhirlUnit::process_package 1 %f %f %f\n",
751 //      plugin->config.angle, plugin->config.pinch, plugin->config.radius);
752         if(w < h)
753         {
754         scale_x = (double)h / w;
755         scale_y = 1.0;
756         }
757         else
758         if(w > h)
759         {
760         scale_x = 1.0;
761         scale_y = (double)w / h;
762         }
763         else
764         {
765         scale_x = 1.0;
766         scale_y = 1.0;
767         }
768
769
770
771         switch(plugin->input->get_color_model())
772         {
773                 case BC_RGB_FLOAT:
774                         WHIRL_MACRO(float, 1, 3);
775                         break;
776                 case BC_RGB888:
777                 case BC_YUV888:
778                         WHIRL_MACRO(unsigned char, 0xff, 3);
779                         break;
780                 case BC_RGBA_FLOAT:
781                         WHIRL_MACRO(float, 1, 4);
782                         break;
783                 case BC_RGBA8888:
784                 case BC_YUVA8888:
785                         WHIRL_MACRO(unsigned char, 0xff, 4);
786                         break;
787                 case BC_RGB161616:
788                 case BC_YUV161616:
789                         WHIRL_MACRO(uint16_t, 0xffff, 3);
790                         break;
791                 case BC_RGBA16161616:
792                 case BC_YUVA16161616:
793                         WHIRL_MACRO(uint16_t, 0xffff, 4);
794                         break;
795
796         }
797 }
798
799
800
801
802
803
804
805 WhirlEngine::WhirlEngine(WhirlEffect *plugin, int cpus)
806 // : LoadServer(1, 1)
807  : LoadServer(cpus, cpus)
808 {
809         this->plugin = plugin;
810 }
811 void WhirlEngine::init_packages()
812 {
813         for(int i = 0; i < LoadServer::get_total_packages(); i++)
814         {
815                 WhirlPackage *pkg = (WhirlPackage*)get_package(i);
816                 pkg->row1 = plugin->input->get_h() * i / LoadServer::get_total_packages();
817                 pkg->row2 = plugin->input->get_h() * (i + 1) / LoadServer::get_total_packages();
818         }
819
820 }
821
822 LoadClient* WhirlEngine::new_client()
823 {
824         return new WhirlUnit(plugin, this);
825 }
826
827 LoadPackage* WhirlEngine::new_package()
828 {
829         return new WhirlPackage;
830 }
831