4 * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
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.
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.
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
22 #include "bcdisplayinfo.h"
29 #include "loadbalance.h"
30 #include "pluginvclient.h"
43 class WhirlDefaultSettings;
49 #define RESET_DEFAULT_SETTINGS 10
51 #define RESET_RADIUS 1
60 void reset(int clear);
62 void copy_from(WhirlConfig &src);
63 int equivalent(WhirlConfig &src);
64 void interpolate(WhirlConfig &prev,
76 class WhirlAngle : public BC_FSlider
79 WhirlAngle(WhirlEffect *plugin, int x, int y);
86 class WhirlPinch : public BC_FSlider
89 WhirlPinch(WhirlEffect *plugin, int x, int y);
96 class WhirlRadius : public BC_FSlider
99 WhirlRadius(WhirlEffect *plugin, int x, int y);
105 class WhirlReset : public BC_GenericButton
108 WhirlReset(WhirlEffect *plugin, WhirlWindow *window, int x, int y);
115 class WhirlDefaultSettings : public BC_GenericButton
118 WhirlDefaultSettings(WhirlEffect *plugin, WhirlWindow *window, int x, int y, int w);
119 ~WhirlDefaultSettings();
125 class WhirlSliderClr : public BC_Button
128 WhirlSliderClr(WhirlEffect *plugin, WhirlWindow *window, int x, int y, int w, int clear);
138 class WhirlWindow : public PluginClientWindow
141 WhirlWindow(WhirlEffect *plugin);
142 void create_objects();
143 void update_gui(int clear);
149 WhirlDefaultSettings *default_settings;
150 WhirlSliderClr *radiusClr, *pinchClr, *angleClr;
157 class WhirlPackage : public LoadPackage
164 class WhirlUnit : public LoadClient
167 WhirlUnit(WhirlEffect *plugin, WhirlEngine *server);
168 void process_package(LoadPackage *package);
175 class WhirlEngine : public LoadServer
178 WhirlEngine(WhirlEffect *plugin, int cpus);
179 void init_packages();
180 LoadClient* new_client();
181 LoadPackage* new_package();
187 class WhirlEffect : public PluginVClient
190 WhirlEffect(PluginServer *server);
193 PLUGIN_CLASS_MEMBERS(WhirlConfig)
194 int process_realtime(VFrame *input, VFrame *output);
197 void save_data(KeyFrame *keyframe);
198 void read_data(KeyFrame *keyframe);
202 VFrame *input, *output;
203 int need_reconfigure;
211 REGISTER_PLUGIN(WhirlEffect)
226 WhirlConfig::WhirlConfig()
231 void WhirlConfig::reset(int clear)
239 case RESET_RADIUS : radius = 0.0;
241 case RESET_PINCH : pinch = 0.0;
243 case RESET_ANGLE : angle = 0.0;
245 case RESET_DEFAULT_SETTINGS :
254 void WhirlConfig::copy_from(WhirlConfig &src)
256 this->angle = src.angle;
257 this->pinch = src.pinch;
258 this->radius = src.radius;
261 int WhirlConfig::equivalent(WhirlConfig &src)
263 return EQUIV(this->angle, src.angle) &&
264 EQUIV(this->pinch, src.pinch) &&
265 EQUIV(this->radius, src.radius);
268 void WhirlConfig::interpolate(WhirlConfig &prev,
274 double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
275 double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
277 this->angle = prev.angle * prev_scale + next.angle * next_scale;
278 this->pinch = prev.pinch * prev_scale + next.pinch * next_scale;
279 this->radius = prev.radius * prev_scale + next.radius * next_scale;
291 WhirlWindow::WhirlWindow(WhirlEffect *plugin)
292 : PluginClientWindow(plugin, xS(280), yS(195), xS(280), yS(195), 0)
294 this->plugin = plugin;
299 void WhirlWindow::create_objects()
301 int xs10 = xS(10), xs50 = xS(50), xs100 = xS(100);
302 int ys10 = yS(10), ys20 = yS(20), ys30 = yS(30), ys35 = yS(35);
303 int x = xs10, y = ys10;
304 int x1 = 0; int clrBtn_w = xs50;
305 int defaultBtn_w = xs100;
307 add_subwindow(new BC_Title(x, y, _("Radius")));
309 add_subwindow(radius = new WhirlRadius(plugin, x, y));
310 x1 = x + radius->get_w() + xs10;
311 add_subwindow(radiusClr = new WhirlSliderClr(plugin, this, x1, y, clrBtn_w, RESET_RADIUS));
314 add_subwindow(new BC_Title(x, y, _("Pinch")));
316 add_subwindow(pinch = new WhirlPinch(plugin, x, y));
317 add_subwindow(pinchClr = new WhirlSliderClr(plugin, this, x1, y, clrBtn_w, RESET_PINCH));
320 add_subwindow(new BC_Title(x, y, _("Angle")));
322 add_subwindow(angle = new WhirlAngle(plugin, x, y));
323 add_subwindow(angleClr = new WhirlSliderClr(plugin, this, x1, y, clrBtn_w, RESET_ANGLE));
326 add_subwindow(reset = new WhirlReset(plugin, this, x, y));
327 add_subwindow(default_settings = new WhirlDefaultSettings(plugin, this,
328 (xS(280) - xs10 - defaultBtn_w), y, defaultBtn_w));
335 void WhirlWindow::update_gui(int clear)
338 case RESET_RADIUS : radius->update(plugin->config.radius);
340 case RESET_PINCH : pinch->update(plugin->config.pinch);
342 case RESET_ANGLE : angle->update(plugin->config.angle);
345 case RESET_DEFAULT_SETTINGS :
347 radius->update(plugin->config.radius);
348 pinch->update(plugin->config.pinch);
349 angle->update(plugin->config.angle);
364 WhirlAngle::WhirlAngle(WhirlEffect *plugin, int x, int y)
372 plugin->config.angle)
374 this->plugin = plugin;
376 int WhirlAngle::handle_event()
378 plugin->config.angle = get_value();
379 plugin->send_configure_change();
386 WhirlPinch::WhirlPinch(WhirlEffect *plugin, int x, int y)
394 plugin->config.pinch)
396 this->plugin = plugin;
398 int WhirlPinch::handle_event()
400 plugin->config.pinch = get_value();
401 plugin->send_configure_change();
408 WhirlRadius::WhirlRadius(WhirlEffect *plugin, int x, int y)
416 plugin->config.radius)
418 this->plugin = plugin;
420 int WhirlRadius::handle_event()
422 plugin->config.radius = get_value();
423 plugin->send_configure_change();
429 WhirlReset::WhirlReset(WhirlEffect *plugin, WhirlWindow *window, int x, int y)
430 : BC_GenericButton(x, y, _("Reset"))
432 this->plugin = plugin;
433 this->window = window;
435 WhirlReset::~WhirlReset()
438 int WhirlReset::handle_event()
440 plugin->config.reset(RESET_ALL);
441 window->update_gui(RESET_ALL);
442 plugin->send_configure_change();
446 WhirlDefaultSettings::WhirlDefaultSettings(WhirlEffect *plugin, WhirlWindow *window, int x, int y, int w)
447 : BC_GenericButton(x, y, w, _("Default"))
449 this->plugin = plugin;
450 this->window = window;
452 WhirlDefaultSettings::~WhirlDefaultSettings()
455 int WhirlDefaultSettings::handle_event()
457 plugin->config.reset(RESET_DEFAULT_SETTINGS);
458 window->update_gui(RESET_DEFAULT_SETTINGS);
459 plugin->send_configure_change();
463 WhirlSliderClr::WhirlSliderClr(WhirlEffect *plugin, WhirlWindow *window, int x, int y, int w, int clear)
464 : BC_Button(x, y, w, plugin->get_theme()->get_image_set("reset_button"))
466 this->plugin = plugin;
467 this->window = window;
470 WhirlSliderClr::~WhirlSliderClr()
473 int WhirlSliderClr::handle_event()
475 // clear==1 ==> Radius slider
476 // clear==2 ==> Pinch slider
477 // clear==3 ==> Angle slider
478 plugin->config.reset(clear);
479 window->update_gui(clear);
480 plugin->send_configure_change();
488 WhirlEffect::WhirlEffect(PluginServer *server)
489 : PluginVClient(server)
491 need_reconfigure = 1;
497 WhirlEffect::~WhirlEffect()
500 if(engine) delete engine;
501 if(temp_frame) delete temp_frame;
509 const char* WhirlEffect::plugin_title() { return N_("Whirl"); }
510 int WhirlEffect::is_realtime() { return 1; }
514 NEW_WINDOW_MACRO(WhirlEffect, WhirlWindow)
517 void WhirlEffect::update_gui()
521 load_configuration();
522 thread->window->lock_window();
523 ((WhirlWindow*)thread->window)->angle->update(config.angle);
524 ((WhirlWindow*)thread->window)->pinch->update(config.pinch);
525 ((WhirlWindow*)thread->window)->radius->update(config.radius);
526 thread->window->unlock_window();
530 LOAD_CONFIGURATION_MACRO(WhirlEffect, WhirlConfig)
535 void WhirlEffect::save_data(KeyFrame *keyframe)
539 // cause data to be stored directly in text
540 output.set_shared_output(keyframe->xbuf);
542 output.tag.set_title("WHIRL");
543 output.tag.set_property("ANGLE", config.angle);
544 output.tag.set_property("PINCH", config.pinch);
545 output.tag.set_property("RADIUS", config.radius);
547 output.tag.set_title("/WHIRL");
549 output.append_newline();
550 output.terminate_string();
551 // data is now in *text
554 void WhirlEffect::read_data(KeyFrame *keyframe)
558 input.set_shared_input(keyframe->xbuf);
564 result = input.read_tag();
568 if(input.tag.title_is("WHIRL"))
570 config.angle = input.tag.get_property("ANGLE", config.angle);
571 config.pinch = input.tag.get_property("PINCH", config.pinch);
572 config.radius = input.tag.get_property("RADIUS", config.radius);
578 int WhirlEffect::process_realtime(VFrame *input, VFrame *output)
580 need_reconfigure |= load_configuration();
582 this->output = output;
584 if(EQUIV(config.angle, 0) ||
585 (EQUIV(config.radius, 0) && EQUIV(config.pinch, 0)))
587 if(input->get_rows()[0] != output->get_rows()[0])
588 output->copy_from(input);
592 if(input->get_rows()[0] == output->get_rows()[0])
594 if(!temp_frame) temp_frame = new VFrame(input->get_w(), input->get_h(),
595 input->get_color_model(), 0);
596 temp_frame->copy_from(input);
597 this->input = temp_frame;
600 if(!engine) engine = new WhirlEngine(this, PluginClient::smp + 1);
602 engine->process_packages();
614 WhirlPackage::WhirlPackage()
621 WhirlUnit::WhirlUnit(WhirlEffect *plugin, WhirlEngine *server)
624 this->plugin = plugin;
629 static int calc_undistorted_coords(double cen_x,
646 double ang, sina, cosa;
649 /* Distances to center, scaled */
651 dx = (wx - cen_x) * scale_x;
652 dy = (wy - cen_y) * scale_y;
654 /* Distance^2 to center of *circle* (scaled ellipse) */
656 d = dx * dx + dy * dy;
658 /* If we are inside circle, then distort.
659 * Else, just return the same position
662 inside = (d < radius2);
666 dist = sqrt(d / radius3) / radius;
670 factor = pow(sin(M_PI / 2 * dist), -pinch);
679 ang = whirl * factor * factor;
684 x = (cosa * dx - sina * dy) / scale_x + cen_x;
685 y = (sina * dx + cosa * dy) / scale_y + cen_y;
693 #define GET_PIXEL(components, x, y, input_rows) \
694 input_rows[CLIP(y, 0, (h - 1))] + components * CLIP(x, 0, (w - 1))
701 static float bilinear(double x, double y, double *values)
707 if(x < 0.0) x += 1.0;
708 if(y < 0.0) y += 1.0;
710 m0 = (double)values[0] + x * ((double)values[1] - values[0]);
711 m1 = (double)values[2] + x * ((double)values[3] - values[2]);
712 return m0 + y * (m1 - m0);
719 #define WHIRL_MACRO(type, max, components) \
721 type **input_rows = (type**)plugin->input->get_rows(); \
722 /* Compiler error requires separate arrays */ \
723 double top_values[4], bot_values[4]; \
724 for( int i=0; i<4; ++i ) top_values[i] = bot_values[i] = 0; \
725 for(int row = pkg->row1 / 2; row <= (pkg->row2 + pkg->row1) / 2; row++) \
727 type *top_row = (type*)plugin->output->get_rows()[row]; \
728 type *bot_row = (type*)plugin->output->get_rows()[h - row - 1]; \
729 type *top_p = top_row; \
730 type *bot_p = bot_row + components * w - components; \
736 for(int col = 0; col < w; col++) \
738 if(calc_undistorted_coords(cen_x, \
752 /* Inside distortion area */ \
757 ix = -((int)-cx + 1); \
762 iy = -((int)-cy + 1); \
764 pixel1 = GET_PIXEL(components, ix, iy, input_rows); \
765 pixel2 = GET_PIXEL(components, ix + 1, iy, input_rows); \
766 pixel3 = GET_PIXEL(components, ix, iy + 1, input_rows); \
767 pixel4 = GET_PIXEL(components, ix + 1, iy + 1, input_rows); \
769 top_values[0] = pixel1[0]; \
770 top_values[1] = pixel2[0]; \
771 top_values[2] = pixel3[0]; \
772 top_values[3] = pixel4[0]; \
773 top_p[0] = (type)bilinear(cx, cy, top_values); \
775 top_values[0] = pixel1[1]; \
776 top_values[1] = pixel2[1]; \
777 top_values[2] = pixel3[1]; \
778 top_values[3] = pixel4[1]; \
779 top_p[1] = (type)bilinear(cx, cy, top_values); \
781 top_values[0] = pixel1[2]; \
782 top_values[1] = pixel2[2]; \
783 top_values[2] = pixel3[2]; \
784 top_values[3] = pixel4[2]; \
785 top_p[2] = (type)bilinear(cx, cy, top_values); \
787 if(components == 4) \
789 top_values[0] = pixel1[3]; \
790 top_values[1] = pixel2[3]; \
791 top_values[2] = pixel3[3]; \
792 top_values[3] = pixel4[3]; \
793 top_p[3] = (type)bilinear(cx, cy, top_values); \
796 top_p += components; \
799 cx = cen_x + (cen_x - cx); \
800 cy = cen_y + (cen_y - cy); \
805 ix = -((int)-cx + 1); \
810 iy = -((int)-cy + 1); \
812 pixel1 = GET_PIXEL(components, ix, iy, input_rows); \
813 pixel2 = GET_PIXEL(components, ix + 1, iy, input_rows); \
814 pixel3 = GET_PIXEL(components, ix, iy + 1, input_rows); \
815 pixel4 = GET_PIXEL(components, ix + 1, iy + 1, input_rows); \
819 bot_values[0] = pixel1[0]; \
820 bot_values[1] = pixel2[0]; \
821 bot_values[2] = pixel3[0]; \
822 bot_values[3] = pixel4[0]; \
823 bot_p[0] = (type)bilinear(cx, cy, bot_values); \
825 bot_values[0] = pixel1[1]; \
826 bot_values[1] = pixel2[1]; \
827 bot_values[2] = pixel3[1]; \
828 bot_values[3] = pixel4[1]; \
829 bot_p[1] = (type)bilinear(cx, cy, bot_values); \
831 bot_values[0] = pixel1[2]; \
832 bot_values[1] = pixel2[2]; \
833 bot_values[2] = pixel3[2]; \
834 bot_values[3] = pixel4[2]; \
835 bot_p[2] = (type)bilinear(cx, cy, bot_values); \
837 if(components == 4) \
839 bot_values[0] = pixel1[3]; \
840 bot_values[1] = pixel2[3]; \
841 bot_values[2] = pixel3[3]; \
842 bot_values[3] = pixel4[3]; \
843 bot_p[3] = (type)bilinear(cx, cy, bot_values); \
846 bot_p -= components; \
852 /* Outside distortion area */ \
854 top_p[0] = input_rows[row][components * col + 0]; \
855 top_p[1] = input_rows[row][components * col + 1]; \
856 top_p[2] = input_rows[row][components * col + 2]; \
857 if(components == 4) top_p[3] = input_rows[row][components * col + 3]; \
860 top_p += components; \
863 int bot_offset = w * components - col * components - components; \
864 int bot_row = h - 1 - row; \
865 bot_p[0] = input_rows[bot_row][bot_offset + 0]; \
866 bot_p[1] = input_rows[bot_row][bot_offset + 1]; \
867 bot_p[2] = input_rows[bot_row][bot_offset + 2]; \
868 if(components == 4) bot_p[3] = input_rows[bot_row][bot_offset + 3]; \
869 bot_p -= components; \
875 void WhirlUnit::process_package(LoadPackage *package)
877 WhirlPackage *pkg = (WhirlPackage*)package;
878 int w = plugin->input->get_w();
879 int h = plugin->input->get_h();
880 double whirl = plugin->config.angle * M_PI / 180;
881 double pinch = plugin->config.pinch / MAXPINCH;
884 double cen_x = (double)(w-1) / 2.0;
885 double cen_y = (double)(h-1) / 2.0;
886 double radius = MAX(w, h);
887 double radius3 = plugin->config.radius / MAXRADIUS;
888 double radius2 = radius * radius * radius3;
893 //printf("WhirlUnit::process_package 1 %f %f %f\n",
894 // plugin->config.angle, plugin->config.pinch, plugin->config.radius);
897 scale_x = (double)h / w;
904 scale_y = (double)w / h;
914 switch(plugin->input->get_color_model())
917 WHIRL_MACRO(float, 1, 3);
921 WHIRL_MACRO(unsigned char, 0xff, 3);
924 WHIRL_MACRO(float, 1, 4);
928 WHIRL_MACRO(unsigned char, 0xff, 4);
932 WHIRL_MACRO(uint16_t, 0xffff, 3);
934 case BC_RGBA16161616:
935 case BC_YUVA16161616:
936 WHIRL_MACRO(uint16_t, 0xffff, 4);
948 WhirlEngine::WhirlEngine(WhirlEffect *plugin, int cpus)
949 // : LoadServer(1, 1)
950 : LoadServer(cpus, cpus)
952 this->plugin = plugin;
954 void WhirlEngine::init_packages()
956 for(int i = 0; i < LoadServer::get_total_packages(); i++)
958 WhirlPackage *pkg = (WhirlPackage*)get_package(i);
959 pkg->row1 = plugin->input->get_h() * i / LoadServer::get_total_packages();
960 pkg->row2 = plugin->input->get_h() * (i + 1) / LoadServer::get_total_packages();
965 LoadClient* WhirlEngine::new_client()
967 return new WhirlUnit(plugin, this);
970 LoadPackage* WhirlEngine::new_package()
972 return new WhirlPackage;