1214 lines
40 KiB
C++
1214 lines
40 KiB
C++
#include"frame.h"
|
|
|
|
#include<wx/menu.h>
|
|
#include<wx/dcclient.h>
|
|
#include<wx/colourdata.h>
|
|
#include<wx/colordlg.h>
|
|
#include<wx/filedlg.h>
|
|
#include<wx/textctrl.h>
|
|
#include<wx/wrapsizer.h>
|
|
#include<wx/stattext.h>
|
|
#include"timectrl.h"
|
|
#include<algorithm>
|
|
#include<hi/mode.h>
|
|
#include<memory>
|
|
#include<tmmintrin.h>
|
|
#include<wx/settings.h>
|
|
#include"hi/microphone.h"
|
|
#include<wx/choicdlg.h>
|
|
#include<wx/app.h>
|
|
#include<thread>
|
|
#include<chrono>
|
|
#include"timeline.h"
|
|
#include<functional>
|
|
#include<algorithm>
|
|
|
|
#define SSE_MATHFUN_WITH_CODE
|
|
#include<hi/kumb.h>
|
|
|
|
#include<hi/linearity.h>
|
|
|
|
static Frame *globaldis;
|
|
|
|
std::string node_name_from_id(uint64_t id) {
|
|
static std::unordered_map<uint64_t, std::string> NODE_ID_NAMES = {
|
|
{CUTIHI_T('CPre','view'), "Preview"},
|
|
{CUTIHI_T('CMix','er '), "Mixer"},
|
|
{CUTIHI_T('CTex','t '), "Text"},
|
|
{CUTIHI_T('CTim','e '), "Time"},
|
|
{CUTIHI_T('CMod','ulat'), "Modulate"},
|
|
{CUTIHI_T('CCns','tCol'), "Constant"},
|
|
{CUTIHI_T('CEmb','ed '), "Frame"},
|
|
{CUTIHI_T('CIma','ge '), "Image"},
|
|
{CUTIHI_T('CWin','dow '), "Window"},
|
|
{CUTIHI_T('CInA','udio'), "Microphone"},
|
|
{CUTIHI_T('CExp','Wave'), "Mux .wav"},
|
|
{CUTIHI_T('CMov','ie '), "Movie"},
|
|
{CUTIHI_T('CEnc','GVP8'), "Encode VP8"},
|
|
{CUTIHI_T('CEnc','GVP9'), "Encode VP9"},
|
|
{CUTIHI_T('CEnc','H264'), "Encode H264"},
|
|
{CUTIHI_T('CExp','Webm'), "Mux .webm"},
|
|
{CUTIHI_T('CKey','hook'), "Keyhook"},
|
|
{CUTIHI_T('CEnc','Opus'), "Encode Opus"},
|
|
{CUTIHI_T('CWeb','Cam '), "Live Digital Camera"},
|
|
{CUTIHI_T('CCmp','nScl'), "Scale"},
|
|
{CUTIHI_T('CChr','omaK'), "Chroma Key"},
|
|
{CUTIHI_T('CStr','RTMP'), "Stream RTMP"},
|
|
{CUTIHI_T('CEnc','AACL'), "Encode AAC-LC"},
|
|
};
|
|
|
|
auto nameit = NODE_ID_NAMES.find(id);
|
|
if(nameit == NODE_ID_NAMES.end()) {
|
|
char n[9] = {};
|
|
memcpy(n, &id, 8);
|
|
|
|
return "Unknown (" + std::string{n} + ")";
|
|
} else {
|
|
return nameit->second;
|
|
}
|
|
}
|
|
|
|
GrNode::Port::Port(wxString name) : Port(name, GrNode::Port::Type::NONE, false, 1, 1) {}
|
|
GrNode::Port::Port(wxString name, Type type) : Port(name, type, false, 1, 1) {}
|
|
GrNode::Port::Port(wxString name, Type type, bool separator) : Port(name, type, separator, 1, 1) {}
|
|
GrNode::Port::Port(wxString name, Type type, bool separator, float dragScalingX, float dragScalingY) : name(name), type(type), separator(separator) {
|
|
dragScaling[0] = dragScalingX;
|
|
dragScaling[1] = dragScalingY;
|
|
}
|
|
|
|
static void ShapeGrNode(GrNode *gn) {
|
|
gn->name = node_name_from_id(gn->logical->type);
|
|
|
|
if(gn->logical->type == CUTIHI_T('CPre','view')) {
|
|
gn->sinks = {{"Video", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CMix','er ')) {
|
|
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
|
|
|
|
int numSinks = gn->logical->sinkCount;
|
|
while(numSinks > 0 && gn->logical->sinks[numSinks - 1].type == CUTIHI_VAL_NONE) {
|
|
numSinks--;
|
|
}
|
|
numSinks++;
|
|
|
|
gn->sinks.clear();
|
|
for(int s = 0; s < numSinks; s++) {
|
|
gn->sinks.push_back({std::string{"Sink "} + std::to_string(s + 1), GrNode::Port::Type::SAMPLE});
|
|
}
|
|
} else if(gn->logical->type == CUTIHI_T('CTex','t ')) {
|
|
gn->sinks = {{"Text", GrNode::Port::Type::TEXT}, {"Color", GrNode::Port::Type::COLOR}, {"DPI", GrNode::Port::Type::VEC1}, {"Font", GrNode::Port::Type::TEXT}};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CTim','e ')) {
|
|
gn->sinks = {};
|
|
gn->sources = {{"t", GrNode::Port::Type::VEC1}};
|
|
} else if(gn->logical->type == CUTIHI_T('CMod','ulat')) {
|
|
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Brightness", GrNode::Port::Type::VEC1, false, 0.01, 0}, {"Contrast", GrNode::Port::Type::VEC1, false, 0.01, 0}, {"Hue", GrNode::Port::Type::VEC1}};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CCns','tCol')) {
|
|
gn->sinks = {{"Color", GrNode::Port::Type::COLOR}, {"Resolution", GrNode::Port::Type::VEC2}};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CEmb','ed ')) {
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
gn->sinks = {{"Frame", GrNode::Port::Type::SAMPLE, true}};
|
|
|
|
int numSubs = 0;
|
|
while(gn->logical->sinkCount > (1 + numSubs * 3 + 0) && gn->logical->sinks[1 + numSubs * 3 + 0].type != CUTIHI_VAL_NONE) {
|
|
numSubs++;
|
|
}
|
|
numSubs++;
|
|
|
|
for(int s = 0; s < numSubs; s++) {
|
|
gn->sinks.push_back({"Sub " + std::to_string(s + 1), GrNode::Port::Type::SAMPLE});
|
|
gn->sinks.push_back({" Pos", GrNode::Port::Type::VEC2});
|
|
gn->sinks.push_back({" Size", GrNode::Port::Type::VEC1, true});
|
|
}
|
|
} else if(gn->logical->type == CUTIHI_T('CIma','ge ')) {
|
|
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CWin','dow ')) {
|
|
gn->sinks = {{"Name", GrNode::Port::Type::WINDOW_SOURCE}};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CInA','udio')) {
|
|
gn->sinks = {{"Source", GrNode::Port::Type::MIC_SOURCE}};
|
|
gn->sources = {{"Audio", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CExp','Wave')) {
|
|
gn->sinks = {{"Filename", GrNode::Port::Type::FILE_SAVE}, {"Audio", GrNode::Port::Type::SAMPLE}};
|
|
gn->sources = {};
|
|
} else if(gn->logical->type == CUTIHI_T('CMov','ie ')) {
|
|
gn->sinks = {{"Filepath", GrNode::Port::Type::FILE_OPEN}, {"Time", GrNode::Port::Type::VEC1}};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Audio", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP8')) {
|
|
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
gn->sources = {{"Bitstream"}};
|
|
} else if(gn->logical->type == CUTIHI_T('CEnc','GVP9')) {
|
|
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
gn->sources = {{"Bitstream"}};
|
|
} else if(gn->logical->type == CUTIHI_T('CEnc','H264')) {
|
|
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
gn->sources = {{"Bitstream"}};
|
|
} else if(gn->logical->type == CUTIHI_T('CExp','Webm')) {
|
|
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"Filename", GrNode::Port::Type::FILE_SAVE}};
|
|
gn->sources = {};
|
|
} else if(gn->logical->type == CUTIHI_T('CKey','hook')) {
|
|
gn->sinks = {{"Key", GrNode::Port::Type::TEXT}, {"Smooth Time", GrNode::Port::Type::VEC1}};
|
|
gn->sources = {{"Bool", GrNode::Port::Type::VEC1}};
|
|
} else if(gn->logical->type == CUTIHI_T('CEnc','Opus')) {
|
|
gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
|
|
gn->sources = {{"Bitstream"}};
|
|
} else if(gn->logical->type == CUTIHI_T('CWeb','Cam ')) {
|
|
gn->sinks = {};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CCmp','nScl')) {
|
|
gn->sinks = {{"Vector", GrNode::Port::Type::VEC4}, {"Sample", GrNode::Port::Type::SAMPLE}};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CChr','omaK')) {
|
|
gn->sinks = {{"Sample", GrNode::Port::Type::SAMPLE}, {"Color", GrNode::Port::Type::COLOR}};
|
|
gn->sources = {{"Sample", GrNode::Port::Type::SAMPLE}};
|
|
} else if(gn->logical->type == CUTIHI_T('CStr','RTMP')) {
|
|
gn->sinks = {{"Video Bitstream"}, {"Audio Bitstream"}, {"URL", GrNode::Port::Type::TEXT}};
|
|
gn->sources = {};
|
|
} else if(gn->logical->type == CUTIHI_T('CEnc','AACL')) {
|
|
gn->sinks = {{"Audio", GrNode::Port::Type::SAMPLE}};
|
|
gn->sources = {{"Bitstream"}};
|
|
}
|
|
|
|
gn->Fit();
|
|
}
|
|
|
|
Frame::Frame() : wxFrame(NULL, wxID_ANY, "Cuticle", wxDefaultPosition, {wxSystemSettings::GetMetric(wxSYS_SCREEN_X) / 2, wxSystemSettings::GetMetric(wxSYS_SCREEN_Y) / 2}) {
|
|
globaldis = this;
|
|
|
|
aui.SetManagedWindow(this);
|
|
|
|
viewer = new ImageViewer(this);
|
|
graph = new NodeGraph(this);
|
|
timeline = new Timeline(this);
|
|
|
|
auto info = wxAuiPaneInfo().Center().Caption("Preview").BestSize(GetSize().x, GetSize().y * 3 / 4);
|
|
aui.AddPane(viewer, info);
|
|
|
|
info = wxAuiPaneInfo().Bottom().Caption("Node Graph").BestSize(GetSize().x, GetSize().y * 1 / 4);
|
|
aui.AddPane(graph, info);
|
|
|
|
info = wxAuiPaneInfo().Bottom().Row(2).Caption("Timeline");
|
|
aui.AddPane(timeline, info);
|
|
|
|
stba = CreateStatusBar();
|
|
tlba = CreateToolBar();
|
|
|
|
static wxBitmap modeImgs[] = {
|
|
[CUTIHI_MODE_LIVE] = wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY},
|
|
[CUTIHI_MODE_OFFLINE] = wxBitmap{"tlmo.bmp", wxBITMAP_TYPE_ANY}
|
|
};
|
|
auto modeTool = tlba->AddTool(wxID_ANY, "Mode", wxBitmap{"tlml.bmp", wxBITMAP_TYPE_ANY});
|
|
CHi_SetMode(CUTIHI_MODE_LIVE);
|
|
|
|
tlba->AddTool(wxID_OPEN, "Load", wxBitmap{"btn_load.bmp", wxBITMAP_TYPE_ANY});
|
|
tlba->AddTool(wxID_SAVE, "Save", wxBitmap{"btn_save.bmp", wxBITMAP_TYPE_ANY});
|
|
|
|
tlba->Bind(wxEVT_COMMAND_TOOL_CLICKED, [=](wxCommandEvent &ev){
|
|
if(ev.GetId() == modeTool->GetId()) {
|
|
CHi_SetMode((CHiMode) ((CHi_GetMode() + 1) % 2));
|
|
tlba->SetToolNormalBitmap(modeTool->GetId(), modeImgs[CHi_GetMode()]);
|
|
|
|
// wxToolBarToolBase::SetShortHelp doesn't work
|
|
if(CHi_GetMode() == CUTIHI_MODE_LIVE) {
|
|
tlba->SetToolShortHelp(modeTool->GetId(), "Set to live processing.");
|
|
} else {
|
|
tlba->SetToolShortHelp(modeTool->GetId(), "Set to offline processing.");
|
|
}
|
|
} else if(ev.GetId() == wxID_SAVE) {
|
|
wxFileDialog save{this, "Save", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_SAVE | wxFD_OVERWRITE_PROMPT};
|
|
|
|
if(save.ShowModal() == wxID_OK) {
|
|
FILE *f = fopen(save.GetPath().mb_str(), "wb");
|
|
|
|
CHi_NodeGraphSave(graph->backendNG, +[](void *ud, const void *data, size_t len){
|
|
auto f = (FILE*) ud;
|
|
|
|
return fwrite(data, 1, len, f);
|
|
}, f);
|
|
|
|
for(size_t n = 0; n < graph->backendNG->count; n++) {
|
|
GrNode *gn = graph->get_graphical(graph->backendNG->nodes[n]);
|
|
|
|
int32_t pos[2] = {gn->GetPosition().x, gn->GetPosition().y};
|
|
fwrite(pos, sizeof(pos), 1, f);
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
} else if(ev.GetId() == wxID_OPEN) {
|
|
wxFileDialog load{this, "Load", "", "", "Cuticle Project (*.ctc)|*.ctc", wxFD_OPEN | wxFD_FILE_MUST_EXIST};
|
|
|
|
if(load.ShowModal() == wxID_OK) {
|
|
FILE *f = fopen(load.GetPath().mb_str(), "rb");
|
|
|
|
if(CHi_NodeGraphLoad(graph->backendNG, +[](void *ud, void *data, size_t len){
|
|
auto f = (FILE*) ud;
|
|
|
|
return fread(data, 1, len, f);
|
|
}, f) == 0) {
|
|
|
|
for(GrNode *gnode : graph->gnodes) {
|
|
gnode->Destroy();
|
|
}
|
|
|
|
graph->gnodes.clear();
|
|
graph->links.clear();
|
|
|
|
for(size_t n = 0; n < graph->backendNG->count; n++) {
|
|
GrNode *gnode = new GrNode(graph);
|
|
|
|
int32_t pos[2];
|
|
fread(pos, sizeof(pos), 1, f);
|
|
|
|
gnode->SetPosition({pos[0], pos[1]});
|
|
|
|
gnode->logical = graph->backendNG->nodes[n];
|
|
|
|
ShapeGrNode(gnode);
|
|
|
|
graph->gnodes.push_back(gnode);
|
|
}
|
|
|
|
for(size_t n = 0; n < graph->backendNG->count; n++) {
|
|
CHiPubNode *node = graph->backendNG->nodes[n];
|
|
|
|
for(size_t s = 0; s < node->sinkCount; s++) {
|
|
if(node->sinks[s].type == CUTIHI_VAL_LINKED) {
|
|
graph->links.push_back(NodeGraph::Link{graph->gnodes[n], s, *std::find_if(graph->gnodes.begin(), graph->gnodes.end(), [=](GrNode *gn){ return gn->logical == node->sinks[s].data.linked.to; }), node->sinks[s].data.linked.idx});
|
|
}
|
|
}
|
|
}
|
|
|
|
toolbar.duration->SetSeconds(std::max(0.f, graph->backendNG->duration));
|
|
|
|
if(graph->backendNG->duration <= 0) {
|
|
toolbar.durationEnable->SetValue(false);
|
|
toolbar.duration->Enable(false);
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
}
|
|
});
|
|
tlba->AddSeparator();
|
|
|
|
toolbar.durationEnable = new wxCheckBox(tlba, wxID_ANY, "");
|
|
toolbar.duration = new ctTimeCtrl(tlba, 120);
|
|
toolbar.duration->Bind(wxEVT_COMMAND_TEXT_UPDATED, [=](wxCommandEvent&){
|
|
CHi_SetDuration(graph->backendNG, toolbar.durationEnable->IsChecked() ? toolbar.duration->GetSeconds() : -1);
|
|
});
|
|
CHi_SetDuration(graph->backendNG, toolbar.duration->GetSeconds());
|
|
|
|
toolbar.durationEnable->SetValue(true);
|
|
toolbar.durationEnable->Bind(wxEVT_CHECKBOX, [=](wxCommandEvent&){
|
|
toolbar.duration->Enable(toolbar.durationEnable->GetValue());
|
|
|
|
if(!toolbar.durationEnable->GetValue()) {
|
|
CHi_SetDuration(graph->backendNG, -1);
|
|
}
|
|
});
|
|
|
|
toolbar.btnPerform = new wxButton(tlba, wxID_ANY, "Compile");
|
|
toolbar.btnPerform->Bind(wxEVT_BUTTON, [=](wxCommandEvent &ev){
|
|
if(toolbar.btnPerform->GetLabel() == "Kill") {
|
|
CHi_StopCompilation(graph->backendNG);
|
|
toolbar.btnPerform->Disable();
|
|
tlba->EnableTool(wxID_SAVE, false);
|
|
tlba->EnableTool(wxID_OPEN, false);
|
|
} else {
|
|
CHi_BeginCompilation(graph->backendNG);
|
|
toolbar.btnPerform->SetLabel("Kill");
|
|
|
|
std::thread{[=](){
|
|
while(graph->backendNG->compilationStatus == CUTIHI_COMP_RUNNING) {
|
|
CallAfter([=](){
|
|
float t = CHi_Time_Get(graph->backendNG);
|
|
stba->SetStatusText(wxString::Format("%02i:%02i:%06.03fs", (int) (t / 3600), (int) (t / 60), fmodf(t, 60)));
|
|
});
|
|
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
}
|
|
|
|
CallAfter([=](){
|
|
stba->SetStatusText("Compilation has ended.");
|
|
});
|
|
}}.detach();
|
|
}
|
|
});
|
|
|
|
tlba->AddControl(toolbar.durationEnable);
|
|
tlba->AddControl(toolbar.duration);
|
|
tlba->AddControl(toolbar.btnPerform);
|
|
|
|
graph->backendNG->eventOnStopComplete = +[](CHiNodeGraph *ng){
|
|
wxTheApp->CallAfter([ng](){
|
|
wxButton *btn = ((Frame*) ng->ud)->toolbar.btnPerform;
|
|
btn->Enable();
|
|
btn->SetLabel("Compile");
|
|
|
|
auto tlba = ((Frame*) ng->ud)->tlba;
|
|
tlba->EnableTool(wxID_SAVE, true);
|
|
tlba->EnableTool(wxID_OPEN, true);
|
|
});
|
|
};
|
|
|
|
graph->backendNG->eventOnError = +[](CHiNodeGraph *ng, CHiPubNode *n) {
|
|
wxTheApp->CallAfter([ng, n](){
|
|
auto frame = (Frame*) ng->ud;
|
|
|
|
GrNode *gn = frame->graph->get_graphical(n);
|
|
gn->Refresh();
|
|
});
|
|
};
|
|
|
|
tlba->Realize();
|
|
|
|
aui.SetFlags(wxAUI_MGR_LIVE_RESIZE | wxAUI_MGR_DEFAULT);
|
|
aui.Update();
|
|
|
|
Centre();
|
|
|
|
graph->CreatePreviewNode();
|
|
}
|
|
|
|
Frame::~Frame() {
|
|
aui.UnInit();
|
|
}
|
|
|
|
bool GrNode::MouseOverPort(wxPoint point, bool &source, int &i) {
|
|
if(point.y < 26 || point.x < 0 || point.x > GetSize().x) return false;
|
|
|
|
int p = (point.y - 26) / 20;
|
|
if((point.x >= 15 || p >= (int) sinks.size()) && (point.x < GetSize().x - 15 || p >= (int) sources.size())) return false;
|
|
int isSource = point.x >= GetSize().x / 2;
|
|
|
|
source = isSource;
|
|
i = p;
|
|
|
|
return true;
|
|
}
|
|
|
|
void GrNode::MakeKeyframe(int sinkIdx) {
|
|
auto ng = (NodeGraph*) GetParent();
|
|
|
|
CHi_MakeKeyframe(ng->backendNG, this->logical, sinkIdx);
|
|
|
|
((Frame*) ng->GetParent())->timeline->Refresh();
|
|
}
|
|
|
|
static bool has_errors(CHiPubNode *pn) {
|
|
for(int i = 0; i < CUTIHI_MAX_ERRORS; i++) {
|
|
if(pn->errors.active[i]) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
GrNode::GrNode(NodeGraph *parent) : wxPanel(parent, wxID_ANY, {0, 0}, {175, 80}) {
|
|
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){
|
|
wxColour bgColor;
|
|
if(has_errors(logical)) {
|
|
bgColor = wxColour{168, 118, 118};
|
|
} else {
|
|
bgColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
|
}
|
|
|
|
wxPaintDC dc(this);
|
|
dc.SetBrush(wxBrush{bgColor});
|
|
dc.SetPen(HasFocus() ? *wxGREY_PEN : *wxBLACK_PEN);
|
|
dc.DrawRoundedRectangle(5, 0, GetSize().x - 10, GetSize().y, 5);
|
|
|
|
dc.DrawText(name, GetSize().x / 2 - dc.GetTextExtent(name).x / 2, 2);
|
|
|
|
bool hoverIsSource;
|
|
int hoverI;
|
|
bool hover = MouseOverPort(ScreenToClient(wxGetMousePosition()), hoverIsSource, hoverI);
|
|
|
|
int y = 13;
|
|
int i = 0;
|
|
for(Port &p : sinks) {
|
|
wxColour col = p.type == GrNode::Port::Type::FILE_OPEN ? wxColour{255, 0, 0}
|
|
: p.type == GrNode::Port::Type::FILE_SAVE ? wxColour{255, 0, 0}
|
|
: p.type == GrNode::Port::Type::COLOR ? wxColour{0, 0, 255}
|
|
: p.type == GrNode::Port::Type::VEC2 ? wxColour{0, 255, 0}
|
|
: p.type == GrNode::Port::Type::TEXT ? wxColour{255, 255, 0}
|
|
: p.type == GrNode::Port::Type::WINDOW_SOURCE ? wxColour{255, 255, 0}
|
|
: p.type == GrNode::Port::Type::SAMPLE ? wxColour{252, 111, 255}
|
|
: p.type == GrNode::Port::Type::MIC_SOURCE ? wxColour{255, 255, 255}
|
|
: wxColour{128, 128, 128};
|
|
|
|
if(hover && !hoverIsSource && i == hoverI) {
|
|
col.Set(std::min(col.Red() + 50, 255), std::min(col.Green() + 50, 255), std::min(col.Blue() + 50, 255));
|
|
}
|
|
|
|
wxSize sz = dc.GetTextExtent(p.name);
|
|
dc.SetBrush(wxBrush{col});
|
|
dc.DrawText(p.name, 15, (y += 20) - sz.y / 2);
|
|
dc.DrawCircle(5, y, 5);
|
|
|
|
if(p.separator) {
|
|
dc.DrawLine(5, y + 10, GetSize().x / 2, y + 10);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
y = 13;
|
|
i = 0;
|
|
for(Port &p : sources) {
|
|
wxColour col = p.type == GrNode::Port::Type::FILE_OPEN ? wxColour{255, 0, 0}
|
|
: p.type == GrNode::Port::Type::FILE_SAVE ? wxColour{255, 0, 0}
|
|
: p.type == GrNode::Port::Type::COLOR ? wxColour{0, 0, 255}
|
|
: p.type == GrNode::Port::Type::VEC2 ? wxColour{0, 255, 0}
|
|
: p.type == GrNode::Port::Type::TEXT ? wxColour{255, 255, 0}
|
|
: p.type == GrNode::Port::Type::WINDOW_SOURCE ? wxColour{255, 255, 0}
|
|
: p.type == GrNode::Port::Type::SAMPLE ? wxColour{252, 111, 255}
|
|
: p.type == GrNode::Port::Type::MIC_SOURCE ? wxColour{255, 255, 255}
|
|
: wxColour{128, 128, 128};
|
|
|
|
if(hover && hoverIsSource && i == hoverI) {
|
|
col.Set(std::min(col.Red() + 50, 255), std::min(col.Green() + 50, 255), std::min(col.Blue() + 50, 255));
|
|
}
|
|
|
|
wxSize sz = dc.GetTextExtent(p.name);
|
|
dc.SetBrush(wxBrush{col});
|
|
dc.DrawText(p.name, GetSize().x - sz.x - 15, (y += 20) - sz.y / 2);
|
|
dc.DrawCircle(GetSize().x - 6, y, 5);
|
|
|
|
if(p.separator) {
|
|
dc.DrawLine(GetSize().x / 2, y + 10, GetSize().x - 5, y + 10);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
});
|
|
Bind(wxEVT_LEFT_DOWN, [parent, this](wxMouseEvent &ev){
|
|
SetFocus();
|
|
parent->Refresh();
|
|
|
|
bool isSource;
|
|
int p;
|
|
if(MouseOverPort(ev.GetPosition(), isSource, p)) {
|
|
if(parent->attacheeNode && parent->attacheePortIsSource != isSource) {
|
|
if(parent->attacheePortIsSource) {
|
|
parent->Alinken({this, p, parent->attacheeNode, parent->attacheePort});
|
|
} else {
|
|
parent->Alinken({parent->attacheeNode, parent->attacheePort, this, p});
|
|
}
|
|
|
|
parent->attacheeNode = NULL;
|
|
} else {
|
|
parent->attacheeNode = this;
|
|
parent->attacheePort = p;
|
|
parent->attacheePortIsSource = isSource;
|
|
}
|
|
} else {
|
|
CaptureMouse();
|
|
parent->attacheeNode = NULL;
|
|
parent->dragged = this;
|
|
parent->dragPos = ClientToScreen(ev.GetPosition());
|
|
}
|
|
});
|
|
Bind(wxEVT_MOTION, [parent, this](wxMouseEvent &ev){
|
|
if(wxGetMouseState().LeftIsDown()) {
|
|
if(HasCapture()) {
|
|
wxPoint neu = ClientToScreen(ev.GetPosition());
|
|
SetPosition(GetPosition() + neu - parent->dragPos);
|
|
parent->dragPos = neu;
|
|
}
|
|
} else if(wxGetMouseState().MiddleIsDown()) {
|
|
if(HasCapture()) {
|
|
wxPoint neu = ClientToScreen(ev.GetPosition());
|
|
wxPoint diff = neu - parent->dragPos;
|
|
parent->dragPos = neu;
|
|
|
|
CHiValue newv = *CHi_Crawl(&this->logical->sinks[sinkValueDragging]);
|
|
|
|
if(newv.type == CUTIHI_VAL_NONE) {
|
|
newv.type = CUTIHI_VAL_VEC4;
|
|
newv.data.vec4[0] = newv.data.vec4[1] = newv.data.vec4[2] = 0;
|
|
newv.data.vec4[3] = 1;
|
|
}
|
|
|
|
assert(newv.type == CUTIHI_VAL_VEC4);
|
|
|
|
newv.data.vec4[0] += diff.x * sinks[sinkValueDragging].dragScaling[0];
|
|
newv.data.vec4[1] += diff.y * sinks[sinkValueDragging].dragScaling[1];
|
|
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
CHi_ConfigureSink(this->logical, sinkValueDragging, newv);
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
|
|
parent->Dirtify(this);
|
|
}
|
|
}
|
|
parent->Refresh();
|
|
|
|
// Set focus if currently focused item is not a control/entry/editor
|
|
if(!dynamic_cast<wxControl*>(wxWindow::FindFocus())) {
|
|
SetFocus();
|
|
}
|
|
});
|
|
Bind(wxEVT_LEFT_UP, [parent, this](wxMouseEvent &ev){
|
|
if(HasCapture()) {
|
|
ReleaseMouse();
|
|
parent->dragged = NULL;
|
|
}
|
|
});
|
|
Bind(wxEVT_LEFT_DCLICK, [parent, this](wxMouseEvent &ev){
|
|
if(ev.GetPosition().y >= 26) {
|
|
parent->attacheeNode = NULL;
|
|
|
|
int p = (ev.GetPosition().y - 26) / 20;
|
|
if(p >= (int) sinks.size()) return;
|
|
|
|
if(sinks[p].type == Port::Type::COLOR) {
|
|
wxColourData data;
|
|
|
|
data.SetChooseFull(true);
|
|
data.SetChooseAlpha(true);
|
|
|
|
CHiValue *currentVal = CHi_Crawl(&this->logical->sinks[p]);
|
|
if(currentVal->type == CUTIHI_VAL_VEC4) {
|
|
data.SetColour({
|
|
(uint8_t) (currentVal->data.vec4[0] * 255.f),
|
|
(uint8_t) (currentVal->data.vec4[1] * 255.f),
|
|
(uint8_t) (currentVal->data.vec4[2] * 255.f),
|
|
(uint8_t) (currentVal->data.vec4[3] * 255.f),
|
|
});
|
|
}
|
|
|
|
wxColourDialog dlg(this, &data);
|
|
|
|
if(dlg.ShowModal() == wxID_OK) {
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
|
|
CHiValue newv;
|
|
newv.type = CUTIHI_VAL_VEC4;
|
|
newv.data.vec4[0] = dlg.GetColourData().GetColour().Red() / 255.f;
|
|
newv.data.vec4[1] = dlg.GetColourData().GetColour().Green() / 255.f;
|
|
newv.data.vec4[2] = dlg.GetColourData().GetColour().Blue() / 255.f;
|
|
newv.data.vec4[3] = dlg.GetColourData().GetColour().Alpha() / 255.f;
|
|
CHi_ConfigureSink(this->logical, p, newv);
|
|
parent->Dirtify(this);
|
|
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
}
|
|
} else if(sinks[p].type == Port::Type::FILE_OPEN) {
|
|
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_OPEN | wxFD_PREVIEW);
|
|
if(dlg.ShowModal() == wxID_OK) {
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
|
|
CHiValue newv;
|
|
newv.type = CUTIHI_VAL_TEXT;
|
|
newv.data.text = strdup(dlg.GetPath().utf8_str());
|
|
CHi_ConfigureSink(this->logical, p, newv);
|
|
parent->Dirtify(this);
|
|
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
}
|
|
} else if(sinks[p].type == Port::Type::FILE_SAVE) {
|
|
wxFileDialog dlg(this, wxFileSelectorPromptStr, wxEmptyString, wxEmptyString, wxFileSelectorDefaultWildcardStr, wxFD_SAVE | wxFD_PREVIEW | wxFD_OVERWRITE_PROMPT);
|
|
if(dlg.ShowModal() == wxID_OK) {
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
|
|
CHiValue newv;
|
|
newv.type = CUTIHI_VAL_TEXT;
|
|
newv.data.text = strdup(dlg.GetPath().utf8_str());
|
|
CHi_ConfigureSink(this->logical, p, newv);
|
|
parent->Dirtify(this);
|
|
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
}
|
|
} else if(sinks[p].type >= Port::Type::VEC1 && sinks[p].type <= Port::Type::VEC4) {
|
|
auto ctrls = std::make_shared<std::vector<wxTextCtrl*>>();
|
|
for(int i = 0; i <= (int) sinks[p].type - (int) Port::Type::VEC1; i++) {
|
|
wxTextCtrl *tc = new wxTextCtrl(GetParent(), wxID_ANY, wxString::Format("%f", this->logical->sinks[p].data.vec4[i]), GetParent()->ScreenToClient(ClientToScreen({5 + 60 * i, (p + 1) * 20})));
|
|
tc->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
|
|
if(ev.GetKeyCode() == WXK_RETURN) {
|
|
double d;
|
|
if(tc->GetValue().ToDouble(&d)) {
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
|
|
CHiValue newv = *CHi_Crawl(&this->logical->sinks[p]);
|
|
newv.type = CUTIHI_VAL_VEC4;
|
|
newv.data.vec4[i] = d;
|
|
CHi_ConfigureSink(this->logical, p, newv);
|
|
|
|
auto it = std::find(ctrls->begin(), ctrls->end(), tc);
|
|
ctrls->operator[]((it - ctrls->begin() + 1) % ctrls->size())->SetFocus();
|
|
ctrls->erase(it);
|
|
|
|
CallAfter([tc](){tc->Destroy();});
|
|
parent->Dirtify(this);
|
|
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
}
|
|
} else if(ev.GetKeyCode() == WXK_TAB) {
|
|
ctrls->operator[]((i + ctrls->size() + (wxGetKeyState(WXK_SHIFT) ? -1 : 1)) % ctrls->size())->SetFocus();
|
|
|
|
parent->Dirtify(this);
|
|
} else ev.Skip();
|
|
});
|
|
ctrls->push_back(tc);
|
|
}
|
|
ctrls->operator[](0)->SetFocus();
|
|
} else if(sinks[p].type == Port::Type::TEXT) {
|
|
wxTextCtrl *ctrl = new wxTextCtrl(GetParent(), wxID_ANY, this->logical->sinks[p].data.text, GetParent()->ScreenToClient(ClientToScreen({5, (p + 1) * 26})));
|
|
ctrl->SetValue(wxString{CHi_Crawl(&this->logical->sinks[p])->data.text});
|
|
ctrl->SetFocus();
|
|
ctrl->Bind(wxEVT_KILL_FOCUS, [=](wxFocusEvent &ev){
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
|
|
CHiValue newv;
|
|
newv.type = CUTIHI_VAL_TEXT;
|
|
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
|
|
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
|
|
newv.data.text = c;
|
|
CHi_ConfigureSink(this->logical, p, newv);
|
|
CallAfter([ctrl](){ctrl->Destroy();});
|
|
parent->Dirtify(this);
|
|
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
});
|
|
ctrl->Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
|
|
if(ev.GetKeyCode() == WXK_RETURN) {
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
|
|
CHiValue newv;
|
|
newv.type = CUTIHI_VAL_TEXT;
|
|
char *c = (char*) malloc(ctrl->GetValue().Len() + 1);
|
|
memcpy(c, ctrl->GetValue().c_str(), ctrl->GetValue().Len() + 1);
|
|
newv.data.text = c;
|
|
CHi_ConfigureSink(this->logical, p, newv);
|
|
CallAfter([ctrl](){ctrl->Destroy();});
|
|
parent->Dirtify(this);
|
|
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
} else ev.Skip();
|
|
});
|
|
} else if(sinks[p].type == Port::Type::MIC_SOURCE) {
|
|
std::vector<wxString> choices;
|
|
std::vector<void*> datae;
|
|
for(size_t i = CHi_Microphone_GetNextSource(-1); i < CHi_Microphone_GetSourceCount(); i = CHi_Microphone_GetNextSource(i)) {
|
|
choices.push_back(wxString::FromUTF8(CHi_Microphone_GetSourceName(i)));
|
|
datae.push_back((void*) (uintptr_t) i);
|
|
}
|
|
|
|
wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), datae.data());
|
|
if(dlg.ShowModal() == wxID_OK) {
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
|
|
CHiValue newv;
|
|
newv.type = CUTIHI_VAL_TEXT;
|
|
newv.data.text = strdup(CHi_Microphone_GetSourceName((size_t) (uintptr_t) dlg.GetSelectionData()));
|
|
CHi_ConfigureSink(this->logical, p, newv);
|
|
parent->Dirtify(this);
|
|
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
}
|
|
} else if(sinks[p].type == Port::Type::WINDOW_SOURCE) {
|
|
std::vector<const char*> choicesOrig;
|
|
std::vector<wxString> choices;
|
|
|
|
for(size_t i = CHi_Window_GetNextSource(-1); i < CHi_Window_GetSourceCount(); i = CHi_Window_GetNextSource(i)) {
|
|
auto name = CHi_Window_GetSourceName(i);
|
|
|
|
choicesOrig.push_back(name);
|
|
choices.push_back(wxString::FromUTF8(name));
|
|
}
|
|
|
|
wxSingleChoiceDialog dlg(this, "", "Choose Source", choices.size(), choices.data(), (void**) nullptr);
|
|
if(dlg.ShowModal() == wxID_OK) {
|
|
pthread_mutex_lock(&this->logical->ng->mut);
|
|
|
|
CHiValue newv;
|
|
newv.type = CUTIHI_VAL_TEXT;
|
|
newv.data.text = strdup(choicesOrig[dlg.GetSelection()]);
|
|
CHi_ConfigureSink(this->logical, p, newv);
|
|
parent->Dirtify(this);
|
|
|
|
pthread_mutex_unlock(&this->logical->ng->mut);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
Bind(wxEVT_KEY_DOWN, [=](wxKeyEvent &ev){
|
|
if(ev.GetKeyCode() == 'I') {
|
|
bool isSource;
|
|
int p;
|
|
if(MouseOverPort(ev.GetPosition(), isSource, p) && !isSource) {
|
|
MakeKeyframe(p);
|
|
}
|
|
} else if(ev.GetKeyCode() == WXK_DELETE) {
|
|
bool isSource;
|
|
int p;
|
|
if(MouseOverPort(ev.GetPosition(), isSource, p)) {
|
|
CHiPubNode *daNode = this->logical;
|
|
int daPortIdx = p;
|
|
|
|
for(auto it = parent->links.begin(); it != parent->links.end(); it++) {
|
|
auto &link = *it;
|
|
if((isSource ? link.output : link.input) == this && (isSource ? link.o : link.i) == p) {
|
|
parent->links.erase(it);
|
|
|
|
if(isSource) {
|
|
daNode = link.input->logical;
|
|
daPortIdx = link.i;
|
|
isSource = false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!isSource) {
|
|
pthread_mutex_lock(&daNode->ng->mut);
|
|
|
|
CHiValue val;
|
|
val.type = CUTIHI_VAL_NONE;
|
|
CHi_ConfigureSink(daNode, daPortIdx, val);
|
|
|
|
pthread_mutex_unlock(&daNode->ng->mut);
|
|
}
|
|
|
|
parent->Dirtify(this);
|
|
parent->Refresh();
|
|
}
|
|
}
|
|
});
|
|
|
|
Bind(wxEVT_MIDDLE_DOWN, [parent, this](wxMouseEvent &ev){
|
|
bool isSource;
|
|
int p;
|
|
if(MouseOverPort(ev.GetPosition(), isSource, p)) {
|
|
if(!isSource && (sinks[p].type == GrNode::Port::Type::VEC1 || sinks[p].type == GrNode::Port::Type::VEC2 || sinks[p].type == GrNode::Port::Type::VEC3 || sinks[p].type == GrNode::Port::Type::VEC4)) {
|
|
CaptureMouse();
|
|
parent->dragPos = ClientToScreen(ev.GetPosition());
|
|
sinkValueDragging = p;
|
|
|
|
ev.StopPropagation();
|
|
}
|
|
}
|
|
});
|
|
Bind(wxEVT_MIDDLE_UP, [parent, this](wxMouseEvent &ev){
|
|
if(HasCapture()) {
|
|
ReleaseMouse();
|
|
|
|
ev.StopPropagation();
|
|
}
|
|
});
|
|
}
|
|
|
|
GrNode::~GrNode() {
|
|
}
|
|
|
|
void GrNode::Fit() {
|
|
SetSize(GetSize().x, (std::max(sinks.size(), sources.size()) + 1) * 23);
|
|
}
|
|
|
|
ImageViewer::ImageViewer(Frame *f) : wxPanel(f, wxID_ANY) {
|
|
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev){
|
|
if(bm.IsOk()) {
|
|
wxPaintDC dc(this);
|
|
dc.DrawBitmap(bm, pos);
|
|
}
|
|
});
|
|
Bind(wxEVT_MIDDLE_DOWN, [this](wxMouseEvent &ev){
|
|
CaptureMouse();
|
|
drag = ev.GetPosition();
|
|
});
|
|
Bind(wxEVT_MOTION, [this](wxMouseEvent &ev){
|
|
if(HasCapture()) {
|
|
pos += ev.GetPosition() - drag;
|
|
drag = ev.GetPosition();
|
|
Refresh();
|
|
}
|
|
});
|
|
Bind(wxEVT_MIDDLE_UP, [this](wxMouseEvent &ev){
|
|
if(HasCapture()) {
|
|
ReleaseMouse();
|
|
}
|
|
});
|
|
Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent &ev){
|
|
img.SetData((unsigned char*) buf, bufW, bufH, true);
|
|
ResizeImage(siez + 25 * ev.GetWheelDelta() / ev.GetWheelRotation());
|
|
});
|
|
}
|
|
|
|
__attribute__((optimize("O3"))) static uint8_t *bgra64torgb24(uint8_t *orig, size_t stride, size_t w, size_t h) {
|
|
auto T0 = wxGetUTCTimeUSec();
|
|
|
|
uint8_t *ret = (uint8_t*) _mm_malloc(w * h * 3 + 16, 16);
|
|
|
|
#pragma omp parallel for
|
|
for(size_t y = 0; y < h; y++) {
|
|
|
|
uint8_t *temp = orig + stride * y;
|
|
uint8_t *dest = ret + 3 * w * y;
|
|
|
|
for(size_t x = 0; x < (w & ~15); x += 16, temp += 128, dest += 48) {
|
|
|
|
__m128i z[8] = {};
|
|
for(int zi = 0; zi < 8; zi++) {
|
|
z[zi] = _mm_loadu_si128((__m128i*) temp + zi);
|
|
z[zi] = apply_gamma_epi16(z[zi], _mm_set_ps(1, 1 / 2.2f, 1 / 2.2f, 1 / 2.2f));
|
|
}
|
|
|
|
__m128i a = _mm_shuffle_epi8(z[0], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5));
|
|
__m128i b = _mm_shuffle_epi8(z[1], _mm_set_epi8(-128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128));
|
|
__m128i c = _mm_shuffle_epi8(z[2], _mm_set_epi8(13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
|
|
__m128i d = _mm_shuffle_epi8(z[2], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11));
|
|
__m128i e = _mm_shuffle_epi8(z[3], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128));
|
|
__m128i f = _mm_shuffle_epi8(z[4], _mm_set_epi8(-128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128));
|
|
__m128i g = _mm_shuffle_epi8(z[5], _mm_set_epi8(3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
|
|
__m128i h = _mm_shuffle_epi8(z[5], _mm_set_epi8(-128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, 9, 11, 13, 1));
|
|
__m128i i = _mm_shuffle_epi8(z[6], _mm_set_epi8(-128, -128, -128, -128, -128, -128, 9, 11, 13, 1, 3, 5, -128, -128, -128, -128));
|
|
__m128i j = _mm_shuffle_epi8(z[7], _mm_set_epi8(9, 11, 13, 1, 3, 5, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128));
|
|
|
|
_mm_storeu_si128((__m128i*) dest + 0, _mm_or_si128(_mm_or_si128(a, b), c));
|
|
_mm_storeu_si128((__m128i*) dest + 1, _mm_or_si128(_mm_or_si128(_mm_or_si128(d, e), f), g));
|
|
_mm_storeu_si128((__m128i*) dest + 2, _mm_or_si128(_mm_or_si128(h, i), j));
|
|
|
|
}
|
|
|
|
for(size_t x = w & ~15; x < w; x++, temp += 8, dest += 3) {
|
|
|
|
uint64_t s = *(uint64_t*) temp;
|
|
|
|
dest[0] = powf(((s >> 40) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
|
|
dest[1] = powf(((s >> 24) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
|
|
dest[2] = powf(((s >> 8) & 0xFF) / 255.f, 1 / 2.2f) * 255.f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
auto T1 = wxGetUTCTimeUSec();
|
|
|
|
printf("%f\n", (T1 - T0).ToDouble() / 1000);
|
|
|
|
return ret;
|
|
}
|
|
void ImageViewer::SetImage(CHiImage *chim) {
|
|
if(!chim) return;
|
|
if(buf) _mm_free(buf);
|
|
buf = bgra64torgb24((uint8_t*) chim->data16, chim->stride, bufW = chim->width, bufH = chim->height);
|
|
img.SetData((unsigned char*) buf, chim->width, chim->height, true);
|
|
ResizeImage(siez);
|
|
}
|
|
void ImageViewer::ResizeImage(float size) {
|
|
float w = size, h = (float) bufH / bufW * siez;
|
|
|
|
if(w <= 1 || h <= 1) {
|
|
return;
|
|
}
|
|
|
|
siez = size;
|
|
img.Rescale(w, h);
|
|
bm = wxBitmap(img);
|
|
Refresh();
|
|
}
|
|
|
|
NodeGraph::NodeGraph(Frame *f) : wxPanel(f, wxID_ANY) {
|
|
backendNG = CHi_NewNodeGraph();
|
|
backendNG->ud = f;
|
|
|
|
Bind(wxEVT_CONTEXT_MENU, [this, f](wxContextMenuEvent &ev){
|
|
wxMenu *menuExports = new wxMenu();
|
|
int idEncodeH264 = menuExports->Append(wxID_ANY, "Encode H264", "")->GetId();
|
|
int idEncodeVp8 = menuExports->Append(wxID_ANY, "Encode VP8", "")->GetId();
|
|
int idEncodeVp9 = menuExports->Append(wxID_ANY, "Encode VP9", "")->GetId();
|
|
int idEncodeOpus = menuExports->Append(wxID_ANY, "Encode Opus", "")->GetId();
|
|
int idEncodeAAC = menuExports->Append(wxID_ANY, "Encode AAC-LC", "")->GetId();
|
|
int idMuxWebm = menuExports->Append(wxID_ANY, "Mux WebM", "")->GetId();
|
|
int idMuxWav = menuExports->Append(wxID_ANY, "Muv Wav", "")->GetId();
|
|
int idStreamRTMP = menuExports->Append(wxID_ANY, "Stream RTMP", "")->GetId();
|
|
|
|
wxMenu *menuRealtime = new wxMenu();
|
|
int idMicrophone = menuRealtime->Append(wxID_ANY, "Microphone", "")->GetId();
|
|
int idCamera = menuRealtime->Append(wxID_ANY, "Live Digital Camera", "")->GetId();
|
|
int idKeyhook = menuRealtime->Append(wxID_ANY, "Keyhook", "")->GetId();
|
|
int idWindow = menuRealtime->Append(wxID_ANY, "Window", "")->GetId();
|
|
|
|
wxMenu menu;
|
|
menu.Append(wxID_ANY, "Exports", menuExports);
|
|
menu.Append(wxID_ANY, "Realtime", menuRealtime);
|
|
|
|
menu.AppendSeparator();
|
|
|
|
int idConstant = menu.Append(wxID_ANY, "Constant", "")->GetId();
|
|
int idImage = menu.Append(wxID_ANY, "Image", "")->GetId();
|
|
int idMovie = menu.Append(wxID_ANY, "Movie", "")->GetId();
|
|
int idText = menu.Append(wxID_ANY, "Text", "")->GetId();
|
|
int idMixer = menu.Append(wxID_ANY, "Audio Mixer", "")->GetId();
|
|
int idTime = menu.Append(wxID_ANY, "Time", "")->GetId();
|
|
int idEmbed = menu.Append(wxID_ANY, "Frame", "")->GetId();
|
|
int idComponentScale = menu.Append(wxID_ANY, "Scale", "")->GetId();
|
|
int idModulate = menu.Append(wxID_ANY, "Modulate", "")->GetId();
|
|
int idChromaKey = menu.Append(wxID_ANY, "Chroma Key", "")->GetId();
|
|
wxPoint position = ScreenToClient(wxGetMousePosition());
|
|
|
|
auto fn = [=](wxCommandEvent &ev) {
|
|
std::function<void()> after = [](){};
|
|
|
|
pthread_mutex_lock(&backendNG->mut);
|
|
|
|
GrNode *noed = nullptr;
|
|
if(ev.GetId() == idConstant) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_ConstantSample();
|
|
} else if(ev.GetId() == idImage) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Image();
|
|
} else if(ev.GetId() == idEmbed) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Embed();
|
|
} else if(ev.GetId() == idModulate) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Modulate();
|
|
} else if(ev.GetId() == idMovie) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Movie();
|
|
|
|
after = [=](){
|
|
size_t portIdx = std::distance(noed->sinks.begin(), std::find_if(noed->sinks.begin(), noed->sinks.end(), [](GrNode::Port& p) -> bool {
|
|
return p.name == "Time";
|
|
}));
|
|
|
|
CHi_MakeKeyframe(backendNG, noed->logical, portIdx);
|
|
|
|
float params[4] = {1};
|
|
CHi_SetExtrapolationMode(backendNG, noed->logical, portIdx, CUTIHI_EXTRAPOLATION_CONSTANT, params);
|
|
};
|
|
} else if(ev.GetId() == idEncodeH264) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_EncodeH264();
|
|
} else if(ev.GetId() == idEncodeVp9) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_EncodeVP9();
|
|
} else if(ev.GetId() == idEncodeVp8) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_EncodeVP8();
|
|
} else if(ev.GetId() == idMuxWebm) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_MuxWebm();
|
|
} else if(ev.GetId() == idWindow) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Window();
|
|
} else if(ev.GetId() == idText) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Text();
|
|
} else if(ev.GetId() == idTime) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Time();
|
|
} else if(ev.GetId() == idMicrophone) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Microphone();
|
|
} else if(ev.GetId() == idMixer) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Mixer();
|
|
} else if(ev.GetId() == idMuxWav) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_ExportWav();
|
|
} else if(ev.GetId() == idEncodeOpus) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_EncodeOpus();
|
|
} else if(ev.GetId() == idCamera) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Camera();
|
|
} else if(ev.GetId() == idComponentScale) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_ComponentScale();
|
|
} else if(ev.GetId() == idKeyhook) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_Keyhook();
|
|
} else if(ev.GetId() == idChromaKey) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_ChromaKey();
|
|
} else if(ev.GetId() == idStreamRTMP) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_StreamRTMP();
|
|
} else if(ev.GetId() == idEncodeAAC) {
|
|
noed = new GrNode(this);
|
|
noed->logical = CHi_EncodeAAC();
|
|
}
|
|
|
|
if(noed) {
|
|
ShapeGrNode(noed);
|
|
|
|
noed->SetPosition(position);
|
|
CHi_RegisterNode(backendNG, noed->logical);
|
|
gnodes.push_back(noed);
|
|
|
|
after();
|
|
|
|
((Frame*) GetParent())->timeline->ResetRows();
|
|
}
|
|
|
|
pthread_mutex_unlock(&backendNG->mut);
|
|
};
|
|
|
|
menuExports->Bind(wxEVT_MENU, fn);
|
|
menuRealtime->Bind(wxEVT_MENU, fn);
|
|
menu.Bind(wxEVT_MENU, fn);
|
|
|
|
PopupMenu(&menu);
|
|
});
|
|
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &ev){
|
|
SetFocusIgnoringChildren();
|
|
Refresh();
|
|
});
|
|
Bind(wxEVT_LEFT_UP, [this](wxMouseEvent &ev){
|
|
if(attacheeNode) attacheeNode = NULL;
|
|
});
|
|
|
|
Bind(wxEVT_MIDDLE_DOWN, [this](wxMouseEvent &ev){
|
|
dragged = nullptr;
|
|
dragPos = ClientToScreen(ev.GetPosition());
|
|
CaptureMouse();
|
|
});
|
|
Bind(wxEVT_MIDDLE_UP, [this](wxMouseEvent &ev){
|
|
if(HasCapture()) {
|
|
ReleaseMouse();
|
|
}
|
|
});
|
|
Bind(wxEVT_MOTION, [this](wxMouseEvent &ev) {
|
|
Refresh();
|
|
|
|
if(HasCapture()) {
|
|
wxPoint neu = ClientToScreen(ev.GetPosition());
|
|
for(auto gr : gnodes) {
|
|
gr->SetPosition(gr->GetPosition() + neu - dragPos);
|
|
}
|
|
dragPos = neu;
|
|
}
|
|
});
|
|
|
|
Bind(wxEVT_PAINT, [this](wxPaintEvent &ev) {
|
|
wxPaintDC dc(this);
|
|
|
|
wxPoint p[2];
|
|
for(Link l : links) {
|
|
p[0] = l.input->GetPosition() + wxPoint{0, 33 + 20 * l.i};
|
|
p[1] = l.output->GetPosition() + wxPoint{l.output->GetSize().x, 33 + 20 * l.o};
|
|
dc.DrawSpline(2, p);
|
|
}
|
|
|
|
if(attacheeNode) {
|
|
p[0] = attacheeNode->GetPosition() + wxPoint{attacheePortIsSource ? attacheeNode->GetSize().x : 0, 33 + 20 * attacheePort};
|
|
p[1] = ScreenToClient(wxGetMousePosition());
|
|
dc.DrawSpline(2, p);
|
|
}
|
|
});
|
|
}
|
|
|
|
NodeGraph::~NodeGraph() {
|
|
}
|
|
|
|
void NodeGraph::Alinken(Link l) {
|
|
pthread_mutex_lock(&backendNG->mut);
|
|
|
|
CHiValue newv;
|
|
newv.type = CUTIHI_VAL_LINKED;
|
|
newv.data.linked.to = l.output->logical;
|
|
newv.data.linked.idx = l.o;
|
|
|
|
int err = CHi_ConfigureSink(l.input->logical, l.i, newv);
|
|
|
|
pthread_mutex_unlock(&backendNG->mut);
|
|
|
|
if(!err) {
|
|
((Frame*) GetParent())->stba->SetStatusText("Uh oh! Hur-hur, there's a WAACKY cycle! Can't do, sorry friend.");
|
|
return;
|
|
}
|
|
|
|
for(auto it = links.begin(); it != links.end(); it++) {
|
|
if((*it).input == l.input && (*it).i == l.i) {
|
|
links.erase(it);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ShapeGrNode(l.input);
|
|
|
|
links.push_back(l);
|
|
|
|
Dirtify(l.input);
|
|
Refresh();
|
|
}
|
|
|
|
void NodeGraph::Dirtify(GrNode *g) {
|
|
/*g->logical->clean = 0;
|
|
for(auto &it : links) {
|
|
if(it.output == g) {
|
|
Dirtify(it.input);
|
|
}
|
|
}
|
|
|
|
if(g->logical->type == CUTIHI_T('CPre','view')) {
|
|
if(CHi_Hysteresis(g->logical)) {
|
|
CHiValue *val = CHi_Crawl(&g->logical->sinks[0]);
|
|
|
|
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
|
|
((Frame*) GetParent())->viewer->SetImage(val->data.sample);
|
|
}
|
|
}
|
|
}*/
|
|
|
|
CHi_Hysteresis(gnodes[0]->logical);
|
|
}
|
|
|
|
bool operator==(const NodeGraph::Link &l, const NodeGraph::Link &r) {
|
|
return l.input == r.input && l.i == r.i && l.output == r.output && l.o == r.o;
|
|
}
|
|
static bool dfs(NodeGraph *ng, std::set<GrNode*> &p, GrNode *g) {
|
|
p.insert(g);
|
|
|
|
bool cyclic = false;
|
|
for(const NodeGraph::Link &l : ng->links) {
|
|
if(l.output == g && (std::find(p.begin(), p.end(), g) != p.end() || dfs(ng, p, l.input))) {
|
|
cyclic = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
p.erase(std::find(p.begin(), p.end(), g));
|
|
|
|
return cyclic;
|
|
}
|
|
bool NodeGraph::DetectCycles(GrNode *root) {
|
|
std::set<GrNode*> p{};
|
|
return dfs(this, p, root);
|
|
}
|
|
|
|
void NodeGraph::CreatePreviewNode() {
|
|
GrNode *v = new GrNode(this);
|
|
v->logical = CHi_Preview();
|
|
ShapeGrNode(v);
|
|
|
|
CHi_RegisterNode(backendNG, v->logical);
|
|
gnodes.push_back(v);
|
|
|
|
v->SetPosition(wxPoint{GetSize().x - v->GetSize().x - 8, 8});
|
|
|
|
// The Preview node is actually a NOOP
|
|
// Must inject our own function in Perform
|
|
v->logical->Perform = +[](CHiPubNode *preview){
|
|
CHiValue *val = CHi_Crawl(&preview->sinks[0]);
|
|
|
|
if(val->type == CUTIHI_VAL_SAMPLE && val->data.sample) {
|
|
globaldis->viewer->SetImage(val->data.sample);
|
|
}
|
|
|
|
return 1;
|
|
};
|
|
}
|
|
|
|
GrNode *NodeGraph::get_graphical(CHiPubNode *n) {
|
|
return *std::find_if(gnodes.begin(), gnodes.end(), [=](GrNode *gn){
|
|
return gn->logical == n;
|
|
});
|
|
}
|