Extract canvas stroke commit and brush preset services
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.*`
|
||||
|
||||
470
src/canvas.cpp
470
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 <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
|
||||
{
|
||||
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)
|
||||
|
||||
479
src/legacy_brush_preset_services.cpp
Normal file
479
src/legacy_brush_preset_services.cpp
Normal 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
|
||||
25
src/legacy_brush_preset_services.h
Normal file
25
src/legacy_brush_preset_services.h
Normal 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
|
||||
555
src/legacy_canvas_stroke_commit_services.cpp
Normal file
555
src/legacy_canvas_stroke_commit_services.cpp
Normal 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);
|
||||
}
|
||||
@@ -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<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(
|
||||
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<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 - 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()
|
||||
|
||||
@@ -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<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;
|
||||
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<Brush>();
|
||||
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> 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)
|
||||
{
|
||||
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<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;
|
||||
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<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;
|
||||
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<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;
|
||||
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()
|
||||
|
||||
@@ -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<NodePanelBrushPreset*> s_panels;
|
||||
bool m_interacted = false;
|
||||
|
||||
Reference in New Issue
Block a user