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