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"
57 void copy_from(WhirlConfig &src);
58 int equivalent(WhirlConfig &src);
59 void interpolate(WhirlConfig &prev,
71 class WhirlAngle : public BC_FSlider
74 WhirlAngle(WhirlEffect *plugin, int x, int y);
81 class WhirlPinch : public BC_FSlider
84 WhirlPinch(WhirlEffect *plugin, int x, int y);
91 class WhirlRadius : public BC_FSlider
94 WhirlRadius(WhirlEffect *plugin, int x, int y);
100 class WhirlReset : public BC_GenericButton
103 WhirlReset(WhirlEffect *plugin, WhirlWindow *window, int x, int y);
111 class WhirlWindow : public PluginClientWindow
114 WhirlWindow(WhirlEffect *plugin);
115 void create_objects();
128 class WhirlPackage : public LoadPackage
135 class WhirlUnit : public LoadClient
138 WhirlUnit(WhirlEffect *plugin, WhirlEngine *server);
139 void process_package(LoadPackage *package);
146 class WhirlEngine : public LoadServer
149 WhirlEngine(WhirlEffect *plugin, int cpus);
150 void init_packages();
151 LoadClient* new_client();
152 LoadPackage* new_package();
158 class WhirlEffect : public PluginVClient
161 WhirlEffect(PluginServer *server);
164 PLUGIN_CLASS_MEMBERS(WhirlConfig)
165 int process_realtime(VFrame *input, VFrame *output);
168 void save_data(KeyFrame *keyframe);
169 void read_data(KeyFrame *keyframe);
173 VFrame *input, *output;
174 int need_reconfigure;
182 REGISTER_PLUGIN(WhirlEffect)
197 WhirlConfig::WhirlConfig()
202 void WhirlConfig::reset()
204 angle = 180.0; // 0.0;
205 pinch = 10.0; // 0.0;
206 radius = 50.0; // 0.0;
209 void WhirlConfig::copy_from(WhirlConfig &src)
211 this->angle = src.angle;
212 this->pinch = src.pinch;
213 this->radius = src.radius;
216 int WhirlConfig::equivalent(WhirlConfig &src)
218 return EQUIV(this->angle, src.angle) &&
219 EQUIV(this->pinch, src.pinch) &&
220 EQUIV(this->radius, src.radius);
223 void WhirlConfig::interpolate(WhirlConfig &prev,
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);
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;
246 WhirlWindow::WhirlWindow(WhirlEffect *plugin)
247 : PluginClientWindow(plugin,
254 this->plugin = plugin;
259 void WhirlWindow::create_objects()
262 add_subwindow(new BC_Title(x, y, _("Radius")));
264 add_subwindow(radius = new WhirlRadius(plugin, x, y));
266 add_subwindow(new BC_Title(x, y, _("Pinch")));
268 add_subwindow(pinch = new WhirlPinch(plugin, x, y));
270 add_subwindow(new BC_Title(x, y, _("Angle")));
272 add_subwindow(angle = new WhirlAngle(plugin, x, y));
274 add_subwindow(reset = new WhirlReset(plugin, this, x, y));
282 void WhirlWindow::update()
284 radius->update(plugin->config.radius);
285 pinch->update(plugin->config.pinch);
286 angle->update(plugin->config.angle);
299 WhirlAngle::WhirlAngle(WhirlEffect *plugin, int x, int y)
307 plugin->config.angle)
309 this->plugin = plugin;
311 int WhirlAngle::handle_event()
313 plugin->config.angle = get_value();
314 plugin->send_configure_change();
321 WhirlPinch::WhirlPinch(WhirlEffect *plugin, int x, int y)
329 plugin->config.pinch)
331 this->plugin = plugin;
333 int WhirlPinch::handle_event()
335 plugin->config.pinch = get_value();
336 plugin->send_configure_change();
343 WhirlRadius::WhirlRadius(WhirlEffect *plugin, int x, int y)
351 plugin->config.radius)
353 this->plugin = plugin;
355 int WhirlRadius::handle_event()
357 plugin->config.radius = get_value();
358 plugin->send_configure_change();
364 WhirlReset::WhirlReset(WhirlEffect *plugin, WhirlWindow *window, int x, int y)
365 : BC_GenericButton(x, y, _("Reset"))
367 this->plugin = plugin;
368 this->window = window;
370 WhirlReset::~WhirlReset()
373 int WhirlReset::handle_event()
375 plugin->config.reset();
377 plugin->send_configure_change();
389 WhirlEffect::WhirlEffect(PluginServer *server)
390 : PluginVClient(server)
392 need_reconfigure = 1;
398 WhirlEffect::~WhirlEffect()
401 if(engine) delete engine;
402 if(temp_frame) delete temp_frame;
410 const char* WhirlEffect::plugin_title() { return N_("Whirl"); }
411 int WhirlEffect::is_realtime() { return 1; }
415 NEW_WINDOW_MACRO(WhirlEffect, WhirlWindow)
418 void WhirlEffect::update_gui()
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();
431 LOAD_CONFIGURATION_MACRO(WhirlEffect, WhirlConfig)
436 void WhirlEffect::save_data(KeyFrame *keyframe)
440 // cause data to be stored directly in text
441 output.set_shared_output(keyframe->xbuf);
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);
448 output.tag.set_title("/WHIRL");
450 output.append_newline();
451 output.terminate_string();
452 // data is now in *text
455 void WhirlEffect::read_data(KeyFrame *keyframe)
459 input.set_shared_input(keyframe->xbuf);
465 result = input.read_tag();
469 if(input.tag.title_is("WHIRL"))
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);
479 int WhirlEffect::process_realtime(VFrame *input, VFrame *output)
481 need_reconfigure |= load_configuration();
483 this->output = output;
485 if(EQUIV(config.angle, 0) ||
486 (EQUIV(config.radius, 0) && EQUIV(config.pinch, 0)))
488 if(input->get_rows()[0] != output->get_rows()[0])
489 output->copy_from(input);
493 if(input->get_rows()[0] == output->get_rows()[0])
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;
501 if(!engine) engine = new WhirlEngine(this, PluginClient::smp + 1);
503 engine->process_packages();
515 WhirlPackage::WhirlPackage()
522 WhirlUnit::WhirlUnit(WhirlEffect *plugin, WhirlEngine *server)
525 this->plugin = plugin;
530 static int calc_undistorted_coords(double cen_x,
547 double ang, sina, cosa;
550 /* Distances to center, scaled */
552 dx = (wx - cen_x) * scale_x;
553 dy = (wy - cen_y) * scale_y;
555 /* Distance^2 to center of *circle* (scaled ellipse) */
557 d = dx * dx + dy * dy;
559 /* If we are inside circle, then distort.
560 * Else, just return the same position
563 inside = (d < radius2);
567 dist = sqrt(d / radius3) / radius;
571 factor = pow(sin(M_PI / 2 * dist), -pinch);
580 ang = whirl * factor * factor;
585 x = (cosa * dx - sina * dy) / scale_x + cen_x;
586 y = (sina * dx + cosa * dy) / scale_y + cen_y;
594 #define GET_PIXEL(components, x, y, input_rows) \
595 input_rows[CLIP(y, 0, (h - 1))] + components * CLIP(x, 0, (w - 1))
602 static float bilinear(double x, double y, double *values)
608 if(x < 0.0) x += 1.0;
609 if(y < 0.0) y += 1.0;
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);
620 #define WHIRL_MACRO(type, max, components) \
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++) \
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; \
637 for(int col = 0; col < w; col++) \
639 if(calc_undistorted_coords(cen_x, \
653 /* Inside distortion area */ \
658 ix = -((int)-cx + 1); \
663 iy = -((int)-cy + 1); \
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); \
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); \
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); \
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); \
688 if(components == 4) \
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); \
697 top_p += components; \
700 cx = cen_x + (cen_x - cx); \
701 cy = cen_y + (cen_y - cy); \
706 ix = -((int)-cx + 1); \
711 iy = -((int)-cy + 1); \
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); \
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); \
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); \
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); \
738 if(components == 4) \
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); \
747 bot_p -= components; \
753 /* Outside distortion area */ \
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]; \
761 top_p += components; \
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; \
776 void WhirlUnit::process_package(LoadPackage *package)
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;
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;
794 //printf("WhirlUnit::process_package 1 %f %f %f\n",
795 // plugin->config.angle, plugin->config.pinch, plugin->config.radius);
798 scale_x = (double)h / w;
805 scale_y = (double)w / h;
815 switch(plugin->input->get_color_model())
818 WHIRL_MACRO(float, 1, 3);
822 WHIRL_MACRO(unsigned char, 0xff, 3);
825 WHIRL_MACRO(float, 1, 4);
829 WHIRL_MACRO(unsigned char, 0xff, 4);
833 WHIRL_MACRO(uint16_t, 0xffff, 3);
835 case BC_RGBA16161616:
836 case BC_YUVA16161616:
837 WHIRL_MACRO(uint16_t, 0xffff, 4);
849 WhirlEngine::WhirlEngine(WhirlEffect *plugin, int cpus)
850 // : LoadServer(1, 1)
851 : LoadServer(cpus, cpus)
853 this->plugin = plugin;
855 void WhirlEngine::init_packages()
857 for(int i = 0; i < LoadServer::get_total_packages(); i++)
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();
866 LoadClient* WhirlEngine::new_client()
868 return new WhirlUnit(plugin, this);
871 LoadPackage* WhirlEngine::new_package()
873 return new WhirlPackage;