#include "pch.h" #include "app.h" #include "action.h" #include "node_dialog_open.h" #include "node_dialog_browse.h" #include "node_dialog_resize.h" #include "node_dialog_cloud.h" #include "node_about.h" #include "node_changelog.h" #include "node_usermanual.h" #include "node_dialog_export_ppbr.h" #include #define MP4V2_NO_STDINT_DEFS #include #ifdef __QUEST__ #include "oculus_vr.h" #elif __WEB__ void webgl_pick_file(std::function callback); void webgl_pick_file_save(const std::string& path, const std::string& name, std::function callback); void webgl_sync(); #endif std::shared_ptr App::show_progress(const std::string& title, int total /*= 0*/) { auto pb = std::make_shared(); pb->set_manager(&layout); pb->init(); pb->create(); pb->loaded(); pb->m_progress->SetWidthP(0); pb->m_title->set_text(title.c_str()); pb->m_total = total; pb->m_count = 0; layout[main_id]->add_child(pb); return pb; } std::shared_ptr App::message_box(const std::string &title, const std::string& text, bool cancel_button) { auto m = std::make_shared(); m->set_manager(&layout); m->init(); m->create(); m->loaded(); m->m_title->set_text(title.c_str()); m->m_message->set_text(text.c_str()); m->btn_ok->m_text->set_text("Ok"); if (!cancel_button) m->btn_cancel->destroy(); layout[main_id]->add_child(m); return m; } std::shared_ptr App::input_box(const std::string& title, const std::string& field_name, const std::string& ok_caption /*= "Ok"*/) { auto m = std::make_shared(); m->set_manager(&layout); m->init(); m->create(); m->loaded(); m->m_title->set_text(title.c_str()); m->m_field_name->set_text(field_name.c_str()); m->btn_ok->m_text->set_text(ok_caption.c_str()); layout[main_id]->add_child(m); return m; } void App::dialog_usermanual() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); } void App::dialog_changelog() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); } void App::dialog_about() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); } void App::dialog_newdoc() { auto show_dialog = [this] { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); dialog->input->set_text("name"); layout[main_id]->add_child(dialog); App::I->showKeyboard(); dialog->btn_ok->on_click = [this, dialog](Node*) { std::string name = dialog->input->m_text; std::string path = work_path + "/" + name + ".ppi"; if (name.empty()) { message_box("Warning", "You need to specify a name to file."); return; } auto action = [this, dialog, name, path] { std::array resolutions{ 512, 1024, 1536, 2048, 4096, 8192 }; int res = resolutions[dialog->m_resolution->m_current_index]; doc_name = name; doc_path = path; doc_filename = name + ".ppi"; doc_dir = work_path; layers->clear(); canvas->m_canvas->m_layers.clear(); canvas->m_canvas->resize(res, res); canvas->reset_camera(); ActionManager::clear(); layers->add_layer("Default", false, true); canvas->m_canvas->m_unsaved = true; canvas->m_canvas->m_newdoc = false; title_update(); dialog->destroy(); App::I->hideKeyboard(); }; if (Asset::exist(path)) { // ask confirm is file already exist auto msgbox = new NodeMessageBox(); msgbox->set_manager(&layout); msgbox->init(); msgbox->m_title->set_text("Warning"); msgbox->m_message->set_text("A document with this name already exists, continue?"); msgbox->btn_ok->on_click = [this, msgbox, action](Node*) { action(); msgbox->destroy(); }; layout[main_id]->add_child(msgbox); } else { action(); } }; dialog->btn_cancel->on_click = [this, dialog](Node*) { dialog->destroy(); App::I->hideKeyboard(); }; }; if (canvas) { if (Canvas::I->m_unsaved) { auto m = layout[main_id]->add_child(); m->m_title->set_text("Unsaved document"); m->m_message->set_text("Would you like to save this document before closing?"); m->btn_ok->m_text->set_text("Yes"); m->btn_cancel->m_text->set_text("No"); m->btn_ok->on_click = [this, m, show_dialog](Node*) { Canvas::I->project_save([this, m, show_dialog](bool success){ if (success) show_dialog(); else message_box("Saving Error", "There was a problem saving the document"); }); m->destroy(); }; m->btn_cancel->on_click = [this, m, show_dialog](Node*) { show_dialog(); m->destroy(); }; } else { show_dialog(); } } } // DEPRECATED void App::dialog_open() { auto show_dialog = [this] { // load thumbnail test auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); dialog->btn_ok->on_click = [this, dialog](Node*) { // canvas->reset_camera(); // layers->clear(); // doc_name = dialog->selected_name; // canvas->m_canvas->project_open(dialog->selected_path, [this](bool success) { // // on complete // async_start(); // title_update(); // for (auto& i : canvas->m_canvas->m_order) // layers->add_layer(canvas->m_canvas->m_layers[i]->m_name.c_str()); // async_end(); // }); // dialog->destroy(); // ActionManager::clear(); }; }; if (canvas) { if (Canvas::I->m_unsaved) { auto m = layout[main_id]->add_child(); m->m_title->set_text("Unsaved document"); m->m_message->set_text("Would you like to save this document before closing?"); m->btn_ok->m_text->set_text("Yes"); m->btn_cancel->m_text->set_text("No"); m->btn_ok->on_click = [this,m,show_dialog](Node*){ Canvas::I->project_save([this,m,show_dialog](bool success){ if (success) show_dialog(); else message_box("Saving Error", "There was a problem saving the document"); }); m->destroy(); }; m->btn_cancel->on_click = [this,m,show_dialog](Node*) { show_dialog(); m->destroy(); }; } else { show_dialog(); } } } void App::dialog_browse() { auto show_dialog = [this] { // load thumbnail test auto dialog = std::make_shared(); dialog->set_manager(&layout); #ifdef __IOS__ dialog->search_paths = {work_path, data_path + "/Inbox"}; #else dialog->search_paths = {work_path}; #endif dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); dialog->btn_ok->on_click = [this, dialog](Node*) { if (dialog->is_selected()) { open_document(dialog->selected_path); dialog->destroy(); } }; }; if (canvas) { if (Canvas::I->m_unsaved) { auto m = layout[main_id]->add_child(); m->m_title->set_text("Unsaved document"); m->m_message->set_text("Would you like to save this document before closing?"); m->btn_ok->m_text->set_text("Yes"); m->btn_cancel->m_text->set_text("No"); m->btn_ok->on_click = [this, m, show_dialog](Node*) { Canvas::I->project_save([this, m, show_dialog](bool success){ if (success) show_dialog(); else message_box("Saving Error", "There was a problem saving the document"); }); m->destroy(); }; m->btn_cancel->on_click = [this, m, show_dialog](Node*) { show_dialog(); m->destroy(); }; } else { show_dialog(); } } } void App::dialog_save_ver() { if (!check_license()) { message_box("License", "This function is disabled in demo mode."); return; } int current = 0; std::string next = doc_name + ".01"; std::string base = doc_name; std::regex r(R"((.*)\.(\w{2})$)"); std::smatch m; if (std::regex_search(doc_name, m, r)) { base = m[1].str(); current = atoi(m[2].str().c_str()); } for (int i = current + 1; i < 99; i++) { static char tmp_name[256]; sprintf(tmp_name, "%s.%02d", base.c_str(), i); next = tmp_name; if (Asset::exist(doc_dir + "/" + next + ".ppi")) continue; break; } doc_name = next; doc_path = doc_dir + "/" + next + ".ppi"; canvas->m_canvas->m_unsaved = true; title_update(); canvas->m_canvas->project_save(doc_path); } void App::dialog_save() { if (!check_license()) { message_box("License", "This function is disabled in demo mode."); return; } if (canvas) { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); dialog->input->set_text(doc_name); App::I->showKeyboard(); dialog->btn_ok->on_click = [this, dialog](Node*) { std::string name = dialog->input->m_text; std::string path = work_path + "/" + name + ".ppi"; if (name.empty()) { message_box("Warning", "You need to specify a name to file."); return; } auto action = [this, dialog, name, path] { canvas->m_canvas->project_save(path); doc_name = name; doc_path = path; doc_dir = work_path; title_update(); dialog->destroy(); App::I->hideKeyboard(); }; if (Asset::exist(path)) { // ask confirm is file already exist auto msgbox = new NodeMessageBox(); msgbox->set_manager(&layout); msgbox->init(); msgbox->m_title->set_text("Warning"); msgbox->m_message->set_text(("Are you sure you want to overwrite " + name + "?").c_str()); msgbox->btn_ok->on_click = [this, msgbox, action](Node*) { action(); msgbox->destroy(); }; layout[main_id]->add_child(msgbox); } else { action(); } }; dialog->btn_cancel->on_click = [this, dialog](Node*) { dialog->destroy(); App::I->hideKeyboard(); }; layout[main_id]->add_child(dialog); } } void App::dialog_export(std::string ext) { if (!check_license()) { message_box("License", "This function is disabled in demo mode."); return; } if (canvas) { // TODO: use picker auto path = work_path + "/" + doc_name + ext; auto name = doc_name + ext; canvas->m_canvas->export_equirectangular(path, [this, path, name]{ #if defined(__IOS__) message_box("Export Equirectangular", "Image exported to Photos"); #elif defined(__OSX__) message_box("Export Equirectangular", "Image exported to Pictures/PanoPainter folder"); #elif defined(_WIN32) message_box("Export Equirectangular", "Image exported to " + work_path); #elif defined(__QUEST__) //auto result = ovr_Media_ShareToFacebook("Sharing from PanoPainter on Oculus Quest", path.c_str(), ovrMediaContentType_Photo); #elif __WEB__ ui_task([=]{ webgl_pick_file_save(path, name, [](bool success){ }); }); #endif }); } } void App::dialog_export_layers() { if (!check_license()) { message_box("License", "This function is disabled in demo mode."); return; } if (canvas) { #if defined(__IOS__) auto dir = work_path + "/" + doc_name + "_layers"; if (Asset::create_dir(dir)) { auto p = dir + "/" + doc_name; canvas->m_canvas->export_layers(p, [this, p] { message_box("Export Layers", "Image layers exported to Files/PanoPainter"); }); } #else pick_dir([this](std::string path) { auto p = path + "/" + doc_name; canvas->m_canvas->export_layers(p, [this, p] { message_box("Export Layers", "Layers exported to: " + p); }); }); #endif } } void App::dialog_export_depth() { if (!check_license()) { message_box("License", "This function is disabled in demo mode."); return; } if (canvas) { // TODO: use picker canvas->m_canvas->export_depth(doc_name, [this] { #if defined(__IOS__) message_box("Export 3D View + Depth", "Image and depth exported to Files/PanoPainter"); #elif defined(__OSX__) message_box("Export 3D View + Depth", "Image and depth exported to Pictures/PanoPainter folder"); #elif defined(_WIN32) message_box("Export 3D View + Depth", "Image and depth exported to " + work_path); #endif }); } } void App::dialog_resize() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); dialog->btn_ok->on_click = [this,dialog](Node*) { int res = dialog->get_resolution(); if (canvas) canvas->m_canvas->resize(res, res); App::I->title_update(); ActionManager::clear(); dialog->destroy(); }; } void App::dialog_export_cube_faces() { if (canvas) { canvas->m_canvas->export_cube_faces(doc_name, [this] { #if defined(__IOS__) message_box("Export Cube Faces", "Image and depth exported to Files/PanoPainter"); #elif defined(__OSX__) message_box("Export Cube Faces", "Image and depth exported to Pictures/PanoPainter folder"); #elif defined(_WIN32) message_box("Export Cube Faces", "Image and depth exported to " + work_path); #endif }); } } void App::dialog_layer_rename() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); dialog->input->set_text(layers->m_current_layer->m_label_text); App::I->showKeyboard(); layout[main_id]->add_child(dialog); dialog->btn_ok->on_click = [this,dialog](Node*) { struct ActionLayerRename : public Action { std::string m_old_name; std::string m_new_name; bool m_unsaved; Layer* m_layer; std::shared_ptr m_layer_node; ActionLayerRename(std::string old_name, std::string new_name, std::shared_ptr layer_node, Layer* layer) : m_old_name(old_name), m_new_name(new_name), m_layer_node(layer_node), m_layer(layer) { } virtual void run() override { } virtual size_t memory() override { return 0; } virtual void undo() override { m_layer_node->set_name(m_old_name.c_str()); m_layer->m_name = m_old_name; } virtual Action* get_redo() override { return new ActionLayerRename(m_new_name, m_old_name, m_layer_node, m_layer); } }; auto layer_node = std::static_pointer_cast(layers->m_current_layer->shared_from_this()); auto* layer = canvas->m_canvas->m_layers[canvas->m_canvas->m_current_layer_idx].get(); ActionManager::add(new ActionLayerRename(layers->m_current_layer->m_label_text, dialog->get_name(), layer_node, layer)); layer_node->set_name(dialog->get_name().c_str()); layer->m_name = dialog->get_name(); dialog->destroy(); App::I->hideKeyboard(); }; dialog->btn_cancel->on_click = [this, dialog](Node*) { dialog->destroy(); App::I->hideKeyboard(); }; } void App::dialog_preset_download() { } void App::dialog_ppbr_export() { auto root = layout[main_id]; auto dialog = root->add_child_ref(); dialog->btn_ok->on_click = [this, dialog] (Node*) { NodePanelBrushPreset::PPBRInfo info; info.author = dialog->txt_author->m_text; info.url = dialog->txt_url->m_text; info.email = dialog->txt_email->m_text; info.descr = dialog->txt_descr->m_text; info.header_image = dialog->m_header_image; info.dest_path = dialog->m_dest_path; if (dialog->export_check) info.export_data = dialog->export_check->checked; #if __IOS__ || __WEB__ App::I->pick_file_save("ppbr", "exported-brushes", [this, dialog, info] (std::string path) { presets->export_ppbr(path, info); }, [dialog] (const std::string& path, bool saved) { if (saved) dialog->destroy(); } ); #else App::I->pick_file_save({ "ppbr" }, [this, dialog, info] (std::string path) { std::thread([this, path, dialog, info] { BT_SetTerminate(); presets->export_ppbr(path, info); dialog->destroy(); App::I->message_box("Export PPBR", "Brushes exported to:\n" + path); }).detach(); }); #endif }; } void App::dialog_timelapse_export() { #if __IOS__ || __WEB__ pick_file_save("mp4", doc_name + "-timelapse", [this](std::string path) { rec_export(path); }, [this](const std::string& path, bool saved) { message_box("Export Timelapse", "Timelapse exported succesfully."); } ); #else pick_file_save({ "mp4" }, [this](std::string path) { std::thread([this, path] { BT_SetTerminate(); rec_export(path); message_box("Export Timelapse", "Timelapse exported to: " + path); }).detach(); }); #endif } void App::dialog_export_mp4() { std::thread([this] { const int video_width = 1024; const int video_height = 512; const int video_frames = 500; auto pb = show_progress("Export MP4", video_frames); ISVCEncoder* encoder; int rv = WelsCreateSVCEncoder(&encoder); //SEncParamBase param; //memset(¶m, 0, sizeof(SEncParamBase)); //param.iUsageType = EUsageType::CAMERA_VIDEO_REAL_TIME; //from EUsageType enum //param.fMaxFrameRate = 25; //param.iPicWidth = 400; //param.iPicHeight = 400; //param.iTargetBitrate = 5000000; //param.iRCMode = RC_TIMESTAMP_MODE; //encoder->Initialize(¶m); //Encoder params SEncParamExt param; encoder->GetDefaultParams(¶m); param.iUsageType = CAMERA_VIDEO_REAL_TIME; param.fMaxFrameRate = 25.f; param.iLtrMarkPeriod = 75; param.iPicWidth = 1024; param.iPicHeight = 512; param.iTargetBitrate = 1000 << 10; param.bEnableDenoise = false; param.iSpatialLayerNum = 1; param.bUseLoadBalancing = false; param.bEnableSceneChangeDetect = false; param.bEnableBackgroundDetection = false; param.bEnableAdaptiveQuant = false; param.bEnableFrameSkip = false; param.iMultipleThreadIdc = 0; //param.uiIntraPeriod = 10; for (int i = 0; i < param.iSpatialLayerNum; i++) { param.sSpatialLayers[i].iVideoWidth = param.iPicWidth >> (param.iSpatialLayerNum - 1 - i); param.sSpatialLayers[i].iVideoHeight = param.iPicHeight >> (param.iSpatialLayerNum - 1 - i); param.sSpatialLayers[i].fFrameRate = 25.f; param.sSpatialLayers[i].iSpatialBitrate = param.iTargetBitrate; param.sSpatialLayers[i].uiProfileIdc = PRO_BASELINE; param.sSpatialLayers[i].uiLevelIdc = LEVEL_4_2; param.sSpatialLayers[i].iDLayerQp = 42; //SSliceArgument sliceArg; //sliceArg.uiSliceMode = SM_FIXEDSLCNUM_SLICE; //sliceArg.uiSliceNum = 0; //param.sSpatialLayers[i].sSliceArgument = sliceArg; } param.uiMaxNalSize = 1500; param.iTargetBitrate *= param.iSpatialLayerNum; encoder->InitializeExt(¶m); int trace_level = WELS_LOG_ERROR; encoder->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level); int videoFormat = videoFormatI420; encoder->SetOption(ENCODER_OPTION_DATAFORMAT, &videoFormat); int frameSize = param.iPicWidth * param.iPicHeight * 3 / 2; std::vector buf(frameSize); SFrameBSInfo info; memset(&info, 0, sizeof(SFrameBSInfo)); SSourcePicture pic; memset(&pic, 0, sizeof(SSourcePicture)); pic.iPicWidth = param.iPicWidth; pic.iPicHeight = param.iPicHeight; pic.iColorFormat = videoFormatI420; pic.iStride[0] = pic.iPicWidth; pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1; pic.pData[0] = buf.data(); pic.pData[1] = pic.pData[0] + (param.iPicWidth * param.iPicHeight); pic.pData[2] = pic.pData[1] + (param.iPicWidth * param.iPicHeight >> 2); std::string mp4_path = data_path + "/out.mp4"; MP4FileHandle mp4 = MP4Create(mp4_path.c_str()); MP4TrackId mp4_track = -1; MP4SetTimeScale(mp4, 90000); const int frames = 500; //std::ofstream f("out.h264", std::ios::binary); for (int num = 0; num < frames; num++) { pb->increment(); printf("encoding %.2f%%\r", (float)num / (float)(frames - 1) * 100.f); float value = sinf((float)num / (float)frames * 10.f); for (int y = 0; y < param.iPicHeight; y++) for (int x = 0; x < param.iPicWidth; x++) buf[y * param.iPicWidth + x] = (((y + num / 4) / 10) % 2) * (255.f * value); //prepare input data rv = encoder->EncodeFrame(&pic, &info); //pic.uiTimeStamp += (1.f/25.f) * 1000.f; //assert (rv == cmResultSuccess); if (info.eFrameType != videoFrameTypeSkip) { //output bitstream handling for (int layer = 0; layer < info.iLayerNum; layer++) { size_t bs_size = 0; for (int nal = 0; nal < info.sLayerInfo[layer].iNalCount; nal++) { std::array nalu_bytes; for (int i = 0; i < nalu_bytes.size(); i++) nalu_bytes[i] = info.sLayerInfo[layer].pBsBuf[bs_size + i]; if (nalu_bytes[4] == 0x67) // SPS { uint8_t avc_profile = info.sLayerInfo[layer].pBsBuf[bs_size + 5]; uint8_t avc_profile_compat = info.sLayerInfo[layer].pBsBuf[bs_size + 6]; uint8_t avc_level = info.sLayerInfo[layer].pBsBuf[bs_size + 7]; if (mp4_track == -1) { mp4_track = MP4AddH264VideoTrack(mp4, 90000, 90000 / 25, param.iPicWidth, param.iPicHeight, avc_profile, avc_profile_compat, avc_level, 3); MP4SetVideoProfileLevel(mp4, 1); } MP4AddH264SequenceParameterSet(mp4, mp4_track, info.sLayerInfo[layer].pBsBuf + bs_size + 4, info.sLayerInfo[layer].pNalLengthInByte[nal] - 4); } else if (nalu_bytes[4] == 0x68) // PPS { MP4AddH264PictureParameterSet(mp4, mp4_track, info.sLayerInfo[layer].pBsBuf + bs_size + 4, info.sLayerInfo[layer].pNalLengthInByte[nal] - 4); } else { int nalu_sz = info.sLayerInfo[layer].pNalLengthInByte[nal]; uint8_t* data = info.sLayerInfo[layer].pBsBuf + bs_size; *(uint32_t*)data = BinaryStream::htonx(nalu_sz - 4); bool sync = false; if (nalu_bytes[4] == 0x65) // I-frame sync = true; else // 0x61 P-frame sync = false; MP4WriteSample(mp4, mp4_track, data, nalu_sz, MP4_INVALID_DURATION, 0, sync); } //printf("nalu %x\n", nalu_bytes[4]); bs_size += info.sLayerInfo[layer].pNalLengthInByte[nal]; } //f.write((const char*)info.sLayerInfo[layer].pBsBuf, bs_size); } } } //f.close(); printf("\n"); MP4Close(mp4); if (encoder) { encoder->Uninitialize(); WelsDestroySVCEncoder(encoder); } pb->destroy(); }).detach(); }