#include "pch.h" #include "canvas.h" #include "app.h" #include "legacy_canvas_stroke_composite_services.h" #include "legacy_ui_gl_dispatch.h" #include "renderer_gl/opengl_capabilities.h" #include "util.h" #include #include #include namespace { GLenum blend_state() { return static_cast(pp::renderer::gl::blend_state()); } void set_active_texture_unit(std::uint32_t unit_index) { pp::legacy::ui_gl::activate_texture_unit(unit_index, "Canvas"); } void 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"); } void apply_canvas_capability(std::uint32_t state, bool enabled) { pp::legacy::ui_gl::set_capability(state, enabled, "Canvas"); } } // namespace void Canvas::layer_merge(int source_idx, int dest_idx) // m_layer index { m_dirty = false; App::I->render_task([&] { apply_canvas_viewport(0, 0, m_width, m_height); apply_canvas_capability(blend_state(), false); for (int i = 0; i < 6; i++) { if (!m_layers[source_idx]->face(i)) continue; m_layers[dest_idx]->rtt(i).bindFramebuffer(); auto& lbox = m_layers[dest_idx]->box(i); lbox = glm::vec4( glm::min(xy(m_layers[source_idx]->box(i)), xy(lbox)), glm::max(zw(m_layers[source_idx]->box(i)), zw(lbox)) ); m_layers[dest_idx]->face(i) = true; set_active_texture_unit(0); m_tex2[i].bind(); copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height); m_tex2[i].unbind(); m_sampler.bind(0); m_sampler_nearest.bind(1); { pp::panopainter::setup_legacy_stroke_composite_shader( pp::panopainter::LegacyStrokeCompositeUniforms { .resolution = m_size, .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f), .layer_alpha = m_layers[source_idx]->m_opacity, .alpha_lock = false, .mask_enabled = false, .use_fragcoord = false, .blend_mode = m_layers[source_idx]->m_blend_mode, .use_dual = false, .dual_alpha = 0.0f, .use_pattern = false, }); set_active_texture_unit(0); m_tex2[i].bind(); set_active_texture_unit(1); m_layers[source_idx]->rtt(i).bindTexture(); m_plane.draw_fill(); m_layers[source_idx]->rtt(i).unbindTexture(); set_active_texture_unit(0); m_tex2[i].unbind(); } m_layers[dest_idx]->rtt(i).unbindFramebuffer(); } }); } void Canvas::flood_fill(int layer, int plane, std::vector pos, FloodData& plane_data, float threshold, glm::vec4 dest_color, std::unique_ptr& source_color) { struct adj_t { int plane; bool flipx; bool flipy; bool flipcoord; adj_t(int plane, bool flipx, bool flipy, int flipcoord) : plane(plane), flipx(flipx), flipy(flipy), flipcoord(flipcoord) { } glm::ivec2 compute(glm::ivec2 p, glm::ivec2 sz) const { glm::ivec2 ret; ret[flipcoord] = flipx ? sz.x - p.x : p.x; ret[1 - flipcoord] = flipy ? sz.y - p.y : p.y; return ret; } }; LOG("flood_fill plane %d", plane); auto& rtt = m_layers[layer]->rtt(plane); auto sz = rtt.getSize(); if (!plane_data.mask[plane]) { plane_data.mask[plane] = std::make_unique((size_t)sz.x * sz.y); plane_data.rgb[plane] = std::unique_ptr( reinterpret_cast(m_layers[layer]->rtt(plane).readTextureData())); plane_data.bb[plane] = { sz.x, sz.y, 0, 0 }; plane_data.dirty[plane] = false; plane_data.layer = m_layers[layer]; } auto& mask = plane_data.mask[plane]; auto& rgb = plane_data.rgb[plane]; if (!source_color) source_color = std::make_unique(rgb[pos.back().y * sz.x + pos.back().x]); const glm::vec4 c = *source_color; std::array, 4> edges; static const std::array adj[6] = { { adj_t(3, 1, 0, 0), adj_t(4, 1, 1, 0), adj_t(1, 0, 0, 0), adj_t(5, 1, 0, 0), }, { adj_t(0, 1, 0, 0), adj_t(4, 1, 0, 1), adj_t(2, 0, 0, 0), adj_t(5, 0, 0, 1), }, { adj_t(1, 1, 0, 0), adj_t(4, 0, 0, 0), adj_t(3, 0, 0, 0), adj_t(5, 0, 1, 0), }, { adj_t(2, 1, 0, 0), adj_t(4, 0, 1, 1), adj_t(0, 0, 0, 0), adj_t(5, 1, 1, 1), }, { adj_t(1, 1, 1, 1), adj_t(0, 1, 1, 0), adj_t(3, 1, 0, 1), adj_t(2, 0, 1, 0), }, { adj_t(1, 0, 0, 1), adj_t(2, 0, 0, 0), adj_t(3, 0, 1, 1), adj_t(0, 1, 0, 0), }, }; auto test = [&](glm::ivec2 p, bool set_color) -> bool { int i = p.y * sz.x + p.x; if (p.x < 0) { edges[0].push_back(adj[plane][0].compute({ -p.x, p.y }, sz)); return false; } else if (p.x >= sz.x) { edges[2].push_back(adj[plane][2].compute({ sz.x - p.x + 1, p.y }, sz)); return false; } else if (p.y < 0) { edges[3].push_back(adj[plane][3].compute({ p.x, -p.y }, sz)); return false; } else if (p.y >= sz.y) { edges[1].push_back(adj[plane][1].compute({ p.x, sz.y - p.y + 1 }, sz)); return false; } if (!mask[i]) { if (c.a == 0 && glm::abs(rgb[i].a - c.a) < threshold || c.a > 0 && rgb[i].a > 0 && glm::distance(glm::vec3(c), glm::vec3(rgb[i])) < threshold) { if (set_color) { mask[i] = true; rgb[i] = dest_color * 255.f; plane_data.dirty[plane] = true; glm::vec2 bb_min = glm::min((glm::vec2)p, xy(plane_data.bb[plane])); glm::vec2 bb_max = glm::max((glm::vec2)p + glm::vec2(1), zw(plane_data.bb[plane])); plane_data.bb[plane] = { bb_min, bb_max }; } pos.push_back(p); } return true; } return false; }; while (!pos.empty()) { auto p = pos.back(); pos.pop_back(); if (!test(p + glm::ivec2(0, 0), true)) continue; test(p + glm::ivec2(-1, 0), false); test(p + glm::ivec2(1, 0), false); test(p + glm::ivec2(0, 1), false); test(p + glm::ivec2(0, -1), false); } for (int i = 0; i < 4; i++) { if (!edges[i].empty()) { flood_fill(layer, adj[plane][i].plane, edges[i], plane_data, threshold, dest_color, source_color); } } } void Canvas::FloodData::apply() { for (int plane = 0; plane < 6; plane++) { if (!dirty[plane]) continue; auto& rtt = layer->rtt(plane); App::I->render_task([&] { rtt.updateRgba8(0, 0, rtt.getWidth(), rtt.getHeight(), rgb[plane].get()); }); layer->face(plane) = true; layer->box(plane) = box_union(layer->box(plane), bb[plane]); } Canvas::I->m_unsaved = true; }