512 lines
16 KiB
C++
512 lines
16 KiB
C++
#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 <fstream>
|
|
#include <regex>
|
|
#include <set>
|
|
|
|
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<NodeBrushPresetItem*>(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<Brush>();
|
|
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<std::string> img_brushes;
|
|
std::set<std::string> img_patterns;
|
|
for (auto& c : owner_.m_container->m_children)
|
|
{
|
|
auto bpi = std::static_pointer_cast<NodeBrushPresetItem>(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<Serializer::String>(info_data.author);
|
|
info.props["email"] = std::make_shared<Serializer::String>(info_data.email);
|
|
info.props["url"] = std::make_shared<Serializer::String>(info_data.url);
|
|
info.props["descr"] = std::make_shared<Serializer::String>(info_data.descr);
|
|
info.props["has_header_image"] = std::make_shared<Serializer::Boolean>(has_header_image);
|
|
info.props["num_brush_tips"] = std::make_shared<Serializer::Integer>(img_brushes.size());
|
|
info.props["num_brush_patt"] = std::make_shared<Serializer::Integer>(img_patterns.size());
|
|
info.props["num_brushes"] = std::make_shared<Serializer::Integer>(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<NodeStrokePreview>();
|
|
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<NodeBrushPresetItem>(c);
|
|
pr->m_brush = std::make_shared<Brush>(*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<NodeBrushPresetItem>(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<Serializer::Integer>("num_brush_tips");
|
|
int num_brush_patt = info.value<Serializer::Integer>("num_brush_patt");
|
|
int num_brushes = info.value<Serializer::Integer>("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<Serializer::Boolean>("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<std::string> img_brushes;
|
|
std::set<std::string> 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<std::shared_ptr<Brush>> brushes_to_add;
|
|
if (brushes_count > 0) {
|
|
brushes_to_add.reserve(static_cast<std::size_t>(brushes_count));
|
|
}
|
|
for (int i = 0; i < brushes_count; i++)
|
|
{
|
|
auto b = std::make_shared<Brush>();
|
|
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<NodePanelBrushPreset>(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<NodePanelBrushPreset>(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
|