diff --git a/android/src/main/java/com/omixlab/panopainter/MainActivity.java b/android/src/main/java/com/omixlab/panopainter/MainActivity.java index ecad1de..41715cf 100644 --- a/android/src/main/java/com/omixlab/panopainter/MainActivity.java +++ b/android/src/main/java/com/omixlab/panopainter/MainActivity.java @@ -74,6 +74,7 @@ public class MainActivity extends NativeActivity { Log.v("PanoPainterJava", "create path failed: " + frames.getAbsolutePath()); } + // brushes File brushes = new File(pano_dir.getAbsolutePath(), "brushes"); if (!brushes.exists()) { @@ -92,6 +93,26 @@ public class MainActivity extends NativeActivity { Log.v("PanoPainterJava", "create path failed: " + brush_thumbs.getAbsolutePath()); } + // patterns + File patterns = new File(pano_dir.getAbsolutePath(), "brushes"); + if (!patterns.exists()) + { + if (patterns.mkdirs()) + Log.v("PanoPainterJava", "create path " + patterns.getAbsolutePath()); + else + Log.v("PanoPainterJava", "create path failed: " + patterns.getAbsolutePath()); + + } + File patterns_thumbs = new File(patterns.getAbsolutePath(), "thumbs"); + if (!patterns_thumbs.exists()) + { + if (patterns_thumbs.mkdirs()) + Log.v("PanoPainterJava", "create path " + patterns_thumbs.getAbsolutePath()); + else + Log.v("PanoPainterJava", "create path failed: " + patterns_thumbs.getAbsolutePath()); + + } + // settings File settings = new File(pano_dir.getAbsolutePath(), "settings"); if (!settings.exists()) { diff --git a/src/abr.cpp b/src/abr.cpp index 5514549..fc94439 100644 --- a/src/abr.cpp +++ b/src/abr.cpp @@ -4,15 +4,24 @@ bool ABR::section_desc() { auto sz = align4(ru32()); - auto n1 = ri32(); // some integer - auto s = rwstring(); // maybe a string + auto n1 = ri32(); // some integer + auto s = rwstring(); // maybe a string auto null = rkey_or_string(); - auto null_val = ri32(); // integer following the null - auto name = rkey_or_string(); - auto list = rstring(4); - auto list_val = call(list); - auto out = list_val->str(0, ""); - std::cout << out; + auto null_val = ri32(); // integer following the null + + // presets list + auto name = rkey_or_string(); // "Brsh" + auto list = rstring(4); // "VlLs" + if (auto presets = std::dynamic_pointer_cast(call(list))) + { + for (auto const& pr : presets->items) + { + if (auto desc = std::dynamic_pointer_cast(pr)) + m_presets.push_back(desc); + } + auto out = presets->str(0, ""); + std::cout << out << '\n'; + } return true; } @@ -25,30 +34,16 @@ bool ABR::section_samp() { auto samp_size = ru32(); auto uid = rpascal(); - printf("sample brush %s\n", uid.c_str()); - skip(4); - auto image = parse_vmem(); - if (image->channels.size() >= 3) + //printf("sample brush %s\n", uid.c_str()); + skip(4); // unknown bytes usually 00 01 00 00 + auto vm = parse_vmem(); + if (vm) { - std::vector out((image->channels[0].depth >> 3) * image->rect.area()); - for (int ch = 0; ch < 3; ch++) + if (auto img = vm->image(true)) { - auto const& raw = image->channels[ch].data; - for (int i = 0; i < raw.size(); i++) - out[i][ch] = raw[i]; + // TODO: check if uid already exists in map + m_samples[uid] = img; } - stbi_write_png(fmt::format("x64/out/{}.png", uid).c_str(), - image->rect.width(), image->rect.height(), 3, out.data(), 0); - } - else if (image->channels.size() == 1) - { - stbi_write_png(fmt::format("x64/out/{}.png", uid).c_str(), - image->rect.width(), image->rect.height(), 1, - std::data(image->channels[0].data), 0); - } - else - { - printf("Error cannot read image of %d channels\n", image->channels.size()); } } return true; @@ -68,7 +63,7 @@ bool ABR::section_patt() auto image_mode = ru32(); if (!(image_mode == 1 || image_mode == 3)) { - printf("skip image mode %d\n", image_mode); + printf("PATT: skip image mode %d\n", image_mode); skip(patt_length - 8); snap(); continue; @@ -82,48 +77,94 @@ bool ABR::section_patt() // read(...); // no worries indexed is skipped anyway - auto image = parse_vmem(); - if (image_mode == 3) + auto vm = parse_vmem(); + if (vm) { - if (image->channels.size() >= 3) + int nc = std::min((int)vm->channels.size(), 3); + if (nc != image_mode) { - std::vector out((image->channels[0].depth >> 3) * image->rect.area()); - for (int ch = 0; ch < 3; ch++) - { - auto const& raw = image->channels[ch].data; - for (int i = 0; i < raw.size(); i++) - out[i][ch] = raw[i]; - } - stbi_write_png(fmt::format("x64/out/{}.png", uid).c_str(), - image->rect.width(), image->rect.height(), 3, out.data(), 0); + printf("PATT: image_mode (%d) and number of channels (%d) not matching\n", + image_mode, vm->channels.size()); } - else + if (auto img = vm->image(true)) { - printf("Error image mode is 3 but channels are only %d\n", image->channels.size()); + // TODO: check if uid already exists in map + m_patterns[uid] = img; } } - else if (image_mode == 1) - { - stbi_write_png(fmt::format("x64/out/{}.png", uid).c_str(), - image->rect.width(), image->rect.height(), 1, - std::data(image->channels[0].data), 0); - } } return true; } -std::shared_ptr ABR::parse_vmem() +std::vector> ABR::compute_brushes(const std::string& path) +{ + std::vector> ret; + for (auto const& p : m_presets) + { + auto samp = p->get("Brsh"); + if (samp->class_id != "sampledBrush") + continue; + auto b = std::make_shared(); + b->m_name = wstr2str(p->value("Nm ")); + //b->m_tip_color = i.m_tip_color; + b->m_tip_size = samp->value("Dmtr") / (800.f * 4.f); + b->m_tip_spacing = samp->value("Spcn") * 0.01f; + b->m_tip_flow = .25f; + b->m_tip_opacity = 1.f; + b->m_tip_angle = glm::radians(samp->value("Angl")); + //b->m_tip_mix = i.m_tip_mix; + //b->m_tip_stencil = i.m_tip_stencil; + b->m_tip_wet = p->value("Wtdg"); + b->m_tip_noise = (float)samp->value("Nose"); + //b->m_tip_hue = i.m_tip_hue; + //b->m_tip_sat = i.m_tip_sat; + //b->m_tip_val = i.m_tip_val; + //b->m_tip_angle_follow = i.m_tip_angle_follow; + //b->m_tip_flow_pressure = i.m_tip_flow_pressure; + //b->m_tip_size_pressure = i.m_tip_size_pressure; + //b->m_tip_hue_pressure = i.m_tip_hue_pressure; + //b->m_tip_sat_pressure = i.m_tip_sat_pressure; + //b->m_tip_val_pressure = i.m_tip_val_pressure; + //b->m_jitter_scale = i.m_jitter_scale; + //b->m_jitter_angle = i.m_jitter_angle; + //b->m_jitter_spread = i.m_jitter_spread; + //b->m_jitter_flow = i.m_jitter_flow; + //b->m_jitter_hue = i.m_jitter_hue; + //b->m_jitter_sat = i.m_jitter_sat; + //b->m_jitter_val = i.m_jitter_val; + //b->m_blend_mode = i.m_blend_mode; + //b->m_name.resize(i.m_name_len); + //b->m_stencil_path.resize(i.m_stencil_path_len); + auto& tip_uid = wstr2str(samp->value("sampledData")); + b->m_brush_path = path + "/brushes/" + tip_uid + ".png"; + b->m_brush_thumb_path = path + "/brushes/thumbs/" + tip_uid + ".png"; + if (auto patt = p->get("Txtr")) + { + auto& patt_uid = wstr2str(patt->value("Idnt")); + b->m_stencil_path = path + "/patterns/" + patt_uid + ".png"; + //b->m_brush_thumb_path = path + "/patterns/thumbs/" + patt_uid + ".png"; + b->m_tip_stencil = p->value("textureDepth") * 0.01f; + } + ret.push_back(b); + } + return ret; +} + +std::shared_ptr ABR::parse_vmem() { // Virtual Memory Array List auto vmem_version = ru32(); // = 3 + assert(vmem_version == 3); auto vmem_length = ru32(); + // TODO: check if at the end there's good data + // check if the bounds are within the parent's size auto vmem_rect = rrect(); auto vmem_channels = ru32(); // The following is a virtual memory array, // repeated for the number of channels // + one for a user mask + one for a sheet mask. vmem_channels += 2; // user and sheet mask - auto ret = std::make_shared(vmem_version, vmem_rect); + auto ret = std::make_shared(vmem_version, vmem_rect); for (int ch = 0; ch < vmem_channels; ch++) { auto array_written = ru32(); // skip if 0 @@ -200,14 +241,6 @@ std::shared_ptr ABR::parse_text() return ret; } -//ABR::Type::Ref ABR::parse_prop() -//{ -// auto name = rstring(4); -// //printf("prop type %s\n", t.c_str()); -// if (!call(name)) -// return false; -//} - std::shared_ptr ABR::parse_objc() { auto ret = std::make_shared(); @@ -297,7 +330,6 @@ ABR::ABR() bool ABR::open(const std::string& path) { - m_parser_table[""] = std::bind(&ABR::parse_bool, this); Asset asset; if (asset.open(path.c_str())) { @@ -328,6 +360,7 @@ bool ABR::open(const std::string& path) skip(align4(ru32())); } } + return true; } return false; } diff --git a/src/abr.h b/src/abr.h index 4d4e4f0..e6e91aa 100644 --- a/src/abr.h +++ b/src/abr.h @@ -3,6 +3,8 @@ #include #include #include "util.h" +#include "image.h" +#include "brush.h" class BinaryStream { @@ -66,7 +68,11 @@ public: auto ptr = advance(len * 2); for (int i = 0; i < len; i++) std::swap(ptr[i * 2], ptr[i * 2 + 1]); - return std::wstring((wchar_t*)ptr, len); + // right trim trailing zeroes + auto wptr = (wchar_t*)ptr; + for (int i = len - 1; i >= 0; i--) + if (wptr[i] == 0) len--; + return std::wstring(wptr, len); } std::string rpascal() { @@ -194,9 +200,9 @@ private: ByteOrder m_byte_order = ByteOrder::Host; }; -class ABR : public BinaryStream +class ABR : private BinaryStream { - struct Type + struct Type { using Vec = std::vector>; using Map = std::map>; @@ -222,19 +228,22 @@ class ABR : public BinaryStream }; struct Double : public Type { + using native_type = double; double value; virtual std::string str(int indent, const std::string& prefix) const override { return std::string(indent, '-') + prefix + fmt::format("double: {}", value); } }; struct UnitFloat : public Type { + using native_type = double; std::string unit; double value; virtual std::string str(int indent, const std::string& prefix) const override { return std::string(indent, '-') + prefix + fmt::format("float: {} ({})", value, unit); } }; struct String : public Type - { + { + using native_type = std::wstring; std::wstring value; virtual std::string str(int indent, const std::string& prefix) const override { return std::string(indent, '-') + prefix + fmt::format("string: {}", wstr2str(value)); } @@ -253,13 +262,15 @@ class ABR : public BinaryStream struct Name : public Type { }; struct Integer : public Type { - int32_t value; + using native_type = int32_t; + int32_t value; virtual std::string str(int indent, const std::string& prefix) const override { return std::string(indent, '-') + prefix + fmt::format("int: {}", value); } }; struct LargeInteger : public Type { }; struct Boolean : public Type - { + { + using native_type = bool; bool value; virtual std::string str(int indent, const std::string& prefix) const override { return std::string(indent, '-') + prefix + fmt::format("bool: {}", value); } @@ -278,12 +289,37 @@ class ABR : public BinaryStream Type::Map props; virtual std::string str(int indent, const std::string& prefix) const override { - auto ret = std::string(indent, '-') + + auto ret = std::string(indent, '-') + prefix + fmt::format("objc {} ({}): {} props:", wstr2str(name), class_id, props.size()); for (const auto& p : props) ret += "\n" + p.second->str(indent + 1, fmt::format("'{}' ", p.first)); return ret; } + bool has(const std::string& key) const + { + return props.find(key) != props.end(); + } + template std::shared_ptr get(const std::string& key) const + { + return has(key) ? std::dynamic_pointer_cast(props.at(key)) : nullptr; + } + template auto value(const std::string& key) const + { + if (auto v = get(key)) + return v->value; + return decltype(T::value){}; + } + template auto value_or(const std::string& key, const D val) const + { + if (auto v = get(key)) + return v->value; + return val; + } + template void value(const std::string& key, D& dest) const + { + if (auto v = get(key)) + dest = static_cast(v->value); + } }; struct Rectangle : public Type { @@ -314,20 +350,53 @@ class ABR : public BinaryStream Channel(uint32_t depth, const Rectangle& rect, uint8_t compression, std::vector& data) : depth(depth), rect(rect), compression(compression), data(std::move(data)) { } }; - struct Image : public Type + struct VMArray : public Type { uint32_t version; // = 3 Rectangle rect; std::vector channels; - Image() = default; - Image(uint32_t version, const Rectangle& rect) : - version(version), rect(rect) { } + VMArray() = default; + VMArray(uint32_t version, const Rectangle& rect) : version(version), rect(rect) { } + std::shared_ptr image(bool grayscale) const + { + int nc = channels.size(); + auto pixels = (channels[0].depth >> 3) * rect.area(); + if (nc == 1 || nc >= 3) + { + auto img = std::make_shared(); + img->comp = 4; + img->width = rect.width(); + img->height = rect.height(); + img->m_data = std::make_unique(pixels * 4); + auto out = reinterpret_cast(img->m_data.get()); + if (grayscale) + { + auto const& raw = channels[0].data; + for (int i = 0; i < raw.size(); i++) + out[i] = glm::u8vec4(glm::u8vec3(255 - raw[i]), 255); + } + else + { + std::fill_n(out, pixels, glm::u8vec4(255)); + for (int ch = 0; ch < std::min(nc, 3); ch++) + { + auto const& raw = channels[ch].data; + for (int i = 0; i < raw.size(); i++) + out[i][ch] = 255 - raw[i]; + } + } + return img; + //stbi_write_png(fmt::format("x64/out/{}.png", uid).c_str(), + // image->rect.width(), image->rect.height(), 4, out.data(), 0); + } + else + { + printf("Error image with %d channels\n", channels.size()); + } + return nullptr; + } }; -public: - ABR(); - bool open(const std::string& path); -private: std::string rkey_or_string() { auto len = ru32(); @@ -351,11 +420,22 @@ private: ret.y = ru16(); return ret; } + + Type::Ref call(std::string t) + { + if (m_parser_table.find(t) != m_parser_table.end()) + { + auto& method = m_parser_table[t]; + return method(); + } + return nullptr; + } + bool section_desc(); bool section_samp(); bool section_patt(); - // Parse Virtual Memory Array List - std::shared_ptr parse_vmem(); + + std::shared_ptr parse_vmem(); // Parse Virtual Memory Array List std::shared_ptr parse_vlls(); std::shared_ptr parse_text(); std::shared_ptr parse_objc(); @@ -365,20 +445,16 @@ private: std::shared_ptr parse_doub(); std::shared_ptr parse_enum(); std::shared_ptr parse_tdta(); - Type::Ref call(std::string t) - { - if (m_parser_table.find(t) != m_parser_table.end()) - { - auto& method = m_parser_table[t]; - return method(); - } - //else if (m_parser_table.find(pick(4)) != m_parser_table.end()) - //{ - // auto& method = m_parser_table[rstring(4)]; - // return method(); - //} - return nullptr; - } - std::map> m_parser_table; + + std::map> m_parser_table; + std::vector> m_presets; + +public: + std::map> m_patterns; + std::map> m_samples; + + ABR(); + bool open(const std::string& path); + std::vector> compute_brushes(const std::string& path); }; diff --git a/src/app.cpp b/src/app.cpp index 0e448ed..05ac221 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -134,15 +134,25 @@ void App::initLog() { LOG("error creating rec path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); } - + // brushes if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/brushes"] withIntermediateDirectories:YES attributes:nil error:&err]) { LOG("error creating brushes path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); } if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/brushes/thumbs"] withIntermediateDirectories:YES attributes:nil error:&err]) { - LOG("error creating thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); + LOG("error creating brushes thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); } + // patterns + if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/patterns"] withIntermediateDirectories:YES attributes:nil error:&err]) + { + LOG("error creating patterns path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); + } + if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/patterns /thumbs"] withIntermediateDirectories:YES attributes:nil error:&err]) + { + LOG("error creating patterns thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); + } + // settings if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/settings"] withIntermediateDirectories:YES attributes:nil error:&err]) { LOG("error creating settings path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); @@ -165,15 +175,25 @@ void App::initLog() { LOG("error creating rec path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); } - + // brushes if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/brushes"] withIntermediateDirectories:YES attributes:nil error:&err]) { LOG("error creating brushes path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); } if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/brushes/thumbs"] withIntermediateDirectories:YES attributes:nil error:&err]) { - LOG("error creating thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); + LOG("error creating brushes thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); } + // patterns + if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/patterns"] withIntermediateDirectories:YES attributes:nil error:&err]) + { + LOG("error creating brushes path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); + } + if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/patterns/thumbs"] withIntermediateDirectories:YES attributes:nil error:&err]) + { + LOG("error creating patterns thumbs path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); + } + // settings if (![[NSFileManager defaultManager] createDirectoryAtPath:[docpath stringByAppendingString:@"/settings"] withIntermediateDirectories:YES attributes:nil error:&err]) { LOG("error creating settings path: %s", [[err localizedDescription] cStringUsingEncoding:NSASCIIStringEncoding]); @@ -214,10 +234,14 @@ void App::initLog() if (!PathFileExistsA((data_path + "\\brushes").c_str())) CreateDirectoryA((data_path + "\\brushes").c_str(), NULL); - if (!PathFileExistsA((data_path + "\\brushes\\thumbs").c_str())) CreateDirectoryA((data_path + "\\brushes\\thumbs").c_str(), NULL); + if (!PathFileExistsA((data_path + "\\patterns").c_str())) + CreateDirectoryA((data_path + "\\patterns").c_str(), NULL); + if (!PathFileExistsA((data_path + "\\patterns\\thumbs").c_str())) + CreateDirectoryA((data_path + "\\patterns\\thumbs").c_str(), NULL); + if (!PathFileExistsA((data_path + "\\settings").c_str())) CreateDirectoryA((data_path + "\\settings").c_str(), NULL); #endif diff --git a/src/app_events.cpp b/src/app_events.cpp index e5426ef..c5d71d4 100644 --- a/src/app_events.cpp +++ b/src/app_events.cpp @@ -100,20 +100,22 @@ void App::pick_file(std::vector types, std::function(); + if (!m_tip_texture->load(m_brush_path)) + return false; + m_tip_texture->create_mipmaps(); + m_tip_texture->auto_destroy = true; + } + if (!m_stencil_path.empty()) + { + m_stencil_texture = std::make_shared(); + if (!m_stencil_texture->load(m_stencil_path)) + return false; + m_stencil_texture->create_mipmaps(); + m_stencil_texture->auto_destroy = true; + } + return true; +} diff --git a/src/brush.h b/src/brush.h index 0d1c7b9..013cd87 100644 --- a/src/brush.h +++ b/src/brush.h @@ -45,6 +45,7 @@ public: int m_blend_mode = 0; bool load_texture(const std::string& path, const std::string& thumb); bool load_stencil(const std::string& path); + bool load(); }; struct StrokeSample diff --git a/src/node_panel_brush.cpp b/src/node_panel_brush.cpp index ab9bbf3..ea5e6cf 100644 --- a/src/node_panel_brush.cpp +++ b/src/node_panel_brush.cpp @@ -9,6 +9,7 @@ #endif #include "canvas.h" #include "app.h" +#include "abr.h" Node* NodeButtonBrush::clone_instantiate() const { @@ -50,29 +51,28 @@ void NodePanelBrush::init() m_btn_add = find("btn-add"); m_btn_add->on_click = [this](Node*) { - App::I.pick_image([this](std::string path) { + App::I.pick_file({ "JPG", "PNG", "ABR" }, [this](std::string path) { + std::string name, base, ext; + std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); + std::smatch m; + if (!std::regex_search(path, m, r)) + return; + base = m[1].str(); + name = m[2].str(); + ext = m[3].str(); Image img; - if (img.load_file(path)) + + if (str_iequals(ext, "abr")) { - std::string name, base, ext; - std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); - std::smatch m; - if (std::regex_search(path, m, r)) + ABR abr; + abr.open(path); + for (const auto& samp : abr.m_samples) { - base = m[1].str(); - name = m[2].str(); - ext = m[3].str(); - - std::string path_high = App::I.data_path + "/brushes/" + name + ".png"; - std::string path_thumb = App::I.data_path + "/brushes/thumbs/" + name + ".png"; - - img = img.resize_squared(); - img.gayscale_alpha(); - - auto thumb = img.resize(64, 64); + 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"; + samp.second->save(path_high); + auto thumb = samp.second->resize(64, 64); thumb.save(path_thumb); - auto po2 = img.resize_power2(); - po2.save(path_high); async_start(); NodeButtonBrush* brush = new NodeButtonBrush; @@ -86,11 +86,54 @@ void NodePanelBrush::init() brush->brush_name = name; brush->m_user_brush = true; brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1); - app_redraw(); async_end(); - save(); } + for (const auto& patt : abr.m_patterns) + { + 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(path_high); + auto thumb = patt.second->resize(64, 64); + thumb.save(path_thumb); + } + auto brushes = abr.compute_brushes(App::I.data_path); + for (const auto& pr : brushes) + { + auto presets = App::I.stroke->m_presets_popup; + if (pr->load()) + presets->add_brush(pr); + } + //save(); + } + else if (img.load_file(path)) + { + std::string path_high = App::I.data_path + "/brushes/" + name + ".png"; + std::string path_thumb = App::I.data_path + "/brushes/thumbs/" + name + ".png"; + + img = img.resize_squared(); + img.gayscale_alpha(); + + auto thumb = img.resize(64, 64); + thumb.save(path_thumb); + auto po2 = img.resize_power2(); + po2.save(path_high); + + async_start(); + NodeButtonBrush* brush = new NodeButtonBrush; + 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, this, std::placeholders::_1); + app_redraw(); + async_end(); + save(); } }); }; @@ -317,7 +360,7 @@ bool NodePanelBrush::restore() brush->thumb_path = path_thumb; brush->high_path = path_high; brush->brush_name = name; - brush->m_user_brush = true; + brush->m_user_brush = i.m_user_brush; brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1); } } @@ -602,3 +645,21 @@ bool NodePanelBrushPreset::restore() } return false; } + +void NodePanelBrushPreset::add_brush(std::shared_ptr brush) +{ + NodeBrushPresetItem* b = new NodeBrushPresetItem; + m_container->add_child(b); + b->init(); + b->create(); + b->loaded(); + b->thumb_path = brush->m_brush_thumb_path; + b->high_path = brush->m_brush_path; + b->m_brush = brush; + //brush->m_brush->m_tip_size = .05f; + b->m_preview->m_brush = brush; + b->m_preview->draw_stroke(); + b->m_thumb->m_use_mipmaps = true; + b->m_thumb->set_image(brush->m_brush_thumb_path); + b->on_click = std::bind(&NodePanelBrushPreset::handle_click, this, std::placeholders::_1); +} diff --git a/src/node_panel_brush.h b/src/node_panel_brush.h index d123fb1..cd211ce 100644 --- a/src/node_panel_brush.h +++ b/src/node_panel_brush.h @@ -44,6 +44,7 @@ class NodePanelBrush : public Node int m_high_len = 0; int m_thumb_len = 0; bool m_deleted = false; + bool m_user_brush = false; }; public: std::function on_brush_changed; @@ -131,5 +132,6 @@ public: void handle_click(Node* target); bool save(); bool restore(); + void add_brush(std::shared_ptr brush); }; diff --git a/src/node_panel_stroke.cpp b/src/node_panel_stroke.cpp index 7e73ec1..6e5215b 100644 --- a/src/node_panel_stroke.cpp +++ b/src/node_panel_stroke.cpp @@ -73,6 +73,8 @@ void NodePanelStroke::init_controls() auto b = std::make_shared(); int br_idx = m_brush_popup->find_brush("Round-Hard"); + if (br_idx == -1) + br_idx = 0; b->load_texture(m_brush_popup->get_texture_path(br_idx), m_brush_popup->get_thumb_path(br_idx)); //b->load_stencil("data/paper.jpg"); b->m_tip_size = .1f; diff --git a/src/util.cpp b/src/util.cpp index 4c63df4..e7ed2e0 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -313,6 +313,17 @@ std::string wstr2str(const std::wstring & wstr) return converted; } +bool str_iequals(const std::string& a, const std::string& b) +{ + unsigned int sz = a.size(); + if (b.size() != sz) + return false; + for (unsigned int i = 0; i < sz; ++i) + if (std::tolower(a[i]) != std::tolower(b[i])) + return false; + return true; +} + static const char* gl2str(GLenum err) { switch (err) diff --git a/src/util.h b/src/util.h index 5d67608..2bcb3c6 100644 --- a/src/util.h +++ b/src/util.h @@ -63,6 +63,7 @@ std::vector split(const std::string& subject, char d, int max_split std::string unescape(const std::string& s); std::wstring str2wstr(const std::string& str); std::string wstr2str(const std::wstring& wstr); +bool str_iequals(const std::string& a, const std::string& b); size_t curl_data_handler(void *contents, size_t size, size_t nmemb, void *userp); size_t curl_data_write(void *ptr, size_t size, size_t nmemb, FILE *stream);