summaryrefslogtreecommitdiffstats
path: root/src/util/object-renderer.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/util/object-renderer.cpp651
1 files changed, 651 insertions, 0 deletions
diff --git a/src/util/object-renderer.cpp b/src/util/object-renderer.cpp
new file mode 100644
index 0000000..f9829e6
--- /dev/null
+++ b/src/util/object-renderer.cpp
@@ -0,0 +1,651 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * Symbol, marker, pattern, gradient renderer
+ *
+ * Copyright (C) 2023 Michael Kowalski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "object-renderer.h"
+#include <cairo.h>
+#include <cairomm/enums.h>
+#include <cairomm/pattern.h>
+#include <cairomm/surface.h>
+#include <gdkmm/rgba.h>
+#include <glibmm/ustring.h>
+#include <optional>
+#include "color.h"
+#include "display/cairo-utils.h"
+#include "document.h"
+#include "gradient-chemistry.h"
+#include "object/sp-gradient.h"
+#include "object/sp-image.h"
+#include "object/sp-marker.h"
+#include "object/sp-object.h"
+#include "object/sp-pattern.h"
+#include "object/sp-use.h"
+#include "ui/svg-renderer.h"
+#include "ui/widget/stroke-style.h"
+#include "xml/node.h"
+#include "object/sp-defs.h"
+#include "object/sp-item.h"
+#include "object/sp-root.h"
+#include "object/sp-symbol.h"
+#include "pattern-manager.h"
+#include "display/drawing.h"
+#include "util/scope_exit.h"
+#include "ui/cache/svg_preview_cache.h"
+#include "xml/href-attribute-helper.h"
+
+namespace Inkscape {
+
+// traverse nodes starting from given 'object' until visitor returns object that evaluates to true
+template<typename V>
+bool visit_until(SPObject& object, V&& visitor) {
+ if (visitor(object)) return true;
+
+ // SPUse inserts referenced object as a child; skip it
+ if (is<SPUse>(&object)) return false;
+
+ for (auto&& child : object.children) {
+ if (visit_until(child, visitor)) return true;
+ }
+
+ return false;
+}
+
+const char* style_from_use_element(const char* id, SPDocument* document) {
+ if (!id || !*id || !document) return nullptr;
+
+ auto root = document->getRoot();
+ if (!root) return nullptr;
+
+ const char* style = nullptr;
+ Glib::ustring ident = "#";
+ ident += id;
+
+ visit_until(*root, [&](SPObject& obj){
+ if (auto use = cast<SPUse>(&obj)) {
+ if (auto href = Inkscape::getHrefAttribute(*use->getRepr()).second) {
+ if (ident == href) {
+ // style = use->getRepr()->attribute("style");
+ style = use->getAttribute("style");
+ return true;
+ }
+ }
+ }
+ return false;
+ });
+
+ return style;
+}
+
+
+SPDocument* symbols_preview_doc() {
+ auto buffer = R"A(
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <use id="the_use" xlink:href="#the_symbol"/>
+</svg>
+)A";
+ return SPDocument::createNewDocFromMem(buffer, strlen(buffer), false);
+}
+
+Cairo::RefPtr<Cairo::Surface> draw_symbol(SPObject& symbol, double box_w, double box_h, double device_scale, SPDocument* preview_document, bool style_from_use) {
+ // Create a copy repr of the symbol with id="the_symbol"
+ Inkscape::XML::Node* repr = symbol.getRepr()->duplicate(preview_document->getReprDoc());
+ repr->setAttribute("id", "the_symbol");
+
+ // First look for default style stored in <symbol>
+ auto style = repr->attribute("inkscape:symbol-style");
+ if (!style) {
+ // If no default style in <symbol>, look in documents.
+
+ // Read style from <use> element pointing to this symbol?
+ if (style_from_use) {
+ // When symbols are inserted from a set into a new document, styles they may rely on
+ // are copied from original document and applied to the <use> symbol.
+ // We need to use those styles to render symbols correctly, because some symbols only
+ // define geometry and no presentation attributes and defaults (black fill, no stroke)
+ // may be completely incorrect (for instance originals may have no fill and stroke).
+ auto id = symbol.getId();
+ style = style_from_use_element(id, symbol.document);
+ }
+ else {
+ style = symbol.document->getReprRoot()->attribute("style");
+ }
+ }
+
+ // This is for display in Symbols dialog only
+ if (style) repr->setAttribute("style", style);
+
+ // reach out to the document for CSS styles, in case symbol uses some class selectors
+ SPDocument::install_reference_document scoped(preview_document, symbol.document);
+
+ preview_document->getDefs()->getRepr()->appendChild(repr);
+ Inkscape::GC::release(repr);
+
+ // Uncomment this to get the preview_document documents saved (useful for debugging)
+ // FILE *fp = fopen (g_strconcat(id, ".svg", NULL), "w");
+ // sp_repr_save_stream(preview_document->getReprDoc(), fp);
+ // fclose (fp);
+
+ // Make sure preview_document is up-to-date.
+ preview_document->ensureUpToDate();
+
+ unsigned dkey = SPItem::display_key_new(1);
+ Inkscape::Drawing drawing; // New drawing for offscreen rendering.
+ drawing.setRoot(preview_document->getRoot()->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY));
+ auto invoke_hide_guard = scope_exit([&] { preview_document->getRoot()->invoke_hide(dkey); });
+ // drawing.root()->setTransform(affine);
+ drawing.setExact(); // Maximum quality for blurs.
+
+ // Make sure we have symbol in preview_document
+ SPObject* object_temp = preview_document->getObjectById("the_use");
+
+ auto item = cast<SPItem>(object_temp);
+ g_assert(item != nullptr);
+
+ // We could use cache here, but it doesn't really work with the structure
+ // of this user interface and we've already cached the pixbuf in the gtklist
+ cairo_surface_t* s = nullptr;
+ // Find object's bbox in document.
+ // Note symbols can have own viewport... ignore for now.
+ Geom::OptRect dbox = item->documentVisualBounds();
+
+ if (dbox) {
+ double width = dbox->width();
+ double height = dbox->height();
+
+ if (width == 0.0) width = 1.0;
+ if (height == 0.0) height = 1.0;
+
+ auto scale = std::min(box_w / width, box_h / height);
+ if (scale > 1.0) {
+ scale = 1.0;
+ }
+
+ s = render_surface(drawing, scale, *dbox, Geom::IntPoint(box_w, box_h), device_scale, nullptr, true);
+ }
+
+ preview_document->getObjectByRepr(repr)->deleteObject(false);
+
+ if (s) {
+ cairo_surface_set_device_scale(s, device_scale, device_scale);
+ }
+
+ return Cairo::RefPtr<Cairo::Surface>(new Cairo::Surface(s, true));
+}
+
+void draw_gradient(const Cairo::RefPtr<Cairo::Context>& cr, SPGradient* gradient, int x, int width) {
+ cairo_pattern_t* check = ink_cairo_pattern_create_checkerboard();
+
+ cairo_set_source(cr->cobj(), check);
+ cr->fill_preserve();
+ cairo_pattern_destroy(check);
+
+ if (gradient) {
+ auto p = gradient->create_preview_pattern(width);
+ cairo_matrix_t m;
+ cairo_matrix_init_translate(&m, -x, 0);
+ cairo_pattern_set_matrix(p, &m);
+ cairo_set_source(cr->cobj(), p);
+ cr->fill();
+ cairo_pattern_destroy(p);
+ }
+}
+
+Cairo::RefPtr<Cairo::Surface> draw_gradient(SPGradient* gradient, double width, double height, double device_scale, bool stops) {
+ auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width * device_scale, height * device_scale);
+ cairo_surface_set_device_scale(surface->cobj(), device_scale, device_scale);
+ auto ctx = Cairo::Context::create(surface);
+
+ auto h = stops ? height / 2 : height;
+ auto x = 0.5 * device_scale;
+ auto y = 0.5 * device_scale;
+ width -= device_scale;
+ h -= device_scale;
+
+ ctx->rectangle(x, y, width, h);
+ draw_gradient(ctx, gradient, 0, width);
+
+ // border
+ ctx->rectangle(x, y, width, h);
+ ctx->set_source_rgb(0.5, 0.5, 0.5);
+ ctx->set_line_width(1.0);
+ ctx->stroke();
+
+ if (stops) {
+ double radius = 3;
+ auto v = gradient->getVector();
+ for (auto& stop : v->vector.stops) {
+ double py = h + 2 * radius;
+ double px = std::round(stop.offset * width);
+ ctx->arc(px, py, radius, 0, 2 * M_PI);
+ ctx->set_source_rgba(stop.color.v.c[0], stop.color.v.c[1], stop.color.v.c[2], stop.opacity);
+ ctx->fill_preserve();
+ ctx->set_source_rgb(0.5, 0.5, 0.5);
+ ctx->stroke();
+ }
+ }
+
+ return surface;
+}
+
+
+std::unique_ptr<SPDocument> ink_markers_preview_doc(const Glib::ustring& group_id)
+{
+gchar const *buffer = R"A(
+ <svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ id="MarkerSample">
+
+ <defs id="defs">
+ <filter id="softGlow" height="1.2" width="1.2" x="0.0" y="0.0">
+ <!-- <feMorphology operator="dilate" radius="1" in="SourceAlpha" result="thicken" id="feMorphology2" /> -->
+ <!-- Use a gaussian blur to create the soft blurriness of the glow -->
+ <feGaussianBlur in="SourceAlpha" stdDeviation="3" result="blurred" id="feGaussianBlur4" />
+ <!-- Change the color -->
+ <feFlood flood-color="rgb(255,255,255)" result="glowColor" id="feFlood6" flood-opacity="0.70" />
+ <!-- Color in the glows -->
+ <feComposite in="glowColor" in2="blurred" operator="in" result="softGlow_colored" id="feComposite8" />
+ <!-- Layer the effects together -->
+ <feMerge id="feMerge14">
+ <feMergeNode in="softGlow_colored" id="feMergeNode10" />
+ <feMergeNode in="SourceGraphic" id="feMergeNode12" />
+ </feMerge>
+ </filter>
+ </defs>
+
+ <!-- cross at the end of the line to help position marker -->
+ <symbol id="cross" width="25" height="25" viewBox="0 0 25 25">
+ <path class="cross" style="mix-blend-mode:difference;stroke:#7ff;stroke-opacity:1;fill:none;display:block" d="M 0,0 M 25,25 M 10,10 15,15 M 10,15 15,10" />
+ <!-- <path class="cross" style="mix-blend-mode:difference;stroke:#7ff;stroke-width:1;stroke-opacity:1;fill:none;display:block;-inkscape-stroke:hairline" d="M 0,0 M 25,25 M 10,10 15,15 M 10,15 15,10" /> -->
+ </symbol>
+
+ <!-- very short path with 1px stroke used to measure size of marker -->
+ <path id="measure-marker" style="stroke-width:1.0;stroke-opacity:0.01;marker-start:url(#sample)" d="M 0,9999 m 0,0.1" />
+
+ <path id="line-marker-start" class="line colors" style="stroke-width:2;stroke-opacity:0.2" d="M 12.5,12.5 l 1000,0" />
+ <!-- <g id="marker-start" class="group" style="filter:url(#softGlow)"> -->
+ <g id="marker-start" class="group">
+ <path class="colors" style="stroke-width:2;stroke-opacity:0;marker-start:url(#sample)"
+ d="M 12.5,12.5 L 25,12.5"/>
+ <rect x="0" y="0" width="25" height="25" style="fill:none;stroke:none"/>
+ <use xlink:href="#cross" width="25" height="25" />
+ </g>
+
+ <path id="line-marker-mid" class="line colors" style="stroke-width:2;stroke-opacity:0.2" d="M -1000,12.5 L 1000,12.5" />
+ <g id="marker-mid" class="group">
+ <path class="colors" style="stroke-width:2;stroke-opacity:0;marker-mid:url(#sample)"
+ d="M 0,12.5 L 12.5,12.5 L 25,12.5"/>
+ <rect x="0" y="0" width="25" height="25" style="fill:none;stroke:none"/>
+ <use xlink:href="#cross" width="25" height="25" />
+ </g>
+
+ <path id="line-marker-end" class="line colors" style="stroke-width:2;stroke-opacity:0.2" d="M -1000,12.5 L 12.5,12.5" />
+ <g id="marker-end" class="group">
+ <path class="colors" style="stroke-width:2;stroke-opacity:0;marker-end:url(#sample)"
+ d="M 0,12.5 L 12.5,12.5"/>
+ <rect x="0" y="0" width="25" height="25" style="fill:none;stroke:none"/>
+ <use xlink:href="#cross" width="25" height="25" />
+ </g>
+
+ </svg>
+)A";
+
+ auto document = std::unique_ptr<SPDocument>(SPDocument::createNewDocFromMem(buffer, strlen(buffer), false));
+ // only leave requested group, so nothing else gets rendered
+ for (auto&& group : document->getObjectsByClass("group")) {
+ assert(group->getId());
+ if (group->getId() != group_id) {
+ group->deleteObject();
+ }
+ }
+ auto id = "line-" + group_id;
+ for (auto&& line : document->getObjectsByClass("line")) {
+ assert(line->getId());
+ if (line->getId() != id) {
+ line->deleteObject();
+ }
+ }
+ return document;
+}
+
+
+Cairo::RefPtr<Cairo::Surface> create_marker_image(
+ const Glib::ustring& group_id,
+ SPDocument* _sandbox,
+ Gdk::RGBA marker_color,
+ Geom::IntPoint pixel_size,
+ const char* mname,
+ SPDocument* source,
+ Inkscape::Drawing& drawing,
+ std::optional<guint32> checkerboard,
+ bool no_clip,
+ double scale,
+ int device_scale)
+{
+ Cairo::RefPtr<Cairo::Surface> g_bad_marker;
+
+ // Retrieve the marker named 'mname' from the source SVG document
+ const SPObject* marker = source ? source->getObjectById(mname) : nullptr;
+ if (marker == nullptr) {
+ g_warning("bad mname: %s", mname);
+ return g_bad_marker;
+ }
+
+ SPObject *oldmarker = _sandbox->getObjectById("sample");
+ if (oldmarker) {
+ oldmarker->deleteObject(false);
+ }
+
+ // Create a copy repr of the marker with id="sample"
+ Inkscape::XML::Document *xml_doc = _sandbox->getReprDoc();
+ Inkscape::XML::Node *mrepr = marker->getRepr()->duplicate(xml_doc);
+ mrepr->setAttribute("id", "sample");
+
+ // Replace the old sample in the sandbox by the new one
+ Inkscape::XML::Node *defsrepr = _sandbox->getObjectById("defs")->getRepr();
+
+ // TODO - This causes a SIGTRAP on windows
+ defsrepr->appendChild(mrepr);
+
+ Inkscape::GC::release(mrepr);
+
+ // If the marker color is a url link to a pattern or gradient copy that too
+ SPObject *mk = source->getObjectById(mname);
+ SPCSSAttr *css_marker = sp_css_attr_from_object(mk->firstChild(), SP_STYLE_FLAG_ALWAYS);
+ //const char *mfill = sp_repr_css_property(css_marker, "fill", "none");
+ const char *mstroke = sp_repr_css_property(css_marker, "fill", "none");
+
+ if (!strncmp (mstroke, "url(", 4)) {
+ SPObject *linkObj = getMarkerObj(mstroke, source);
+ if (linkObj) {
+ Inkscape::XML::Node *grepr = linkObj->getRepr()->duplicate(xml_doc);
+ SPObject *oldmarker = _sandbox->getObjectById(linkObj->getId());
+ if (oldmarker) {
+ oldmarker->deleteObject(false);
+ }
+ defsrepr->appendChild(grepr);
+ Inkscape::GC::release(grepr);
+
+ if (is<SPGradient>(linkObj)) {
+ SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(cast<SPGradient>(linkObj), false);
+ if (vector) {
+ Inkscape::XML::Node *grepr = vector->getRepr()->duplicate(xml_doc);
+ SPObject *oldmarker = _sandbox->getObjectById(vector->getId());
+ if (oldmarker) {
+ oldmarker->deleteObject(false);
+ }
+ defsrepr->appendChild(grepr);
+ Inkscape::GC::release(grepr);
+ }
+ }
+ }
+ }
+
+// Uncomment this to get the sandbox documents saved (useful for debugging)
+ // FILE *fp = fopen (g_strconcat(combo_id, mname, ".svg", nullptr), "w");
+ // sp_repr_save_stream(_sandbox->getReprDoc(), fp);
+ // fclose (fp);
+
+ // object to render; note that the id is the same as that of the combo we're building
+ SPObject *object = _sandbox->getObjectById(group_id);
+
+ if (object == nullptr || !is<SPItem>(object)) {
+ g_warning("no obj: %s", group_id.c_str());
+ return g_bad_marker;
+ }
+
+ Gdk::RGBA fg = marker_color;
+ auto fgcolor = rgba_to_css_color(fg);
+ fg.set_red(1 - fg.get_red());
+ fg.set_green(1 - fg.get_green());
+ fg.set_blue(1 - fg.get_blue());
+ auto bgcolor = rgba_to_css_color(fg);
+ auto objects = _sandbox->getObjectsBySelector(".colors");
+ for (auto el : objects) {
+ if (SPCSSAttr* css = sp_repr_css_attr(el->getRepr(), "style")) {
+ sp_repr_css_set_property(css, "fill", bgcolor.c_str());
+ sp_repr_css_set_property(css, "stroke", fgcolor.c_str());
+ el->changeCSS(css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+ }
+
+ auto cross = _sandbox->getObjectsBySelector(".cross");
+ double stroke = 0.5;
+ for (auto el : cross) {
+ if (SPCSSAttr* css = sp_repr_css_attr(el->getRepr(), "style")) {
+ sp_repr_css_set_property(css, "display", checkerboard ? "block" : "none");
+ sp_repr_css_set_property_double(css, "stroke-width", stroke);
+ el->changeCSS(css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+ }
+
+ // SPDocument::install_reference_document scoped(_sandbox, source);
+
+ _sandbox->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ _sandbox->ensureUpToDate();
+
+ auto item = cast<SPItem>(object);
+ // Find object's bbox in document
+ Geom::OptRect dbox = item->documentVisualBounds();
+
+ if (!dbox) {
+ g_warning("no dbox");
+ return g_bad_marker;
+ }
+
+ if (auto measure = cast<SPItem>(_sandbox->getObjectById("measure-marker"))) {
+ if (auto box = measure->documentVisualBounds()) {
+ // check size of the marker applied to a path with stroke of 1px
+ auto size = std::max(box->width(), box->height());
+ const double small = 5.0;
+ // if too small, then scale up; clip needs to be enabled for scale to work
+ if (size > 0 && size < small) {
+ auto factor = 1 + small - size;
+ scale *= factor;
+ no_clip = false;
+
+ // adjust cross stroke
+ stroke /= factor;
+ for (auto el : cross) {
+ if (SPCSSAttr* css = sp_repr_css_attr(el->getRepr(), "style")) {
+ sp_repr_css_set_property_double(css, "stroke-width", stroke);
+ el->changeCSS(css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+ }
+
+ _sandbox->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
+ _sandbox->ensureUpToDate();
+ }
+ }
+ }
+
+ /* Update to renderable state */
+ // const double device_scale = get_scale_factor();
+ guint32 bgnd_color = checkerboard.has_value() ? *checkerboard : 0;
+ auto surface = render_surface(drawing, scale, *dbox, pixel_size, device_scale, checkerboard.has_value() ? &bgnd_color : nullptr, no_clip);
+ cairo_surface_set_device_scale(surface, device_scale, device_scale);
+ return Cairo::RefPtr<Cairo::Surface>(new Cairo::Surface(surface, true));
+}
+
+Cairo::RefPtr<Cairo::Surface> render_image(const Inkscape::Pixbuf* pixbuf, int width, int height, int device_scale) {
+ Cairo::RefPtr<Cairo::Surface> surface;
+
+ if (!pixbuf || width <= 0 || height <= 0 || pixbuf->width() <= 0 || pixbuf->height() <= 0) return surface;
+
+ auto src = Cairo::RefPtr<Cairo::Surface>(new Cairo::Surface(pixbuf->getSurfaceRaw(), false));
+ surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width * device_scale, height * device_scale);
+ cairo_surface_set_device_scale(surface->cobj(), device_scale, device_scale);
+
+ auto ctx = Cairo::Context::create(surface);
+
+ double sw = pixbuf->width();
+ double sh = pixbuf->height();
+ double sx = sw / width;
+ double sy = sh / height;
+ auto scale = 1.0 / std::max(sx, sy);
+ double dx = width - scale * sw;
+ double dy = height - scale * sh;
+
+ ctx->translate(dx / 2, dy / 2);
+ ctx->scale(scale, scale);
+ ctx->set_source(src, 0, 0);
+ ctx->set_operator(Cairo::OPERATOR_OVER);
+ ctx->paint();
+
+ return surface;
+}
+
+Cairo::RefPtr<Cairo::Surface> add_background_to_image(Cairo::RefPtr<Cairo::Surface> image, uint32_t rgb, double margin, double radius, int device_scale, std::optional<uint32_t> border) {
+ auto w = image ? cairo_image_surface_get_width(image->cobj()) : 0;
+ auto h = image ? cairo_image_surface_get_height(image->cobj()) : 0;
+ auto width = w / device_scale + 2 * margin;
+ auto height = h / device_scale + 2 * margin;
+
+ auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width * device_scale, height * device_scale);
+ cairo_surface_set_device_scale(surface->cobj(), device_scale, device_scale);
+ auto ctx = Cairo::Context::create(surface);
+
+ auto x = 0;
+ auto y = 0;
+ if (border.has_value()) {
+ x += 0.5 * device_scale;
+ y += 0.5 * device_scale;
+ width -= device_scale;
+ height -= device_scale;
+ }
+ ctx->arc(x + width - radius, y + radius, radius, -M_PI_2, 0);
+ ctx->arc(x + width - radius, y + height - radius, radius, 0, M_PI_2);
+ ctx->arc(x + radius, y + height - radius, radius, M_PI_2, M_PI);
+ ctx->arc(x + radius, y + radius, radius, M_PI, 3 * M_PI_2);
+ ctx->close_path();
+
+ ctx->set_source_rgb(SP_RGBA32_R_F(rgb), SP_RGBA32_G_F(rgb), SP_RGBA32_B_F(rgb));
+ if (border.has_value()) {
+ ctx->fill_preserve();
+
+ auto b = *border;
+ ctx->set_source_rgb(SP_RGBA32_R_F(b), SP_RGBA32_G_F(b), SP_RGBA32_B_F(b));
+ ctx->set_line_width(1.0);
+ ctx->stroke();
+ }
+ else {
+ ctx->fill();
+ }
+
+ if (image) {
+ ctx->set_source(image, margin, margin);
+ ctx->paint();
+ }
+
+ return surface;
+}
+
+Cairo::RefPtr<Cairo::Surface> draw_frame(Cairo::RefPtr<Cairo::Surface> image, double image_alpha, uint32_t frame_rgba, double thickness, std::optional<uint32_t> checkerboard_color, int device_scale) {
+ if (!image) return image;
+
+ auto w = cairo_image_surface_get_width(image->cobj());
+ auto h = cairo_image_surface_get_height(image->cobj());
+ auto width = w / device_scale + 2 * thickness;
+ auto height = h / device_scale + 2 * thickness;
+
+ auto surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width * device_scale, height * device_scale);
+ cairo_surface_set_device_scale(surface->cobj(), device_scale, device_scale);
+ auto ctx = Cairo::Context::create(surface);
+
+ if (checkerboard_color) {
+ Cairo::RefPtr<Cairo::Pattern> pattern(new Cairo::Pattern(ink_cairo_pattern_create_checkerboard(*checkerboard_color)));
+ ctx->save();
+ ctx->set_operator(Cairo::OPERATOR_SOURCE);
+ ctx->set_source(pattern);
+ ctx->rectangle(thickness, thickness, width - 2*thickness, height - 2*thickness);
+ ctx->fill();
+ ctx->restore();
+ }
+
+ ctx->rectangle(thickness / 2, thickness / 2, width - thickness, height - thickness);
+
+ if (thickness > 0) {
+ ctx->set_source_rgba(SP_RGBA32_R_F(frame_rgba), SP_RGBA32_G_F(frame_rgba), SP_RGBA32_B_F(frame_rgba), SP_RGBA32_A_F(frame_rgba));
+ ctx->set_line_width(thickness);
+ ctx->stroke();
+ }
+
+ ctx->set_source(image, thickness, thickness);
+ ctx->paint_with_alpha(image_alpha);
+
+ return surface;
+}
+
+
+object_renderer:: object_renderer() {
+}
+
+Cairo::RefPtr<Cairo::Surface> object_renderer::render(SPObject& object, double width, double height, double device_scale, object_renderer::options opt) {
+
+ Cairo::RefPtr<Cairo::Surface> surface;
+ if (opt._draw_frame) {
+ width -= 2 * opt._stroke;
+ height -= 2 * opt._stroke;
+ }
+ if (width <= 0 || height <= 0) return surface;
+
+ if (is<SPSymbol>(&object)) {
+ if (!_symbol_document) {
+ _symbol_document.reset(symbols_preview_doc());
+ }
+ surface = draw_symbol(object, width, height, device_scale, _symbol_document.get(), opt._symbol_style_from_use);
+ }
+ else if (is<SPMarker>(&object)) {
+ const auto group = "marker-mid";
+ if (!_sandbox) {
+ _sandbox = ink_markers_preview_doc(group);
+ }
+ std::optional<guint32> checkerboard; // rgb background color
+ bool no_clip = true;
+ double scale = 1.0;
+
+ unsigned const dkey = SPItem::display_key_new(1);
+ Inkscape::Drawing drawing; // New drawing for offscreen rendering.
+ drawing.setRoot(_sandbox->getRoot()->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY));
+ auto invoke_hide_guard = scope_exit([&] { _sandbox->getRoot()->invoke_hide(dkey); });
+ drawing.setExact(); // Maximum quality for blurs.
+
+ surface = create_marker_image(group, _sandbox.get(), opt._foreground, Geom::IntPoint(width, height), object.getId(),
+ object.document, drawing, checkerboard, no_clip, scale, device_scale);
+ }
+ else if (is<SPGradient>(&object)) {
+ surface = draw_gradient(cast<SPGradient>(&object), width, height, device_scale, false);
+ }
+ else if (auto pattern = cast<SPPattern>(&object)) {
+ surface = PatternManager::get().get_image(pattern, width, height, device_scale);
+ }
+ else if (auto image = cast<SPImage>(&object)) {
+ surface = render_image(image->pixbuf.get(), width, height, device_scale);
+ }
+ else {
+ g_warning("object_renderer: don't know how to render this object type");
+ }
+
+ if (opt._add_background) {
+ surface = add_background_to_image(surface, opt._background, opt._margin, opt._radius, device_scale);
+ }
+
+ // extra decorators: frame, opacity change, checkerboard background
+ if (opt._draw_frame || opt._image_opacity != 1 || opt._checkerboard.has_value()) {
+ surface = draw_frame(surface, opt._image_opacity, opt._frame_rgba, opt._stroke, opt._checkerboard, device_scale);
+ }
+
+ return surface;
+}
+
+} // namespace