diff --git a/data/layout.xml b/data/layout.xml
index 8c29a9b..d70d014 100644
--- a/data/layout.xml
+++ b/data/layout.xml
@@ -37,6 +37,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1944,6 +1984,6 @@ Here's a list of what's available in this release.
-->
-
+
diff --git a/src/app.cpp b/src/app.cpp
index 15cc3d9..6f28b28 100644
--- a/src/app.cpp
+++ b/src/app.cpp
@@ -57,7 +57,7 @@ void App::open_document(std::string path)
if (str_iequals(m[3].str(), "abr"))
{
- std::thread(&NodePanelStroke::import_abr, stroke, path).detach();
+ std::thread(&NodePanelBrushPreset::import_abr, presets, path).detach();
}
else
{
diff --git a/src/app_layout.cpp b/src/app_layout.cpp
index 2db2db9..6092f0b 100644
--- a/src/app_layout.cpp
+++ b/src/app_layout.cpp
@@ -537,7 +537,7 @@ void App::init_menu_file()
if (str_iequals(ext, "abr"))
{
- std::thread([this,path] { stroke->import_abr(path); }).detach();
+ std::thread([this,path] { presets->import_abr(path); }).detach();
}
else
{
diff --git a/src/brush.cpp b/src/brush.cpp
index 1c752ac..73b5998 100644
--- a/src/brush.cpp
+++ b/src/brush.cpp
@@ -373,6 +373,30 @@ bool Brush::valid()
return true;
}
+void Brush::relocate_paths(std::string base)
+{
+ if (!Asset::is_asset(m_brush_path))
+ m_brush_path = replace_path(m_brush_path, base + "/brushes/");
+ if (!Asset::is_asset(m_dual_path))
+ m_dual_path = replace_path(m_dual_path, base + "/brushes/");
+ if (!Asset::is_asset(m_pattern_path))
+ m_pattern_path = replace_path(m_pattern_path, base + "/patterns/");
+}
+
+std::string Brush::replace_path(std::string path, std::string new_base)
+{
+ if (path.empty())
+ return path;
+ std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
+ std::smatch m;
+ if (!std::regex_search(path, m, r))
+ return path;
+ std::string base = m[1].str();
+ std::string name = m[2].str();
+ std::string ext = m[3].str();
+ return new_base + "/" + name + "." + ext;
+}
+
bool Brush::read(BinaryStreamReader& r)
{
Serializer::Descriptor d;
diff --git a/src/brush.h b/src/brush.h
index 6a34128..9e29b8a 100644
--- a/src/brush.h
+++ b/src/brush.h
@@ -102,6 +102,8 @@ public:
bool preload();
void unload();
bool valid();
+ void relocate_paths(std::string base);
+ std::string replace_path(std::string path, std::string new_base);
virtual bool read(BinaryStreamReader& r) override;
virtual void write(BinaryStreamWriter& w) const override;
diff --git a/src/image.cpp b/src/image.cpp
index 28ec5b0..68b569d 100644
--- a/src/image.cpp
+++ b/src/image.cpp
@@ -197,9 +197,9 @@ Image Image::resize_squared(const glm::u8vec4& bg) const
bool Image::read(BinaryStreamReader& r)
{
Serializer::Descriptor d;
+ r >> d;
if (d.class_id != "image_png")
return false;
- r >> d;
d.value("width", width);
d.value("height", height);
d.value("comp", comp);
@@ -230,7 +230,7 @@ void Image::write(BinaryStreamWriter& w) const
stbi_write_png_to_func([](void* context, void* data, int size) {
Serializer::Descriptor& d = *static_cast(context);
d.props["data"] = std::make_shared(std::vector((uint8_t*)data, (uint8_t*)data + size));
- }, &d, width, height, comp, m_data.get(), 0);
+ }, &d, width, height, comp, m_data.get(), 0);
w << d;
}
diff --git a/src/main.cpp b/src/main.cpp
index 69d5c1c..37f33ff 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -288,7 +288,7 @@ std::string win32_save_file(const char* filter)
ofn.lpstrFilter = filter;
ofn.lpstrFile = fileName;
ofn.nMaxFile = MAX_PATH;
- ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;
+ ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt = "";
ofn.lpstrInitialDir = "";
if (GetSaveFileNameA(&ofn) != NULL)
diff --git a/src/node_panel_brush.cpp b/src/node_panel_brush.cpp
index a76c40b..861ed2c 100644
--- a/src/node_panel_brush.cpp
+++ b/src/node_panel_brush.cpp
@@ -469,18 +469,42 @@ void NodePanelBrushPreset::init()
popup->on_select = [this, popup] (Node* target, int index) {
switch (index)
{
- case 0: // download
- break;
- case 1: // import
+ case 0: // import file
App::I->pick_file({"abr", "ppbr"}, [this] (std::string path) {
std::thread([this, path] {
BT_SetTerminate();
- App::I->stroke->import_abr(path);
- m_notification->SetVisibility(m_container->m_children.size() == 0);
+
+ std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
+ std::smatch m;
+ if (!std::regex_search(path, m, r))
+ return;
+ std::string base = m[1].str();
+ std::string name = m[2].str();
+ std::string ext = m[3].str();
+
+ if (ext == "ppbr")
+ {
+ import_ppbr(path);
+ }
+ else
+ {
+ import_abr(path);
+ }
+
+ App::I->ui_task([this] {
+ m_notification->SetVisibility(m_container->m_children.size() == 0);
+ });
}).detach();
});
break;
- case 2: // export
+ case 1: // export file
+ App::I->pick_file_save({ "ppbr" }, [this] (std::string path) {
+ export_ppbr(path, {});
+ });
+ break;
+ case 2: // download
+ break;
+ case 3: // upload
break;
}
popup->destroy();
@@ -491,7 +515,7 @@ void NodePanelBrushPreset::init()
App::I->pick_file({ "abr", "ppbr" }, [this](std::string path) {
std::thread([this, path] {
BT_SetTerminate();
- App::I->stroke->import_abr(path);
+ import_abr(path);
m_notification->SetVisibility(m_container->m_children.size() == 0);
}).detach();
});
@@ -555,7 +579,8 @@ void NodePanelBrushPreset::handle_click(Node* target)
bool NodePanelBrushPreset::save()
{
- std::ofstream f(App::I->data_path + "/settings/presets.bin", std::ios::binary);
+ auto path = App::I->data_path + "/settings/presets.bin";
+ std::ofstream f(path, std::ios::binary);
if (f.good())
{
BinaryStreamWriter sw;
@@ -577,8 +602,8 @@ bool NodePanelBrushPreset::save()
bool NodePanelBrushPreset::restore()
{
- Asset f;
auto path = App::I->data_path + "/settings/presets.bin";
+ Asset f;
if (f.open(path.c_str()))
{
f.read_all();
@@ -587,10 +612,11 @@ bool NodePanelBrushPreset::restore()
sr.init(f.m_data, f.m_len);
// sanity checks
- if (sr.rstring(4) != "PPVR")
+ auto magic = sr.rstring(4);
+ if (magic != "PPVR")
{
LOG("PPVR tag not found")
- return false;
+ return false;
}
auto vmaj = sr.ru16();
auto vmin = sr.ru16();
@@ -613,19 +639,21 @@ bool NodePanelBrushPreset::restore()
if (b->valid())
{
- NodeBrushPresetItem* brush = new NodeBrushPresetItem;
- m_container->add_child(brush);
- brush->init();
- brush->create();
- brush->loaded();
- brush->thumb_path = b->m_brush_thumb_path;
- brush->high_path = b->m_brush_path;
- brush->m_brush = b;
- brush->m_preview->m_brush = b;
- brush->m_preview->draw_stroke();
- brush->m_caption_size->set_text_format("%d", (int)b->m_tip_size);
- brush->m_thumb->set_image(brush->m_brush->m_brush_thumb_path);
- brush->on_click = std::bind(&NodePanelBrushPreset::handle_click, this, std::placeholders::_1);
+ App::I->ui_task([this, b] {
+ NodeBrushPresetItem* brush = new NodeBrushPresetItem;
+ m_container->add_child(brush);
+ brush->init();
+ brush->create();
+ brush->loaded();
+ brush->thumb_path = b->m_brush_thumb_path;
+ brush->high_path = b->m_brush_path;
+ brush->m_brush = b;
+ brush->m_preview->m_brush = b;
+ brush->m_preview->draw_stroke();
+ brush->m_caption_size->set_text_format("%d", (int)b->m_tip_size);
+ brush->m_thumb->set_image(brush->m_brush->m_brush_thumb_path);
+ brush->on_click = std::bind(&NodePanelBrushPreset::handle_click, this, std::placeholders::_1);
+ });
}
}
m_notification->SetVisibility(m_container->m_children.size() == 0);
@@ -654,6 +682,268 @@ void NodePanelBrushPreset::add_brush(std::shared_ptr brush)
m_notification->SetVisibility(m_container->m_children.size() == 0);
}
+bool NodePanelBrushPreset::export_ppbr(const std::string& path, const Image& header_image)
+{
+ 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);
+
+ // header image
+ sw << header_image;
+
+ // create previews
+ sw.wu32((int)m_container->m_children.size());
+ auto pr = std::make_unique();
+ pr->m_preview_size = pr->m_size = { 256, 128 };
+ for (auto& c : m_container->m_children)
+ {
+ auto bpi = std::static_pointer_cast(c);
+ pr->m_brush = std::make_shared(*bpi->m_brush); // create copy
+ pr->m_brush->load();
+ Image img = pr->render_to_image();
+ img.file_name = pr->m_brush->m_name;
+ sw << img;
+ }
+
+ // list of images
+ std::set img_brushes;
+ std::set img_patterns;
+ for (auto& c : 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);
+ }
+
+ // write brushes
+ 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;
+ }
+
+ // write patterns
+ 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;
+ }
+
+ // write brush settings
+ sw.wu32((int)m_container->m_children.size());
+ for (auto& c : m_container->m_children)
+ {
+ auto bpi = std::static_pointer_cast(c);
+ sw << *bpi->m_brush;
+ }
+ f.write((char*)sw.m_data.data(), sw.m_data.size());
+ return true;
+ }
+ return false;
+}
+
+bool NodePanelBrushPreset::import_ppbr(const std::string& path)
+{
+ Asset f;
+ if (f.open(path.c_str()))
+ {
+ f.read_all();
+
+ BinaryStreamReader sr;
+ sr.init(f.m_data, f.m_len, BinaryStream::ByteOrder::LittleEndian);
+
+ // sanity checks
+ auto magic = sr.rstring(4);
+ if (magic != "PPBR")
+ {
+ LOG("PPBR 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;
+ }
+
+ // header image
+ Image header_image;
+ sr >> header_image;
+
+ // stroke previews
+ auto previews_count = sr.ru32();
+ for (int i = 0; i < previews_count; i++)
+ {
+ Image img;
+ sr >> img;
+ }
+
+ // list of images
+ std::set img_brushes;
+ std::set img_patterns;
+
+ // brush tips
+ auto tips_count = sr.ru32();
+ for (int i = 0; i < tips_count; i++)
+ {
+ Image img;
+ sr >> img;
+ std::string path = App::I->data_path + "/brushes/" + img.file_name + "." + img.file_ext;
+ if (!Asset::exist(path))
+ {
+ img.save_png(path);
+ }
+ else
+ {
+ LOG("import_ppbr: brush image already exists in %s", path.c_str());
+ }
+ img_brushes.insert(path);
+ }
+
+ // brush patterns
+ auto patt_count = sr.ru32();
+ for (int i = 0; i < patt_count; i++)
+ {
+ Image img;
+ sr >> img;
+ std::string path = App::I->data_path + "/patterns/" + img.file_name + "." + img.file_ext;
+ if (!Asset::exist(path))
+ {
+ img.save_png(path);
+ }
+ else
+ {
+ LOG("import_ppbr: brush image already exists in %s", path.c_str());
+ }
+ img_patterns.insert(path);
+ }
+
+ // brush settings
+ auto brushes_count = sr.ru32();
+ 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())
+ {
+ NodeBrushPresetItem* brush = new NodeBrushPresetItem;
+ m_container->add_child(brush);
+ brush->init();
+ brush->create();
+ brush->loaded();
+ brush->thumb_path = b->m_brush_thumb_path;
+ brush->high_path = b->m_brush_path;
+ brush->m_brush = b;
+ brush->m_preview->m_brush = b;
+ brush->m_preview->draw_stroke();
+ brush->m_caption_size->set_text_format("%d", (int)b->m_tip_size);
+ brush->m_thumb->set_image(brush->m_brush->m_brush_thumb_path);
+ brush->on_click = std::bind(&NodePanelBrushPreset::handle_click, this, std::placeholders::_1);
+ }
+ }
+
+ return true;
+ }
+ return false;
+}
+
+bool NodePanelBrushPreset::import_abr(const std::string& path)
+{
+ BT_SetTerminate();
+
+ ABR abr;
+ LOG("ABR detected");
+
+ std::string name, base, ext;
+ std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
+ std::smatch m;
+ if (!std::regex_search(path, m, r))
+ return false;
+ base = m[1].str();
+ name = m[2].str();
+ ext = m[3].str();
+
+ if (!str_iequals(ext, "abr") || !Asset::exist(path))
+ return false;
+
+ auto pb = App::I->show_progress("Importing ABR");
+
+ abr.open(path);
+
+ int tot = (int)(abr.m_samples.size() + abr.m_patterns.size() + abr.m_presets.size());
+ std::atomic_int count(0);
+
+ parallel_for(abr.m_samples.size(), [&](size_t i)
+ //for (const auto& samp : abr.m_samples)
+ {
+ auto ii = abr.m_samples.begin();
+ std::advance(ii, i);
+ const auto& samp = *ii;
+ std::string path_high = App::I->data_path + "/brushes/" + samp.first + ".png";
+ std::string path_thumb = App::I->data_path + "/brushes/thumbs/" + samp.first + ".png";
+ auto padded = samp.second->resize_squared(glm::u8vec4(255));
+ //auto high = padded.resize_power2();
+ //high.save(path_high);
+ samp.second->save_png(path_high);
+ auto thumb = padded.resize(64, 64);
+ thumb.save_png(path_thumb);
+
+ count++;
+ pb->set_progress((float)count / (float)tot);
+ });
+
+ parallel_for(abr.m_patterns.size(), [&](size_t i)
+ //for (const auto& patt : abr.m_patterns)
+ {
+ auto ii = abr.m_patterns.begin();
+ std::advance(ii, i);
+ const auto& patt = *ii;
+ std::string path_high = App::I->data_path + "/patterns/" + patt.first + ".png";
+ std::string path_thumb = App::I->data_path + "/patterns/thumbs/" + patt.first + ".png";
+ patt.second->save_png(path_high);
+ auto thumb = patt.second->resize(64, 64);
+ thumb.save_png(path_thumb);
+
+ count++;
+ pb->set_progress((float)count / (float)tot);
+ });
+
+ auto brushes = abr.compute_brushes(App::I->data_path);
+ for (const auto& pr : brushes)
+ {
+ if (pr->valid())
+ {
+ LOG("add preset %s", pr->m_name.c_str());
+ App::I->presets->add_brush(pr);
+ }
+ count++;
+ pb->set_progress((float)count / (float)tot);
+ }
+
+ App::I->presets->save();
+ pb->destroy();
+
+ return true;
+}
+
void NodePanelBrushPreset::clear_brushes()
{
m_container->remove_all_children();
diff --git a/src/node_panel_brush.h b/src/node_panel_brush.h
index 9204c6d..34263d8 100644
--- a/src/node_panel_brush.h
+++ b/src/node_panel_brush.h
@@ -98,6 +98,10 @@ public:
bool save();
bool restore();
void add_brush(std::shared_ptr brush);
+ bool export_ppbr(const std::string& path, const Image& header_image);
+ bool import_ppbr(const std::string& path);
+ bool import_abr(const std::string& path);
+ std::string replace_path(std::string path, std::string new_base);
void clear_brushes();
};
diff --git a/src/node_panel_stroke.cpp b/src/node_panel_stroke.cpp
index 8fb93f4..a571e79 100644
--- a/src/node_panel_stroke.cpp
+++ b/src/node_panel_stroke.cpp
@@ -23,110 +23,6 @@ void NodePanelStroke::init()
init_controls();
}
-bool NodePanelStroke::import_abr(const std::string& path)
-{
- BT_SetTerminate();
-
- ABR abr;
- LOG("ABR detected");
-
- std::string name, base, ext;
- std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)");
- std::smatch m;
- if (!std::regex_search(path, m, r))
- return false;
- base = m[1].str();
- name = m[2].str();
- ext = m[3].str();
-
- if (!str_iequals(ext, "abr") || !Asset::exist(path))
- return false;
-
- auto pb = App::I->show_progress("Importing ABR");
-
- abr.open(path);
-
- int tot = (int)(abr.m_samples.size() + abr.m_patterns.size() + abr.m_presets.size());
- std::atomic_int count(0);
-
- parallel_for(abr.m_samples.size(), [&](size_t i)
- //for (const auto& samp : abr.m_samples)
- {
- auto ii = abr.m_samples.begin();
- std::advance(ii, i);
- const auto& samp = *ii;
- std::string path_high = App::I->data_path + "/brushes/" + samp.first + ".png";
- std::string path_thumb = App::I->data_path + "/brushes/thumbs/" + samp.first + ".png";
- auto padded = samp.second->resize_squared(glm::u8vec4(255));
- //auto high = padded.resize_power2();
- //high.save(path_high);
- samp.second->save_png(path_high);
- auto thumb = padded.resize(64, 64);
- thumb.save_png(path_thumb);
-
- NodeButtonBrush* brush = new NodeButtonBrush;
- m_brush_popup->m_container->add_child(brush);
- brush->init();
- brush->create();
- brush->loaded();
- brush->set_icon(path_thumb.c_str());
- brush->thumb_path = path_thumb;
- brush->high_path = path_high;
- brush->brush_name = name;
- brush->m_user_brush = true;
- brush->on_click = std::bind(&NodePanelBrush::handle_click, m_brush_popup, std::placeholders::_1);
- count++;
- pb->set_progress((float)count / (float)tot);
- });
- m_brush_popup->save();
-
- parallel_for(abr.m_patterns.size(), [&](size_t i)
- //for (const auto& patt : abr.m_patterns)
- {
- auto ii = abr.m_patterns.begin();
- std::advance(ii, i);
- const auto& patt = *ii;
- std::string path_high = App::I->data_path + "/patterns/" + patt.first + ".png";
- std::string path_thumb = App::I->data_path + "/patterns/thumbs/" + patt.first + ".png";
- patt.second->save_png(path_high);
- auto thumb = patt.second->resize(64, 64);
- thumb.save_png(path_thumb);
-
- NodeButtonBrush* brush = new NodeButtonBrush;
- m_pattern_popup->m_container->add_child(brush);
- brush->init();
- brush->create();
- brush->loaded();
- brush->set_icon(path_thumb.c_str());
- brush->thumb_path = path_thumb;
- brush->high_path = path_high;
- brush->brush_name = name;
- brush->m_user_brush = true;
- brush->on_click = std::bind(&NodePanelBrush::handle_click, m_pattern_popup, std::placeholders::_1);
- count++;
- pb->set_progress((float)count / (float)tot);
- });
- m_pattern_popup->save();
-
- auto brushes = abr.compute_brushes(App::I->data_path);
- for (const auto& pr : brushes)
- {
- if (pr->valid())
- {
- LOG("add preset %s", pr->m_name.c_str());
- App::I->presets->add_brush(pr);
- }
- count++;
- pb->set_progress((float)count / (float)tot);
- }
-
- App::I->presets->save();
- pb->destroy();
- //save();
-
- return true;
-}
-
void NodePanelStroke::update_controls()
{
const auto& b = Canvas::I->m_current_brush;
diff --git a/src/node_panel_stroke.h b/src/node_panel_stroke.h
index b086d0a..5705b37 100644
--- a/src/node_panel_stroke.h
+++ b/src/node_panel_stroke.h
@@ -109,7 +109,6 @@ public:
virtual void init() override;
virtual kEventResult handle_event(Event* e) override;
- bool import_abr(const std::string& path);
void init_controls();
void update_controls();