Extract canvas stroke commit and brush preset services

This commit is contained in:
2026-06-16 19:07:04 +02:00
parent a5002a4e3e
commit 200265e11d
10 changed files with 1188 additions and 1005 deletions

View File

@@ -22,6 +22,7 @@ set(PP_LEGACY_PAINT_DOCUMENT_SOURCES
src/canvas.cpp src/canvas.cpp
src/canvas_actions.cpp src/canvas_actions.cpp
src/canvas_layer.cpp src/canvas_layer.cpp
src/legacy_canvas_stroke_commit_services.cpp
src/legacy_canvas_document_io_services.cpp src/legacy_canvas_document_io_services.cpp
src/legacy_canvas_object_draw_services.cpp src/legacy_canvas_object_draw_services.cpp
src/legacy_canvas_object_draw_services.h 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_import_services.h
src/legacy_brush_package_export_services.cpp src/legacy_brush_package_export_services.cpp
src/legacy_brush_package_export_services.h 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.cpp
src/legacy_cloud_services.h src/legacy_cloud_services.h
src/legacy_document_export_services.cpp src/legacy_document_export_services.cpp

View File

@@ -79,14 +79,14 @@ What is still carrying too much live ownership:
Current hotspot files: Current hotspot files:
- `src/canvas.cpp`: 2592 lines - `src/canvas.cpp`: 2122 lines
- `src/app_layout.cpp`: 125 lines - `src/app_layout.cpp`: 125 lines
- `src/canvas_modes.cpp`: 1626 lines - `src/canvas_modes.cpp`: 1626 lines
- `src/node.cpp`: 1368 lines - `src/node.cpp`: 1368 lines
- `src/main.cpp`: 271 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_stroke_preview.cpp`: 910 lines
- `src/node_canvas.cpp`: 872 lines - `src/node_canvas.cpp`: 877 lines
- `src/app.cpp`: 575 lines - `src/app.cpp`: 575 lines
- `src/app_dialogs.cpp`: 168 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 while `Canvas::draw_objects_direct(...)` and `Canvas::draw_objects(...)` now
route through `src/legacy_canvas_object_draw_services.*` instead of staying route through `src/legacy_canvas_object_draw_services.*` instead of staying
inline in `src/canvas.cpp`, which trims another coherent object-draw and 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, viewport-state execution family from the live canvas shell,
while `NodePanelBrush` save/restore/scan/reload/find/get-path ownership now while `NodePanelBrush` save/restore/scan/reload/find/get-path ownership now
routes through `src/legacy_brush_panel_services.*` instead of staying inline routes through `src/legacy_brush_panel_services.*` instead of staying inline
in `src/node_panel_brush.cpp`, which trims another retained brush-workflow 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 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 `CanvasModeBasicCamera` and `CanvasModeCamera` input handlers now also route
through `src/legacy_canvas_mode_helpers.*` instead of staying inline in through `src/legacy_canvas_mode_helpers.*` instead of staying inline in
`src/canvas_modes.cpp`, while `src/canvas_modes.cpp`, while

View File

@@ -91,7 +91,7 @@ Status: In Progress
Why now: Why now:
`src/canvas.cpp` is still the biggest single architectural blocker at about `src/canvas.cpp` is still the biggest single architectural blocker at about
2592 lines. 2122 lines.
Current slice: Current slice:
- Canvas state-management helpers for picking, clear/clear-all, layer - 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 live in `src/legacy_canvas_object_draw_services.*` instead of staying inline
in `src/canvas.cpp`, which trims another coherent object-draw and in `src/canvas.cpp`, which trims another coherent object-draw and
viewport-state execution pocket from the live canvas shell. 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 - Shared canvas-mode GL wrappers plus the `CanvasModeBasicCamera` and
`CanvasModeCamera` input handlers now also live in `CanvasModeCamera` input handlers now also live in
`src/legacy_canvas_mode_helpers.*` instead of staying inline 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 route through `execute_node_canvas_draw_merged_pass(...)`, which trims
another coherent merged draw-orchestration block from the live node even another coherent merged draw-orchestration block from the live node even
though the broader draw loop still remains inline. 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: Write scope:
- `src/node_stroke_preview.cpp` - `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 #### ARC-RND-003 - Replace App-Facing `Texture2D`/`RTT` Use With Renderer API Contracts
Status: Ready Status: In Progress
Why now: Why now:
Future Vulkan and Metal work needs the live app to stop treating retained 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 `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 pocket from the live UI node even though cloud and package-worker ownership
still remain separate follow-up work. 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: Write scope:
- `src/legacy_cloud_services.*` - `src/legacy_cloud_services.*`

View File

@@ -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 <typename SetActiveTextureUnit>
static auto make_canvas_stroke_commit_callbacks(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> 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<float>(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<vertex_t>& 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<vertex_t>& 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<const vertex_t> vertices) {
m_brush_shape.update_vertices(
const_cast<vertex_t*>(vertices.data()),
static_cast<int>(vertices.size()));
},
.draw_brush_shape = [&](int) {
m_brush_shape.draw_fill();
},
};
}
template <typename BuildRequest>
static auto execute_canvas_stroke_commit_sequence(
BuildRequest&& build_request)
{
return pp::panopainter::execute_legacy_canvas_stroke_commit_sequence(
build_request());
}
template <typename SetActiveTextureUnit>
static auto make_canvas_stroke_commit_request(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> 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<SetActiveTextureUnit>(set_active_texture_unit),
action,
current_stroke,
sequence,
stroke_material);
const std::array<pp::panopainter::LegacyCanvasStrokeCommitFace, 6> 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 <typename SetActiveTextureUnit>
static auto execute_canvas_stroke_commit_request(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> 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<SetActiveTextureUnit>(set_active_texture_unit),
action,
current_stroke,
sequence,
stroke_material);
});
}
template <typename SetActiveTextureUnit>
static auto execute_canvas_stroke_commit_dispatch(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> 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<SetActiveTextureUnit>(set_active_texture_unit),
action,
current_stroke,
sequence,
stroke_material);
}
struct CanvasStrokeCommitPrelude {
pp::renderer::gl::OpenGlViewportRect viewport;
std::array<float, 4> 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<uint8_t[]>(
static_cast<std::size_t>(box_sz.x * box_sz.y * 4));
canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).readPixelsRgba8(
static_cast<int>(canvas.m_dirty_box[i].x),
static_cast<int>(canvas.m_dirty_box[i].y),
static_cast<int>(box_sz.x),
static_cast<int>(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::StrokeFrame> Canvas::stroke_draw_compute(Stroke& stroke) const std::vector<Canvas::StrokeFrame> Canvas::stroke_draw_compute(Stroke& stroke) const
{ {
auto samples = stroke.compute_samples(); 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); 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() void Canvas::stroke_commit_timelapse()
{ {
if (m_encoder && App::I->rec_running) if (m_encoder && App::I->rec_running)

View File

@@ -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 <fstream>
#include <regex>
#include <set>
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<NodeBrushPresetItem*>(owner_.m_container->get_child_at(ci));
sw << *bpi->m_brush;
}
f.write((char*)sw.m_data.data(), sw.m_data.size());
return true;
}
return false;
}
bool LegacyBrushPresetServices::restore()
{
auto path = App::I->data_path + "/settings/presets.bin";
Asset f;
if (f.open(path.c_str()))
{
f.read_all();
BinaryStreamReader sr;
sr.init(f.m_data, f.m_len);
auto magic = sr.rstring(4);
if (magic != "PPVR")
{
LOG("PPVR tag not found")
return false;
}
auto vmaj = sr.ru16();
auto vmin = sr.ru16();
if (vmaj != 0 && vmin != 1)
{
LOG("unrecognised version %d.%d", vmaj, vmin);
return false;
}
auto count = sr.ru32();
for (int k = 0; k < count; k++)
{
auto b = std::make_shared<Brush>();
if (!b->read(sr))
{
LOG("error deserializing the brush");
return false;
}
if (b->valid())
{
owner_.add_brush(b);
}
}
return true;
}
return false;
}
bool LegacyBrushPresetServices::export_ppbr(const std::string& path_in, const NodePanelBrushPreset::PPBRInfo& info_data)
{
const auto data_directory_policy = App::I->uses_ppbr_export_data_directory_override()
? pp::assets::PpbrDataDirectoryPolicy::override_directory
: pp::assets::PpbrDataDirectoryPolicy::next_to_package;
const auto export_paths = pp::assets::plan_ppbr_export_paths(
path_in,
info_data.dest_path,
info_data.export_data,
data_directory_policy);
if (!export_paths) {
LOG("export_ppbr invalid path: %s", export_paths.status().message);
return false;
}
const auto& path = export_paths.value().package_path;
LOG("export ppbr to: %s", path.c_str());
const auto& out_path = export_paths.value().data_directory;
bool path_created = export_paths.value().data_directory_enabled ? Asset::create_dir(out_path) : false;
std::ofstream f(path, std::ios::binary);
if (f.good())
{
BinaryStreamWriter sw;
sw.init(BinaryStream::ByteOrder::LittleEndian);
sw.wstring_raw("PPBR");
sw.wu16(0);
sw.wu16(1);
std::set<std::string> img_brushes;
std::set<std::string> img_patterns;
for (auto& c : owner_.m_container->m_children)
{
auto bpi = std::static_pointer_cast<NodeBrushPresetItem>(c);
if (!bpi->m_brush->m_brush_path.empty() && !Asset::is_asset(bpi->m_brush->m_brush_path))
img_brushes.insert(bpi->m_brush->m_brush_path);
if (!bpi->m_brush->m_dual_path.empty() && !Asset::is_asset(bpi->m_brush->m_dual_path))
img_brushes.insert(bpi->m_brush->m_dual_path);
if (!bpi->m_brush->m_pattern_path.empty() && !Asset::is_asset(bpi->m_brush->m_pattern_path))
img_patterns.insert(bpi->m_brush->m_pattern_path);
}
Serializer::Descriptor info;
info.class_id = "ppbr_info";
info.name = L"info header";
bool has_header_image = info_data.header_image != nullptr;
info.props["author"] = std::make_shared<Serializer::String>(info_data.author);
info.props["email"] = std::make_shared<Serializer::String>(info_data.email);
info.props["url"] = std::make_shared<Serializer::String>(info_data.url);
info.props["descr"] = std::make_shared<Serializer::String>(info_data.descr);
info.props["has_header_image"] = std::make_shared<Serializer::Boolean>(has_header_image);
info.props["num_brush_tips"] = std::make_shared<Serializer::Integer>(img_brushes.size());
info.props["num_brush_patt"] = std::make_shared<Serializer::Integer>(img_patterns.size());
info.props["num_brushes"] = std::make_shared<Serializer::Integer>(owner_.m_container->m_children.size());
auto pb = App::I->show_progress("Exporting PPBR", 1 + img_brushes.size() +
img_patterns.size() + owner_.m_container->m_children.size() * 2);
sw << info;
if (has_header_image)
{
sw << *info_data.header_image;
if (path_created)
info_data.header_image->save_jpg(out_path + "/header.jpg", 75);
}
pb->increment();
sw.wu32((int)owner_.m_container->m_children.size());
auto pr = std::make_unique<NodeStrokePreview>();
pr->m_preview_size = pr->m_size = { 256, 128 };
int thumb_counter = 0;
for (auto& c : owner_.m_container->m_children)
{
auto bpi = std::static_pointer_cast<NodeBrushPresetItem>(c);
pr->m_brush = std::make_shared<Brush>(*bpi->m_brush);
pr->m_brush->load();
Image img = pr->render_to_image();
img.file_name = pr->m_brush->m_name;
sw << img;
if (path_created)
img.save_jpg(fmt::format(out_path + "/thumb-{:04d}.jpg", thumb_counter), 75);
thumb_counter++;
pb->increment();
}
sw.wu32((int)img_brushes.size());
for (std::string image_path : img_brushes)
{
Image img;
if (!img.load(image_path))
LOG("export_ppbr failed to load image: %s", image_path.c_str());
sw << img;
pb->increment();
}
sw.wu32((int)img_patterns.size());
for (std::string image_path : img_patterns)
{
Image img;
if (!img.load(image_path))
LOG("export_ppbr failed to load image: %s", image_path.c_str());
sw << img;
pb->increment();
}
sw.wu32((int)owner_.m_container->m_children.size());
for (auto& c : owner_.m_container->m_children)
{
auto bpi = std::static_pointer_cast<NodeBrushPresetItem>(c);
sw << *bpi->m_brush;
pb->increment();
}
f.write((char*)sw.m_data.data(), sw.m_data.size());
pp::panopainter::close_legacy_dialog_node(*pb);
return true;
}
LOG("export failed file creation");
return false;
}
bool LegacyBrushPresetServices::import_ppbr(const std::string& path)
{
BT_SetTerminate();
Asset f;
if (f.open(path.c_str()))
{
f.read_all();
BinaryStreamReader sr;
sr.init(f.m_data, f.m_len, BinaryStream::ByteOrder::LittleEndian);
auto magic = sr.rstring(4);
auto vmaj = sr.ru16();
auto vmin = sr.ru16();
const auto header_status = pp::assets::validate_ppbr_header(magic, vmaj, vmin);
if (!header_status.ok())
{
LOG("PPBR header rejected: %s (%d.%d)", header_status.message, vmaj, vmin);
return false;
}
Serializer::Descriptor info;
sr >> info;
int num_brush_tips = info.value<Serializer::Integer>("num_brush_tips");
int num_brush_patt = info.value<Serializer::Integer>("num_brush_patt");
int num_brushes = info.value<Serializer::Integer>("num_brushes");
std::string info_dump = info.str(0, "Info");
LOG("%s", info_dump.c_str());
auto pb = App::I->show_progress("Importing PPBR", 1 + num_brush_patt + num_brush_tips + num_brushes * 2);
Image header_image;
if (info.value<Serializer::Boolean>("has_header_image"))
sr >> header_image;
pb->increment();
auto previews_count = sr.ru32();
for (int i = 0; i < previews_count; i++)
{
Image img;
sr >> img;
pb->increment();
}
std::set<std::string> img_brushes;
std::set<std::string> img_patterns;
auto tips_count = sr.ru32();
for (int i = 0; i < tips_count; i++)
{
Image img;
sr >> img;
const auto target_paths = pp::assets::plan_brush_package_image_target_paths(
App::I->data_path,
pp::assets::BrushPackageImageKind::brush_tip,
img.file_name,
img.file_ext);
if (!target_paths) {
LOG("import_ppbr invalid brush image target: %s", target_paths.status().message);
return false;
}
const auto& image_path = target_paths.value().image_path;
const auto& path_thumb = target_paths.value().thumbnail_path;
if (!Asset::exist(image_path))
{
img.save_png(image_path);
auto thumb = img.resize(64, 64);
thumb.save_png(path_thumb);
}
else
{
LOG("import_ppbr: brush image already exists in %s", image_path.c_str());
}
img_brushes.insert(image_path);
pb->increment();
}
auto patt_count = sr.ru32();
for (int i = 0; i < patt_count; i++)
{
Image img;
sr >> img;
const auto target_paths = pp::assets::plan_brush_package_image_target_paths(
App::I->data_path,
pp::assets::BrushPackageImageKind::pattern,
img.file_name,
img.file_ext);
if (!target_paths) {
LOG("import_ppbr invalid pattern image target: %s", target_paths.status().message);
return false;
}
const auto& image_path = target_paths.value().image_path;
const auto& path_thumb = target_paths.value().thumbnail_path;
if (!Asset::exist(image_path))
{
img.save_png(image_path);
auto thumb = img.resize(64, 64);
thumb.save_png(path_thumb);
}
else
{
LOG("import_ppbr: brush image already exists in %s", image_path.c_str());
}
pb->increment();
img_patterns.insert(image_path);
}
auto brushes_count = sr.ru32();
std::vector<std::shared_ptr<Brush>> brushes_to_add;
if (brushes_count > 0) {
brushes_to_add.reserve(static_cast<std::size_t>(brushes_count));
}
for (int i = 0; i < brushes_count; i++)
{
auto b = std::make_shared<Brush>();
sr >> *b;
b->relocate_paths(App::I->data_path);
LOG("import_ppbr brush name %s", b->m_name.c_str());
if (b->valid())
{
brushes_to_add.push_back(b);
}
pb->increment();
}
auto owner = std::static_pointer_cast<NodePanelBrushPreset>(owner_.shared_from_this());
App::I->ui_task([owner, brushes_to_add = std::move(brushes_to_add), pb]() mutable {
for (const auto& b : brushes_to_add)
{
for (auto p : 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<NodePanelBrushPreset>(owner_.shared_from_this());
App::I->ui_task([owner, brushes = std::move(brushes), pb]() mutable {
for (const auto& b : brushes)
{
if (b->valid())
{
LOG("add preset %s", b->m_name.c_str());
for (auto p : 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

View File

@@ -0,0 +1,25 @@
#pragma once
#include "node_panel_brush.h"
#include <string>
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

View File

@@ -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 <array>
#include <cstdint>
#include <utility>
namespace {
GLenum blend_state()
{
return static_cast<GLenum>(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<float, 4> 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<float, 4> 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<float, 4> 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 <typename SetActiveTextureUnit>
static auto make_canvas_stroke_commit_callbacks(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> 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<float>(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 <typename BuildRequest>
static auto execute_canvas_stroke_commit_sequence(
BuildRequest&& build_request)
{
return pp::panopainter::execute_legacy_canvas_stroke_commit_sequence(
build_request());
}
template <typename SetActiveTextureUnit>
static auto make_canvas_stroke_commit_request(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> 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<SetActiveTextureUnit>(set_active_texture_unit),
action,
current_stroke,
sequence,
stroke_material);
const std::array<pp::panopainter::LegacyCanvasStrokeCommitFace, 6> 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 <typename SetActiveTextureUnit>
static auto execute_canvas_stroke_commit_request(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> 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<SetActiveTextureUnit>(set_active_texture_unit),
action,
current_stroke,
sequence,
stroke_material);
});
}
template <typename SetActiveTextureUnit>
static auto execute_canvas_stroke_commit_dispatch(
Canvas& canvas,
pp::renderer::gl::OpenGlViewportRect vp,
std::array<float, 4> 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<SetActiveTextureUnit>(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<uint8_t[]>(
static_cast<std::size_t>(box_sz.x * box_sz.y * 4));
canvas.m_layers[canvas.m_current_layer_idx]->rtt(i).readPixelsRgba8(
static_cast<int>(canvas.m_dirty_box[i].x),
static_cast<int>(canvas.m_dirty_box[i].y),
static_cast<int>(box_sz.x),
static_cast<int>(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<vertex_t>& 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<vertex_t>& 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<const vertex_t> vertices) {
m_brush_shape.update_vertices(
const_cast<vertex_t*>(vertices.data()),
static_cast<int>(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);
}

View File

@@ -231,6 +231,88 @@ void run_canvas_tool_mode(pp::app::CanvasToolMode mode)
LOG("Canvas input tool action failed: %s", status.message); 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<MouseEvent*>(e);
KeyEvent* ke = static_cast<KeyEvent*>(e);
GestureEvent* ge = static_cast<GestureEvent*>(e);
TouchEvent* te = static_cast<TouchEvent*>(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( pp::panopainter::LegacyCanvasDrawMergeLayerPathExecution make_node_canvas_layer_path_execution(
NodeCanvas& node_canvas, NodeCanvas& node_canvas,
size_t layer_index, 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) kEventResult NodeCanvas::handle_event(Event* e)
{ {
static bool stylus_eraser = false;
Node::handle_event(e); Node::handle_event(e);
MouseEvent* me = static_cast<MouseEvent*>(e); return execute_node_canvas_handle_event(*this, e);
KeyEvent* ke = static_cast<KeyEvent*>(e);
GestureEvent* ge = static_cast<GestureEvent*>(e);
TouchEvent* te = static_cast<TouchEvent*>(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;
} }
void NodeCanvas::reset_camera() void NodeCanvas::reset_camera()

View File

@@ -2,10 +2,9 @@
#include "log.h" #include "log.h"
#include "node_panel_brush.h" #include "node_panel_brush.h"
#include "legacy_brush_panel_services.h" #include "legacy_brush_panel_services.h"
#include "assets/brush_package.h"
#include "app_core/brush_ui.h" #include "app_core/brush_ui.h"
#include "legacy_brush_ui_services.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 "legacy_ui_overlay_services.h"
#include "asset.h" #include "asset.h"
#include "texture.h" #include "texture.h"
@@ -586,73 +585,16 @@ void NodePanelBrushPreset::handle_click(Node* target)
bool NodePanelBrushPreset::save() bool NodePanelBrushPreset::save()
{ {
auto path = App::I->data_path + "/settings/presets.bin"; return pp::panopainter::LegacyBrushPresetServices(*this).save();
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<NodeBrushPresetItem*>(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 NodePanelBrushPreset::restore() bool NodePanelBrushPreset::restore()
{ {
auto path = App::I->data_path + "/settings/presets.bin"; const auto ok = pp::panopainter::LegacyBrushPresetServices(*this).restore();
Asset f; if (ok) {
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<Brush>();
if (!b->read(sr))
{
LOG("error deserializing the brush");
return false;
}
if (b->valid())
{
add_brush(b);
}
}
m_notification->SetVisibility(m_container->m_children.size() == 0); m_notification->SetVisibility(m_container->m_children.size() == 0);
return true;
} }
return false; return ok;
} }
void NodePanelBrushPreset::add_brush(std::shared_ptr<Brush> brush) void NodePanelBrushPreset::add_brush(std::shared_ptr<Brush> brush)
@@ -676,406 +618,22 @@ void NodePanelBrushPreset::add_brush(std::shared_ptr<Brush> brush)
bool NodePanelBrushPreset::export_ppbr(const std::string& path_in, const PPBRInfo& info_data) 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() return pp::panopainter::LegacyBrushPresetServices(*this).export_ppbr(path_in, info_data);
? 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<std::string> img_brushes;
std::set<std::string> img_patterns;
for (auto& c : m_container->m_children)
{
auto bpi = std::static_pointer_cast<NodeBrushPresetItem>(c);
if (!bpi->m_brush->m_brush_path.empty() && !Asset::is_asset(bpi->m_brush->m_brush_path))
img_brushes.insert(bpi->m_brush->m_brush_path);
if (!bpi->m_brush->m_dual_path.empty() && !Asset::is_asset(bpi->m_brush->m_dual_path))
img_brushes.insert(bpi->m_brush->m_dual_path);
if (!bpi->m_brush->m_pattern_path.empty() && !Asset::is_asset(bpi->m_brush->m_pattern_path))
img_patterns.insert(bpi->m_brush->m_pattern_path);
}
Serializer::Descriptor info;
info.class_id = "ppbr_info";
info.name = L"info header";
bool has_header_image = info_data.header_image != nullptr;
info.props["author"] = std::make_shared<Serializer::String>(info_data.author);
info.props["email"] = std::make_shared<Serializer::String>(info_data.email);
info.props["url"] = std::make_shared<Serializer::String>(info_data.url);
info.props["descr"] = std::make_shared<Serializer::String>(info_data.descr);
info.props["has_header_image"] = std::make_shared<Serializer::Boolean>(has_header_image);
info.props["num_brush_tips"] = std::make_shared<Serializer::Integer>(img_brushes.size());
info.props["num_brush_patt"] = std::make_shared<Serializer::Integer>(img_patterns.size());
info.props["num_brushes"] = std::make_shared<Serializer::Integer>(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<NodeStrokePreview>();
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<NodeBrushPresetItem>(c);
pr->m_brush = std::make_shared<Brush>(*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<NodeBrushPresetItem>(c);
sw << *bpi->m_brush;
pb->increment();
}
f.write((char*)sw.m_data.data(), sw.m_data.size());
pp::panopainter::close_legacy_dialog_node(*pb);
return true;
}
LOG("export failed file creation");
return false;
} }
bool NodePanelBrushPreset::import_ppbr(const std::string& path) bool NodePanelBrushPreset::import_ppbr(const std::string& path)
{ {
BT_SetTerminate(); return pp::panopainter::LegacyBrushPresetServices(*this).import_ppbr(path);
Asset f;
if (f.open(path.c_str()))
{
f.read_all();
BinaryStreamReader sr;
sr.init(f.m_data, f.m_len, BinaryStream::ByteOrder::LittleEndian);
// sanity checks
auto magic = sr.rstring(4);
auto vmaj = sr.ru16();
auto vmin = sr.ru16();
const auto header_status = pp::assets::validate_ppbr_header(magic, vmaj, vmin);
if (!header_status.ok())
{
LOG("PPBR header rejected: %s (%d.%d)", header_status.message, vmaj, vmin);
return false;
}
Serializer::Descriptor info;
sr >> info;
int num_brush_tips = info.value<Serializer::Integer>("num_brush_tips");
int num_brush_patt = info.value<Serializer::Integer>("num_brush_patt");
int num_brushes = info.value<Serializer::Integer>("num_brushes");
std::string info_dump = info.str(0, "Info");
LOG("%s", info_dump.c_str());
auto pb = App::I->show_progress("Importing PPBR", 1 + num_brush_patt + num_brush_tips + num_brushes * 2);
// header image
Image header_image;
if (info.value<Serializer::Boolean>("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<std::string> img_brushes;
std::set<std::string> 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<std::shared_ptr<Brush>> brushes_to_add;
if (brushes_count > 0) {
brushes_to_add.reserve(static_cast<std::size_t>(brushes_count));
}
for (int i = 0; i < brushes_count; i++)
{
auto b = std::make_shared<Brush>();
sr >> *b;
b->relocate_paths(App::I->data_path);
LOG("import_ppbr brush name %s", b->m_name.c_str());
if (b->valid())
{
brushes_to_add.push_back(b);
}
pb->increment();
}
auto owner = std::static_pointer_cast<NodePanelBrushPreset>(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;
} }
bool NodePanelBrushPreset::import_abr(const std::string& path) bool NodePanelBrushPreset::import_abr(const std::string& path)
{ {
BT_SetTerminate(); return pp::panopainter::LegacyBrushPresetServices(*this).import_abr(path);
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<NodePanelBrushPreset>(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;
} }
bool NodePanelBrushPreset::import_brush(const std::string& path) bool NodePanelBrushPreset::import_brush(const std::string& path)
{ {
std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); return pp::panopainter::LegacyBrushPresetServices(*this).import_brush(path);
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;
} }
void NodePanelBrushPreset::clear_brushes() void NodePanelBrushPreset::clear_brushes()

View File

@@ -16,6 +16,7 @@ struct BrushPresetListPlan;
namespace pp::panopainter { namespace pp::panopainter {
class LegacyBrushTextureListServices; class LegacyBrushTextureListServices;
class LegacyBrushPresetListServices; class LegacyBrushPresetListServices;
class LegacyBrushPresetServices;
} }
class NodeButtonBrush : public NodeButtonCustom, public Serializer::Type class NodeButtonBrush : public NodeButtonCustom, public Serializer::Type
@@ -90,6 +91,7 @@ public:
class NodePanelBrushPreset : public Node class NodePanelBrushPreset : public Node
{ {
friend class pp::panopainter::LegacyBrushPresetListServices; friend class pp::panopainter::LegacyBrushPresetListServices;
friend class pp::panopainter::LegacyBrushPresetServices;
static std::vector<NodePanelBrushPreset*> s_panels; static std::vector<NodePanelBrushPreset*> s_panels;
bool m_interacted = false; bool m_interacted = false;