diff options
Diffstat (limited to 'src/display/drawing.cpp')
-rw-r--r-- | src/display/drawing.cpp | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/src/display/drawing.cpp b/src/display/drawing.cpp new file mode 100644 index 0000000..6e4966b --- /dev/null +++ b/src/display/drawing.cpp @@ -0,0 +1,390 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * SVG drawing for display. + *//* + * Authors: + * Krzysztof KosiĆski <tweenk.pl@gmail.com> + * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl> + * + * Copyright (C) 2011-2012 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <array> +#include <thread> +#include "display/drawing.h" +#include "display/control/canvas-item-drawing.h" +#include "nr-filter-gaussian.h" +#include "nr-filter-types.h" + +// Grayscale colormode +#include "cairo-templates.h" +#include "drawing-context.h" + +namespace Inkscape { + +// Hardcoded grayscale color matrix values as default. +static auto constexpr grayscale_matrix = std::array{ + 0.21, 0.72, 0.072, 0.0, 0.0, + 0.21, 0.72, 0.072, 0.0, 0.0, + 0.21, 0.72, 0.072, 0.0, 0.0, + 0.0 , 0.0 , 0.0 , 1.0, 0.0 +}; + +static auto rendermode_to_renderflags(RenderMode mode) +{ + switch (mode) { + case RenderMode::OUTLINE: return DrawingItem::RENDER_OUTLINE; + case RenderMode::NO_FILTERS: return DrawingItem::RENDER_NO_FILTERS; + case RenderMode::VISIBLE_HAIRLINES: return DrawingItem::RENDER_VISIBLE_HAIRLINES; + default: return DrawingItem::RenderFlags::RENDER_DEFAULT; + } +} + +static auto default_numthreads() +{ + auto ret = std::thread::hardware_concurrency(); + return ret == 0 ? 4 : ret; // Sensible fallback if not reported. +} + +Drawing::Drawing(Inkscape::CanvasItemDrawing *canvas_item_drawing) + : _canvas_item_drawing(canvas_item_drawing) + , _grayscale_matrix(std::vector<double>(grayscale_matrix.begin(), grayscale_matrix.end())) +{ + _loadPrefs(); +} + +Drawing::~Drawing() +{ + delete _root; +} + +void Drawing::setRoot(DrawingItem *root) +{ + delete _root; + _root = root; + if (_root) { + assert(_root->_child_type == DrawingItem::ChildType::ORPHAN); + _root->_child_type = DrawingItem::ChildType::ROOT; + } +} + +void Drawing::setRenderMode(RenderMode mode) +{ + assert(mode != RenderMode::OUTLINE_OVERLAY && "Drawing::setRenderMode: OUTLINE_OVERLAY is not a true render mode"); + + defer([=] { + if (mode == _rendermode) return; + _root->_markForRendering(); + _rendermode = mode; + _root->_markForUpdate(DrawingItem::STATE_ALL, true); + _clearCache(); + }); +} + +void Drawing::setColorMode(ColorMode mode) +{ + defer([=] { + if (mode == _colormode) return; + _colormode = mode; + if (_rendermode != RenderMode::OUTLINE || _image_outline_mode) { + _root->_markForRendering(); + } + }); +} + +void Drawing::setOutlineOverlay(bool outlineoverlay) +{ + defer([=] { + if (outlineoverlay == _outlineoverlay) return; + _outlineoverlay = outlineoverlay; + _root->_markForUpdate(DrawingItem::STATE_ALL, true); + }); +} + +void Drawing::setGrayscaleMatrix(double value_matrix[20]) +{ + defer([=] { + _grayscale_matrix = Filters::FilterColorMatrix::ColorMatrixMatrix(std::vector<double>(value_matrix, value_matrix + 20)); + if (_rendermode != RenderMode::OUTLINE) { + _root->_markForRendering(); + } + }); +} + +void Drawing::setClipOutlineColor(uint32_t col) +{ + defer([=] { + _clip_outline_color = col; + if (_rendermode == RenderMode::OUTLINE || _outlineoverlay) { + _root->_markForRendering(); + } + }); +} + +void Drawing::setMaskOutlineColor(uint32_t col) +{ + defer([=] { + _mask_outline_color = col; + if (_rendermode == RenderMode::OUTLINE || _outlineoverlay) { + _root->_markForRendering(); + } + }); +} + +void Drawing::setImageOutlineColor(uint32_t col) +{ + defer([=] { + _image_outline_color = col; + if ((_rendermode == RenderMode::OUTLINE || _outlineoverlay) && !_image_outline_mode) { + _root->_markForRendering(); + } + }); +} + +void Drawing::setImageOutlineMode(bool enabled) +{ + defer([=] { + _image_outline_mode = enabled; + if (_rendermode == RenderMode::OUTLINE || _outlineoverlay) { + _root->_markForRendering(); + } + }); +} + +void Drawing::setFilterQuality(int quality) +{ + defer([=] { + _filter_quality = quality; + if (!(_rendermode == RenderMode::OUTLINE || _rendermode == RenderMode::NO_FILTERS)) { + _root->_markForUpdate(DrawingItem::STATE_ALL, true); + _clearCache(); + } + }); +} + +void Drawing::setBlurQuality(int quality) +{ + defer([=] { + _blur_quality = quality; + if (!(_rendermode == RenderMode::OUTLINE || _rendermode == RenderMode::NO_FILTERS)) { + _root->_markForUpdate(DrawingItem::STATE_ALL, true); + _clearCache(); + } + }); +} + +void Drawing::setDithering(bool use_dithering) +{ + defer([=] { + _use_dithering = use_dithering; + #ifdef CAIRO_HAS_DITHER + if (_rendermode != RenderMode::OUTLINE) { + _root->_markForUpdate(DrawingItem::STATE_ALL, true); + _clearCache(); + } + #endif + }); +} + +void Drawing::setCacheBudget(size_t bytes) +{ + defer([=] { + _cache_budget = bytes; + _pickItemsForCaching(); + }); +} + +void Drawing::setCacheLimit(Geom::OptIntRect const &rect) +{ + defer([=] { + _cache_limit = rect; + for (auto item : _cached_items) { + item->_markForUpdate(DrawingItem::STATE_CACHE, false); + } + }); +} + +void Drawing::setClip(std::optional<Geom::PathVector> &&clip) +{ + defer([=] { + if (clip == _clip) return; + _clip = std::move(clip); + _root->_markForRendering(); + }); +} + +void Drawing::update(Geom::IntRect const &area, Geom::Affine const &affine, unsigned flags, unsigned reset) +{ + if (_root) { + _root->update(area, { affine }, flags, reset); + } + if (flags & DrawingItem::STATE_CACHE) { + // Process the updated cache scores. + _pickItemsForCaching(); + } +} + +void Drawing::render(DrawingContext &dc, Geom::IntRect const &area, unsigned flags, int antialiasing_override) const +{ + int antialias = _root->antialiasing(); + if (antialiasing_override >= 0) { + antialias = antialiasing_override; + } + apply_antialias(dc, antialias); + + auto rc = RenderContext{ 0xff }; // black outlines + flags |= rendermode_to_renderflags(_rendermode); + + if (_clip) { + dc.save(); + dc.path(*_clip * _root->_ctm); + dc.clip(); + } + _root->render(dc, rc, area, flags); + if (_clip) { + dc.restore(); + } +} + +DrawingItem *Drawing::pick(Geom::Point const &p, double delta, unsigned flags) +{ + return _root->pick(p, delta, flags); +} + +void Drawing::snapshot() +{ + assert(!_snapshotted); + _snapshotted = true; +} + +void Drawing::unsnapshot() +{ + assert(_snapshotted); + _snapshotted = false; // Unsnapshot before replaying log so further work is not deferred. + _funclog(); +} + +void Drawing::_pickItemsForCaching() +{ + // Build sorted list of items that should be cached. + std::vector<DrawingItem*> to_cache; + size_t used = 0; + for (auto &rec : _candidate_items) { + if (used + rec.cache_size > _cache_budget) break; + to_cache.emplace_back(rec.item); + used += rec.cache_size; + } + std::sort(to_cache.begin(), to_cache.end()); + + // Uncache the items that are cached but should not be cached. + // Note: setCached() modifies _cached_items, so the temporary container is necessary. + std::vector<DrawingItem*> to_uncache; + std::set_difference(_cached_items.begin(), _cached_items.end(), + to_cache.begin(), to_cache.end(), + std::back_inserter(to_uncache)); + for (auto item : to_uncache) { + item->_setCached(false); + } + + // Cache all items that should be cached (no-op if already cached). + for (auto item : to_cache) { + item->_setCached(true); + } +} + +void Drawing::_clearCache() +{ + // Note: setCached() modifies _cached_items, so the temporary container is necessary. + std::vector<DrawingItem*> to_uncache; + std::copy(_cached_items.begin(), _cached_items.end(), std::back_inserter(to_uncache)); + for (auto item : to_uncache) { + item->_setCached(false, true); + } +} + +void Drawing::_loadPrefs() +{ + auto prefs = Inkscape::Preferences::get(); + + // Set the initial values of preferences. + _clip_outline_color = prefs->getIntLimited("/options/wireframecolors/clips", 0x00ff00ff, 0, 0xffffffff); // Green clip outlines by default. + _mask_outline_color = prefs->getIntLimited("/options/wireframecolors/masks", 0x0000ffff, 0, 0xffffffff); // Blue mask outlines by default. + _image_outline_color = prefs->getIntLimited("/options/wireframecolors/images", 0xff0000ff, 0, 0xffffffff); // Red image outlines by default. + _image_outline_mode = prefs->getBool ("/options/rendering/imageinoutlinemode", false); + _filter_quality = prefs->getIntLimited("/options/filterquality/value", 0, Filters::FILTER_QUALITY_WORST, Filters::FILTER_QUALITY_BEST); + _blur_quality = prefs->getInt ("/options/blurquality/value", 0); + _use_dithering = prefs->getBool ("/options/dithering/value", true); + _cursor_tolerance = prefs->getDouble ("/options/cursortolerance/value", 1.0); + _select_zero_opacity = prefs->getBool ("/options/selection/zeroopacity", false); + + // Enable caching only for the Canvas's drawing, since only it is persistent. + if (_canvas_item_drawing) { + // Preference is stored in MiB; convert to bytes, taking care not to overflow. + _cache_budget = (size_t{1} << 20) * prefs->getIntLimited("/options/renderingcache/size", 64, 0, 4096); + } else { + _cache_budget = 0; + } + + // Set the global variable governing the number of filter threads, and track it too. (This is ugly, but hopefully transitional.) + set_num_filter_threads(prefs->getIntLimited("/options/threading/numthreads", default_numthreads(), 1, 256)); + + // Similarly, enable preference tracking only for the Canvas's drawing. + if (_canvas_item_drawing) { + std::unordered_map<std::string, std::function<void (Preferences::Entry const &)>> actions; + + // Todo: (C++20) Eliminate this repetition by baking the preference metadata into the variables themselves using structural templates. + actions.emplace("/options/wireframecolors/clips", [this] (auto &entry) { setClipOutlineColor (entry.getIntLimited(0x00ff00ff, 0, 0xffffffff)); }); + actions.emplace("/options/wireframecolors/masks", [this] (auto &entry) { setMaskOutlineColor (entry.getIntLimited(0x0000ffff, 0, 0xffffffff)); }); + actions.emplace("/options/wireframecolors/images", [this] (auto &entry) { setImageOutlineColor(entry.getIntLimited(0xff0000ff, 0, 0xffffffff)); }); + actions.emplace("/options/rendering/imageinoutlinemode", [this] (auto &entry) { setImageOutlineMode(entry.getBool(false)); }); + actions.emplace("/options/filterquality/value", [this] (auto &entry) { setFilterQuality(entry.getIntLimited(0, Filters::FILTER_QUALITY_WORST, Filters::FILTER_QUALITY_BEST)); }); + actions.emplace("/options/blurquality/value", [this] (auto &entry) { setBlurQuality(entry.getInt(0)); }); + actions.emplace("/options/dithering/value", [this] (auto &entry) { setDithering(entry.getBool(true)); }); + actions.emplace("/options/cursortolerance/value", [this] (auto &entry) { setCursorTolerance(entry.getDouble(1.0)); }); + actions.emplace("/options/selection/zeroopacity", [this] (auto &entry) { setSelectZeroOpacity(entry.getBool(false)); }); + actions.emplace("/options/renderingcache/size", [this] (auto &entry) { setCacheBudget((1 << 20) * entry.getIntLimited(64, 0, 4096)); }); + actions.emplace("/options/threading/numthreads", [this] (auto &entry) { set_num_filter_threads(entry.getIntLimited(default_numthreads(), 1, 256)); }); + + _pref_tracker = Inkscape::Preferences::PreferencesObserver::create("/options", [actions = std::move(actions)] (auto &entry) { + auto it = actions.find(entry.getPath()); + if (it == actions.end()) return; + it->second(entry); + }); + } +} + +/* + * Return average color over area. Used by Calligraphic, Dropper, and Spray tools. + */ +void Drawing::averageColor(Geom::IntRect const &area, double &R, double &G, double &B, double &A) const +{ + auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, area.width(), area.height()); + auto dc = Inkscape::DrawingContext(surface->cobj(), area.min()); + render(dc, area); + + ink_cairo_surface_average_color_premul(surface->cobj(), R, G, B, A); +} + +/* + * Convenience function to set high quality options for export. + */ +void Drawing::setExact() +{ + setFilterQuality(Filters::FILTER_QUALITY_BEST); + setBlurQuality(BLUR_QUALITY_BEST); +} + +} // 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:fileencoding=utf-8:textwidth=99 : |