#include "pch.h" #include "log.h" #include "node_panel_brush.h" #include "asset.h" #include "texture.h" #ifdef __APPLE__ #include #endif #include "canvas.h" #include "app.h" #include "abr.h" Node* NodeButtonBrush::clone_instantiate() const { return new NodeButtonBrush(); } void NodeButtonBrush::init() { init_template("tpl-brush-icon"); color_hover = glm::vec4(.7, .7, .7, 1); color_normal = glm::vec4(.3, .3, .3, 1); m_color = color_normal; img = (NodeImage*)m_children[0].get(); } void NodeButtonBrush::set_icon(const char* path) { img->m_path = path; img->m_tex_id = const_hash(img->m_path.c_str()); img->m_use_mipmaps = true; img->create(); } void NodeButtonBrush::draw() { m_color = m_mouse_inside ? color_hover : color_normal; m_color = m_selected ? glm::vec4(.9, 0, 0, 1) : m_color; NodeButtonCustom::draw(); } Node* NodePanelBrush::clone_instantiate() const { return new NodePanelBrush(); } void NodePanelBrush::init() { init_template("tpl-panel-brushes"); m_btn_add = find("btn-add"); m_btn_add->on_click = [this](Node*) { App::I.pick_file({ "JPG", "PNG" }, [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 (!m_dir_name.empty() && img.load_file(path)) { std::string path_high = App::I.data_path + "/" + m_dir_name + "/" + name + ".png"; std::string path_thumb = App::I.data_path + "/" + m_dir_name + "/thumbs/" + name + ".png"; img = img.resize_squared(glm::u8vec4(255)); //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(); } }); }; m_btn_remove = find("btn-remove"); m_btn_remove->on_click = [this](Node*) { if (m_current) { int idx = m_container->get_child_index(m_current); if (m_current->m_user_brush) { // only delete user brushes Asset::delete_file(m_current->thumb_path); Asset::delete_file(m_current->high_path); } m_container->remove_child(m_current); if (m_container->m_children.size() > 0) { idx = std::max(0, std::min(idx, (int)m_container->m_children.size() - 1)); m_current = (NodeButtonBrush*)m_container->m_children[idx].get(); m_current->m_selected = true; if (on_brush_changed) on_brush_changed(this, idx); } else { m_current = nullptr; } save(); } }; m_btn_up = find("btn-up"); m_btn_up->on_click = [this](Node*) { if (m_current) { int idx = m_container->get_child_index(m_current); idx = std::max(0, std::min(idx - 1, (int)m_container->m_children.size() - 1)); m_container->move_child(m_current, idx); save(); } }; m_btn_down = find("btn-down"); m_btn_down->on_click = [this](Node*) { if (m_current) { int idx = m_container->get_child_index(m_current); idx = std::max(0, std::min(idx + 1, (int)m_container->m_children.size() - 1)); m_container->move_child(m_current, idx); save(); } }; m_container = find("brushes"); restore(); if (m_container->m_children.empty() && !m_dir_name.empty()) { auto icons = Asset::list_files("data/" + m_dir_name, true, ".*\\.png$"); for (auto& i : icons) { std::string path = "data/" + m_dir_name + "/thumbs/" + i; std::string path_hi = "data/" + m_dir_name + "/" + i; NodeButtonBrush* brush = new NodeButtonBrush; m_container->add_child(brush); brush->init(); brush->create(); brush->loaded(); brush->set_icon(path.c_str()); brush->thumb_path = path; brush->high_path = path_hi; brush->brush_name = i; brush->m_user_brush = false; // system brush, cannot be deleted from file brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1); } auto custom_icons = Asset::list_files(App::I.data_path + "/" + m_dir_name, true, ".*\\.png$"); for (auto& i : custom_icons) { std::string path_thumb = App::I.data_path + "/" + m_dir_name + "/thumbs/" + i; std::string path_high = App::I.data_path + "/" + m_dir_name + "/" + i; 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 = i; brush->m_user_brush = true; brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1); } } save(); } kEventResult NodePanelBrush::handle_event(Event* e) { switch (e->m_type) { case kEventType::MouseUpL: if (!m_mouse_inside) { mouse_release(); parent->remove_child(this); if (on_popup_close) on_popup_close(this); } break; default: return kEventResult::Available; break; } return kEventResult::Consumed; } void NodePanelBrush::handle_click(Node* target) { if (target == m_current) return; if (m_current) m_current->m_selected = false; m_current = (NodeButtonBrush*)target; m_current->m_selected = true; if (on_brush_changed) on_brush_changed(this, m_container->get_child_index(target)); } int NodePanelBrush::find_brush(const std::string & name) const { for (int i = 0; i < m_container->m_children.size(); i++) { NodeButtonBrush* b = (NodeButtonBrush*)m_container->m_children[i].get(); if (b->brush_name.find(name) != std::string::npos) return i; } return -1; } std::string NodePanelBrush::get_texture_path(int index) const { if (index < 0 || index >= m_container->m_children.size()) return ""; return ((NodeButtonBrush*)m_container->m_children[index].get())->high_path; } std::string NodePanelBrush::get_thumb_path(int index) const { if (index < 0 || index >= m_container->m_children.size()) return ""; return ((NodeButtonBrush*)m_container->m_children[index].get())->thumb_path; } bool NodePanelBrush::save() { auto path = App::I.data_path + "/settings/" + m_dir_name + ".bin"; if (FILE* fp = fopen(path.c_str(), "wb")) { header_t h; h.brushes = m_container->m_children.size(); fwrite(&h, sizeof(h), 1, fp); for (const auto& child : m_container->m_children) { auto b = std::static_pointer_cast(child); item_t i; i.m_name_len = b->brush_name.size(); i.m_high_len = b->high_path.size(); i.m_thumb_len = b->thumb_path.size(); fwrite(&i, sizeof(i), 1, fp); fwrite(b->brush_name.c_str(), 1, b->brush_name.size(), fp); fwrite(b->high_path.c_str(), 1, b->high_path.size(), fp); fwrite(b->thumb_path.c_str(), 1, b->thumb_path.size(), fp); } for (const auto& d : m_deleted) { item_t i; i.m_high_len = d.size(); i.m_thumb_len = 0; fwrite(&i, sizeof(i), 1, fp); fwrite(d.c_str(), 1, d.size(), fp); } fclose(fp); return true; } return false; } bool NodePanelBrush::restore() { auto path = App::I.data_path + "/settings/" + m_dir_name + ".bin"; if (FILE* fp = fopen(path.c_str(), "rb")) { header_t h; fread(&h, sizeof(h), 1, fp); if (strcmp(h.magic, "PPBR") != 0) { fclose(fp); LOG("Brushes file malformed: %s", path.c_str()); return false; } if (h.version < 0 || h.version > 0) { fclose(fp); LOG("Brushes file version %d not supported: %s", h.version, path.c_str()); return false; } for (int k = 0; k < h.brushes; k++) { item_t i; fread(&i, sizeof(i), 1, fp); std::string name(i.m_name_len, 0); std::string path_high(i.m_high_len, 0); std::string path_thumb(i.m_thumb_len, 0); fread((char*)name.c_str(), 1, name.size(), fp); fread((char*)path_high.c_str(), 1, path_high.size(), fp); fread((char*)path_thumb.c_str(), 1, path_thumb.size(), fp); if (Asset::exist(path_high)) { 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 = i.m_user_brush; brush->on_click = std::bind(&NodePanelBrush::handle_click, this, std::placeholders::_1); } } fclose(fp); return true; } return false; } // ----------------------------------------------------------------------- Node* NodeBrushPresetItem::clone_instantiate() const { return new NodeBrushPresetItem(); } void NodeBrushPresetItem::init() { init_template("tpl-brush-preset"); color_hover = glm::vec4(.7, .7, .7, 1); color_normal = glm::vec4(.3, .3, .3, 1); m_color = color_normal; m_thumb = find("thumb"); m_preview = find("canvas"); m_preview->m_min_flow = 1.f; } void NodeBrushPresetItem::draw() { m_color = m_mouse_inside ? color_hover : color_normal; m_color = m_selected ? glm::vec4(.9, 0, 0, 1) : m_color; NodeButtonCustom::draw(); } //--- Node* NodePanelBrushPreset::clone_instantiate() const { return new NodePanelBrushPreset(); } void NodePanelBrushPreset::init() { init_template("tpl-panel-brush-preset"); m_container = find("brushes"); m_btn_add = find("btn-add"); m_btn_add->on_click = [this] (Node*) { NodeBrushPresetItem* brush = new NodeBrushPresetItem; m_container->add_child(brush); brush->init(); brush->create(); brush->loaded(); brush->thumb_path = Canvas::I->m_current_brush->m_brush_thumb_path; brush->high_path = Canvas::I->m_current_brush->m_brush_path; brush->m_brush = std::make_shared(*Canvas::I->m_current_brush); //brush->m_brush->m_tip_size = .05f; brush->m_preview->m_brush = brush->m_brush; brush->m_preview->draw_stroke(); brush->m_thumb->m_use_mipmaps = true; brush->m_thumb->set_image(brush->m_brush->m_brush_thumb_path); brush->on_click = std::bind(&NodePanelBrushPreset::handle_click, this, std::placeholders::_1); save(); }; m_btn_up = find("btn-up"); m_btn_up->on_click = [this](Node*) { if (m_current) { m_container->move_child(m_current, std::max(m_container->get_child_index(m_current) - 1, 0)); save(); } }; m_btn_down = find("btn-down"); m_btn_down->on_click = [this](Node*) { if (m_current) { m_container->move_child(m_current, std::min(m_container->get_child_index(m_current) + 1, (int)m_container->m_children.size() - 1)); save(); } }; m_btn_save = find("btn-save"); m_btn_save->on_click = [this](Node*) { if (m_current) { *m_current->m_brush = *Canvas::I->m_current_brush; m_current->m_preview->draw_stroke(); m_current->m_thumb->set_image(m_current->m_brush->m_brush_thumb_path); save(); } }; m_btn_delete = find("btn-remove"); m_btn_delete->on_click = [this](Node*) { if (!m_current) return; int index = m_container->get_child_index(m_current); m_current->destroy(); if (m_container->m_children.empty()) { m_current = nullptr; } else { int next = std::min(m_container->m_children.size() - 1, index); m_current = (NodeBrushPresetItem*)m_container->m_children[next].get(); m_current->m_selected = true; } save(); }; restore(); } kEventResult NodePanelBrushPreset::handle_event(Event* e) { switch (e->m_type) { case kEventType::MouseUpL: if (!m_mouse_inside) { mouse_release(); parent->remove_child(this); if (on_popup_close) on_popup_close(this); } break; default: return kEventResult::Available; break; } return kEventResult::Consumed; } void NodePanelBrushPreset::handle_click(Node* target) { if (target == m_current) return; if (m_current) m_current->m_selected = false; m_current = (NodeBrushPresetItem*)target; m_current->m_selected = true; if (on_brush_changed) on_brush_changed(this, m_current->m_brush); } bool NodePanelBrushPreset::save() { auto path = App::I.data_path + "/settings/presets.bin"; if (FILE* fp = fopen(path.c_str(), "wb")) { header_t h; h.count = m_container->m_children.size(); fwrite(&h, sizeof(h), 1, fp); for (int ci = 0; ci < m_container->m_children.size(); ci++) { auto bpi = static_cast(m_container->get_child_at(ci)); auto& b = bpi->m_brush; item_t i; i.m_name_len = b->m_name.size(); i.m_brush_path_len = b->m_brush_path.size(); i.m_brush_thumb_path_len = b->m_brush_thumb_path.size(); i.m_dual_path_len = b->m_brush_path.size(); i.m_dual_thumb_path_len = b->m_brush_thumb_path.size(); i.m_stencil_path_len = b->m_pattern_path.size(); i.m_stencil_thumb_path_len = b->m_pattern_thumb_path.size(); i.m_tip_color = b->m_tip_color; i.m_tip_size = b->m_tip_size; i.m_tip_spacing = b->m_tip_spacing; i.m_tip_flow = b->m_tip_flow; i.m_tip_opacity = b->m_tip_opacity; i.m_tip_angle = b->m_tip_angle; i.m_tip_angle_delay = b->m_tip_angle_delay; i.m_tip_mix = b->m_tip_mix; i.m_tip_wet = b->m_tip_wet; i.m_tip_noise = b->m_tip_noise; i.m_tip_hue = b->m_tip_hue; i.m_tip_sat = b->m_tip_sat; i.m_tip_val = b->m_tip_val; i.m_tip_angle_follow = b->m_tip_angle_follow; i.m_tip_flow_pressure = b->m_tip_flow_pressure; i.m_tip_size_pressure = b->m_tip_size_pressure; i.m_jitter_scale = b->m_jitter_scale; i.m_jitter_angle = b->m_jitter_angle; i.m_jitter_spread = b->m_jitter_spread; i.m_jitter_flow = b->m_jitter_flow; i.m_jitter_hue = b->m_jitter_hue; i.m_jitter_sat = b->m_jitter_sat; i.m_jitter_val = b->m_jitter_val; i.m_blend_mode = b->m_blend_mode; i.m_tip_invert = b->m_tip_invert; i.m_tip_flipx = b->m_tip_flipx; i.m_tip_flipy = b->m_tip_flipy; i.m_pattern_enabled = b->m_pattern_enabled; i.m_dual_enabled = b->m_dual_enabled; i.m_dual_blend_mode = b->m_dual_blend_mode; i.m_dual_randflip = b->m_dual_randflip; i.m_dual_size = b->m_dual_size; i.m_dual_spacing = b->m_dual_spacing; i.m_dual_scatter = b->m_dual_scatter; i.m_dual_scatter_axis = b->m_dual_scatter_axis; i.m_dual_invert = b->m_dual_invert; i.m_dual_flipx = b->m_dual_flipx; i.m_dual_flipy = b->m_dual_flipy; i.m_tip_randflipx = b->m_tip_randflipx; i.m_tip_randflipy = b->m_tip_randflipy; i.m_tip_aspect = b->m_tip_aspect; i.m_dual_flow = b->m_dual_flow; i.m_dual_opacity = b->m_dual_opacity; i.m_dual_rotate = b->m_dual_rotate; i.m_pattern_eachsample = b->m_pattern_eachsample; i.m_pattern_invert = b->m_pattern_invert; i.m_pattern_flipx = b->m_pattern_flipx; i.m_pattern_flipy = b->m_pattern_flipy; i.m_pattern_scale = b->m_pattern_scale; i.m_pattern_brightness = b->m_pattern_brightness; i.m_pattern_contrast = b->m_pattern_contrast; i.m_pattern_rand_offset = b->m_pattern_rand_offset; i.m_pattern_depth = b->m_pattern_depth; fwrite(&i, sizeof(i), 1, fp); fwrite(b->m_name.c_str(), 1, b->m_name.size(), fp); fwrite(b->m_brush_path.c_str(), 1, b->m_brush_path.size(), fp); fwrite(b->m_brush_thumb_path.c_str(), 1, b->m_brush_thumb_path.size(), fp); fwrite(b->m_dual_path.c_str(), 1, b->m_brush_path.size(), fp); fwrite(b->m_dual_thumb_path.c_str(), 1, b->m_brush_thumb_path.size(), fp); fwrite(b->m_pattern_path.c_str(), 1, b->m_pattern_path.size(), fp); fwrite(b->m_pattern_thumb_path.c_str(), 1, b->m_pattern_thumb_path.size(), fp); } fclose(fp); return true; } return false; } bool NodePanelBrushPreset::restore() { auto path = App::I.data_path + "/settings/presets.bin"; if (FILE* fp = fopen(path.c_str(), "rb")) { header_t h; fread(&h, sizeof(h), 1, fp); if (strcmp(h.magic, "PPPR") != 0) { fclose(fp); LOG("Presets file malformed: %s", path.c_str()); return false; } if (h.version < 0 || h.version > 0) { fclose(fp); LOG("Presets file version %d not supported: %s", h.version, path.c_str()); return false; } for (int k = 0; k < h.count; k++) { item_t i; fread(&i, sizeof(i), 1, fp); auto b = std::make_shared(); b->m_tip_color = i.m_tip_color; b->m_tip_size = i.m_tip_size; b->m_tip_spacing = i.m_tip_spacing; b->m_tip_flow = i.m_tip_flow; b->m_tip_opacity = i.m_tip_opacity; b->m_tip_angle = i.m_tip_angle; b->m_tip_angle_delay = i.m_tip_angle_delay; b->m_tip_mix = i.m_tip_mix; b->m_tip_wet = i.m_tip_wet; b->m_tip_noise = i.m_tip_noise; 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_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_tip_invert = i.m_tip_invert; b->m_tip_flipx = i.m_tip_flipx; b->m_tip_flipy = i.m_tip_flipy; b->m_pattern_enabled = i.m_pattern_enabled; b->m_dual_enabled = i.m_dual_enabled; b->m_dual_blend_mode = i.m_dual_blend_mode; b->m_dual_randflip = i.m_dual_randflip; b->m_dual_size = i.m_dual_size; b->m_dual_spacing = i.m_dual_spacing; b->m_dual_scatter = i.m_dual_scatter; b->m_dual_scatter_axis = i.m_dual_scatter_axis; b->m_dual_invert = i.m_dual_invert; b->m_dual_flipx = i.m_dual_flipx; b->m_dual_flipy = i.m_dual_flipy; b->m_tip_aspect = i.m_tip_aspect; b->m_dual_flow = i.m_dual_flow; b->m_dual_opacity = i.m_dual_opacity; b->m_dual_rotate = i.m_dual_rotate; b->m_pattern_eachsample = i.m_pattern_eachsample; b->m_pattern_invert = i.m_pattern_invert; b->m_pattern_flipx = i.m_pattern_flipx; b->m_pattern_flipy = i.m_pattern_flipy; b->m_pattern_scale = i.m_pattern_scale; b->m_pattern_brightness = i.m_pattern_brightness; b->m_pattern_contrast = i.m_pattern_contrast; b->m_pattern_rand_offset = i.m_pattern_rand_offset; b->m_pattern_depth = i.m_pattern_depth; b->m_name.resize(i.m_name_len); b->m_brush_path.resize(i.m_brush_path_len); b->m_brush_thumb_path.resize(i.m_brush_thumb_path_len); b->m_dual_path.resize(i.m_brush_path_len); b->m_dual_thumb_path.resize(i.m_brush_thumb_path_len); b->m_pattern_path.resize(i.m_stencil_path_len); b->m_pattern_thumb_path.resize(i.m_stencil_thumb_path_len); fread((char*)b->m_name.c_str(), 1, b->m_name.size(), fp); fread((char*)b->m_brush_path.c_str(), 1, b->m_brush_path.size(), fp); fread((char*)b->m_brush_thumb_path.c_str(), 1, b->m_brush_thumb_path.size(), fp); fread((char*)b->m_dual_path.c_str(), 1, b->m_brush_path.size(), fp); fread((char*)b->m_dual_thumb_path.c_str(), 1, b->m_brush_thumb_path.size(), fp); fread((char*)b->m_pattern_path.c_str(), 1, b->m_pattern_path.size(), fp); fread((char*)b->m_pattern_thumb_path.c_str(), 1, b->m_pattern_thumb_path.size(), fp); if (b->load_tip(b->m_brush_path, b->m_brush_thumb_path)) { if (!b->m_pattern_path.empty()) b->load_pattern(b->m_pattern_path, b->m_pattern_thumb_path); 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_brush->m_tip_size = .05f; brush->m_preview->m_brush = b; brush->m_preview->draw_stroke(); brush->m_thumb->set_image(brush->m_brush->m_brush_thumb_path); brush->on_click = std::bind(&NodePanelBrushPreset::handle_click, this, std::placeholders::_1); } } fclose(fp); return true; } 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); }