#include "pch.h" #include "legacy_brush_preset_services.h" #include "legacy_brush_preset_list_services.h" #include "abr.h" #include "app.h" #include "asset.h" #include "assets/brush_package.h" #include "legacy_brush_package_import_services.h" #include "legacy_ui_overlay_services.h" #include #include #include pp::panopainter::LegacyBrushPresetServices::LegacyBrushPresetServices(NodePanelBrushPreset& owner) noexcept : owner_(owner) { } bool NodePanelBrushPreset::save() { return pp::panopainter::LegacyBrushPresetServices(*this).save(); } bool NodePanelBrushPreset::restore() { return pp::panopainter::LegacyBrushPresetServices(*this).restore(); } bool NodePanelBrushPreset::export_ppbr(const std::string& path_in, const PPBRInfo& info_data) { return pp::panopainter::LegacyBrushPresetServices(*this).export_ppbr(path_in, info_data); } bool NodePanelBrushPreset::import_ppbr(const std::string& path) { return pp::panopainter::LegacyBrushPresetServices(*this).import_ppbr(path); } bool NodePanelBrushPreset::import_abr(const std::string& path) { return pp::panopainter::LegacyBrushPresetServices(*this).import_abr(path); } bool NodePanelBrushPreset::import_brush(const std::string& path) { return pp::panopainter::LegacyBrushPresetServices(*this).import_brush(path); } namespace pp::panopainter { bool LegacyBrushPresetServices::save() { auto path = App::I->data_path + "/settings/presets.bin"; std::ofstream f(path, std::ios::binary); if (f.good()) { BinaryStreamWriter sw; sw.init(); sw.wstring_raw("PPVR"); sw.wu16(0); sw.wu16(1); sw.wu32((int)owner_.m_container->m_children.size()); for (int ci = 0; ci < owner_.m_container->m_children.size(); ci++) { auto bpi = static_cast(owner_.m_container->get_child_at(ci)); sw << *bpi->m_brush; } f.write((char*)sw.m_data.data(), sw.m_data.size()); return true; } return false; } bool LegacyBrushPresetServices::restore() { auto path = App::I->data_path + "/settings/presets.bin"; Asset f; if (f.open(path.c_str())) { f.read_all(); BinaryStreamReader sr; sr.init(f.m_data, f.m_len); auto magic = sr.rstring(4); if (magic != "PPVR") { LOG("PPVR tag not found") return false; } auto vmaj = sr.ru16(); auto vmin = sr.ru16(); if (vmaj != 0 && vmin != 1) { LOG("unrecognised version %d.%d", vmaj, vmin); return false; } auto count = sr.ru32(); for (int k = 0; k < count; k++) { auto b = std::make_shared(); if (!b->read(sr)) { LOG("error deserializing the brush"); return false; } if (b->valid()) { owner_.add_brush(b); } } owner_.m_notification->SetVisibility(owner_.m_container->m_children.size() == 0); return true; } return false; } bool LegacyBrushPresetServices::export_ppbr(const std::string& path_in, const NodePanelBrushPreset::PPBRInfo& info_data) { const auto data_directory_policy = App::I->uses_ppbr_export_data_directory_override() ? pp::assets::PpbrDataDirectoryPolicy::override_directory : pp::assets::PpbrDataDirectoryPolicy::next_to_package; const auto export_paths = pp::assets::plan_ppbr_export_paths( path_in, info_data.dest_path, info_data.export_data, data_directory_policy); if (!export_paths) { LOG("export_ppbr invalid path: %s", export_paths.status().message); return false; } const auto& path = export_paths.value().package_path; LOG("export ppbr to: %s", path.c_str()); const auto& out_path = export_paths.value().data_directory; bool path_created = export_paths.value().data_directory_enabled ? Asset::create_dir(out_path) : false; std::ofstream f(path, std::ios::binary); if (f.good()) { BinaryStreamWriter sw; sw.init(BinaryStream::ByteOrder::LittleEndian); sw.wstring_raw("PPBR"); sw.wu16(0); sw.wu16(1); std::set img_brushes; std::set img_patterns; for (auto& c : owner_.m_container->m_children) { auto bpi = std::static_pointer_cast(c); if (!bpi->m_brush->m_brush_path.empty() && !Asset::is_asset(bpi->m_brush->m_brush_path)) img_brushes.insert(bpi->m_brush->m_brush_path); if (!bpi->m_brush->m_dual_path.empty() && !Asset::is_asset(bpi->m_brush->m_dual_path)) img_brushes.insert(bpi->m_brush->m_dual_path); if (!bpi->m_brush->m_pattern_path.empty() && !Asset::is_asset(bpi->m_brush->m_pattern_path)) img_patterns.insert(bpi->m_brush->m_pattern_path); } Serializer::Descriptor info; info.class_id = "ppbr_info"; info.name = L"info header"; bool has_header_image = info_data.header_image != nullptr; info.props["author"] = std::make_shared(info_data.author); info.props["email"] = std::make_shared(info_data.email); info.props["url"] = std::make_shared(info_data.url); info.props["descr"] = std::make_shared(info_data.descr); info.props["has_header_image"] = std::make_shared(has_header_image); info.props["num_brush_tips"] = std::make_shared(img_brushes.size()); info.props["num_brush_patt"] = std::make_shared(img_patterns.size()); info.props["num_brushes"] = std::make_shared(owner_.m_container->m_children.size()); auto pb = App::I->show_progress("Exporting PPBR", 1 + img_brushes.size() + img_patterns.size() + owner_.m_container->m_children.size() * 2); sw << info; if (has_header_image) { sw << *info_data.header_image; if (path_created) info_data.header_image->save_jpg(out_path + "/header.jpg", 75); } pb->increment(); sw.wu32((int)owner_.m_container->m_children.size()); auto pr = std::make_unique(); pr->m_preview_size = pr->m_size = { 256, 128 }; int thumb_counter = 0; for (auto& c : owner_.m_container->m_children) { auto bpi = std::static_pointer_cast(c); pr->m_brush = std::make_shared(*bpi->m_brush); pr->m_brush->load(); Image img = pr->render_to_image(); img.file_name = pr->m_brush->m_name; sw << img; if (path_created) img.save_jpg(fmt::format(out_path + "/thumb-{:04d}.jpg", thumb_counter), 75); thumb_counter++; pb->increment(); } sw.wu32((int)img_brushes.size()); for (std::string image_path : img_brushes) { Image img; if (!img.load(image_path)) LOG("export_ppbr failed to load image: %s", image_path.c_str()); sw << img; pb->increment(); } sw.wu32((int)img_patterns.size()); for (std::string image_path : img_patterns) { Image img; if (!img.load(image_path)) LOG("export_ppbr failed to load image: %s", image_path.c_str()); sw << img; pb->increment(); } sw.wu32((int)owner_.m_container->m_children.size()); for (auto& c : owner_.m_container->m_children) { auto bpi = std::static_pointer_cast(c); sw << *bpi->m_brush; pb->increment(); } f.write((char*)sw.m_data.data(), sw.m_data.size()); pp::panopainter::close_legacy_dialog_node(*pb); return true; } LOG("export failed file creation"); return false; } bool LegacyBrushPresetServices::import_ppbr(const std::string& path) { BT_SetTerminate(); Asset f; if (f.open(path.c_str())) { f.read_all(); BinaryStreamReader sr; sr.init(f.m_data, f.m_len, BinaryStream::ByteOrder::LittleEndian); auto magic = sr.rstring(4); auto vmaj = sr.ru16(); auto vmin = sr.ru16(); const auto header_status = pp::assets::validate_ppbr_header(magic, vmaj, vmin); if (!header_status.ok()) { LOG("PPBR header rejected: %s (%d.%d)", header_status.message, vmaj, vmin); return false; } Serializer::Descriptor info; sr >> info; int num_brush_tips = info.value("num_brush_tips"); int num_brush_patt = info.value("num_brush_patt"); int num_brushes = info.value("num_brushes"); std::string info_dump = info.str(0, "Info"); LOG("%s", info_dump.c_str()); auto pb = App::I->show_progress("Importing PPBR", 1 + num_brush_patt + num_brush_tips + num_brushes * 2); Image header_image; if (info.value("has_header_image")) sr >> header_image; pb->increment(); auto previews_count = sr.ru32(); for (int i = 0; i < previews_count; i++) { Image img; sr >> img; pb->increment(); } std::set img_brushes; std::set img_patterns; auto tips_count = sr.ru32(); for (int i = 0; i < tips_count; i++) { Image img; sr >> img; const auto target_paths = pp::assets::plan_brush_package_image_target_paths( App::I->data_path, pp::assets::BrushPackageImageKind::brush_tip, img.file_name, img.file_ext); if (!target_paths) { LOG("import_ppbr invalid brush image target: %s", target_paths.status().message); return false; } const auto& image_path = target_paths.value().image_path; const auto& path_thumb = target_paths.value().thumbnail_path; if (!Asset::exist(image_path)) { img.save_png(image_path); auto thumb = img.resize(64, 64); thumb.save_png(path_thumb); } else { LOG("import_ppbr: brush image already exists in %s", image_path.c_str()); } img_brushes.insert(image_path); pb->increment(); } auto patt_count = sr.ru32(); for (int i = 0; i < patt_count; i++) { Image img; sr >> img; const auto target_paths = pp::assets::plan_brush_package_image_target_paths( App::I->data_path, pp::assets::BrushPackageImageKind::pattern, img.file_name, img.file_ext); if (!target_paths) { LOG("import_ppbr invalid pattern image target: %s", target_paths.status().message); return false; } const auto& image_path = target_paths.value().image_path; const auto& path_thumb = target_paths.value().thumbnail_path; if (!Asset::exist(image_path)) { img.save_png(image_path); auto thumb = img.resize(64, 64); thumb.save_png(path_thumb); } else { LOG("import_ppbr: brush image already exists in %s", image_path.c_str()); } pb->increment(); img_patterns.insert(image_path); } auto brushes_count = sr.ru32(); std::vector> brushes_to_add; if (brushes_count > 0) { brushes_to_add.reserve(static_cast(brushes_count)); } for (int i = 0; i < brushes_count; i++) { auto b = std::make_shared(); sr >> *b; b->relocate_paths(App::I->data_path); LOG("import_ppbr brush name %s", b->m_name.c_str()); if (b->valid()) { brushes_to_add.push_back(b); } pb->increment(); } auto owner = std::static_pointer_cast(owner_.shared_from_this()); App::I->ui_task([owner, brushes_to_add = std::move(brushes_to_add), pb]() mutable { for (const auto& b : brushes_to_add) { for (auto p : legacy_brush_preset_panels()) p->add_brush(b); } owner->save(); App::I->stroke->m_brush_popup->reload(); pp::panopainter::close_legacy_dialog_node(*pb); }); return true; } return false; } bool LegacyBrushPresetServices::import_abr(const std::string& path) { BT_SetTerminate(); ABR abr; LOG("ABR detected"); std::string ext; std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return false; ext = m[3].str(); if (!str_iequals(ext, "abr") || !Asset::exist(path)) return false; abr.open(path); auto pb = App::I->show_progress("Importing ABR", abr.m_samples.size() + abr.m_patterns.size() + abr.m_presets.size()); parallel_for(abr.m_samples.size(), [&](size_t i) { auto ii = abr.m_samples.begin(); std::advance(ii, i); const auto& samp = *ii; const auto target_paths = pp::assets::plan_brush_package_image_target_paths( App::I->data_path, pp::assets::BrushPackageImageKind::brush_tip, samp.first, "png"); if (!target_paths) { LOG("import_abr invalid brush image target: %s", target_paths.status().message); return; } const auto& path_high = target_paths.value().image_path; const auto& path_thumb = target_paths.value().thumbnail_path; auto padded = samp.second->resize_squared(glm::u8vec4(255)); samp.second->save_png(path_high); auto thumb = padded.resize(64, 64); thumb.save_png(path_thumb); pb->increment(); }); parallel_for(abr.m_patterns.size(), [&](size_t i) { auto ii = abr.m_patterns.begin(); std::advance(ii, i); const auto& patt = *ii; const auto target_paths = pp::assets::plan_brush_package_image_target_paths( App::I->data_path, pp::assets::BrushPackageImageKind::pattern, patt.first, "png"); if (!target_paths) { LOG("import_abr invalid pattern image target: %s", target_paths.status().message); return; } const auto& path_high = target_paths.value().image_path; const auto& path_thumb = target_paths.value().thumbnail_path; patt.second->save_png(path_high); auto thumb = patt.second->resize(64, 64); thumb.save_png(path_thumb); pb->increment(); }); auto brushes = abr.compute_brushes(App::I->data_path); auto owner = std::static_pointer_cast(owner_.shared_from_this()); App::I->ui_task([owner, brushes = std::move(brushes), pb]() mutable { for (const auto& b : brushes) { if (b->valid()) { LOG("add preset %s", b->m_name.c_str()); for (auto p : legacy_brush_preset_panels()) p->add_brush(b); } pb->increment(); } owner->save(); App::I->stroke->m_brush_popup->reload(); pp::panopainter::close_legacy_dialog_node(*pb); }); return true; } bool LegacyBrushPresetServices::import_brush(const std::string& path) { std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return false; std::string ext = m[3].str(); const auto kind = ext == "ppbr" ? pp::app::BrushPackageImportKind::ppbr : pp::app::BrushPackageImportKind::abr; const auto status = pp::panopainter::execute_legacy_brush_package_import(*App::I, kind, path); if (!status.ok()) { LOG("Brush package import request failed: %s", status.message); return false; } return true; } void LegacyBrushPresetServices::clear_brushes() { owner_.m_container->remove_all_children(); } } // namespace pp::panopainter