diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/ui/widget/canvas/cairographics.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-upstream.tar.xz inkscape-upstream.zip |
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ui/widget/canvas/cairographics.cpp')
-rw-r--r-- | src/ui/widget/canvas/cairographics.cpp | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/src/ui/widget/canvas/cairographics.cpp b/src/ui/widget/canvas/cairographics.cpp new file mode 100644 index 0000000..42b3353 --- /dev/null +++ b/src/ui/widget/canvas/cairographics.cpp @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <2geom/parallelogram.h> +#include "ui/util.h" +#include "helper/geom.h" +#include "cairographics.h" +#include "stores.h" +#include "prefs.h" +#include "util.h" +#include "framecheck.h" + +namespace Inkscape { +namespace UI { +namespace Widget { + +CairoGraphics::CairoGraphics(Prefs const &prefs, Stores const &stores, PageInfo const &pi) + : prefs(prefs) + , stores(stores) + , pi(pi) {} + +std::unique_ptr<Graphics> Graphics::create_cairo(Prefs const &prefs, Stores const &stores, PageInfo const &pi) +{ + return std::make_unique<CairoGraphics>(prefs, stores, pi); +} + +void CairoGraphics::set_outlines_enabled(bool enabled) +{ + outlines_enabled = enabled; + if (!enabled) { + store.outline_surface.clear(); + snapshot.outline_surface.clear(); + } +} + +void CairoGraphics::recreate_store(Geom::IntPoint const &dims) +{ + auto surface_size = dims * scale_factor; + + auto make_surface = [&, this] { + auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, surface_size.x(), surface_size.y()); + cairo_surface_set_device_scale(surface->cobj(), scale_factor, scale_factor); // No C++ API! + return surface; + }; + + // Recreate the store surface. + bool reuse_surface = store.surface && dimensions(store.surface) == surface_size; + if (!reuse_surface) { + store.surface = make_surface(); + } + + // Ensure the store surface is filled with the correct default background. + if (background_in_stores) { + auto cr = Cairo::Context::create(store.surface); + paint_background(stores.store(), pi, page, desk, cr); + } else if (reuse_surface) { + auto cr = Cairo::Context::create(store.surface); + cr->set_operator(Cairo::OPERATOR_CLEAR); + cr->paint(); + } + + // Do the same for the outline surface (except always clearing it to transparent). + if (outlines_enabled) { + bool reuse_outline_surface = store.outline_surface && dimensions(store.outline_surface) == surface_size; + if (!reuse_outline_surface) { + store.outline_surface = make_surface(); + } else { + auto cr = Cairo::Context::create(store.outline_surface); + cr->set_operator(Cairo::OPERATOR_CLEAR); + cr->paint(); + } + } +} + +void CairoGraphics::shift_store(Fragment const &dest) +{ + auto surface_size = dest.rect.dimensions() * scale_factor; + + // Determine the geometry of the shift. + auto shift = dest.rect.min() - stores.store().rect.min(); + auto reuse_rect = (dest.rect & cairo_to_geom(stores.store().drawn->get_extents())).regularized(); + assert(reuse_rect); // Should not be called if there is no overlap. + + auto make_surface = [&, this] { + auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, surface_size.x(), surface_size.y()); + cairo_surface_set_device_scale(surface->cobj(), scale_factor, scale_factor); // No C++ API! + return surface; + }; + + // Create the new store surface. + bool reuse_surface = snapshot.surface && dimensions(snapshot.surface) == surface_size; + auto new_surface = reuse_surface ? std::move(snapshot.surface) : make_surface(); + + // Paint background into region of store not covered by next operation. + auto cr = Cairo::Context::create(new_surface); + if (background_in_stores || reuse_surface) { + auto reg = Cairo::Region::create(geom_to_cairo(dest.rect)); + reg->subtract(geom_to_cairo(*reuse_rect)); + reg->translate(-dest.rect.left(), -dest.rect.top()); + cr->save(); + region_to_path(cr, reg); + cr->clip(); + if (background_in_stores) { + paint_background(dest, pi, page, desk, cr); + } else { // otherwise, reuse_surface is true + cr->set_operator(Cairo::OPERATOR_CLEAR); + cr->paint(); + } + cr->restore(); + } + + // Copy re-usuable contents of old store into new store, shifted. + cr->rectangle(reuse_rect->left() - dest.rect.left(), reuse_rect->top() - dest.rect.top(), reuse_rect->width(), reuse_rect->height()); + cr->clip(); + cr->set_source(store.surface, -shift.x(), -shift.y()); + cr->set_operator(Cairo::OPERATOR_SOURCE); + cr->paint(); + + // Set the result as the new store surface. + snapshot.surface = std::move(store.surface); + store.surface = std::move(new_surface); + + // Do the same for the outline store + if (outlines_enabled) { + // Create. + bool reuse_outline_surface = snapshot.outline_surface && dimensions(snapshot.outline_surface) == surface_size; + auto new_outline_surface = reuse_outline_surface ? std::move(snapshot.outline_surface) : make_surface(); + // Background. + auto cr = Cairo::Context::create(new_outline_surface); + if (reuse_outline_surface) { + cr->set_operator(Cairo::OPERATOR_CLEAR); + cr->paint(); + } + // Copy. + cr->rectangle(reuse_rect->left() - dest.rect.left(), reuse_rect->top() - dest.rect.top(), reuse_rect->width(), reuse_rect->height()); + cr->clip(); + cr->set_source(store.outline_surface, -shift.x(), -shift.y()); + cr->set_operator(Cairo::OPERATOR_SOURCE); + cr->paint(); + // Set. + snapshot.outline_surface = std::move(store.outline_surface); + store.outline_surface = std::move(new_outline_surface); + } +} + +void CairoGraphics::swap_stores() +{ + std::swap(store, snapshot); +} + +void CairoGraphics::fast_snapshot_combine() +{ + auto copy = [&, this] (Cairo::RefPtr<Cairo::ImageSurface> const &from, + Cairo::RefPtr<Cairo::ImageSurface> const &to) { + auto cr = Cairo::Context::create(to); + cr->set_antialias(Cairo::ANTIALIAS_NONE); + cr->set_operator(Cairo::OPERATOR_SOURCE); + cr->translate(-stores.snapshot().rect.left(), -stores.snapshot().rect.top()); + cr->transform(geom_to_cairo(stores.store().affine.inverse() * stores.snapshot().affine)); + cr->translate(-1.0, -1.0); + region_to_path(cr, shrink_region(stores.store().drawn, 2)); + cr->translate(1.0, 1.0); + cr->clip(); + cr->set_source(from, stores.store().rect.left(), stores.store().rect.top()); + Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::FILTER_FAST); + cr->paint(); + }; + + copy(store.surface, snapshot.surface); + if (outlines_enabled) copy(store.outline_surface, snapshot.outline_surface); +} + +void CairoGraphics::snapshot_combine(Fragment const &dest) +{ + // Create the new fragment. + auto content_size = dest.rect.dimensions() * scale_factor; + + auto make_surface = [&] { + auto result = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, content_size.x(), content_size.y()); + cairo_surface_set_device_scale(result->cobj(), scale_factor, scale_factor); // No C++ API! + return result; + }; + + CairoFragment fragment; + fragment.surface = make_surface(); + if (outlines_enabled) fragment.outline_surface = make_surface(); + + auto copy = [&, this] (Cairo::RefPtr<Cairo::ImageSurface> const &store_from, + Cairo::RefPtr<Cairo::ImageSurface> const &snapshot_from, + Cairo::RefPtr<Cairo::ImageSurface> const &to, bool background) { + auto cr = Cairo::Context::create(to); + cr->set_antialias(Cairo::ANTIALIAS_NONE); + cr->set_operator(Cairo::OPERATOR_SOURCE); + if (background) paint_background(dest, pi, page, desk, cr); + cr->translate(-dest.rect.left(), -dest.rect.top()); + cr->transform(geom_to_cairo(stores.snapshot().affine.inverse() * dest.affine)); + cr->rectangle(stores.snapshot().rect.left(), stores.snapshot().rect.top(), stores.snapshot().rect.width(), stores.snapshot().rect.height()); + cr->set_source(snapshot_from, stores.snapshot().rect.left(), stores.snapshot().rect.top()); + Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::FILTER_FAST); + cr->fill(); + cr->transform(geom_to_cairo(stores.store().affine.inverse() * stores.snapshot().affine)); + cr->translate(-1.0, -1.0); + region_to_path(cr, shrink_region(stores.store().drawn, 2)); + cr->translate(1.0, 1.0); + cr->clip(); + cr->set_source(store_from, stores.store().rect.left(), stores.store().rect.top()); + Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::FILTER_FAST); + cr->paint(); + }; + + copy(store.surface, snapshot.surface, fragment.surface, background_in_stores); + if (outlines_enabled) copy(store.outline_surface, snapshot.outline_surface, fragment.outline_surface, false); + + snapshot = std::move(fragment); +} + +Cairo::RefPtr<Cairo::ImageSurface> CairoGraphics::request_tile_surface(Geom::IntRect const &rect, bool /*nogl*/) +{ + // Create temporary surface, isolated from store. + auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, rect.width() * scale_factor, rect.height() * scale_factor); + cairo_surface_set_device_scale(surface->cobj(), scale_factor, scale_factor); + return surface; +} + +void CairoGraphics::draw_tile(Fragment const &fragment, Cairo::RefPtr<Cairo::ImageSurface> surface, Cairo::RefPtr<Cairo::ImageSurface> outline_surface) +{ + // Blit from the temporary surface to the store. + auto diff = fragment.rect.min() - stores.store().rect.min(); + + auto cr = Cairo::Context::create(store.surface); + cr->set_operator(Cairo::OPERATOR_SOURCE); + cr->set_source(surface, diff.x(), diff.y()); + cr->rectangle(diff.x(), diff.y(), fragment.rect.width(), fragment.rect.height()); + cr->fill(); + + if (outlines_enabled) { + auto cr = Cairo::Context::create(store.outline_surface); + cr->set_operator(Cairo::OPERATOR_SOURCE); + cr->set_source(outline_surface, diff.x(), diff.y()); + cr->rectangle(diff.x(), diff.y(), fragment.rect.width(), fragment.rect.height()); + cr->fill(); + } +} + +void CairoGraphics::paint_widget(Fragment const &view, PaintArgs const &a, Cairo::RefPtr<Cairo::Context> const &cr) +{ + auto f = FrameCheck::Event(); + + // Turn off anti-aliasing while compositing the widget for large performance gains. (We can usually + // get away with it without any negative visual impact; when we can't, we turn it back on.) + cr->set_antialias(Cairo::ANTIALIAS_NONE); + + // Due to a Cairo bug, Cairo sometimes draws outside of its clip region. This results in flickering as Canvas content is drawn + // over the bottom scrollbar. This cannot be fixed by setting the correct clip region, as Cairo detects that and turns it into + // a no-op. Hence the following workaround, which recreates the clip region from scratch, is required. + auto rlist = cairo_copy_clip_rectangle_list(cr->cobj()); + cr->reset_clip(); + for (int i = 0; i < rlist->num_rectangles; i++) { + cr->rectangle(rlist->rectangles[i].x, rlist->rectangles[i].y, rlist->rectangles[i].width, rlist->rectangles[i].height); + } + cr->clip(); + cairo_rectangle_list_destroy(rlist); + + // Draw background if solid colour optimisation is not enabled. (If enabled, it is baked into the stores.) + if (!background_in_stores) { + if (prefs.debug_framecheck) f = FrameCheck::Event("background"); + paint_background(view, pi, page, desk, cr); + } + + // Even if in solid colour mode, draw the part of background that is not going to be rendered. + if (background_in_stores) { + auto const &s = stores.mode() == Stores::Mode::Decoupled ? stores.snapshot() : stores.store(); + if (!(Geom::Parallelogram(s.rect) * s.affine.inverse() * view.affine).contains(view.rect)) { + if (prefs.debug_framecheck) f = FrameCheck::Event("background", 2); + cr->save(); + cr->set_fill_rule(Cairo::FILL_RULE_EVEN_ODD); + cr->rectangle(0, 0, view.rect.width(), view.rect.height()); + cr->translate(-view.rect.left(), -view.rect.top()); + cr->transform(geom_to_cairo(s.affine.inverse() * view.affine)); + cr->rectangle(s.rect.left(), s.rect.top(), s.rect.width(), s.rect.height()); + cr->clip(); + cr->transform(geom_to_cairo(view.affine.inverse() * s.affine)); + cr->translate(view.rect.left(), view.rect.top()); + paint_background(view, pi, page, desk, cr); + cr->restore(); + } + } + + auto draw_store = [&, this] (Cairo::RefPtr<Cairo::ImageSurface> const &store, Cairo::RefPtr<Cairo::ImageSurface> const &snapshot_store) { + if (stores.mode() == Stores::Mode::Normal) { + // Blit store to view. + if (prefs.debug_framecheck) f = FrameCheck::Event("draw"); + cr->save(); + auto const &r = stores.store().rect; + cr->translate(-view.rect.left(), -view.rect.top()); + cr->transform(geom_to_cairo(stores.store().affine.inverse() * view.affine)); // Almost always the identity. + cr->rectangle(r.left(), r.top(), r.width(), r.height()); + cr->set_source(store, r.left(), r.top()); + Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::FILTER_FAST); + cr->fill(); + cr->restore(); + } else { + // Draw transformed snapshot, clipped to the complement of the store's clean region. + if (prefs.debug_framecheck) f = FrameCheck::Event("composite", 1); + + cr->save(); + cr->set_fill_rule(Cairo::FILL_RULE_EVEN_ODD); + cr->rectangle(0, 0, view.rect.width(), view.rect.height()); + cr->translate(-view.rect.left(), -view.rect.top()); + cr->transform(geom_to_cairo(stores.store().affine.inverse() * view.affine)); + region_to_path(cr, stores.store().drawn); + cr->transform(geom_to_cairo(stores.snapshot().affine.inverse() * stores.store().affine)); + cr->clip(); + auto const &r = stores.snapshot().rect; + cr->rectangle(r.left(), r.top(), r.width(), r.height()); + cr->clip(); + cr->set_source(snapshot_store, r.left(), r.top()); + Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::FILTER_FAST); + cr->paint(); + if (prefs.debug_show_snapshot) { + cr->set_source_rgba(0, 0, 1, 0.2); + cr->set_operator(Cairo::OPERATOR_OVER); + cr->paint(); + } + cr->restore(); + + // Draw transformed store, clipped to drawn region. + if (prefs.debug_framecheck) f = FrameCheck::Event("composite", 0); + cr->save(); + cr->translate(-view.rect.left(), -view.rect.top()); + cr->transform(geom_to_cairo(stores.store().affine.inverse() * view.affine)); + cr->set_source(store, stores.store().rect.left(), stores.store().rect.top()); + Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::FILTER_FAST); + region_to_path(cr, stores.store().drawn); + cr->fill(); + cr->restore(); + } + }; + + auto draw_overlay = [&, this] { + // Get whitewash opacity. + double outline_overlay_opacity = prefs.outline_overlay_opacity / 100.0; + + // Partially obscure drawing by painting semi-transparent white, then paint outline content. + // Note: Unfortunately this also paints over the background, but this is unavoidable. + cr->save(); + cr->set_operator(Cairo::OPERATOR_OVER); + cr->set_source_rgb(1.0, 1.0, 1.0); + cr->paint_with_alpha(outline_overlay_opacity); + draw_store(store.outline_surface, snapshot.outline_surface); + cr->restore(); + }; + + if (a.splitmode == Inkscape::SplitMode::SPLIT) { + + // Calculate the clipping rectangles for split view. + auto [store_clip, outline_clip] = calc_splitview_cliprects(view.rect.dimensions(), a.splitfrac, a.splitdir); + + // Draw normal content. + cr->save(); + cr->rectangle(store_clip.left(), store_clip.top(), store_clip.width(), store_clip.height()); + cr->clip(); + cr->set_operator(background_in_stores ? Cairo::OPERATOR_SOURCE : Cairo::OPERATOR_OVER); + draw_store(store.surface, snapshot.surface); + if (a.render_mode == Inkscape::RenderMode::OUTLINE_OVERLAY) draw_overlay(); + cr->restore(); + + // Draw outline. + if (background_in_stores) { + cr->save(); + cr->translate(outline_clip.left(), outline_clip.top()); + paint_background(Fragment{view.affine, view.rect.min() + outline_clip}, pi, page, desk, cr); + cr->restore(); + } + cr->save(); + cr->rectangle(outline_clip.left(), outline_clip.top(), outline_clip.width(), outline_clip.height()); + cr->clip(); + cr->set_operator(Cairo::OPERATOR_OVER); + draw_store(store.outline_surface, snapshot.outline_surface); + cr->restore(); + + } else { + + // Draw the normal content over the whole view. + cr->set_operator(background_in_stores ? Cairo::OPERATOR_SOURCE : Cairo::OPERATOR_OVER); + draw_store(store.surface, snapshot.surface); + if (a.render_mode == Inkscape::RenderMode::OUTLINE_OVERLAY) draw_overlay(); + + // Draw outline if in X-ray mode. + if (a.splitmode == Inkscape::SplitMode::XRAY && a.mouse) { + // Clip to circle + cr->set_antialias(Cairo::ANTIALIAS_DEFAULT); + cr->arc(a.mouse->x(), a.mouse->y(), prefs.xray_radius, 0, 2 * M_PI); + cr->clip(); + cr->set_antialias(Cairo::ANTIALIAS_NONE); + // Draw background. + paint_background(view, pi, page, desk, cr); + // Draw outline. + cr->set_operator(Cairo::OPERATOR_OVER); + draw_store(store.outline_surface, snapshot.outline_surface); + } + } + + // The rest can be done with antialiasing. + cr->set_antialias(Cairo::ANTIALIAS_DEFAULT); + + if (a.splitmode == Inkscape::SplitMode::SPLIT) { + paint_splitview_controller(view.rect.dimensions(), a.splitfrac, a.splitdir, a.hoverdir, cr); + } +} + +} // namespace Widget +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : |