diff --git a/cmake/PanoPainterSources.cmake b/cmake/PanoPainterSources.cmake index b2f5079d..cb628ee2 100644 --- a/cmake/PanoPainterSources.cmake +++ b/cmake/PanoPainterSources.cmake @@ -22,6 +22,7 @@ set(PP_LEGACY_PAINT_DOCUMENT_SOURCES src/canvas.cpp src/canvas_actions.cpp src/canvas_layer.cpp + src/legacy_canvas_stroke_commit_services.cpp src/legacy_canvas_document_io_services.cpp src/legacy_canvas_object_draw_services.cpp src/legacy_canvas_object_draw_services.h @@ -129,6 +130,8 @@ set(PP_PANOPAINTER_APP_SOURCES src/legacy_brush_package_import_services.h src/legacy_brush_package_export_services.cpp src/legacy_brush_package_export_services.h + src/legacy_brush_preset_services.cpp + src/legacy_brush_preset_services.h src/legacy_cloud_services.cpp src/legacy_cloud_services.h src/legacy_document_export_services.cpp diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 623a0813..546b39df 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -79,14 +79,14 @@ What is still carrying too much live ownership: Current hotspot files: -- `src/canvas.cpp`: 2592 lines +- `src/canvas.cpp`: 2122 lines - `src/app_layout.cpp`: 125 lines - `src/canvas_modes.cpp`: 1626 lines - `src/node.cpp`: 1368 lines - `src/main.cpp`: 271 lines -- `src/node_panel_brush.cpp`: 1094 lines +- `src/node_panel_brush.cpp`: 652 lines - `src/node_stroke_preview.cpp`: 910 lines -- `src/node_canvas.cpp`: 872 lines +- `src/node_canvas.cpp`: 877 lines - `src/app.cpp`: 575 lines - `src/app_dialogs.cpp`: 168 lines @@ -251,12 +251,24 @@ Current architecture mismatches that must be treated as real blockers: while `Canvas::draw_objects_direct(...)` and `Canvas::draw_objects(...)` now route through `src/legacy_canvas_object_draw_services.*` instead of staying inline in `src/canvas.cpp`, which trims another coherent object-draw and + viewport-state execution family from the live canvas shell, while + `Canvas::stroke_draw_samples(...)`, `Canvas::stroke_commit()`, and the + larger stroke commit/sample execution family now also route through + `src/legacy_canvas_stroke_commit_services.*` instead of staying inline in + `src/canvas.cpp`, which trims another large retained stroke-render and viewport-state execution family from the live canvas shell, while `NodePanelBrush` save/restore/scan/reload/find/get-path ownership now routes through `src/legacy_brush_panel_services.*` instead of staying inline in `src/node_panel_brush.cpp`, which trims another retained brush-workflow pocket from the live UI node even though the broader panel still remains - large, while shared canvas-mode GL wrappers plus the + large, while `NodePanelBrushPreset` save/restore and package + import/export/import-ABR routing now also lives in + `src/legacy_brush_preset_services.*` instead of staying inline in + `src/node_panel_brush.cpp`, which trims another large preset-workflow pocket + from the live UI node, while `NodeCanvas::handle_event()` now also routes + through `execute_node_canvas_handle_event(...)`, which trims another coherent + input-routing block from `src/node_canvas.cpp` even though the file is still + a live canvas/controller shell, while shared canvas-mode GL wrappers plus the `CanvasModeBasicCamera` and `CanvasModeCamera` input handlers now also route through `src/legacy_canvas_mode_helpers.*` instead of staying inline in `src/canvas_modes.cpp`, while diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 7aef0f95..21fa474d 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -91,7 +91,7 @@ Status: In Progress Why now: `src/canvas.cpp` is still the biggest single architectural blocker at about -2592 lines. +2122 lines. Current slice: - Canvas state-management helpers for picking, clear/clear-all, layer @@ -112,6 +112,11 @@ Current slice: live in `src/legacy_canvas_object_draw_services.*` instead of staying inline in `src/canvas.cpp`, which trims another coherent object-draw and viewport-state execution pocket from the live canvas shell. +- `Canvas::stroke_draw_samples(...)`, `Canvas::stroke_commit()`, and the + larger stroke sample/commit execution family now also live in + `src/legacy_canvas_stroke_commit_services.*` instead of staying inline in + `src/canvas.cpp`, which trims another large retained stroke-render pocket + from the live canvas shell. - Shared canvas-mode GL wrappers plus the `CanvasModeBasicCamera` and `CanvasModeCamera` input handlers now also live in `src/legacy_canvas_mode_helpers.*` instead of staying inline in @@ -271,6 +276,10 @@ Current slice: route through `execute_node_canvas_draw_merged_pass(...)`, which trims another coherent merged draw-orchestration block from the live node even though the broader draw loop still remains inline. +- `NodeCanvas::handle_event()` now also routes through the local + `execute_node_canvas_handle_event(...)` helper, which trims another coherent + input-routing block from `src/node_canvas.cpp` even though the node still + owns broader canvas/controller behavior. Write scope: - `src/node_stroke_preview.cpp` @@ -299,7 +308,7 @@ Mini-model packet: #### ARC-RND-003 - Replace App-Facing `Texture2D`/`RTT` Use With Renderer API Contracts -Status: Ready +Status: In Progress Why now: Future Vulkan and Metal work needs the live app to stop treating retained @@ -984,6 +993,11 @@ Current slice: `src/node_panel_brush.cpp`, which trims a coherent retained brush-workflow pocket from the live UI node even though cloud and package-worker ownership still remain separate follow-up work. +- `NodePanelBrushPreset` save/restore plus PPBR/ABR import/export routing now + also lives in `src/legacy_brush_preset_services.*` instead of staying inline + in `src/node_panel_brush.cpp`, which trims another large preset-workflow + pocket from the live UI node even though the broader cloud/package worker + split still remains follow-up work. Write scope: - `src/legacy_cloud_services.*` diff --git a/src/canvas.cpp b/src/canvas.cpp index 945f523f..8deea45c 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -932,448 +932,6 @@ pp::panopainter::LegacyCanvasStrokeMixPassShell make_canvas_stroke_mix_pass_shel }); } -static void stamp_canvas_stroke_commit_action( - Canvas& canvas, - ActionStroke* action); - -static void capture_canvas_stroke_commit_layer_state( - Canvas& canvas, - ActionStroke* action, - int i); - -static void apply_canvas_stroke_commit_dirty_mutation( - Canvas& canvas, - int i); - -static void copy_canvas_stroke_commit_layer_image( - Canvas& canvas, - int i); - -template -static auto make_canvas_stroke_commit_callbacks( - Canvas& canvas, - pp::renderer::gl::OpenGlViewportRect vp, - std::array cc, - bool blend, - SetActiveTextureUnit&& set_active_texture_unit, - ActionStroke* action, - const Stroke* current_stroke, - const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, - const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) -{ - const auto& b = current_stroke->m_brush; - auto bind_commit_inputs = [&](int i) { - pp::panopainter::bind_legacy_canvas_stroke_commit_face_inputs( - sequence, - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&](pp::paint_renderer::CanvasStrokeCommitTextureRole role) { - switch (role) { - case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch: - canvas.m_tex2[i].bind(); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke: - canvas.m_tmp[i].bindTexture(); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask: - canvas.m_smask.rtt(i).bindTexture(); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke: - canvas.m_tmp_dual[i].bindTexture(); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern: - b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d(); - break; - } - }, - [&](pp::paint_renderer::CanvasStrokeCommitTextureRole role, int texture_slot) { - switch (role) { - case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch: - canvas.m_sampler.bind(texture_slot); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke: - canvas.m_sampler_nearest.bind(texture_slot); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask: - case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke: - canvas.m_sampler.bind(texture_slot); - break; - case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern: - canvas.m_sampler_stencil.bind(texture_slot); - break; - } - }); - }; - return pp::panopainter::make_legacy_canvas_stroke_commit_callbacks( - [&]() { - canvas.m_dirty = false; - canvas.m_dirty_stroke = true; // new stroke ready for timelapse capture - App::I->redraw = true; - canvas.m_unsaved = true; - App::I->title_update(); - }, - []() {}, - [&]() { - apply_canvas_viewport(0, 0, canvas.m_width, canvas.m_height); - apply_canvas_capability(blend_state(), false); - }, - [&]() { - blend ? apply_canvas_capability(blend_state(), true) : apply_canvas_capability(blend_state(), false); - apply_canvas_viewport(vp.x, vp.y, vp.width, vp.height); - apply_canvas_clear_color(cc); - set_active_texture_unit(0); - }, - [&]() { stamp_canvas_stroke_commit_action(canvas, action); }, - [&]() { - canvas.stroke_commit_timelapse(); - }, - [](int) {}, - [&](int i) { capture_canvas_stroke_commit_layer_state(canvas, action, i); }, - [&](int i) { apply_canvas_stroke_commit_dirty_mutation(canvas, i); }, - [&](int i) { copy_canvas_stroke_commit_layer_image(canvas, i); }, - [&](int i) { - bind_commit_inputs(i); - }, - [&](int) { - pp::panopainter::execute_legacy_canvas_stroke_commit_erase( - [&]() { - pp::panopainter::setup_legacy_stroke_erase_shader( - pp::panopainter::LegacyStrokeEraseUniforms { - .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), - .texture_slot = 0, - .stroke_texture_slot = 1, - .mask_texture_slot = 2, - .alpha = 1.0f, - .mask_enabled = canvas.m_smask_active, - }); - }, - [&]() { - canvas.m_plane.draw_fill(); - }); - }, - [&](int) { - glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale); - if (b->m_pattern_flipx) patt_scale.x *= -1.f; - if (b->m_pattern_flipy) patt_scale.y *= -1.f; - - pp::panopainter::execute_legacy_canvas_stroke_commit_paint( - [&]() { - pp::panopainter::setup_legacy_stroke_composite_shader( - pp::panopainter::LegacyStrokeCompositeUniforms { - .resolution = canvas.m_size, - .pattern = { - .scale = patt_scale, - .invert = static_cast(b->m_pattern_invert), - .brightness = b->m_pattern_brightness, - .contrast = b->m_pattern_contrast, - .depth = b->m_pattern_depth, - .blend_mode = b->m_pattern_blend_mode, - .offset = canvas.m_pattern_offset, - }, - .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), - .layer_alpha = 1.0f, - .alpha_lock = canvas.m_layers[canvas.m_current_layer_idx]->m_alpha_locked, - .mask_enabled = canvas.m_smask_active, - .use_fragcoord = false, - .blend_mode = b->m_blend_mode, - .use_dual = stroke_material.composite_pass.use_dual, - .dual_blend_mode = stroke_material.composite_pass.dual_blend_mode, - .dual_alpha = stroke_material.composite_pass.dual_alpha, - .use_pattern = stroke_material.composite_pass.use_pattern, - }); - }, - [&]() { - canvas.m_plane.draw_fill(); - }); - }, - [&](int i) { - pp::panopainter::copy_legacy_canvas_stroke_commit_to_dilate_source( - sequence, - [&]() { - pp::panopainter::setup_legacy_stroke_dilate_shader( - pp::panopainter::LegacyStrokeDilateUniforms { - .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), - }); - }, - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&]() { - canvas.m_tex2[i].bind(); - }, - [&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) { - copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height); - }, - pp::panopainter::LegacyCanvasStrokeCommitCopyExtent { - .width = canvas.m_width, - .height = canvas.m_height, - }); - }, - [&](int) { - pp::panopainter::execute_legacy_canvas_stroke_commit_dilate([&]() { - canvas.m_plane.draw_fill(); - }); - }, - [&](int i) { - canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).unbindFramebuffer(); - }); -} - -glm::vec4 Canvas::stroke_draw_samples( - int i, - std::vector& P, - bool copy_stroke_destination) -{ - constexpr std::array destination_texture_binding { - pp::panopainter::LegacyCanvasStrokeTextureBinding { - .input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination, - .slot = 1, - }, - }; - const auto result = pp::panopainter::execute_legacy_canvas_stroke_face_sample_polygon( - make_stroke_draw_samples_request( - i, - P, - copy_stroke_destination), - destination_texture_binding, - make_stroke_draw_samples_destination_texture_dispatch(i)); - - return result.dirty_bounds; -} - -pp::panopainter::LegacyCanvasStrokeTextureInputDispatch Canvas::make_stroke_draw_samples_destination_texture_dispatch( - int face_index) -{ - return pp::panopainter::make_legacy_canvas_stroke_destination_texture_dispatch( - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - [&, face_index] { - m_tex[face_index].bind(); // bg, copy of framebuffer (copied before drawing) - }, - [&, face_index] { - m_tex[face_index].unbind(); - }); -} - -pp::panopainter::LegacyStrokeFaceSamplePolygonExecutionRequest Canvas::make_stroke_draw_samples_request( - int face_index, - std::vector& polygon_vertices, - bool copy_stroke_destination) -{ - return pp::panopainter::LegacyStrokeFaceSamplePolygonExecutionRequest { - .context = "Canvas::stroke_draw_samples", - .target_size = { m_width, m_height }, - .polygon_vertices = polygon_vertices, - .face_index = face_index, - .copy_stroke_destination = copy_stroke_destination, - .copy_framebuffer_to_destination_texture = []( - int, - int src_x, - int src_y, - int dst_x, - int dst_y, - int width, - int height) { - copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height); - }, - .upload_brush_vertices = [&](int, std::span vertices) { - m_brush_shape.update_vertices( - const_cast(vertices.data()), - static_cast(vertices.size())); - }, - .draw_brush_shape = [&](int) { - m_brush_shape.draw_fill(); - }, - }; -} - -template -static auto execute_canvas_stroke_commit_sequence( - BuildRequest&& build_request) -{ - return pp::panopainter::execute_legacy_canvas_stroke_commit_sequence( - build_request()); -} - -template -static auto make_canvas_stroke_commit_request( - Canvas& canvas, - pp::renderer::gl::OpenGlViewportRect vp, - std::array cc, - bool blend, - SetActiveTextureUnit&& set_active_texture_unit, - ActionStroke* action, - const Stroke* current_stroke, - const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, - const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) -{ - const auto commit_callbacks = make_canvas_stroke_commit_callbacks( - canvas, - vp, - cc, - blend, - std::forward(set_active_texture_unit), - action, - current_stroke, - sequence, - stroke_material); - - const std::array faces { - pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 0, .dirty = canvas.m_dirty_face[0] }, - pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 1, .dirty = canvas.m_dirty_face[1] }, - pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 2, .dirty = canvas.m_dirty_face[2] }, - pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 3, .dirty = canvas.m_dirty_face[3] }, - pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 4, .dirty = canvas.m_dirty_face[4] }, - pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 5, .dirty = canvas.m_dirty_face[5] }, - }; - - return pp::panopainter::make_legacy_canvas_stroke_commit_request(faces, sequence, commit_callbacks); -} - -template -static auto execute_canvas_stroke_commit_request( - Canvas& canvas, - pp::renderer::gl::OpenGlViewportRect vp, - std::array cc, - bool blend, - SetActiveTextureUnit&& set_active_texture_unit, - ActionStroke* action, - const Stroke* current_stroke, - const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, - const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) -{ - return execute_canvas_stroke_commit_sequence([&]() { - return make_canvas_stroke_commit_request( - canvas, - vp, - cc, - blend, - std::forward(set_active_texture_unit), - action, - current_stroke, - sequence, - stroke_material); - }); -} - -template -static auto execute_canvas_stroke_commit_dispatch( - Canvas& canvas, - pp::renderer::gl::OpenGlViewportRect vp, - std::array cc, - bool blend, - SetActiveTextureUnit&& set_active_texture_unit, - ActionStroke* action, - const Stroke* current_stroke, - const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, - const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) -{ - return execute_canvas_stroke_commit_request( - canvas, - vp, - cc, - blend, - std::forward(set_active_texture_unit), - action, - current_stroke, - sequence, - stroke_material); -} - -struct CanvasStrokeCommitPrelude { - pp::renderer::gl::OpenGlViewportRect viewport; - std::array clear_color; - bool blend; - ActionStroke* action; -}; - -static CanvasStrokeCommitPrelude make_canvas_stroke_commit_prelude(Canvas& canvas) -{ - CanvasStrokeCommitPrelude prelude { - .viewport = query_canvas_viewport(), - .clear_color = query_canvas_clear_color(), - .blend = query_canvas_capability(blend_state()), - .action = new ActionStroke, - }; - prelude.action->was_saved = !canvas.m_unsaved; - return prelude; -} - -static pp::paint_renderer::CanvasStrokeCommitSequencePlan -make_canvas_stroke_commit_sequence_plan( - const Canvas& canvas, - kCanvasMode current_mode, - int current_layer_idx, - bool smask_active, - const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) -{ - return pp::paint_renderer::plan_canvas_stroke_commit_sequence( - pp::paint_renderer::CanvasStrokeCommitRequest { - .erase_mode = current_mode == kCanvasMode::Erase, - .alpha_locked = canvas.m_layers[current_layer_idx]->m_alpha_locked, - .selection_mask_active = smask_active, - .dual_stroke_enabled = stroke_material.composite_pass.use_dual, - .pattern_enabled = stroke_material.composite_pass.use_pattern, - }); -} - -static void stamp_canvas_stroke_commit_action( - Canvas& canvas, - ActionStroke* action) -{ - action->m_layer_idx = canvas.m_current_layer_idx; - action->m_frame_idx = canvas.layer().m_frame_index; - action->m_canvas = &canvas; - ActionManager::add(action); -} - -static void capture_canvas_stroke_commit_layer_state( - Canvas& canvas, - ActionStroke* action, - int i) -{ - canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).bindFramebuffer(); - - glm::vec2 box_sz = zw(canvas.m_dirty_box[i]) - xy(canvas.m_dirty_box[i]); - action->m_image[i] = std::make_unique( - static_cast(box_sz.x * box_sz.y * 4)); - canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).readPixelsRgba8( - static_cast(canvas.m_dirty_box[i].x), - static_cast(canvas.m_dirty_box[i].y), - static_cast(box_sz.x), - static_cast(box_sz.y), - action->m_image[i].get()); - - action->m_box[i] = canvas.m_dirty_box[i]; - action->m_old_box[i] = canvas.m_layers[canvas.m_current_layer_idx]->box(i); - action->m_old_dirty[i] = canvas.m_layers[canvas.m_current_layer_idx]->face(i); -} - -static void apply_canvas_stroke_commit_dirty_mutation( - Canvas& canvas, - int i) -{ - if (!canvas.m_layers[canvas.m_current_layer_idx]->m_alpha_locked) { - auto& lbox = canvas.m_layers[canvas.m_current_layer_idx]->box(i); - lbox = glm::vec4( - glm::min(xy(canvas.m_dirty_box[i]), xy(lbox)), - glm::max(zw(canvas.m_dirty_box[i]), zw(lbox))); - } - canvas.m_layers[canvas.m_current_layer_idx]->face(i) = true; -} - -static void copy_canvas_stroke_commit_layer_image( - Canvas& canvas, - int i) -{ - set_active_texture_unit(0); - canvas.m_tex2[i].bind(); - copy_framebuffer_to_texture_2d(0, 0, 0, 0, canvas.m_width, canvas.m_height); - canvas.m_tex2[i].unbind(); -} - std::vector Canvas::stroke_draw_compute(Stroke& stroke) const { auto samples = stroke.compute_samples(); @@ -2063,34 +1621,6 @@ glm::vec3 Canvas::point_trace(glm::vec2 loc) { return pp::panopainter::legacy_canvas_point_trace(*this, loc); } -void Canvas::stroke_commit() -{ - if (!m_dirty || m_layers.empty()) - return; - const auto prelude = make_canvas_stroke_commit_prelude(*this); - - const auto& b = m_current_stroke->m_brush; - const auto stroke_material = canvas_stroke_material_plan(*b, false); - const auto stroke_commit_sequence = make_canvas_stroke_commit_sequence_plan( - *this, - m_current_mode, - m_current_layer_idx, - m_smask_active, - stroke_material); - [[maybe_unused]] const auto commit_result = execute_canvas_stroke_commit_dispatch( - *this, - prelude.viewport, - prelude.clear_color, - prelude.blend, - [&](int texture_slot) { - set_active_texture_unit(texture_slot); - }, - prelude.action, - m_current_stroke.get(), - stroke_commit_sequence, - stroke_material); -} - void Canvas::stroke_commit_timelapse() { if (m_encoder && App::I->rec_running) diff --git a/src/legacy_brush_preset_services.cpp b/src/legacy_brush_preset_services.cpp new file mode 100644 index 00000000..c72368ab --- /dev/null +++ b/src/legacy_brush_preset_services.cpp @@ -0,0 +1,479 @@ +#include "pch.h" + +#include "legacy_brush_preset_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 + +namespace pp::panopainter { + +LegacyBrushPresetServices::LegacyBrushPresetServices(NodePanelBrushPreset& owner) noexcept + : owner_(owner) +{ +} + +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); + } + } + 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 : NodePanelBrushPreset::s_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 : NodePanelBrushPreset::s_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 diff --git a/src/legacy_brush_preset_services.h b/src/legacy_brush_preset_services.h new file mode 100644 index 00000000..66d29f7f --- /dev/null +++ b/src/legacy_brush_preset_services.h @@ -0,0 +1,25 @@ +#pragma once + +#include "node_panel_brush.h" + +#include + +namespace pp::panopainter { + +class LegacyBrushPresetServices final { +public: + explicit LegacyBrushPresetServices(NodePanelBrushPreset& owner) noexcept; + + bool save(); + bool restore(); + bool export_ppbr(const std::string& path, const NodePanelBrushPreset::PPBRInfo& info); + bool import_ppbr(const std::string& path); + bool import_abr(const std::string& path); + bool import_brush(const std::string& path); + void clear_brushes(); + +private: + NodePanelBrushPreset& owner_; +}; + +} // namespace pp::panopainter diff --git a/src/legacy_canvas_stroke_commit_services.cpp b/src/legacy_canvas_stroke_commit_services.cpp new file mode 100644 index 00000000..9937c400 --- /dev/null +++ b/src/legacy_canvas_stroke_commit_services.cpp @@ -0,0 +1,555 @@ +#include "pch.h" +#include "canvas.h" +#include "app.h" +#include "legacy_canvas_stroke_commit_services.h" +#include "legacy_canvas_stroke_composite_services.h" +#include "legacy_canvas_stroke_execution_services.h" +#include "legacy_canvas_stroke_erase_services.h" +#include "legacy_canvas_stroke_edge_services.h" +#include "legacy_canvas_stroke_shader_services.h" +#include "legacy_ui_gl_dispatch.h" +#include "renderer_gl/opengl_capabilities.h" +#include "util.h" + +#include +#include +#include + +namespace { + +GLenum blend_state() +{ + return static_cast(pp::renderer::gl::blend_state()); +} + +void set_active_texture_unit(std::uint32_t unit_index) +{ + pp::legacy::ui_gl::activate_texture_unit(unit_index, "Canvas"); +} + +void unbind_texture_2d() +{ + pp::legacy::ui_gl::unbind_texture_2d("Canvas"); +} + +void apply_canvas_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) +{ + pp::legacy::ui_gl::apply_viewport(x, y, width, height, "Canvas"); +} + +pp::renderer::gl::OpenGlViewportRect query_canvas_viewport() +{ + return pp::legacy::ui_gl::query_viewport_rect("Canvas"); +} + +std::array query_canvas_clear_color() +{ + return pp::legacy::ui_gl::query_clear_color("Canvas"); +} + +bool query_canvas_capability(std::uint32_t state) +{ + return pp::legacy::ui_gl::query_capability(state, "Canvas"); +} + +void apply_canvas_clear_color(std::array color) +{ + pp::legacy::ui_gl::set_clear_color(color, "Canvas"); +} + +void apply_canvas_capability(std::uint32_t capability, bool enabled) +{ + pp::legacy::ui_gl::set_capability(capability, enabled, "Canvas"); +} + +pp::paint_renderer::CanvasStrokeMaterialPlan canvas_stroke_material_plan( + const Brush& brush, + bool destination_feedback_needed) noexcept +{ + return pp::panopainter::plan_legacy_canvas_stroke_material( + pp::paint_renderer::CanvasStrokeMaterialRequest { + .destination_feedback_needed = destination_feedback_needed, + .pattern_enabled = brush.m_pattern_enabled, + .pattern_eachsample = brush.m_pattern_eachsample, + .wet_blend = brush.m_tip_wet > 0.F, + .mix_blend = brush.m_tip_mix > 0.F, + .noise_enabled = brush.m_tip_noise > 0.F, + .dual_brush_enabled = brush.m_dual_enabled, + .dual_blend_mode = brush.m_dual_blend_mode, + .pattern_blend_mode = brush.m_pattern_blend_mode, + .dual_alpha = brush.m_dual_opacity, + }); +} + +struct CanvasStrokeCommitPrelude { + pp::renderer::gl::OpenGlViewportRect viewport; + std::array clear_color; + bool blend; + ActionStroke* action; +}; + +void stamp_canvas_stroke_commit_action( + Canvas& canvas, + ActionStroke* action); + +void capture_canvas_stroke_commit_layer_state( + Canvas& canvas, + ActionStroke* action, + int i); + +void apply_canvas_stroke_commit_dirty_mutation( + Canvas& canvas, + int i); + +void copy_canvas_stroke_commit_layer_image( + Canvas& canvas, + int i); + +template +static auto make_canvas_stroke_commit_callbacks( + Canvas& canvas, + pp::renderer::gl::OpenGlViewportRect vp, + std::array cc, + bool blend, + SetActiveTextureUnit&& set_active_texture_unit, + ActionStroke* action, + const Stroke* current_stroke, + const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, + const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) +{ + const auto& b = current_stroke->m_brush; + auto bind_commit_inputs = [&](int i) { + pp::panopainter::bind_legacy_canvas_stroke_commit_face_inputs( + sequence, + [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + [&](pp::paint_renderer::CanvasStrokeCommitTextureRole role) { + switch (role) { + case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch: + canvas.m_tex2[i].bind(); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke: + canvas.m_tmp[i].bindTexture(); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask: + canvas.m_smask.rtt(i).bindTexture(); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke: + canvas.m_tmp_dual[i].bindTexture(); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern: + b->m_pattern_texture ? b->m_pattern_texture->bind() : unbind_texture_2d(); + break; + } + }, + [&](pp::paint_renderer::CanvasStrokeCommitTextureRole role, int texture_slot) { + switch (role) { + case pp::paint_renderer::CanvasStrokeCommitTextureRole::layer_scratch: + canvas.m_sampler.bind(texture_slot); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::stroke: + canvas.m_sampler_nearest.bind(texture_slot); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::selection_mask: + case pp::paint_renderer::CanvasStrokeCommitTextureRole::dual_stroke: + canvas.m_sampler.bind(texture_slot); + break; + case pp::paint_renderer::CanvasStrokeCommitTextureRole::pattern: + canvas.m_sampler_stencil.bind(texture_slot); + break; + } + }); + }; + return pp::panopainter::make_legacy_canvas_stroke_commit_callbacks( + [&]() { + canvas.m_dirty = false; + canvas.m_dirty_stroke = true; // new stroke ready for timelapse capture + App::I->redraw = true; + canvas.m_unsaved = true; + App::I->title_update(); + }, + []() {}, + [&]() { + apply_canvas_viewport(0, 0, canvas.m_width, canvas.m_height); + apply_canvas_capability(blend_state(), false); + }, + [&]() { + blend ? apply_canvas_capability(blend_state(), true) : apply_canvas_capability(blend_state(), false); + apply_canvas_viewport(vp.x, vp.y, vp.width, vp.height); + apply_canvas_clear_color(cc); + set_active_texture_unit(0); + }, + [&]() { stamp_canvas_stroke_commit_action(canvas, action); }, + [&]() { + canvas.stroke_commit_timelapse(); + }, + [](int) {}, + [&](int i) { capture_canvas_stroke_commit_layer_state(canvas, action, i); }, + [&](int i) { apply_canvas_stroke_commit_dirty_mutation(canvas, i); }, + [&](int i) { copy_canvas_stroke_commit_layer_image(canvas, i); }, + [&](int i) { + bind_commit_inputs(i); + }, + [&](int) { + pp::panopainter::execute_legacy_canvas_stroke_commit_erase( + [&]() { + pp::panopainter::setup_legacy_stroke_erase_shader( + pp::panopainter::LegacyStrokeEraseUniforms { + .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), + .texture_slot = 0, + .stroke_texture_slot = 1, + .mask_texture_slot = 2, + .alpha = 1.0f, + .mask_enabled = canvas.m_smask_active, + }); + }, + [&]() { + canvas.m_plane.draw_fill(); + }); + }, + [&](int) { + glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale); + if (b->m_pattern_flipx) patt_scale.x *= -1.f; + if (b->m_pattern_flipy) patt_scale.y *= -1.f; + + pp::panopainter::execute_legacy_canvas_stroke_commit_paint( + [&]() { + pp::panopainter::setup_legacy_stroke_composite_shader( + pp::panopainter::LegacyStrokeCompositeUniforms { + .resolution = canvas.m_size, + .pattern = { + .scale = patt_scale, + .invert = static_cast(b->m_pattern_invert), + .brightness = b->m_pattern_brightness, + .contrast = b->m_pattern_contrast, + .depth = b->m_pattern_depth, + .blend_mode = b->m_pattern_blend_mode, + .offset = canvas.m_pattern_offset, + }, + .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), + .layer_alpha = 1.0f, + .alpha_lock = canvas.m_layers[canvas.m_current_layer_idx]->m_alpha_locked, + .mask_enabled = canvas.m_smask_active, + .use_fragcoord = false, + .blend_mode = b->m_blend_mode, + .use_dual = stroke_material.composite_pass.use_dual, + .dual_blend_mode = stroke_material.composite_pass.dual_blend_mode, + .dual_alpha = stroke_material.composite_pass.dual_alpha, + .use_pattern = stroke_material.composite_pass.use_pattern, + }); + }, + [&]() { + canvas.m_plane.draw_fill(); + }); + }, + [&](int i) { + const pp::panopainter::LegacyStrokeDilateUniforms dilate_uniforms { + .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), + }; + const pp::panopainter::LegacyCanvasStrokeCommitCopyExtent copy_extent { + .width = canvas.m_width, + .height = canvas.m_height, + }; + pp::panopainter::copy_legacy_canvas_stroke_commit_to_dilate_source( + sequence, + [&]() { + pp::panopainter::setup_legacy_stroke_dilate_shader(dilate_uniforms); + }, + [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + [&]() { + canvas.m_tex2[i].bind(); + }, + [&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) { + copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height); + }, + copy_extent); + }, + [&](int) { + pp::panopainter::execute_legacy_canvas_stroke_commit_dilate([&]() { + canvas.m_plane.draw_fill(); + }); + }, + [&](int i) { + canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).unbindFramebuffer(); + }); +} + +template +static auto execute_canvas_stroke_commit_sequence( + BuildRequest&& build_request) +{ + return pp::panopainter::execute_legacy_canvas_stroke_commit_sequence( + build_request()); +} + +template +static auto make_canvas_stroke_commit_request( + Canvas& canvas, + pp::renderer::gl::OpenGlViewportRect vp, + std::array cc, + bool blend, + SetActiveTextureUnit&& set_active_texture_unit, + ActionStroke* action, + const Stroke* current_stroke, + const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, + const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) +{ + const auto commit_callbacks = make_canvas_stroke_commit_callbacks( + canvas, + vp, + cc, + blend, + std::forward(set_active_texture_unit), + action, + current_stroke, + sequence, + stroke_material); + + const std::array faces { + pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 0, .dirty = canvas.m_dirty_face[0] }, + pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 1, .dirty = canvas.m_dirty_face[1] }, + pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 2, .dirty = canvas.m_dirty_face[2] }, + pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 3, .dirty = canvas.m_dirty_face[3] }, + pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 4, .dirty = canvas.m_dirty_face[4] }, + pp::panopainter::LegacyCanvasStrokeCommitFace { .index = 5, .dirty = canvas.m_dirty_face[5] }, + }; + + return pp::panopainter::make_legacy_canvas_stroke_commit_request(faces, sequence, commit_callbacks); +} + +template +static auto execute_canvas_stroke_commit_request( + Canvas& canvas, + pp::renderer::gl::OpenGlViewportRect vp, + std::array cc, + bool blend, + SetActiveTextureUnit&& set_active_texture_unit, + ActionStroke* action, + const Stroke* current_stroke, + const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, + const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) +{ + return execute_canvas_stroke_commit_sequence([&]() { + return make_canvas_stroke_commit_request( + canvas, + vp, + cc, + blend, + std::forward(set_active_texture_unit), + action, + current_stroke, + sequence, + stroke_material); + }); +} + +template +static auto execute_canvas_stroke_commit_dispatch( + Canvas& canvas, + pp::renderer::gl::OpenGlViewportRect vp, + std::array cc, + bool blend, + SetActiveTextureUnit&& set_active_texture_unit, + ActionStroke* action, + const Stroke* current_stroke, + const pp::paint_renderer::CanvasStrokeCommitSequencePlan& sequence, + const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) +{ + return execute_canvas_stroke_commit_request( + canvas, + vp, + cc, + blend, + std::forward(set_active_texture_unit), + action, + current_stroke, + sequence, + stroke_material); +} + +CanvasStrokeCommitPrelude make_canvas_stroke_commit_prelude(Canvas& canvas) +{ + CanvasStrokeCommitPrelude prelude { + .viewport = query_canvas_viewport(), + .clear_color = query_canvas_clear_color(), + .blend = query_canvas_capability(blend_state()), + .action = new ActionStroke, + }; + prelude.action->was_saved = !canvas.m_unsaved; + return prelude; +} + +pp::paint_renderer::CanvasStrokeCommitSequencePlan +make_canvas_stroke_commit_sequence_plan( + const Canvas& canvas, + kCanvasMode current_mode, + int current_layer_idx, + bool smask_active, + const pp::paint_renderer::CanvasStrokeMaterialPlan& stroke_material) +{ + return pp::paint_renderer::plan_canvas_stroke_commit_sequence( + pp::paint_renderer::CanvasStrokeCommitRequest { + .erase_mode = current_mode == kCanvasMode::Erase, + .alpha_locked = canvas.m_layers[current_layer_idx]->m_alpha_locked, + .selection_mask_active = smask_active, + .dual_stroke_enabled = stroke_material.composite_pass.use_dual, + .pattern_enabled = stroke_material.composite_pass.use_pattern, + }); +} + +void stamp_canvas_stroke_commit_action( + Canvas& canvas, + ActionStroke* action) +{ + action->m_layer_idx = canvas.m_current_layer_idx; + action->m_frame_idx = canvas.layer().m_frame_index; + action->m_canvas = &canvas; + ActionManager::add(action); +} + +void capture_canvas_stroke_commit_layer_state( + Canvas& canvas, + ActionStroke* action, + int i) +{ + canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).bindFramebuffer(); + + glm::vec2 box_sz = zw(canvas.m_dirty_box[i]) - xy(canvas.m_dirty_box[i]); + action->m_image[i] = std::make_unique( + static_cast(box_sz.x * box_sz.y * 4)); + canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).readPixelsRgba8( + static_cast(canvas.m_dirty_box[i].x), + static_cast(canvas.m_dirty_box[i].y), + static_cast(box_sz.x), + static_cast(box_sz.y), + action->m_image[i].get()); + + action->m_box[i] = canvas.m_dirty_box[i]; + action->m_old_box[i] = canvas.m_layers[canvas.m_current_layer_idx]->box(i); + action->m_old_dirty[i] = canvas.m_layers[canvas.m_current_layer_idx]->face(i); +} + +void apply_canvas_stroke_commit_dirty_mutation( + Canvas& canvas, + int i) +{ + if (!canvas.m_layers[canvas.m_current_layer_idx]->m_alpha_locked) { + auto& lbox = canvas.m_layers[canvas.m_current_layer_idx]->box(i); + lbox = glm::vec4( + glm::min(xy(canvas.m_dirty_box[i]), xy(lbox)), + glm::max(zw(canvas.m_dirty_box[i]), zw(lbox))); + } + canvas.m_layers[canvas.m_current_layer_idx]->face(i) = true; +} + +void copy_canvas_stroke_commit_layer_image( + Canvas& canvas, + int i) +{ + set_active_texture_unit(0); + canvas.m_tex2[i].bind(); + copy_framebuffer_to_texture_2d(0, 0, 0, 0, canvas.m_width, canvas.m_height); + canvas.m_tex2[i].unbind(); +} + +} // namespace + +glm::vec4 Canvas::stroke_draw_samples( + int i, + std::vector& P, + bool copy_stroke_destination) +{ + constexpr std::array destination_texture_binding { + pp::panopainter::LegacyCanvasStrokeTextureBinding { + .input = pp::panopainter::LegacyCanvasStrokeTextureInput::stroke_destination, + .slot = 1, + }, + }; + const auto result = pp::panopainter::execute_legacy_canvas_stroke_face_sample_polygon( + make_stroke_draw_samples_request( + i, + P, + copy_stroke_destination), + destination_texture_binding, + make_stroke_draw_samples_destination_texture_dispatch(i)); + + return result.dirty_bounds; +} + +pp::panopainter::LegacyCanvasStrokeTextureInputDispatch Canvas::make_stroke_draw_samples_destination_texture_dispatch( + int face_index) +{ + return pp::panopainter::make_legacy_canvas_stroke_destination_texture_dispatch( + [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + [&, face_index] { + m_tex[face_index].bind(); // bg, copy of framebuffer (copied before drawing) + }, + [&, face_index] { + m_tex[face_index].unbind(); + }); +} + +pp::panopainter::LegacyStrokeFaceSamplePolygonExecutionRequest Canvas::make_stroke_draw_samples_request( + int face_index, + std::vector& polygon_vertices, + bool copy_stroke_destination) +{ + return pp::panopainter::LegacyStrokeFaceSamplePolygonExecutionRequest { + .context = "Canvas::stroke_draw_samples", + .target_size = { m_width, m_height }, + .polygon_vertices = polygon_vertices, + .face_index = face_index, + .copy_stroke_destination = copy_stroke_destination, + .copy_framebuffer_to_destination_texture = []( + int, + int src_x, + int src_y, + int dst_x, + int dst_y, + int width, + int height) { + copy_framebuffer_to_texture_2d(src_x, src_y, dst_x, dst_y, width, height); + }, + .upload_brush_vertices = [&](int, std::span vertices) { + m_brush_shape.update_vertices( + const_cast(vertices.data()), + static_cast(vertices.size())); + }, + .draw_brush_shape = [&](int) { + m_brush_shape.draw_fill(); + }, + }; +} + +void Canvas::stroke_commit() +{ + if (!m_dirty || m_layers.empty()) + return; + const auto prelude = make_canvas_stroke_commit_prelude(*this); + + const auto& b = m_current_stroke->m_brush; + const auto stroke_material = canvas_stroke_material_plan(*b, false); + const auto stroke_commit_sequence = make_canvas_stroke_commit_sequence_plan( + *this, + m_current_mode, + m_current_layer_idx, + m_smask_active, + stroke_material); + [[maybe_unused]] const auto commit_result = execute_canvas_stroke_commit_dispatch( + *this, + prelude.viewport, + prelude.clear_color, + prelude.blend, + [&](int texture_slot) { + set_active_texture_unit(texture_slot); + }, + prelude.action, + m_current_stroke.get(), + stroke_commit_sequence, + stroke_material); +} diff --git a/src/node_canvas.cpp b/src/node_canvas.cpp index e3e0fe92..9b3fdec0 100644 --- a/src/node_canvas.cpp +++ b/src/node_canvas.cpp @@ -231,6 +231,88 @@ void run_canvas_tool_mode(pp::app::CanvasToolMode mode) LOG("Canvas input tool action failed: %s", status.message); } +[[nodiscard]] kEventResult execute_node_canvas_handle_event(NodeCanvas& node_canvas, Event* e) +{ + static bool stylus_eraser = false; + MouseEvent* me = static_cast(e); + KeyEvent* ke = static_cast(e); + GestureEvent* ge = static_cast(e); + TouchEvent* te = static_cast(e); + auto loc = (me->m_pos - node_canvas.m_pos) * node_canvas.root()->m_zoom; + + switch (e->m_type) + { + case kEventType::MouseMove: + if (stylus_eraser != me->m_eraser) + { + run_canvas_tool_mode(me->m_eraser ? + pp::app::CanvasToolMode::erase : + pp::app::CanvasToolMode::draw); + stylus_eraser = me->m_eraser; + } + case kEventType::MouseScroll: + case kEventType::MouseDownL: + case kEventType::MouseUpL: + case kEventType::MouseDownR: + case kEventType::MouseUpR: + case kEventType::MouseCancel: + node_canvas.m_canvas->m_cur_pos = loc; + node_canvas.update_cursor(); + for (auto& mode : *node_canvas.m_canvas->m_mode) + mode->on_MouseEvent(me, loc); + break; + case kEventType::MouseUnfocus: + (*node_canvas.m_canvas->m_mode)[0]->m_draw_tip = false; + App::I->show_cursor(); + break; + case kEventType::MouseFocus: + node_canvas.update_cursor(); + break; + case kEventType::KeyDown: + run_canvas_hotkey( + pp::app::CanvasHotkeyEvent::key_down, + ke->m_key, + node_canvas.m_mouse_focus); + for (auto& mode : *node_canvas.m_canvas->m_mode) + mode->on_KeyEvent(ke); + break; + case kEventType::KeyUp: + node_canvas.update_cursor(); + run_canvas_hotkey( + pp::app::CanvasHotkeyEvent::key_up, + ke->m_key, + node_canvas.m_mouse_focus); + for (auto& mode : *node_canvas.m_canvas->m_mode) + mode->on_KeyEvent(ke); + break; + case kEventType::GestureStart: + node_canvas.mouse_capture(); + for (auto& mode : *node_canvas.m_canvas->m_mode) + mode->on_GestureEvent(ge); + break; + case kEventType::GestureMove: + for (auto& mode : *node_canvas.m_canvas->m_mode) + mode->on_GestureEvent(ge); + break; + case kEventType::GestureEnd: + pp::panopainter::release_legacy_mouse_capture(node_canvas); + for (auto& mode : *node_canvas.m_canvas->m_mode) + mode->on_GestureEvent(ge); + break; + case kEventType::TouchTap: + run_canvas_hotkey( + pp::app::CanvasHotkeyEvent::touch_tap, + kKey::Unknown, + node_canvas.m_mouse_focus, + te->m_finger_count); + break; + default: + return kEventResult::Available; + break; + } + return kEventResult::Consumed; +} + pp::panopainter::LegacyCanvasDrawMergeLayerPathExecution make_node_canvas_layer_path_execution( NodeCanvas& node_canvas, size_t layer_index, @@ -708,85 +790,8 @@ void NodeCanvas::handle_resize(glm::vec2 old_size, glm::vec2 new_size, float zoo kEventResult NodeCanvas::handle_event(Event* e) { - static bool stylus_eraser = false; Node::handle_event(e); - MouseEvent* me = static_cast(e); - KeyEvent* ke = static_cast(e); - GestureEvent* ge = static_cast(e); - TouchEvent* te = static_cast(e); - auto loc = (me->m_pos - m_pos) * root()->m_zoom; - - switch (e->m_type) - { - case kEventType::MouseMove: - if (stylus_eraser != me->m_eraser) - { - run_canvas_tool_mode(me->m_eraser ? - pp::app::CanvasToolMode::erase : - pp::app::CanvasToolMode::draw); - stylus_eraser = me->m_eraser; - } - case kEventType::MouseScroll: - case kEventType::MouseDownL: - case kEventType::MouseUpL: - case kEventType::MouseDownR: - case kEventType::MouseUpR: - case kEventType::MouseCancel: - m_canvas->m_cur_pos = loc; - update_cursor(); - for (auto& mode : *m_canvas->m_mode) - mode->on_MouseEvent(me, loc); - break; - case kEventType::MouseUnfocus: - (*m_canvas->m_mode)[0]->m_draw_tip = false; - App::I->show_cursor(); - break; - case kEventType::MouseFocus: - update_cursor(); - break; - case kEventType::KeyDown: - run_canvas_hotkey( - pp::app::CanvasHotkeyEvent::key_down, - ke->m_key, - m_mouse_focus); - for (auto& mode : *m_canvas->m_mode) - mode->on_KeyEvent(ke); - break; - case kEventType::KeyUp: - update_cursor(); - run_canvas_hotkey( - pp::app::CanvasHotkeyEvent::key_up, - ke->m_key, - m_mouse_focus); - for (auto& mode : *m_canvas->m_mode) - mode->on_KeyEvent(ke); - break; - case kEventType::GestureStart: - mouse_capture(); - for (auto& mode : *m_canvas->m_mode) - mode->on_GestureEvent(ge); - break; - case kEventType::GestureMove: - for (auto& mode : *m_canvas->m_mode) - mode->on_GestureEvent(ge); - break; - case kEventType::GestureEnd: - pp::panopainter::release_legacy_mouse_capture(*this); - for (auto& mode : *m_canvas->m_mode) - mode->on_GestureEvent(ge); - break; - case kEventType::TouchTap: - run_canvas_hotkey( - pp::app::CanvasHotkeyEvent::touch_tap, - kKey::Unknown, - m_mouse_focus, - te->m_finger_count); - break; - default: - return kEventResult::Available; - break; - } - return kEventResult::Consumed; + return execute_node_canvas_handle_event(*this, e); } void NodeCanvas::reset_camera() diff --git a/src/node_panel_brush.cpp b/src/node_panel_brush.cpp index 7a31638a..970078e0 100644 --- a/src/node_panel_brush.cpp +++ b/src/node_panel_brush.cpp @@ -2,10 +2,9 @@ #include "log.h" #include "node_panel_brush.h" #include "legacy_brush_panel_services.h" -#include "assets/brush_package.h" #include "app_core/brush_ui.h" #include "legacy_brush_ui_services.h" -#include "legacy_brush_package_import_services.h" +#include "legacy_brush_preset_services.h" #include "legacy_ui_overlay_services.h" #include "asset.h" #include "texture.h" @@ -586,73 +585,16 @@ void NodePanelBrushPreset::handle_click(Node* target) bool NodePanelBrushPreset::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)m_container->m_children.size()); - for (int ci = 0; ci < m_container->m_children.size(); ci++) - { - auto bpi = static_cast(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; + return pp::panopainter::LegacyBrushPresetServices(*this).save(); } bool NodePanelBrushPreset::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); - - // sanity checks - 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()) - { - add_brush(b); - } - } + const auto ok = pp::panopainter::LegacyBrushPresetServices(*this).restore(); + if (ok) { m_notification->SetVisibility(m_container->m_children.size() == 0); - return true; } - return false; + return ok; } void NodePanelBrushPreset::add_brush(std::shared_ptr brush) @@ -676,406 +618,22 @@ void NodePanelBrushPreset::add_brush(std::shared_ptr brush) bool NodePanelBrushPreset::export_ppbr(const std::string& path_in, const 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); - - // count 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); - } - - 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(m_container->m_children.size()); - - auto pb = App::I->show_progress("Exporting PPBR", 1 + img_brushes.size() + - img_patterns.size() + m_container->m_children.size() * 2); - - sw << info; - - // header image - 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(); - - // create previews - sw.wu32((int)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 : 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; - if (path_created) - img.save_jpg(fmt::format(out_path + "/thumb-{:04d}.jpg", thumb_counter), 75); - thumb_counter++; - pb->increment(); - } - - // 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; - pb->increment(); - } - - // 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; - pb->increment(); - } - - // 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; - 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; + return pp::panopainter::LegacyBrushPresetServices(*this).export_ppbr(path_in, info_data); } bool NodePanelBrushPreset::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); - - // sanity checks - 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); - - // header image - Image header_image; - if (info.value("has_header_image")) - sr >> header_image; - - pb->increment(); - - // stroke previews - auto previews_count = sr.ru32(); - for (int i = 0; i < previews_count; i++) - { - Image img; - sr >> img; - pb->increment(); - } - - // 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; - 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& path = target_paths.value().image_path; - const auto& path_thumb = target_paths.value().thumbnail_path; - if (!Asset::exist(path)) - { - img.save_png(path); - auto thumb = img.resize(64, 64); - thumb.save_png(path_thumb); - } - else - { - LOG("import_ppbr: brush image already exists in %s", path.c_str()); - } - img_brushes.insert(path); - pb->increment(); - } - - // brush patterns - 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& path = target_paths.value().image_path; - const auto& path_thumb = target_paths.value().thumbnail_path; - if (!Asset::exist(path)) - { - img.save_png(path); - auto thumb = img.resize(64, 64); - thumb.save_png(path_thumb); - } - else - { - LOG("import_ppbr: brush image already exists in %s", path.c_str()); - } - pb->increment(); - img_patterns.insert(path); - } - - // brush settings - 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(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 : s_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; + return pp::panopainter::LegacyBrushPresetServices(*this).import_ppbr(path); } 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; - - 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) - //for (const auto& samp : abr.m_samples) - { - 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)); - //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); - pb->increment(); - }); - - 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; - 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(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 : s_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; + return pp::panopainter::LegacyBrushPresetServices(*this).import_abr(path); } bool NodePanelBrushPreset::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 base = m[1].str(); - std::string name = m[2].str(); - 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; + return pp::panopainter::LegacyBrushPresetServices(*this).import_brush(path); } void NodePanelBrushPreset::clear_brushes() diff --git a/src/node_panel_brush.h b/src/node_panel_brush.h index db4f3b19..cb9e2c59 100644 --- a/src/node_panel_brush.h +++ b/src/node_panel_brush.h @@ -16,6 +16,7 @@ struct BrushPresetListPlan; namespace pp::panopainter { class LegacyBrushTextureListServices; class LegacyBrushPresetListServices; +class LegacyBrushPresetServices; } class NodeButtonBrush : public NodeButtonCustom, public Serializer::Type @@ -90,6 +91,7 @@ public: class NodePanelBrushPreset : public Node { friend class pp::panopainter::LegacyBrushPresetListServices; + friend class pp::panopainter::LegacyBrushPresetServices; static std::vector s_panels; bool m_interacted = false;