--- /dev/null
+
+/*
+ * CINELERRA
+ * Copyright (C) 2017 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "affine.h"
+#include "bcdisplayinfo.h"
+#include "bchash.h"
+#include "bcsignals.h"
+#include "clip.h"
+#include "filexml.h"
+#include "language.h"
+#include "spherecam.h"
+#include "theme.h"
+
+#include <string.h>
+
+// largely based on equations from http://paulbourke.net/dome/fish2/
+
+REGISTER_PLUGIN(SphereCamMain)
+
+
+SphereCamConfig::SphereCamConfig()
+{
+ feather = 0;
+
+ for( int i = 0; i < EYES; i++ ) {
+ enabled[i] = 1;
+ fov[i] = 180;
+ radius[i] = 100.0;
+ center_y[i] = 50.0;
+ rotate_y[i] = 50.0;
+ rotate_z[i] = 0;
+ }
+
+ center_x[0] = 25.0;
+ center_x[1] = 75.0;
+ rotate_x[0] = 50.0;
+ rotate_x[1] = 100.0;
+ draw_guides = 1;
+ mode = SphereCamConfig::DO_NOTHING;
+}
+
+int SphereCamConfig::equivalent(SphereCamConfig &that)
+{
+ for( int i = 0; i < EYES; i++ ) {
+ if( enabled[i] != that.enabled[i] ||
+ !EQUIV(fov[i], that.fov[i]) ||
+ !EQUIV(radius[i], that.radius[i]) ||
+ !EQUIV(center_x[i], that.center_x[i]) ||
+ !EQUIV(center_y[i], that.center_y[i]) ||
+ !EQUIV(rotate_x[i], that.rotate_x[i]) ||
+ !EQUIV(rotate_y[i], that.rotate_y[i]) ||
+ !EQUIV(rotate_z[i], that.rotate_z[i]) ) {
+ return 0;
+ }
+ }
+
+ if( feather != that.feather || mode != that.mode ||
+ draw_guides != that.draw_guides ) {
+ return 0;
+ }
+
+
+ return 1;
+}
+
+void SphereCamConfig::copy_from(SphereCamConfig &that)
+{
+ *this = that;
+}
+
+void SphereCamConfig::interpolate(SphereCamConfig &prev,
+ SphereCamConfig &next,
+ int64_t prev_frame,
+ int64_t next_frame,
+ int64_t current_frame)
+{
+ double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
+ double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
+
+ for( int i = 0; i < EYES; i++ ) {
+ enabled[i] = prev.enabled[i];
+ fov[i] = prev.fov[i] * prev_scale + next.fov[i] * next_scale;
+ radius[i] = prev.radius[i] * prev_scale + next.radius[i] * next_scale;
+ center_x[i] = prev.center_x[i] * prev_scale + next.center_x[i] * next_scale;
+ center_y[i] = prev.center_y[i] * prev_scale + next.center_y[i] * next_scale;
+ rotate_x[i] = prev.rotate_x[i] * prev_scale + next.rotate_x[i] * next_scale;
+ rotate_y[i] = prev.rotate_y[i] * prev_scale + next.rotate_y[i] * next_scale;
+ rotate_z[i] = prev.rotate_z[i] * prev_scale + next.rotate_z[i] * next_scale;
+ }
+
+ feather = prev.feather * prev_scale + next.feather * next_scale;
+ draw_guides = prev.draw_guides;
+ mode = prev.mode;
+
+ boundaries();
+}
+
+void SphereCamConfig::boundaries()
+{
+ for( int i = 0; i < EYES; i++ ) {
+ CLAMP(fov[i], 1.0, 359.0);
+ CLAMP(radius[i], 1.0, 150.0);
+ CLAMP(center_x[i], 0.0, 100.0);
+ CLAMP(center_y[i], 0.0, 100.0);
+ CLAMP(rotate_x[i], 0.0, 100.0);
+ CLAMP(rotate_y[i], 0.0, 100.0);
+ CLAMP(rotate_z[i], -180.0, 180.0);
+ }
+
+ CLAMP(feather, 0, 50);
+}
+
+
+SphereCamSlider::SphereCamSlider(SphereCamMain *client,
+ SphereCamGUI *gui,
+ SphereCamText *text,
+ float *output,
+ int x,
+ int y,
+ float min,
+ float max)
+ : BC_FSlider(x,
+ y,
+ 0,
+ gui->get_w() / 2 - client->get_theme()->widget_border * 3 - 100,
+ gui->get_w() / 2 - client->get_theme()->widget_border * 3 - 100,
+ min,
+ max,
+ *output)
+{
+ this->gui = gui;
+ this->client = client;
+ this->output = output;
+ this->text = text;
+ set_precision(0.01);
+}
+
+int SphereCamSlider::handle_event()
+{
+ *output = get_value();
+ text->update(*output);
+ client->send_configure_change();
+ return 1;
+}
+
+
+SphereCamText::SphereCamText(SphereCamMain *client,
+ SphereCamGUI *gui,
+ SphereCamSlider *slider,
+ float *output,
+ int x,
+ int y)
+ : BC_TextBox(x, y, 100, 1, *output)
+{
+ this->gui = gui;
+ this->client = client;
+ this->output = output;
+ this->slider = slider;
+}
+
+int SphereCamText::handle_event()
+{
+ *output = atof(get_text());
+ slider->update(*output);
+ client->send_configure_change();
+ return 1;
+}
+
+
+SphereCamToggle::SphereCamToggle(SphereCamMain *client,
+ int *output, int x, int y, const char *text)
+ : BC_CheckBox(x, y, *output, text)
+{
+ this->output = output;
+ this->client = client;
+}
+
+int SphereCamToggle::handle_event()
+{
+ *output = get_value();
+ client->send_configure_change();
+ return 1;
+}
+
+
+SphereCamMode::SphereCamMode(SphereCamMain *plugin,
+ SphereCamGUI *gui, int x, int y)
+ : BC_PopupMenu(x, y, calculate_w(gui), "", 1)
+{
+ this->plugin = plugin;
+ this->gui = gui;
+}
+
+int SphereCamMode::handle_event()
+{
+ plugin->config.mode = from_text(get_text());
+ plugin->send_configure_change();
+ return 1;
+
+}
+
+void SphereCamMode::create_objects()
+{
+ add_item(new BC_MenuItem(to_text(SphereCamConfig::DO_NOTHING)));
+ add_item(new BC_MenuItem(to_text(SphereCamConfig::EQUIRECT)));
+ add_item(new BC_MenuItem(to_text(SphereCamConfig::ALIGN)));
+ update(plugin->config.mode);
+}
+
+void SphereCamMode::update(int mode)
+{
+ char string[BCTEXTLEN];
+ sprintf(string, "%s", to_text(mode));
+ set_text(string);
+}
+
+int SphereCamMode::calculate_w(SphereCamGUI *gui)
+{
+ int result = 0;
+ result = MAX(result, gui->get_text_width(MEDIUMFONT, to_text(SphereCamConfig::EQUIRECT)));
+ result = MAX(result, gui->get_text_width(MEDIUMFONT, to_text(SphereCamConfig::DO_NOTHING)));
+ result = MAX(result, gui->get_text_width(MEDIUMFONT, to_text(SphereCamConfig::ALIGN)));
+ return result + 50;
+}
+
+int SphereCamMode::from_text(char *text)
+{
+ for( int i = 0; i < 3; i++ ) {
+ if( !strcmp(text, to_text(i)) ) {
+ return i;
+ }
+ }
+
+ return SphereCamConfig::EQUIRECT;
+}
+
+const char* SphereCamMode::to_text(int mode)
+{
+ switch( mode ) {
+ case SphereCamConfig::DO_NOTHING:
+ return "Do nothing";
+ break;
+ case SphereCamConfig::EQUIRECT:
+ return "Equirectangular";
+ break;
+ case SphereCamConfig::ALIGN:
+ return "Align only";
+ break;
+ }
+ return "Equirectangular";
+}
+
+
+SphereCamGUI::SphereCamGUI(SphereCamMain *client)
+ : PluginClientWindow(client, 640, 600, 640, 600, 0)
+{
+ this->client = client;
+}
+
+SphereCamGUI::~SphereCamGUI()
+{
+}
+
+
+void SphereCamGUI::create_objects()
+{
+ int margin = client->get_theme()->widget_border;
+ int margin2 = margin;
+ int y = margin;
+ int x[EYES];
+
+ x[0] = margin;
+ x[1] = get_w() / 2 + margin / 2;
+
+ BC_Title *title;
+
+ for( int i = 0; i < EYES; i++ ) {
+ int x3 = x[i];
+ y = margin;
+
+ add_tool(title = new BC_Title(x[i], y,
+ i == 0 ? _("Left Eye") : _("Right Eye"), LARGEFONT));
+ y += title->get_h() + margin2;
+
+ add_tool(enabled[i] = new SphereCamToggle(client,
+ &client->config.enabled[i], x3, y, _("Enabled")));
+ y += enabled[i]->get_h() + margin2;
+
+ add_tool(title = new BC_Title(x3, y, _("FOV:")));
+ y += title->get_h() + margin2;
+ add_tool(fov_slider[i] = new SphereCamSlider(client, this,
+ 0, &client->config.fov[i], x3, y, 1, 359));
+ fov_slider[i]->set_precision(0.1);
+ x3 += fov_slider[i]->get_w() + margin;
+ add_tool(fov_text[i] = new SphereCamText(client, this,
+ fov_slider[i], &client->config.fov[i], x3, y));
+ fov_slider[i]->text = fov_text[i];
+ y += fov_text[i]->get_h() + margin2;
+
+ x3 = x[i];
+ add_tool(title = new BC_Title(x3, y, _("Radius:")));
+ y += title->get_h() + margin2;
+ add_tool(radius_slider[i] = new SphereCamSlider(client, this,
+ 0, &client->config.radius[i], x3, y, 1, 150));
+ radius_slider[i]->set_precision(0.1);
+ x3 += radius_slider[i]->get_w() + margin;
+ add_tool(radius_text[i] = new SphereCamText(client, this,
+ radius_slider[i], &client->config.radius[i], x3, y));
+ radius_slider[i]->text = radius_text[i];
+ y += radius_text[i]->get_h() + margin2;
+
+ x3 = x[i];
+ add_tool(title = new BC_Title(x3, y, _("Input X:")));
+ y += title->get_h() + margin2;
+ add_tool(centerx_slider[i] = new SphereCamSlider(client, this,
+ 0, &client->config.center_x[i], x3, y, 0, 100));
+ centerx_slider[i]->set_precision(0.1);
+ x3 += centerx_slider[i]->get_w() + margin;
+ add_tool(centerx_text[i] = new SphereCamText(client, this,
+ centerx_slider[i], &client->config.center_x[i], x3, y));
+ centerx_slider[i]->text = centerx_text[i];
+ y += centerx_text[i]->get_h() + margin2;
+
+ x3 = x[i];
+ add_tool(title = new BC_Title(x3, y, _("Input Y:")));
+ y += title->get_h() + margin2;
+ add_tool(centery_slider[i] = new SphereCamSlider(client, this,
+ 0, &client->config.center_y[i], x3, y, 0, 100));
+ centery_slider[i]->set_precision(0.1);
+ x3 += centery_slider[i]->get_w() + margin;
+ add_tool(centery_text[i] = new SphereCamText(client, this,
+ centery_slider[i], &client->config.center_y[i], x3, y));
+ centery_slider[i]->text = centery_text[i];
+ y += centery_text[i]->get_h() + margin2;
+
+ x3 = x[i];
+ add_tool(title = new BC_Title(x3, y, _("Output X:")));
+ y += title->get_h() + margin2;
+ add_tool(rotatex_slider[i] = new SphereCamSlider(client, this,
+ 0, &client->config.rotate_x[i], x3, y, 0, 100));
+ rotatex_slider[i]->set_precision(0.1);
+ x3 += rotatex_slider[i]->get_w() + margin;
+ add_tool(rotatex_text[i] = new SphereCamText(client, this,
+ rotatex_slider[i], &client->config.rotate_x[i], x3, y));
+ rotatex_slider[i]->text = rotatex_text[i];
+ y += rotatex_text[i]->get_h() + margin2;
+
+ x3 = x[i];
+ add_tool(title = new BC_Title(x3, y, _("Output Y:")));
+ y += title->get_h() + margin2;
+ add_tool(rotatey_slider[i] = new SphereCamSlider(client, this,
+ 0, &client->config.rotate_y[i], x3, y, 0, 100));
+ rotatey_slider[i]->set_precision(0.1);
+ x3 += rotatey_slider[i]->get_w() + margin;
+ add_tool(rotatey_text[i] = new SphereCamText(client, this,
+ rotatey_slider[i], &client->config.rotate_y[i], x3, y));
+ rotatey_slider[i]->text = rotatey_text[i];
+ y += rotatey_text[i]->get_h() + margin2;
+
+ x3 = x[i];
+ add_tool(title = new BC_Title(x3, y, _("Rotate:")));
+ y += title->get_h() + margin2;
+ add_tool(rotatez_slider[i] = new SphereCamSlider(client, this,
+ 0, &client->config.rotate_z[i], x3, y, -180, 180));
+ rotatez_slider[i]->set_precision(0.1);
+ x3 += rotatez_slider[i]->get_w() + margin;
+ add_tool(rotatez_text[i] = new SphereCamText(client, this,
+ rotatez_slider[i], &client->config.rotate_z[i], x3, y));
+ rotatez_slider[i]->text = rotatez_text[i];
+ y += rotatez_text[i]->get_h() + margin2;
+ }
+
+ int x3 = x[0];
+ add_tool(title = new BC_Title(x3, y, _("Feather:")));
+ y += title->get_h() + margin2;
+ add_tool(feather_slider = new SphereCamSlider(client, this,
+ 0, &client->config.feather, x3, y, 0, 100));
+ feather_slider->set_precision(0.1);
+ x3 += feather_slider->get_w() + margin;
+ add_tool(feather_text = new SphereCamText(client, this,
+ feather_slider, &client->config.feather, x3, y));
+ feather_slider->text = feather_text;
+ y += feather_text->get_h() + margin2;
+
+//printf("SphereCamGUI::create_objects %d %f\n", __LINE__, client->config.distance);
+
+ x3 = x[0];
+ add_tool(draw_guides = new SphereCamToggle(client,
+ &client->config.draw_guides, x3, y, _("Draw guides")));
+ y += draw_guides->get_h() + margin2;
+
+ add_tool(title = new BC_Title(x3, y, _("Mode:")));
+ add_tool(mode = new SphereCamMode(client,
+ this,
+ x3 + title->get_w() + margin,
+ y));
+ mode->create_objects();
+ y += mode->get_h() + margin2;
+
+ show_window();
+ flush();
+}
+
+
+SphereCamMain::SphereCamMain(PluginServer *server)
+ : PluginVClient(server)
+{
+ engine = 0;
+ affine = 0;
+}
+
+SphereCamMain::~SphereCamMain()
+{
+ delete engine;
+ delete affine;
+}
+
+NEW_WINDOW_MACRO(SphereCamMain, SphereCamGUI)
+LOAD_CONFIGURATION_MACRO(SphereCamMain, SphereCamConfig)
+int SphereCamMain::is_realtime() { return 1; }
+const char* SphereCamMain::plugin_title() { return N_("Sphere Cam"); }
+
+void SphereCamMain::update_gui()
+{
+ if( !thread ) return;
+ if( !load_configuration() ) return;
+ ((SphereCamGUI*)thread->window)->lock_window("SphereCamMain::update_gui");
+ SphereCamGUI *window = (SphereCamGUI*)thread->window;
+
+ for( int i = 0; i < EYES; i++ ) {
+ window->enabled[i]->update(config.enabled[i]);
+
+ window->fov_slider[i]->update(config.fov[i]);
+ window->fov_text[i]->update(config.fov[i]);
+
+ window->radius_slider[i]->update(config.radius[i]);
+ window->radius_text[i]->update(config.radius[i]);
+
+ window->centerx_slider[i]->update(config.center_x[i]);
+ window->centerx_text[i]->update(config.center_x[i]);
+
+ window->centery_slider[i]->update(config.center_y[i]);
+ window->centery_text[i]->update(config.center_y[i]);
+
+ window->rotatex_slider[i]->update(config.rotate_x[i]);
+ window->rotatex_text[i]->update(config.rotate_x[i]);
+
+ window->rotatey_slider[i]->update(config.rotate_y[i]);
+ window->rotatey_text[i]->update(config.rotate_y[i]);
+
+ window->rotatez_slider[i]->update(config.rotate_z[i]);
+ window->rotatez_text[i]->update(config.rotate_z[i]);
+ }
+
+ window->feather_slider->update(config.feather);
+ window->feather_text->update(config.feather);
+
+ window->mode->update(config.mode);
+ window->draw_guides->update(config.draw_guides);
+ window->unlock_window();
+}
+
+void SphereCamMain::save_data(KeyFrame *keyframe)
+{
+ FileXML output;
+ char string[BCTEXTLEN];
+
+// cause data to be stored directly in text
+ output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
+ output.tag.set_title("SPHERECAM");
+
+ for( int i = 0; i < EYES; i++ ) {
+ sprintf(string, "ENABLED_%d", i);
+ output.tag.set_property(string, config.enabled[i]);
+ sprintf(string, "FOV_%d", i);
+ output.tag.set_property(string, config.fov[i]);
+ sprintf(string, "RADIUS_%d", i);
+ output.tag.set_property(string, config.radius[i]);
+ sprintf(string, "CENTER_X_%d", i);
+ output.tag.set_property(string, config.center_x[i]);
+ sprintf(string, "CENTER_Y_%d", i);
+ output.tag.set_property(string, config.center_y[i]);
+ sprintf(string, "ROTATE_X_%d", i);
+ output.tag.set_property(string, config.rotate_x[i]);
+ sprintf(string, "ROTATE_Y_%d", i);
+ output.tag.set_property(string, config.rotate_y[i]);
+ sprintf(string, "ROTATE_Z_%d", i);
+ output.tag.set_property(string, config.rotate_z[i]);
+ }
+
+ output.tag.set_property("FEATHER", config.feather);
+ output.tag.set_property("DRAW_GUIDES", config.draw_guides);
+ output.tag.set_property("MODE", config.mode);
+ output.append_tag();
+ output.terminate_string();
+}
+
+
+void SphereCamMain::read_data(KeyFrame *keyframe)
+{
+ FileXML input;
+ char string[BCTEXTLEN];
+
+
+ input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
+
+ int result = 0;
+
+ while(!result)
+ {
+ result = input.read_tag();
+
+ if( !result ) {
+ if( input.tag.title_is("SPHERECAM") ) {
+ for( int i = 0; i < EYES; i++ ) {
+ sprintf(string, "ENABLED_%d", i);
+ config.enabled[i] = input.tag.get_property(string, config.enabled[i]);
+ sprintf(string, "FOV_%d", i);
+ config.fov[i] = input.tag.get_property(string, config.fov[i]);
+ sprintf(string, "RADIUS_%d", i);
+ config.radius[i] = input.tag.get_property(string, config.radius[i]);
+ sprintf(string, "CENTER_X_%d", i);
+ config.center_x[i] = input.tag.get_property(string, config.center_x[i]);
+ sprintf(string, "CENTER_Y_%d", i);
+ config.center_y[i] = input.tag.get_property(string, config.center_y[i]);
+ sprintf(string, "ROTATE_X_%d", i);
+ config.rotate_x[i] = input.tag.get_property(string, config.rotate_x[i]);
+ sprintf(string, "ROTATE_Y_%d", i);
+ config.rotate_y[i] = input.tag.get_property(string, config.rotate_y[i]);
+ sprintf(string, "ROTATE_Z_%d", i);
+ config.rotate_z[i] = input.tag.get_property(string, config.rotate_z[i]);
+ }
+
+ config.feather = input.tag.get_property("FEATHER", config.feather);
+ config.draw_guides = input.tag.get_property("DRAW_GUIDES", config.draw_guides);
+ config.mode = input.tag.get_property("MODE", config.mode);
+ }
+ }
+ }
+}
+
+
+
+int SphereCamMain::process_buffer(VFrame *frame,
+ int64_t start_position,
+ double frame_rate)
+{
+ load_configuration();
+
+ VFrame *input = new_temp(frame->get_w(), frame->get_h(), frame->get_color_model());
+
+ read_frame(input, 0, start_position, frame_rate, 0); // use opengl
+
+// find_lenses();
+ calculate_extents();
+
+ if( config.mode == SphereCamConfig::DO_NOTHING ) {
+ get_output()->copy_from(input);
+ }
+ else {
+ get_output()->clear_frame();
+ if( !engine ) engine = new SphereCamEngine(this);
+ engine->process_packages();
+ }
+
+ if( config.draw_guides ) {
+// input regions
+// printf("SphereCamMain::process_buffer %d %d %d\n", __LINE__, out_x1[0], out_x4[0]);
+ for( int eye = 0; eye < EYES; eye++ ) {
+ if( config.enabled[eye] ) {
+// input regions
+ get_output()->draw_oval(input_x[eye] - radius[eye],
+ input_y[eye] - radius[eye],
+ input_x[eye] + radius[eye],
+ input_y[eye] + radius[eye]);
+
+
+// output regions. If they overlap, don't xor out the line
+ if( eye == 0 || (out_x1[eye] != out_x1[0] && out_x1[eye] != out_x4[0]) ) {
+ get_output()->draw_line(out_x1[eye], 0, out_x1[eye], h);
+ }
+
+ if( eye == 0 || (out_x4[eye] != out_x4[0] && out_x4[eye] != out_x1[0]) ) {
+ get_output()->draw_line(out_x4[eye], 0, out_x4[eye], h);
+ }
+
+ if( feather != 0 && eye == 1 ) {
+// crosshatch feather of right eye only, since only the right eye feathers
+ int feather_gap = h / 20;
+ for( int j = 0; j < h + feather; j += feather_gap ) {
+ draw_feather(out_x1[eye], out_x1[eye] + feather, eye, j);
+ draw_feather(out_x4[eye] - feather, out_x4[eye], eye, j);
+ }
+ }
+ }
+ }
+
+ }
+
+ return 0;
+}
+
+void SphereCamMain::draw_feather(int left, int right, int eye, int y)
+{
+ int slope = 1;
+ if( eye == 1 ) {
+ slope = -1;
+ }
+
+// wrap
+ if( left < 0 ) {
+ int left2 = w + left;
+ get_output()->draw_line(left2, y, left2 + right - left, y + feather * slope);
+ }
+
+ if( right > w ) {
+ int right2 = right - w;
+ get_output()->draw_line(0, y, right2, y + feather * slope);
+
+ }
+
+// proper
+ get_output()->draw_line(left, y, right, y + feather * slope);
+
+}
+
+
+// void SphereCamMain::find_lenses()
+// {
+// }
+
+
+void SphereCamMain::calculate_extents()
+{
+ w = get_output()->get_w();
+ h = get_output()->get_h();
+
+ feather = (int)(config.feather * w / 100);
+
+ for( int i = 0; i < EYES; i++ ) {
+// input regions
+ input_x[i] = (int)(config.center_x[i] * w / 100);
+ input_y[i] = (int)(config.center_y[i] * h / 100);
+ radius[i] = (int)(h / 2 * config.radius[i] / 100);
+
+// output regions
+ output_x[i] = (int)(config.rotate_x[i] * w / 100);
+ output_y[i] = (int)(config.rotate_y[i] * h / 100);
+
+
+// Assume each lens fills 1/2 the width
+ out_x1[i] = output_x[i] - w / 4 - feather / 2;
+ out_x2[i] = output_x[i];
+ out_x3[i] = output_x[i];
+ out_x4[i] = output_x[i] + w / 4 + feather / 2;
+ }
+
+// If the output isn't 50% apart, we have to expand the left eye to fill the feathering region
+//printf("SphereCamMain::calculate_extents %d %f\n", __LINE__, config.rotate_x[0] - config.rotate_x[1]);
+ float x_separation = config.rotate_x[0] - config.rotate_x[1];
+ if( !EQUIV(fabs(x_separation), 50) ) {
+ if( x_separation < -50 ) {
+ out_x4[0] += (-49.5 - x_separation) * w / 100;
+ }
+ else
+ if( x_separation < 0 ) {
+ out_x1[0] -= (x_separation + 50) * w / 100;
+ }
+ else
+ if( x_separation < 50 ) {
+ out_x4[0] += (50.5 - x_separation) * w / 100;
+ }
+ else {
+ out_x1[0] -= (x_separation - 49.5) * w / 100;
+ }
+ }
+
+
+// wrap around
+ for( int i = 0; i < EYES; i++ ) {
+ if( out_x1[i] < 0 ) {
+ out_x1[i] = w + out_x1[i];
+ out_x2[i] = w;
+ out_x3[i] = 0;
+ }
+
+ if( out_x4[i] > w ) {
+ out_x2[i] = w;
+ out_x3[i] = 0;
+ out_x4[i] -= w;
+ }
+
+// printf("SphereCamMain::calculate_extents %d i=%d x=%d y=%d radius=%d\n",
+// __LINE__,
+// i,
+// center_x[i],
+// center_y[i],
+// radius[i]);
+ }
+}
+
+SphereCamPackage::SphereCamPackage()
+ : LoadPackage() {}
+
+SphereCamUnit::SphereCamUnit(SphereCamEngine *engine, SphereCamMain *plugin)
+ : LoadClient(engine)
+{
+ this->plugin = plugin;
+}
+
+
+SphereCamUnit::~SphereCamUnit()
+{
+}
+
+
+
+// interpolate & accumulate a pixel in the output
+#define BLEND_PIXEL(type, components) \
+ if( x_in < 0.0 || x_in >= w - 1 || \
+ y_in < 0.0 || y_in >= h - 1 ) { \
+ out_row += components; \
+ } \
+ else { \
+ float y1_fraction = y_in - floor(y_in); \
+ float y2_fraction = 1.0 - y1_fraction; \
+ float x1_fraction = x_in - floor(x_in); \
+ float x2_fraction = 1.0 - x1_fraction; \
+ type *in_pixel1 = in_rows[(int)y_in] + (int)x_in * components; \
+ type *in_pixel2 = in_rows[(int)y_in + 1] + (int)x_in * components; \
+ for( int i = 0; i < components; i++ ) { \
+ float value = in_pixel1[i] * x2_fraction * y2_fraction + \
+ in_pixel2[i] * x2_fraction * y1_fraction + \
+ in_pixel1[i + components] * x1_fraction * y2_fraction + \
+ in_pixel2[i + components] * x1_fraction * y1_fraction; \
+ value = *out_row * inv_a + value * a; \
+ *out_row++ = (type)value; \
+ } \
+ }
+
+
+// nearest neighbor & accumulate a pixel in the output
+#define BLEND_NEAREST(type, components) \
+ if( x_in < 0.0 || x_in >= w - 1 || \
+ y_in < 0.0 || y_in >= h - 1 ) { \
+ out_row += components; \
+ } \
+ else { \
+ type *in_pixel = in_rows[(int)y_in] + (int)x_in * components; \
+ for( int i = 0; i < components; i++ ) { \
+ float value = in_pixel[i]; \
+ value = *out_row * inv_a + value * a; \
+ *out_row++ = (type)value; \
+ } \
+ }
+
+
+#define COLORSPACE_SWITCH(function) \
+ switch( plugin->get_input()->get_color_model() ) { \
+ case BC_RGB888: function(unsigned char, 3, 0x0); break; \
+ case BC_RGBA8888: function(unsigned char, 4, 0x0); break; \
+ case BC_RGB_FLOAT: function(float, 3, 0.0); break; \
+ case BC_RGBA_FLOAT: function(float, 4, 0.0); break; \
+ case BC_YUV888: function(unsigned char, 3, 0x80); break; \
+ case BC_YUVA8888: function(unsigned char, 4, 0x80); break; \
+ }
+
+void SphereCamUnit::process_equirect(SphereCamPackage *pkg)
+{
+ VFrame *input = plugin->get_temp();
+ VFrame *output = plugin->get_output();
+
+
+// overlay a single eye
+#define PROCESS_EQUIRECT(type, components, chroma) \
+{ \
+ type **in_rows = (type**)input->get_rows(); \
+ type **out_rows = (type**)output->get_rows(); \
+ \
+ for( int out_y = row1; out_y < row2; out_y++ ) { \
+ type *out_row = out_rows[out_y]; \
+/* polar angle - -M_PI/2 to M_PI/2 */ \
+ float y_diff = out_y - output_y; \
+ float phi = M_PI / 2 * (y_diff / radius); \
+ \
+ for( int out_x = 0; out_x < w; out_x++ ) { \
+/* alpha */ \
+ float a = alpha[out_x]; \
+ float inv_a = 1.0f - a; \
+ if( a > 0 ) { \
+/* polar angle */ \
+ float x_diff = out_x - output_x; \
+/* -M_PI/2 to M_PI/2 */ \
+ float theta = M_PI / 2 * (x_diff / radius); \
+/* vector in 3D space */ \
+ float vect_x = cos(phi) * sin(theta); \
+ float vect_y = cos(phi) * cos(theta); \
+ float vect_z = sin(phi); \
+/* fisheye angle & radius */ \
+ float theta2 = atan2(vect_z, vect_x) - rotate_z; \
+ float phi2 = atan2(hypot(vect_x, vect_z), vect_y); \
+ float r = radius * 2 * phi2 / fov; \
+/* pixel in fisheye space */ \
+ float x_in = input_x + r * cos(theta2); \
+ float y_in = input_y + r * sin(theta2); \
+ \
+ BLEND_PIXEL(type, components) \
+ } \
+ else { \
+ out_row += components; \
+ } \
+ } \
+ } \
+}
+
+ int row1 = pkg->row1;
+ int row2 = pkg->row2;
+ for( int eye = 0; eye < EYES; eye++ ) {
+ if( plugin->config.enabled[eye] ) {
+ int w = plugin->w;
+ int h = plugin->h;
+ float fov = plugin->config.fov[eye] * M_PI * 2 / 360;
+// float radius = plugin->radius[eye];
+ float radius = h / 2;
+ float input_x = plugin->input_x[eye];
+ float input_y = plugin->input_y[eye];
+ float output_x = plugin->output_x[eye];
+ float output_y = plugin->output_y[eye];
+ float rotate_z = plugin->config.rotate_z[eye] * M_PI * 2 / 360;
+ int feather = plugin->feather;
+ int out_x1 = plugin->out_x1[eye];
+ int out_x2 = plugin->out_x2[eye];
+ int out_x3 = plugin->out_x3[eye];
+ int out_x4 = plugin->out_x4[eye];
+
+// compute the alpha for every X
+ float alpha[w];
+ for( int i = 0; i < w; i++ ) {
+ alpha[i] = 0;
+ }
+
+ for( int i = out_x1; i < out_x2; i++ ) {
+ alpha[i] = 1.0f;
+ }
+
+ for( int i = out_x3; i < out_x4; i++ ) {
+ alpha[i] = 1.0f;
+ }
+
+ if( eye == 1 ) {
+ for( int i = out_x1; i < out_x1 + feather; i++ ) {
+ float value = (float)(i - out_x1) / feather;
+ if( i >= w ) {
+ alpha[i - w] = value;
+ }
+ else {
+ alpha[i] = value;
+ }
+ }
+
+ for( int i = out_x4 - feather; i < out_x4; i++ ) {
+ float value = (float)(out_x4 - i) / feather;
+ if( i < 0 ) {
+ alpha[w + i] = value;
+ }
+ else {
+ alpha[i] = value;
+ }
+ }
+ }
+
+
+//printf("SphereCamUnit::process_equirect %d rotate_x=%f rotate_y=%f rotate_z=%f\n",
+// __LINE__, rotate_x, rotate_y, rotate_z);
+
+ COLORSPACE_SWITCH(PROCESS_EQUIRECT)
+ }
+ }
+}
+
+
+
+void SphereCamUnit::process_align(SphereCamPackage *pkg)
+{
+ VFrame *input = plugin->get_temp();
+ VFrame *output = plugin->get_output();
+
+
+// overlay a single eye
+#define PROCESS_ALIGN(type, components, chroma) \
+{ \
+ type **in_rows = (type**)input->get_rows(); \
+ type **out_rows = (type**)output->get_rows(); \
+ \
+ for( int out_y = row1; out_y < row2; out_y++ ) { \
+ type *out_row = out_rows[out_y]; \
+/* polar angle */ \
+/* -M_PI/2 to M_PI/2 */ \
+ float y_diff = out_y - output_y; \
+ float phi = M_PI / 2 * (y_diff / radius); \
+ \
+ for( int out_x = 0; out_x < w; out_x++ ) { \
+/* alpha */ \
+ float a = alpha[out_x]; \
+ float inv_a = 1.0f - a; \
+ if( a > 0 ) { \
+/* polar angle */ \
+ float x_diff = out_x - output_x; \
+/* -M_PI/2 to M_PI/2 */ \
+ float theta = M_PI / 2 * (x_diff / radius); \
+ /* vector in 3D space */ \
+ float vect_x = cos(phi) * sin(theta); \
+ float vect_y = cos(phi) * cos(theta); \
+ float vect_z = sin(phi); \
+ /* fisheye angle & radius */ \
+ float theta2 = atan2(vect_z, vect_x) - rotate_z; \
+ float phi2 = atan2(hypot(vect_x, vect_z), vect_y); \
+ float r = radius * 2 * phi2 / fov; \
+ /* pixel in fisheye space */ \
+ float x_in = input_x + r * cos(theta2); \
+ float y_in = input_y + r * sin(theta2); \
+ \
+ BLEND_NEAREST(type, components) \
+ } \
+ else { \
+ out_row += components; \
+ } \
+ } \
+ } \
+}
+
+ int row1 = pkg->row1;
+ int row2 = pkg->row2;
+ for( int eye = 0; eye < EYES; eye++ ) {
+ if( plugin->config.enabled[eye] ) {
+ int w = plugin->w;
+ int h = plugin->h;
+ float fov = plugin->config.fov[eye] * M_PI * 2 / 360;
+// float radius = plugin->radius[eye];
+ float radius = h / 2;
+ float input_x = plugin->input_x[eye];
+ float input_y = plugin->input_y[eye];
+ float output_x = plugin->output_x[eye];
+ float output_y = plugin->output_y[eye];
+ float rotate_z = plugin->config.rotate_z[eye] * M_PI * 2 / 360;
+ int out_x1 = plugin->out_x1[eye];
+ int out_x2 = plugin->out_x2[eye];
+ int out_x3 = plugin->out_x3[eye];
+ int out_x4 = plugin->out_x4[eye];
+// left eye is opaque. right eye overlaps
+// compute the alpha for every X
+ float alpha[w];
+ for( int i = 0; i < w; i++ ) alpha[i] = 0;
+ float v = eye == 0 || !plugin->config.enabled[0] ? 1.0f : 0.5f;
+ for( int i = out_x1; i < out_x2; i++ ) alpha[i] = v;
+ for( int i = out_x3; i < out_x4; i++ ) alpha[i] = v;
+
+ COLORSPACE_SWITCH(PROCESS_ALIGN)
+ }
+ }
+}
+
+void SphereCamUnit::process_package(LoadPackage *package)
+{
+ SphereCamPackage *pkg = (SphereCamPackage*)package;
+
+ switch( plugin->config.mode ) {
+ case SphereCamConfig::EQUIRECT:
+ process_equirect(pkg);
+ break;
+ case SphereCamConfig::ALIGN:
+ process_align(pkg);
+ break;
+ }
+}
+
+
+SphereCamEngine::SphereCamEngine(SphereCamMain *plugin)
+ : LoadServer(plugin->PluginClient::smp + 1, plugin->PluginClient::smp + 1)
+// : LoadServer(1, 1)
+{
+ this->plugin = plugin;
+}
+
+SphereCamEngine::~SphereCamEngine()
+{
+}
+
+void SphereCamEngine::init_packages()
+{
+ for( int i = 0; i < LoadServer::get_total_packages(); i++ ) {
+ SphereCamPackage *package = (SphereCamPackage*)LoadServer::get_package(i);
+ package->row1 = plugin->get_input()->get_h() * i / LoadServer::get_total_packages();
+ package->row2 = plugin->get_input()->get_h() * (i + 1) / LoadServer::get_total_packages();
+ }
+}
+
+LoadClient* SphereCamEngine::new_client()
+{
+ return new SphereCamUnit(this, plugin);
+}
+
+LoadPackage* SphereCamEngine::new_package()
+{
+ return new SphereCamPackage;
+}
+
+