Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
cairo-utils.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Helper functions to use cairo with inkscape
4 *
5 * Copyright (C) 2007 bulia byak
6 * Copyright (C) 2008 Johan Engelen
7 *
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 *
10 */
11
12#include "display/cairo-utils.h"
13
14#include <2geom/affine.h>
15#include <2geom/curves.h>
16#include <2geom/path-sink.h>
17#include <2geom/path.h>
18#include <2geom/pathvector.h>
19#include <2geom/point.h>
21#include <2geom/transforms.h>
22#include <boost/algorithm/string.hpp>
23#include <boost/operators.hpp>
24#include <boost/optional/optional.hpp>
25#include <cairomm/context.h>
26#include <cairomm/matrix.h>
27#include <cairomm/pattern.h>
28#include <cairomm/refptr.h>
29#include <cairomm/surface.h>
30#include <cmath>
31#include <cstdint>
32#include <gdk-pixbuf/gdk-pixbuf.h>
33#include <glib/gstdio.h>
34#include <glibmm/fileutils.h>
35
36#include "cairo-templates.h"
37#include "colors/manager.h"
38#include "colors/utils.h"
39#include "document.h"
40#include "helper/pixbuf-ops.h"
41#include "preferences.h"
42#include "ui/util.h"
43#include "util/scope_exit.h"
44#include "util/units.h"
45#include "util/uri.h"
46
47#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 17, 6)
48#define CAIRO_HAS_HAIRLINE
49#endif
50
56static cairo_user_data_key_t ink_color_interpolation_key;
57
58namespace Inkscape {
59
60/* The class below implement the following hack:
61 *
62 * The pixels formats of Cairo and GdkPixbuf are different.
63 * GdkPixbuf accesses pixels as bytes, alpha is not premultiplied,
64 * and successive bytes of a single pixel contain R, G, B and A components.
65 * Cairo accesses pixels as 32-bit ints, alpha is premultiplied,
66 * and each int contains as 0xAARRGGBB, accessed with bitwise operations.
67 *
68 * In other words, on a little endian system, a GdkPixbuf will contain:
69 * char *data = "rgbargbargba...."
70 * int *data = { 0xAABBGGRR, 0xAABBGGRR, 0xAABBGGRR, ... }
71 * while a Cairo image surface will contain:
72 * char *data = "bgrabgrabgra...."
73 * int *data = { 0xAARRGGBB, 0xAARRGGBB, 0xAARRGGBB, ... }
74 *
75 * It is possible to convert between these two formats (almost) losslessly.
76 * Some color information from partially transparent regions of the image
77 * is lost, but the result when displaying this image will remain the same.
78 *
79 * The class allows interoperation between GdkPixbuf
80 * and Cairo surfaces without creating a copy of the image.
81 * This is implemented by creating a GdkPixbuf and a Cairo image surface
82 * which share their data. Depending on what is needed at a given time,
83 * the pixels are converted in place to the Cairo or the GdkPixbuf format.
84 */
85
90 : _pixbuf(gdk_pixbuf_new_from_data(
91 cairo_image_surface_get_data(s), GDK_COLORSPACE_RGB, TRUE, 8,
92 cairo_image_surface_get_width(s), cairo_image_surface_get_height(s),
93 cairo_image_surface_get_stride(s),
95 , _surface(s)
96 , _mod_time(0)
97 , _pixel_format(PF_CAIRO)
98 , _cairo_store(true)
99{}
100
105 : _pixbuf(pb)
106 , _surface(nullptr)
107 , _mod_time(0)
108 , _pixel_format(PF_GDK)
109 , _cairo_store(false)
110{
111 _forceAlpha();
112 _surface = cairo_image_surface_create_for_data(
113 gdk_pixbuf_get_pixels(_pixbuf), CAIRO_FORMAT_ARGB32,
114 gdk_pixbuf_get_width(_pixbuf), gdk_pixbuf_get_height(_pixbuf), gdk_pixbuf_get_rowstride(_pixbuf));
115}
116
118 : _pixbuf(gdk_pixbuf_copy(other._pixbuf))
119 , _surface(cairo_image_surface_create_for_data(
120 gdk_pixbuf_get_pixels(_pixbuf), CAIRO_FORMAT_ARGB32,
121 gdk_pixbuf_get_width(_pixbuf), gdk_pixbuf_get_height(_pixbuf), gdk_pixbuf_get_rowstride(_pixbuf)))
122 , _mod_time(other._mod_time)
123 , _path(other._path)
124 , _pixel_format(other._pixel_format)
125 , _cairo_store(false)
126{}
127
129{
130 if (!_cairo_store) {
131 cairo_surface_destroy(_surface);
132 }
133 g_object_unref(_pixbuf);
134}
135
136#if !GDK_PIXBUF_CHECK_VERSION(2, 41, 0)
142 GdkPixbufLoader *loader, guchar *decoded, gsize decoded_len, GError **error)
143{
144 bool success = true;
145 gsize bytes_left = decoded_len;
146 gsize secret_limit = 0xffff;
147 guchar *decoded_head = decoded;
148 while (bytes_left && success) {
149 gsize bytes = (bytes_left > secret_limit) ? secret_limit : bytes_left;
150 success = gdk_pixbuf_loader_write(loader, decoded_head, bytes, error);
151 decoded_head += bytes;
152 bytes_left -= bytes;
153 }
154
155 return success;
156}
157#define gdk_pixbuf_loader_write _workaround_issue_70__gdk_pixbuf_loader_write
158#endif
159
164{
165 GdkPixbuf *copy = nullptr;
166 auto source = _pixbuf;
167 if (_pixel_format == PF_CAIRO) {
168 // This copies twice, but can be run on const, which is useful.
169 copy = gdk_pixbuf_copy(_pixbuf);
170 ensure_pixbuf(copy);
171 source = copy;
172 }
173 auto cropped = gdk_pixbuf_new_subpixbuf(source,
174 area.left(), area.top(), area.width(), area.height());
175 if (copy) {
176 // Clean up our pixbuf copy
177 g_object_unref(copy);
178 }
179 return new Pixbuf(cropped);
180}
181
182Pixbuf *Pixbuf::create_from_data_uri(gchar const *uri_data, double svgdpi)
183{
184 Pixbuf *pixbuf = nullptr;
185 auto [data, type] = extract_uri_data(uri_data);
186
187 if ((*data) && type == Base64Data::RASTER) {
188 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
189
190 if (!loader) return nullptr;
191
192 gsize decoded_len = 0;
193 guchar *decoded = g_base64_decode(data, &decoded_len);
194
195 if (gdk_pixbuf_loader_write(loader, decoded, decoded_len, nullptr)) {
196 gdk_pixbuf_loader_close(loader, nullptr);
197 GdkPixbuf *buf = gdk_pixbuf_loader_get_pixbuf(loader);
198 if (buf) {
199 g_object_ref(buf);
202 pixbuf = new Pixbuf(buf);
203
204 if (!has_ori) {
205 // We DO NOT want to store the original data if it contains orientation
206 // data since many exports that will use the surface do not handle it.
207 // TODO: Preserve the original meta data from the file by stripping out
208 // orientation but keeping all other aspects of the raster.
209 GdkPixbufFormat *fmt = gdk_pixbuf_loader_get_format(loader);
210 gchar *fmt_name = gdk_pixbuf_format_get_name(fmt);
211 pixbuf->_setMimeData(decoded, decoded_len, fmt_name);
212 g_free(fmt_name);
213 }
214 } else {
215 g_free(decoded);
216 }
217 } else {
218 g_free(decoded);
219 }
220 g_object_unref(loader);
221 }
222
223 if ((*data) && type == Base64Data::SVG) {
224 gsize decoded_len = 0;
225 guchar *decoded = g_base64_decode(data, &decoded_len);
226 auto svgDoc = SPDocument::createNewDocFromMem({reinterpret_cast<char const *>(decoded), decoded_len}, false);
227 // Check the document loaded properly
228 if (!svgDoc || !svgDoc->getRoot()) {
229 return nullptr;
230 }
232 double dpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", 96.0);
233 if (svgdpi && svgdpi > 0) {
234 dpi = svgdpi;
235 }
236
237 // Get the size of the document
238 Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
239 Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
240 const double svgWidth_px = svgWidth.value("px");
241 const double svgHeight_px = svgHeight.value("px");
242 if (svgWidth_px < 0 || svgHeight_px < 0) {
243 g_warning("create_from_data_uri: malformed document: svgWidth_px=%f, svgHeight_px=%f", svgWidth_px,
244 svgHeight_px);
245 return nullptr;
246 }
247
248 assert(!pixbuf);
249 Geom::Rect area(0, 0, svgWidth_px, svgHeight_px);
250 pixbuf = sp_generate_internal_bitmap(svgDoc.get(), area, dpi);
251 GdkPixbuf const *buf = pixbuf->getPixbufRaw();
252
253 // Tidy up
254 if (buf == nullptr) {
255 std::cerr << "Pixbuf::create_from_data: failed to load contents: " << std::endl;
256 delete pixbuf;
257 g_free(decoded);
258 return nullptr;
259 } else {
260 pixbuf->_setMimeData(decoded, decoded_len, "svg+xml");
261 }
262 }
263
264 return pixbuf;
265}
266
267Pixbuf *Pixbuf::create_from_file(std::string const &fn, double svgdpi)
268{
269 Pixbuf *pb = nullptr;
270 // test correctness of filename
271 if (!g_file_test(fn.c_str(), G_FILE_TEST_EXISTS)) {
272 return nullptr;
273 }
274 GStatBuf stdir;
275 int val = g_stat(fn.c_str(), &stdir);
276 if (val == 0 && stdir.st_mode & S_IFDIR){
277 return nullptr;
278 }
279 // we need to load the entire file into memory,
280 // since we'll store it as MIME data
281 gchar *data = nullptr;
282 gsize len = 0;
283 GError *error = nullptr;
284
285 if (g_file_get_contents(fn.c_str(), &data, &len, &error)) {
286
287 if (error != nullptr) {
288 std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
289 std::cerr << " (" << fn << ")" << std::endl;
290 return nullptr;
291 }
292
293 pb = Pixbuf::create_from_buffer(std::move(data), len, svgdpi, fn);
294
295 if (pb) {
296 pb->_mod_time = stdir.st_mtime;
297 }
298 } else {
299 std::cerr << "Pixbuf::create_from_file: failed to get contents: " << fn << std::endl;
300 return nullptr;
301 }
302
303 return pb;
304}
305
307{
308 GdkPixbuf *old = buf;
309 buf = gdk_pixbuf_apply_embedded_orientation(buf);
310 g_object_unref(old);
311 return buf;
312}
313
318{
319 // See gdk_pixbuf_apply_embedded_orientation in gdk-pixbuf
320 if (auto opt_str = gdk_pixbuf_get_option(buf, "orientation")) {
321 switch ((int)g_ascii_strtoll(opt_str, NULL, 10)) {
322 case 2: // Flip Horz
323 return Geom::Scale(-1, 1);
324 case 3: // +180 Rotate
325 return Geom::Scale(-1, -1);
326 case 4: // Flip Vert
327 return Geom::Scale(1, -1);
328 case 5: // +90 Rotate & Flip Horz
329 return Geom::Rotate(90) * Geom::Scale(-1, 1);
330 case 6: // +90 Rotate
331 return Geom::Rotate(90);
332 case 7: // +90 Rotate * Flip Vert
333 return Geom::Rotate(90) * Geom::Scale(1, -1);
334 case 8: // -90 Rotate
335 return Geom::Rotate(-90);
336 default:
337 break;
338
339 }
340 }
341 return Geom::identity();
342}
343
344Pixbuf *Pixbuf::create_from_buffer(std::string const &buffer, double svgdpi, std::string const &fn)
345{
346#if GLIB_CHECK_VERSION(2,67,3)
347 auto datacopy = (gchar *)g_memdup2(buffer.data(), buffer.size());
348#else
349 auto datacopy = (gchar *)g_memdup(buffer.data(), buffer.size());
350#endif
351 return Pixbuf::create_from_buffer(std::move(datacopy), buffer.size(), svgdpi, fn);
352}
353
354Pixbuf *Pixbuf::create_from_buffer(gchar *&&data, gsize len, double svgdpi, std::string const &fn)
355{
356 bool has_ori = false;
357 Pixbuf *pb = nullptr;
358 GError *error = nullptr;
359 {
360 GdkPixbuf *buf = nullptr;
361 GdkPixbufLoader *loader = nullptr;
362 std::string::size_type idx;
363 idx = fn.rfind('.');
364 bool is_svg = false;
365 if(idx != std::string::npos)
366 {
367 if (boost::iequals(fn.substr(idx+1).c_str(), "svg")) {
368 auto svgDoc = SPDocument::createNewDocFromMem({data, len}, true, fn.c_str());
369
370 // Check the document loaded properly
371 if (!svgDoc || !svgDoc->getRoot()) {
372 return nullptr;
373 }
374
376 double dpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", 96.0);
377 if (svgdpi && svgdpi > 0) {
378 dpi = svgdpi;
379 }
380
381 // Get the size of the document
382 Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
383 Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
384 // Limit the size of the document to 100 inches square
385 const double svgWidth_px = std::min(svgWidth.value("px"), dpi * 100);
386 const double svgHeight_px = std::min(svgHeight.value("px"), dpi * 100);
387 if (svgWidth_px < 0 || svgHeight_px < 0) {
388 g_warning("create_from_buffer: malformed document: svgWidth_px=%f, svgHeight_px=%f", svgWidth_px,
389 svgHeight_px);
390 return nullptr;
391 }
392
393 Geom::Rect area(0, 0, svgWidth_px, svgHeight_px);
394 pb = sp_generate_internal_bitmap(svgDoc.get(), area, dpi);
395 if (!pb)
396 return nullptr;
397
398 buf = pb->getPixbufRaw();
399
400 // Tidy up
401 if (buf == nullptr) {
402 delete pb;
403 return nullptr;
404 }
406 is_svg = true;
407 }
408 }
409 if (!is_svg) {
410 loader = gdk_pixbuf_loader_new();
411 gdk_pixbuf_loader_write(loader, (guchar *) data, len, &error);
412 if (error != nullptr) {
413 std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
414 std::cerr << " (" << fn << ")" << std::endl;
415 g_free(data);
416 g_object_unref(loader);
417 return nullptr;
418 }
419
420 gdk_pixbuf_loader_close(loader, &error);
421 if (error != nullptr) {
422 std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
423 std::cerr << " (" << fn << ")" << std::endl;
424 g_free(data);
425 g_object_unref(loader);
426 return nullptr;
427 }
428
429 buf = gdk_pixbuf_loader_get_pixbuf(loader);
430 if (buf) {
431 // gdk_pixbuf_loader_get_pixbuf returns a borrowed reference
432 g_object_ref(buf);
435 pb = new Pixbuf(buf);
436 }
437 }
438
439 if (pb) {
440 pb->_path = fn;
441 if (is_svg) {
442 pb->_setMimeData((guchar *) data, len, "svg");
443 } else if(!has_ori) {
444 // We DO NOT want to store the original data if it contains orientation
445 // data since many exports that will use the surface do not handle it.
446 GdkPixbufFormat *fmt = gdk_pixbuf_loader_get_format(loader);
447 gchar *fmt_name = gdk_pixbuf_format_get_name(fmt);
448 pb->_setMimeData((guchar *) data, len, fmt_name);
449 g_free(fmt_name);
450 g_object_unref(loader);
451 }
452 } else {
453 std::cerr << "Pixbuf::create_from_file: failed to load contents: " << fn << std::endl;
454 g_free(data);
455 }
456
457 // TODO: we could also read DPI, ICC profile, gamma correction, and other information
458 // from the file. This can be done by using format-specific libraries e.g. libpng.
459 }
460
461 return pb;
462}
463
468GdkPixbuf *Pixbuf::getPixbufRaw(bool convert_format)
469{
470 if (convert_format) {
472 }
473 return _pixbuf;
474}
475
477{
478 assert(_pixel_format == PF_GDK);
479 return _pixbuf;
480}
481
495
497{
498 assert(_pixel_format == PF_CAIRO);
499 return _surface;
500}
501
502/* Declaring this function in the header requires including <gdkmm/pixbuf.h>,
503 * which stupidly includes <glibmm.h> which in turn pulls in <glibmm/threads.h>.
504 * However, since glib 2.32, <glibmm/threads.h> has to be included before <glib.h>
505 * when compiling with G_DISABLE_DEPRECATED, as we do in non-release builds.
506 * This necessitates spamming a lot of files with #include <glibmm/threads.h>
507 * at the top.
508 *
509 * Since we don't really use gdkmm, do not define this function for now. */
510
511/*
512Glib::RefPtr<Gdk::Pixbuf> Pixbuf::getPixbuf(bool convert_format = true)
513{
514 g_object_ref(_pixbuf);
515 Glib::RefPtr<Gdk::Pixbuf> p(getPixbuf(convert_format));
516 return p;
517}
518*/
519
520Cairo::RefPtr<Cairo::Surface> Pixbuf::getSurface()
521{
522 return Cairo::RefPtr<Cairo::Surface>(new Cairo::Surface(getSurfaceRaw(), false));
523}
524
527guchar const *Pixbuf::getMimeData(gsize &len, std::string &mimetype) const
528{
529 static gchar const *mimetypes[] = {
530 CAIRO_MIME_TYPE_JPEG, CAIRO_MIME_TYPE_JP2, CAIRO_MIME_TYPE_PNG, nullptr };
531 static guint mimetypes_len = g_strv_length(const_cast<gchar**>(mimetypes));
532
533 guchar const *data = nullptr;
534
535 for (guint i = 0; i < mimetypes_len; ++i) {
536 unsigned long len_long = 0;
537 cairo_surface_get_mime_data(const_cast<cairo_surface_t*>(_surface), mimetypes[i], &data, &len_long);
538 if (data != nullptr) {
539 len = len_long;
540 mimetype = mimetypes[i];
541 break;
542 }
543 }
544
545 return data;
546}
547
548int Pixbuf::width() const {
549 return gdk_pixbuf_get_width(const_cast<GdkPixbuf*>(_pixbuf));
550}
551int Pixbuf::height() const {
552 return gdk_pixbuf_get_height(const_cast<GdkPixbuf*>(_pixbuf));
553}
554int Pixbuf::rowstride() const {
555 return gdk_pixbuf_get_rowstride(const_cast<GdkPixbuf*>(_pixbuf));
556}
557guchar const *Pixbuf::pixels() const {
558 return gdk_pixbuf_get_pixels(const_cast<GdkPixbuf*>(_pixbuf));
559}
560guchar *Pixbuf::pixels() {
561 return gdk_pixbuf_get_pixels(_pixbuf);
562}
564 cairo_surface_mark_dirty(_surface);
565}
566
568{
569 if (gdk_pixbuf_get_has_alpha(_pixbuf)) return;
570
571 GdkPixbuf *old = _pixbuf;
572 _pixbuf = gdk_pixbuf_add_alpha(old, FALSE, 0, 0, 0);
573 g_object_unref(old);
574}
575
576void Pixbuf::_setMimeData(guchar *data, gsize len, Glib::ustring const &format)
577{
578 gchar const *mimetype = nullptr;
579
580 if (format == "jpeg") {
581 mimetype = CAIRO_MIME_TYPE_JPEG;
582 } else if (format == "jpeg2000") {
583 mimetype = CAIRO_MIME_TYPE_JP2;
584 } else if (format == "png") {
585 mimetype = CAIRO_MIME_TYPE_PNG;
586 }
587
588 if (mimetype != nullptr) {
589 cairo_surface_set_mime_data(_surface, mimetype, data, len, g_free, data);
590 //g_message("Setting Cairo MIME data: %s", mimetype);
591 } else {
592 g_free(data);
593 //g_message("Not setting Cairo MIME data: unknown format %s", name.c_str());
594 }
595}
596
601{
602 if (fmt == PF_CAIRO && _pixel_format == PF_GDK) {
605 } else if (fmt == PF_GDK && _pixel_format == PF_CAIRO) {
608 } else if (fmt != _pixel_format) {
609 g_assert_not_reached();
610 }
611}
612
620{
622 gdk_pixbuf_get_pixels(pb),
623 gdk_pixbuf_get_width(pb),
624 gdk_pixbuf_get_height(pb),
625 gdk_pixbuf_get_rowstride(pb));
626}
627
633{
635 gdk_pixbuf_get_pixels(pb),
636 gdk_pixbuf_get_width(pb),
637 gdk_pixbuf_get_height(pb),
638 gdk_pixbuf_get_rowstride(pb));
639}
640
641} // namespace Inkscape
642
643/*
644 * Can be called recursively.
645 * If optimize_stroke == false, the view Rect is not used.
646 */
647static void
648feed_curve_to_cairo(cairo_t *cr, Geom::Curve const &c, Geom::Affine const &trans, Geom::Rect const &view, bool optimize_stroke)
649{
650 using Geom::X;
651 using Geom::Y;
652
653 unsigned order = 0;
654 if (auto b = dynamic_cast<Geom::BezierCurve const*>(&c)) {
655 order = b->order();
656 }
657
658 // handle the three typical curve cases
659 switch (order) {
660 case 1:
661 {
662 Geom::Point end_tr = c.finalPoint() * trans;
663 if (!optimize_stroke) {
664 cairo_line_to(cr, end_tr[0], end_tr[1]);
665 } else {
666 Geom::Rect swept(c.initialPoint()*trans, end_tr);
667 if (swept.intersects(view)) {
668 cairo_line_to(cr, end_tr[0], end_tr[1]);
669 } else {
670 cairo_move_to(cr, end_tr[0], end_tr[1]);
671 }
672 }
673 }
674 break;
675 case 2:
676 {
677 auto quadratic_bezier = static_cast<Geom::QuadraticBezier const*>(&c);
678 std::array<Geom::Point, 3> points;
679 for (int i = 0; i < 3; i++) {
680 points[i] = quadratic_bezier->controlPoint(i) * trans;
681 }
682 // degree-elevate to cubic Bezier, since Cairo doesn't do quadratic Beziers
683 Geom::Point b1 = points[0] + (2./3) * (points[1] - points[0]);
684 Geom::Point b2 = b1 + (1./3) * (points[2] - points[0]);
685 if (!optimize_stroke) {
686 cairo_curve_to(cr, b1[X], b1[Y], b2[X], b2[Y], points[2][X], points[2][Y]);
687 } else {
688 Geom::Rect swept(points[0], points[2]);
689 swept.expandTo(points[1]);
690 if (swept.intersects(view)) {
691 cairo_curve_to(cr, b1[X], b1[Y], b2[X], b2[Y], points[2][X], points[2][Y]);
692 } else {
693 cairo_move_to(cr, points[2][X], points[2][Y]);
694 }
695 }
696 }
697 break;
698 case 3:
699 {
700 auto cubic_bezier = static_cast<Geom::CubicBezier const*>(&c);
701 std::array<Geom::Point, 4> points;
702 for (int i = 0; i < 4; i++) {
703 points[i] = cubic_bezier->controlPoint(i);
704 }
705 //points[0] *= trans; // don't do this one here for fun: it is only needed for optimized strokes
706 points[1] *= trans;
707 points[2] *= trans;
708 points[3] *= trans;
709 if (!optimize_stroke) {
710 cairo_curve_to(cr, points[1][X], points[1][Y], points[2][X], points[2][Y], points[3][X], points[3][Y]);
711 } else {
712 points[0] *= trans; // didn't transform this point yet
713 Geom::Rect swept(points[0], points[3]);
714 swept.expandTo(points[1]);
715 swept.expandTo(points[2]);
716 if (swept.intersects(view)) {
717 cairo_curve_to(cr, points[1][X], points[1][Y], points[2][X], points[2][Y], points[3][X], points[3][Y]);
718 } else {
719 cairo_move_to(cr, points[3][X], points[3][Y]);
720 }
721 }
722 }
723 break;
724 default:
725 {
726 if (Geom::EllipticalArc const *arc = dynamic_cast<Geom::EllipticalArc const*>(&c)) {
727 if (arc->isChord()) {
728 Geom::Point endPoint(arc->finalPoint());
729 cairo_line_to(cr, endPoint[0], endPoint[1]);
730 } else {
731 Geom::Affine xform = arc->unitCircleTransform() * trans;
732 // Don't draw anything if the angle is borked
733 if(std::isnan(arc->initialAngle()) || std::isnan(arc->finalAngle())) {
734 g_warning("Bad angle while drawing EllipticalArc");
735 break;
736 }
737
738 // Apply the transformation to the current context
739 auto cm = geom_to_cairo(xform);
740
741 cairo_save(cr);
742 cairo_transform(cr, &cm);
743
744 // Draw the circle
745 if (arc->sweep()) {
746 cairo_arc(cr, 0, 0, 1, arc->initialAngle(), arc->finalAngle());
747 } else {
748 cairo_arc_negative(cr, 0, 0, 1, arc->initialAngle(), arc->finalAngle());
749 }
750 // Revert the current context
751 cairo_restore(cr);
752 }
753 } else {
754 // handles sbasis as well as all other curve types
755 // this is very slow
756 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
757
758 // recurse to convert the new path resulting from the sbasis to svgd
759 for (const auto & iter : sbasis_path) {
760 feed_curve_to_cairo(cr, iter, trans, view, optimize_stroke);
761 }
762 }
763 }
764 break;
765 }
766}
767
768
770static void
772{
773 if (path.empty())
774 return;
775
776 cairo_move_to(ct, path.initialPoint()[0], path.initialPoint()[1] );
777
778 for (Geom::Path::const_iterator cit = path.begin(); cit != path.end_open(); ++cit) {
779 feed_curve_to_cairo(ct, *cit, Geom::identity(), Geom::Rect(), false); // optimize_stroke is false, so the view rect is not used
780 }
781
782 if (path.closed()) {
783 cairo_close_path(ct);
784 }
785}
786
788static void
789feed_path_to_cairo (cairo_t *ct, Geom::Path const &path, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width)
790{
791 if (!area)
792 return;
793 if (path.empty())
794 return;
795
796 // Transform all coordinates to coords within "area"
797 Geom::Point shift = area->min();
798 Geom::Rect view = *area;
799 view.expandBy (stroke_width);
800 view = view * (Geom::Affine)Geom::Translate(-shift);
801 // Pass transformation to feed_curve, so that we don't need to create a whole new path.
802 Geom::Affine transshift(trans * Geom::Translate(-shift));
803
804 Geom::Point initial = path.initialPoint() * transshift;
805 cairo_move_to(ct, initial[0], initial[1] );
806
807 for(Geom::Path::const_iterator cit = path.begin(); cit != path.end_open(); ++cit) {
808 feed_curve_to_cairo(ct, *cit, transshift, view, optimize_stroke);
809 }
810
811 if (path.closed()) {
812 if (!optimize_stroke) {
813 cairo_close_path(ct);
814 } else {
815 cairo_line_to(ct, initial[0], initial[1]);
816 /* We cannot use cairo_close_path(ct) here because some parts of the path may have been
817 clipped and not drawn (maybe the before last segment was outside view area), which
818 would result in closing the "subpath" after the last interruption, not the entire path.
819
820 However, according to cairo documentation:
821 The behavior of cairo_close_path() is distinct from simply calling cairo_line_to() with the equivalent coordinate
822 in the case of stroking. When a closed sub-path is stroked, there are no caps on the ends of the sub-path. Instead,
823 there is a line join connecting the final and initial segments of the sub-path.
824
825 The correct fix will be possible when cairo introduces methods for moving without
826 ending/starting subpaths, which we will use for skipping invisible segments; then we
827 will be able to use cairo_close_path here. This issue also affects ps/eps/pdf export,
828 see bug 168129
829 */
830 }
831 }
832}
833
836void
837feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width)
838{
839 if (!area)
840 return;
841 if (pathv.empty())
842 return;
843
844 for(const auto & it : pathv) {
845 feed_path_to_cairo(ct, it, trans, area, optimize_stroke, stroke_width);
846 }
847}
848
851void
853{
854 if (pathv.empty())
855 return;
856
857 for(const auto & it : pathv) {
858 feed_path_to_cairo(ct, it);
859 }
860}
861
862/*
863 * Pulls out the last cairo path context and reconstitutes it
864 * into a local geom path vector for inkscape use.
865 *
866 * @param ct - The cairo context
867 *
868 * @returns an optioal Geom::PathVector object
869 */
870std::optional<Geom::PathVector> extract_pathvector_from_cairo(cairo_t *ct)
871{
872 cairo_path_t *path = cairo_copy_path(ct);
873 if (!path)
874 return std::nullopt;
875
876 auto path_freer = scope_exit([&] { cairo_path_destroy(path); });
877
879 auto end = &path->data[path->num_data];
880 for (auto p = &path->data[0]; p < end; p += p->header.length) {
881 switch (p->header.type) {
882 case CAIRO_PATH_MOVE_TO:
883 if (p->header.length != 2)
884 return std::nullopt;
885 res.moveTo(Geom::Point(p[1].point.x, p[1].point.y));
886 break;
887
888 case CAIRO_PATH_LINE_TO:
889 if (p->header.length != 2)
890 return std::nullopt;
891 res.lineTo(Geom::Point(p[1].point.x, p[1].point.y));
892 break;
893
894 case CAIRO_PATH_CURVE_TO:
895 if (p->header.length != 4)
896 return std::nullopt;
897 res.curveTo(Geom::Point(p[1].point.x, p[1].point.y), Geom::Point(p[2].point.x, p[2].point.y),
898 Geom::Point(p[3].point.x, p[3].point.y));
899 break;
900
901 case CAIRO_PATH_CLOSE_PATH:
902 if (p->header.length != 1)
903 return std::nullopt;
904 res.closePath();
905 break;
906 default:
907 return std::nullopt;
908 }
909 }
910
911 res.flush();
912 return res.peek();
913}
914
917 void* data = cairo_surface_get_user_data( surface, &ink_color_interpolation_key );
918 if( data != nullptr ) {
919 return (SPColorInterpolation)GPOINTER_TO_INT( data );
920 } else {
922 }
923}
924
927void
929
930 if( cairo_surface_get_content( surface ) != CAIRO_CONTENT_ALPHA ) {
931
933
934 if( ci_in == SP_CSS_COLOR_INTERPOLATION_SRGB &&
937 }
941 }
942
943 cairo_surface_set_user_data(surface, &ink_color_interpolation_key, GINT_TO_POINTER (ci), nullptr);
944 }
945}
946
947void
949 cairo_surface_set_user_data(out, &ink_color_interpolation_key, cairo_surface_get_user_data(in, &ink_color_interpolation_key), nullptr);
950}
951
952void
957
958void
959ink_cairo_set_source_rgba32(Cairo::RefPtr<Cairo::Context> ctx, guint32 rgba)
960{
961 ctx->set_source_rgba(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba));
962}
963
964void
965ink_cairo_set_source_rgba32(Cairo::Context &ctx, guint32 rgba)
966{
967 ctx.set_source_rgba(SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba));
968}
969
974void ink_cairo_set_source_color(Cairo::RefPtr<Cairo::Context> ctx, Inkscape::Colors::Color const &color, double opacity)
975{
976 ink_cairo_set_source_rgba32(ctx, color.toRGBA(opacity));
977}
978void ink_cairo_set_source_color(cairo_t *ctx, Inkscape::Colors::Color const &color, double opacity)
979{
980 ink_cairo_set_source_rgba32(ctx, color.toRGBA(opacity));
981}
983{
984 auto c = color.toRGBA(opacity);
985 cairo_pattern_add_color_stop_rgba(ptn, offset, SP_RGBA32_R_F(c), SP_RGBA32_G_F(c), SP_RGBA32_B_F(c), SP_RGBA32_A_F(c));
986}
988{
989 auto c = color.toRGBA(opacity);
990 return cairo_pattern_create_rgba(SP_RGBA32_R_F(c), SP_RGBA32_G_F(c), SP_RGBA32_B_F(c), SP_RGBA32_A_F(c));
991}
992
993
994void ink_matrix_to_2geom(Geom::Affine &m, cairo_matrix_t const &cm)
995{
996 m[0] = cm.xx;
997 m[2] = cm.xy;
998 m[4] = cm.x0;
999 m[1] = cm.yx;
1000 m[3] = cm.yy;
1001 m[5] = cm.y0;
1002}
1003
1004void ink_matrix_to_cairo(cairo_matrix_t &cm, Geom::Affine const &m)
1005{
1006 cm.xx = m[0];
1007 cm.xy = m[2];
1008 cm.x0 = m[4];
1009 cm.yx = m[1];
1010 cm.yy = m[3];
1011 cm.y0 = m[5];
1012}
1013
1014Geom::Affine ink_matrix_to_2geom(cairo_matrix_t const &cairo_matrix)
1015{
1017 ink_matrix_to_2geom(result, cairo_matrix);
1018 return result;
1019}
1020
1021void
1023{
1024 cairo_matrix_t cm;
1025 ink_matrix_to_cairo(cm, m);
1026 cairo_transform(ct, &cm);
1027}
1028
1029void
1031{
1032 cairo_matrix_t cm;
1033 ink_matrix_to_cairo(cm, m);
1034 cairo_pattern_set_matrix(cp, &cm);
1035}
1036
1037void
1039{
1040#ifdef CAIRO_HAS_HAIRLINE
1041 cairo_set_hairline(ct, true);
1042#else
1043 // As a backup, use a device unit of 1
1044 double x = 1.0, y = 0.0;
1045 cairo_device_to_user_distance(ct, &x, &y);
1046 cairo_set_line_width(ct, std::hypot(x, y));
1047#endif
1048}
1049
1051{
1052#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 18, 0)
1053 cairo_pattern_set_dither(pattern, enabled ? CAIRO_DITHER_BEST : CAIRO_DITHER_NONE);
1054#endif
1055}
1056
1064{
1066
1067 if (cairo_surface_get_type(s) == CAIRO_SURFACE_TYPE_IMAGE) {
1068 // use memory copy instead of using a Cairo context
1069 cairo_surface_flush(s);
1070 int stride = cairo_image_surface_get_stride(s);
1071 int h = cairo_image_surface_get_height(s);
1072 memcpy(cairo_image_surface_get_data(ns), cairo_image_surface_get_data(s), stride * h);
1073 cairo_surface_mark_dirty(ns);
1074 } else {
1075 // generic implementation
1076 cairo_t *ct = cairo_create(ns);
1077 cairo_set_source_surface(ct, s, 0, 0);
1078 cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
1079 cairo_paint(ct);
1080 cairo_destroy(ct);
1081 }
1082
1083 return ns;
1084}
1085
1089Cairo::RefPtr<Cairo::ImageSurface>
1090ink_cairo_surface_copy(Cairo::RefPtr<Cairo::ImageSurface> surface )
1091{
1092 int width = surface->get_width();
1093 int height = surface->get_height();
1094 int stride = surface->get_stride();
1095 auto new_surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, width, height); // device scale?
1096
1097 surface->flush();
1098 memcpy(new_surface->get_data(), surface->get_data(), stride * height);
1099 new_surface->mark_dirty(); // Clear caches. Mandatory after messing directly with contents.
1100
1101 return new_surface;
1102}
1103
1111{
1112 cairo_surface_t *ns = ink_cairo_surface_create_same_size(s, cairo_surface_get_content(s));
1113 cairo_surface_set_user_data(ns, &ink_color_interpolation_key, cairo_surface_get_user_data(s, &ink_color_interpolation_key), nullptr);
1114 return ns;
1115}
1116
1119{
1120 // ink_cairo_surface_get_width()/height() returns value in pixels
1121 // cairo_surface_create_similar() uses device units
1122 double x_scale = 0;
1123 double y_scale = 0;
1124 cairo_surface_get_device_scale( s, &x_scale, &y_scale );
1125
1126 assert (x_scale > 0);
1127 assert (y_scale > 0);
1128
1129 cairo_surface_t *ns =
1130 cairo_surface_create_similar(s, c,
1131 ink_cairo_surface_get_width(s)/x_scale,
1132 ink_cairo_surface_get_height(s)/y_scale);
1133 return ns;
1134}
1135
1143{
1144 cairo_surface_t *alpha = ink_cairo_surface_create_same_size(s, CAIRO_CONTENT_ALPHA);
1145
1146 cairo_t *ct = cairo_create(alpha);
1147 cairo_set_source_surface(ct, s, 0, 0);
1148 cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
1149 cairo_paint(ct);
1150 cairo_destroy(ct);
1151
1152 return alpha;
1153}
1154
1157{
1158 cairo_content_t imgt = cairo_surface_get_content(image);
1159 cairo_content_t bgt = cairo_surface_get_content(bg);
1160 cairo_surface_t *out = nullptr;
1161
1162 if (bgt == CAIRO_CONTENT_ALPHA && imgt == CAIRO_CONTENT_ALPHA) {
1164 } else {
1165 out = ink_cairo_surface_create_same_size(bg, CAIRO_CONTENT_COLOR_ALPHA);
1166 }
1167
1168 return out;
1169}
1170
1171void
1173{
1174 if (cairo_surface_get_type(src) == CAIRO_SURFACE_TYPE_IMAGE &&
1175 cairo_surface_get_type(dest) == CAIRO_SURFACE_TYPE_IMAGE &&
1176 cairo_image_surface_get_format(src) == cairo_image_surface_get_format(dest) &&
1177 cairo_image_surface_get_height(src) == cairo_image_surface_get_height(dest) &&
1178 cairo_image_surface_get_width(src) == cairo_image_surface_get_width(dest) &&
1179 cairo_image_surface_get_stride(src) == cairo_image_surface_get_stride(dest))
1180 {
1181 // use memory copy instead of using a Cairo context
1182 cairo_surface_flush(src);
1183 int stride = cairo_image_surface_get_stride(src);
1184 int h = cairo_image_surface_get_height(src);
1185 memcpy(cairo_image_surface_get_data(dest), cairo_image_surface_get_data(src), stride * h);
1186 cairo_surface_mark_dirty(dest);
1187 } else {
1188 // generic implementation
1189 cairo_t *ct = cairo_create(dest);
1190 cairo_set_source_surface(ct, src, 0, 0);
1191 cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
1192 cairo_paint(ct);
1193 cairo_destroy(ct);
1194 }
1195}
1196
1200int
1202{
1203 // For now only image surface is handled.
1204 // Later add others, e.g. cairo-gl
1205 assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
1206 return cairo_image_surface_get_width(surface);
1207}
1208
1212int
1214{
1215 assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
1216 return cairo_image_surface_get_height(surface);
1217}
1218
1219static double ink_cairo_surface_average_color_internal(cairo_surface_t *surface, cairo_surface_t *mask, double &rf, double &gf, double &bf, double &af)
1220{
1221 rf = gf = bf = af = 0.0;
1222
1223 cairo_surface_flush(surface);
1224 int width = cairo_image_surface_get_width(surface);
1225 int height = cairo_image_surface_get_height(surface);
1226 unsigned char *data = cairo_image_surface_get_data(surface);
1227 double count = 0;
1228
1229 if (width * 4 != cairo_image_surface_get_stride(surface)) {
1230 g_warning("Stride and width don't match, expect zero buffer for average color.");
1231 }
1232
1233 unsigned char *mask_data = nullptr;
1234 if (mask) {
1235 cairo_surface_flush(mask);
1236 if (width != cairo_image_surface_get_width(mask)
1237 || height != cairo_image_surface_get_height(mask)) {
1238 g_warning("Mask size must be exactly the same size as the image surface.");
1239 mask = nullptr;
1240 } else {
1241 mask_data = cairo_image_surface_get_data(mask);
1242 }
1243 }
1244
1245 for (int p = 0; p < (width * height); ++p, data += 4) {
1246 EXTRACT_ARGB32(*reinterpret_cast<guint32*>(data), a, r, g, b)
1247
1248 double amount = mask_data ? mask_data[p] / 255.0 : 1.0;
1249 rf += r / 255.0 * amount;
1250 gf += g / 255.0 * amount;
1251 bf += b / 255.0 * amount;
1252 af += a / 255.0 * amount;
1253 count += amount;
1254 }
1255 return count;
1256}
1257
1258// We extract colors from pattern background, if we need to extract sometimes from a gradient we can add
1259// a extra parameter with the spot number and use cairo_pattern_get_color_stop_rgba
1260// also if the pattern is a image we can pass a boolean like solid = false to get the color by image average ink_cairo_surface_average_color
1262{
1263 double red = 0;
1264 double green = 0;
1265 double blue = 0;
1266 double alpha = 0;
1267 auto status = cairo_pattern_get_rgba(pattern, &red, &green, &blue, &alpha);
1268 if (status != CAIRO_STATUS_PATTERN_TYPE_MISMATCH) {
1269 // in ARGB32 format
1270 return SP_RGBA32_F_COMPOSE(alpha, red, green, blue);
1271 }
1272
1274 status = cairo_pattern_get_surface (pattern, &surface);
1275 if (status != CAIRO_STATUS_PATTERN_TYPE_MISMATCH) {
1276 // first pixel only
1277 auto *pxbsurface = cairo_image_surface_get_data(surface);
1278 return *reinterpret_cast<guint32 const *>(pxbsurface);
1279 }
1280 return 0;
1281}
1282
1290{
1291 double r, g, b, a = 0.0;
1292 double count = ink_cairo_surface_average_color_internal(surface, mask, r, g, b, a);
1293 auto color = Colors::Color(Colors::Space::Type::RGB, {r / a, g / a, b / a, a / count});
1294 color.normalize();
1295 return color;
1296}
1297
1298static guint32 srgb_to_linear( const guint32 c, const guint32 a ) {
1299
1300 const guint32 c1 = unpremul_alpha( c, a );
1301
1302 double cc = c1/255.0;
1303
1304 if( cc < 0.04045 ) {
1305 cc /= 12.92;
1306 } else {
1307 cc = pow( (cc+0.055)/1.055, 2.4 );
1308 }
1309 cc *= 255.0;
1310
1311 const guint32 c2 = (int)cc;
1312
1313 return premul_alpha( c2, a );
1314}
1315
1316static guint32 linear_to_srgb( const guint32 c, const guint32 a ) {
1317
1318 const guint32 c1 = unpremul_alpha( c, a );
1319
1320 double cc = c1/255.0;
1321
1322 if( cc < 0.0031308 ) {
1323 cc *= 12.92;
1324 } else {
1325 cc = pow( cc, 1.0/2.4 )*1.055-0.055;
1326 }
1327 cc *= 255.0;
1328
1329 const guint32 c2 = (int)cc;
1330
1331 return premul_alpha( c2, a );
1332}
1333
1334static uint32_t srgb_to_linear_argb32(uint32_t in)
1335{
1336 EXTRACT_ARGB32(in, a, r, g, b);
1337 if (a != 0) {
1338 r = srgb_to_linear(r, a);
1339 g = srgb_to_linear(g, a);
1340 b = srgb_to_linear(b, a);
1341 }
1342 ASSEMBLE_ARGB32(out, a, r, g, b);
1343 return out;
1344}
1345
1347{
1348 cairo_surface_flush(surface);
1349 int width = cairo_image_surface_get_width(surface);
1350 int height = cairo_image_surface_get_height(surface);
1351
1353
1354 return width * height;
1355}
1356
1357static uint32_t linear_to_srgb_argb32(uint32_t in)
1358{
1359 EXTRACT_ARGB32(in, a, r, g, b);
1360 if (a != 0) {
1361 r = linear_to_srgb(r, a);
1362 g = linear_to_srgb(g, a);
1363 b = linear_to_srgb(b, a);
1364 }
1365 ASSEMBLE_ARGB32(out, a, r, g, b);
1366 return out;
1367}
1368
1369SPBlendMode ink_cairo_operator_to_css_blend(cairo_operator_t cairo_operator)
1370{
1371 // All of the blend modes are implemented in Cairo as of 1.10.
1372 // For a detailed description, see:
1373 // http://cairographics.org/operators/
1374
1375 switch (cairo_operator) {
1376 case CAIRO_OPERATOR_MULTIPLY:
1377 return SP_CSS_BLEND_MULTIPLY;
1378 case CAIRO_OPERATOR_SCREEN:
1379 return SP_CSS_BLEND_SCREEN;
1380 case CAIRO_OPERATOR_DARKEN:
1381 return SP_CSS_BLEND_DARKEN;
1382 case CAIRO_OPERATOR_LIGHTEN:
1383 return SP_CSS_BLEND_LIGHTEN;
1384 case CAIRO_OPERATOR_OVERLAY:
1385 return SP_CSS_BLEND_OVERLAY;
1386 case CAIRO_OPERATOR_COLOR_DODGE:
1388 case CAIRO_OPERATOR_COLOR_BURN:
1390 case CAIRO_OPERATOR_HARD_LIGHT:
1392 case CAIRO_OPERATOR_SOFT_LIGHT:
1394 case CAIRO_OPERATOR_DIFFERENCE:
1396 case CAIRO_OPERATOR_EXCLUSION:
1398 case CAIRO_OPERATOR_HSL_HUE:
1399 return SP_CSS_BLEND_HUE;
1400 case CAIRO_OPERATOR_HSL_SATURATION:
1402 case CAIRO_OPERATOR_HSL_COLOR:
1403 return SP_CSS_BLEND_COLOR;
1404 case CAIRO_OPERATOR_HSL_LUMINOSITY:
1406 case CAIRO_OPERATOR_OVER:
1407 return SP_CSS_BLEND_NORMAL;
1408 default:
1409 return SP_CSS_BLEND_NORMAL;
1410 }
1411}
1412
1414{
1415 // All of the blend modes are implemented in Cairo as of 1.10.
1416 // For a detailed description, see:
1417 // http://cairographics.org/operators/
1418
1419 switch (css_blend) {
1421 return CAIRO_OPERATOR_MULTIPLY;
1423 return CAIRO_OPERATOR_SCREEN;
1425 return CAIRO_OPERATOR_DARKEN;
1427 return CAIRO_OPERATOR_LIGHTEN;
1429 return CAIRO_OPERATOR_OVERLAY;
1431 return CAIRO_OPERATOR_COLOR_DODGE;
1433 return CAIRO_OPERATOR_COLOR_BURN;
1435 return CAIRO_OPERATOR_HARD_LIGHT;
1437 return CAIRO_OPERATOR_SOFT_LIGHT;
1439 return CAIRO_OPERATOR_DIFFERENCE;
1441 return CAIRO_OPERATOR_EXCLUSION;
1442 case SP_CSS_BLEND_HUE:
1443 return CAIRO_OPERATOR_HSL_HUE;
1445 return CAIRO_OPERATOR_HSL_SATURATION;
1446 case SP_CSS_BLEND_COLOR:
1447 return CAIRO_OPERATOR_HSL_COLOR;
1449 return CAIRO_OPERATOR_HSL_LUMINOSITY;
1451 return CAIRO_OPERATOR_OVER;
1452 default:
1453 g_error("Invalid SPBlendMode %d", css_blend);
1454 return CAIRO_OPERATOR_OVER;
1455 }
1456}
1457
1459{
1460 cairo_surface_flush(surface);
1461 int width = cairo_image_surface_get_width(surface);
1462 int height = cairo_image_surface_get_height(surface);
1463
1465
1466 return width * height;
1467}
1468
1469Cairo::RefPtr<Cairo::Pattern> ink_cairo_pattern_create_slanting_stripes(uint32_t color)
1470{
1471 constexpr int width = 10;
1472 constexpr int line_width = 10;
1473
1474 auto surface = Cairo::ImageSurface::create(Cairo::ImageSurface::Format::ARGB32, width, 1);
1475 auto context = Cairo::Context::create(surface);
1476 context->rectangle(0, 0, line_width / 2.0, 1);
1477 ink_cairo_set_source_rgba32(context, color);
1478 context->fill();
1479 context->paint();
1480
1481 auto pattern = Cairo::SurfacePattern::create(surface);
1482 pattern->set_extend(Cairo::SurfacePattern::Extend::REPEAT);
1483 pattern->set_filter(Cairo::SurfacePattern::Filter::NEAREST);
1484 pattern->set_matrix(Cairo::rotation_matrix(3 * M_PI / 4));
1485 return pattern;
1486}
1487
1490{
1491 int const w = 6;
1492 int const h = 6;
1493
1494 auto color_a = Colors::Color(rgba, use_alpha);
1495 auto color_b = Inkscape::Colors::make_contrasted_color(color_a, 1.0);
1496 // Once the second color is generated, the original doesn't need alpha
1497 color_a.enableOpacity(false);
1498
1499 cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2*w, 2*h);
1500
1501 cairo_t *ct = cairo_create(s);
1502 cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
1503 ink_cairo_set_source_color(ct, color_a);
1504 cairo_paint(ct);
1505 ink_cairo_set_source_color(ct, color_b);
1506 cairo_rectangle(ct, 0, 0, w, h);
1507 cairo_rectangle(ct, w, h, w, h);
1508 cairo_fill(ct);
1509 cairo_destroy(ct);
1510
1511 cairo_pattern_t *p = cairo_pattern_create_for_surface(s);
1512 cairo_pattern_set_extend(p, CAIRO_EXTEND_REPEAT);
1513 cairo_pattern_set_filter(p, CAIRO_FILTER_NEAREST);
1514
1515 cairo_surface_destroy(s);
1516 return p;
1517}
1518
1519
1523void ink_cairo_draw_drop_shadow(const Cairo::RefPtr<Cairo::Context> &ctx, const Geom::Rect& rect, double size, guint32 color, double color_alpha) {
1524 // draw fake drop shadow built from gradients
1525 const auto r = SP_RGBA32_R_F(color);
1526 const auto g = SP_RGBA32_G_F(color);
1527 const auto b = SP_RGBA32_B_F(color);
1528 const auto a = color_alpha;
1529 const Geom::Point corners[] = { rect.corner(0), rect.corner(1), rect.corner(2), rect.corner(3) };
1530 // space for gradient shadow
1531 double sw = size;
1532 double half = sw / 2;
1533 using Geom::X;
1534 using Geom::Y;
1535 // 8 gradients total: 4 sides + 4 corners
1536 auto grad_top = Cairo::LinearGradient::create(0, corners[0][Y] + half, 0, corners[0][Y] - half);
1537 auto grad_right = Cairo::LinearGradient::create(corners[1][X], 0, corners[1][X] + sw, 0);
1538 auto grad_bottom = Cairo::LinearGradient::create(0, corners[2][Y], 0, corners[2][Y] + sw);
1539 auto grad_left = Cairo::LinearGradient::create(corners[0][X] + half, 0, corners[0][X] - half, 0);
1540 auto grad_btm_right = Cairo::RadialGradient::create(corners[2][X], corners[2][Y], 0, corners[2][X], corners[2][Y], sw);
1541 auto grad_top_right = Cairo::RadialGradient::create(corners[1][X], corners[1][Y] + half, 0, corners[1][X], corners[1][Y] + half, sw);
1542 auto grad_btm_left = Cairo::RadialGradient::create(corners[3][X] + half, corners[3][Y], 0, corners[3][X] + half, corners[3][Y], sw);
1543 auto grad_top_left = Cairo::RadialGradient::create(corners[0][X], corners[0][Y], 0, corners[0][X], corners[0][Y], half);
1544 const int N = 15; // number of gradient stops; stops used to make it non-linear
1545 // using easing function here: (exp(a*(1-t)) - 1) / (exp(a) - 1);
1546 // it has a nice property of growing from 0 to 1 for t in [0..1]
1547 const auto A = 4.0; // this coefficient changes how steep the curve is and controls shadow drop-off
1548 const auto denominator = exp(A) - 1;
1549 for (int i = 0; i <= N; ++i) {
1550 auto pos = static_cast<double>(i) / N;
1551 // exponential decay for drop shadow - long tail, with values from 100% down to 0% opacity
1552 auto t = 1 - pos; // reverse 't' so alpha drops from 1 to 0
1553 auto alpha = (exp(A * t) - 1) / denominator;
1554 grad_top->add_color_stop_rgba(pos, r, g, b, alpha * a);
1555 grad_bottom->add_color_stop_rgba(pos, r, g, b, alpha * a);
1556 grad_right->add_color_stop_rgba(pos, r, g, b, alpha * a);
1557 grad_left->add_color_stop_rgba(pos, r, g, b, alpha * a);
1558 grad_btm_right->add_color_stop_rgba(pos, r, g, b, alpha * a);
1559 grad_top_right->add_color_stop_rgba(pos, r, g, b, alpha * a);
1560 grad_btm_left->add_color_stop_rgba(pos, r, g, b, alpha * a);
1561 // this left/top corner is just a silver of the shadow: half of it is "hidden" beneath the page
1562 if (pos >= 0.5) {
1563 grad_top_left->add_color_stop_rgba(2 * (pos - 0.5), r, g, b, alpha * a);
1564 }
1565 }
1566
1567 // shadow at the top (faint)
1568 ctx->rectangle(corners[0][X], corners[0][Y] - half, std::max(corners[1][X] - corners[0][X], 0.0), half);
1569 ctx->set_source(grad_top);
1570 ctx->fill();
1571
1572 // right side
1573 ctx->rectangle(corners[1][X], corners[1][Y] + half, sw, std::max(corners[2][Y] - corners[1][Y] - half, 0.0));
1574 ctx->set_source(grad_right);
1575 ctx->fill();
1576
1577 // bottom side
1578 ctx->rectangle(corners[0][X] + half, corners[2][Y], std::max(corners[1][X] - corners[0][X] - half, 0.0), sw);
1579 ctx->set_source(grad_bottom);
1580 ctx->fill();
1581
1582 // left side (faint)
1583 ctx->rectangle(corners[0][X] - half, corners[0][Y], half, std::max(corners[2][Y] - corners[1][Y], 0.0));
1584 ctx->set_source(grad_left);
1585 ctx->fill();
1586
1587 // bottom corners
1588 ctx->rectangle(corners[2][X], corners[2][Y], sw, sw);
1589 ctx->set_source(grad_btm_right);
1590 ctx->fill();
1591
1592 ctx->rectangle(corners[3][X] - half, corners[3][Y], std::min(sw, rect.width() + half), sw);
1593 ctx->set_source(grad_btm_left);
1594 ctx->fill();
1595
1596 // top corners
1597 ctx->rectangle(corners[1][X], corners[1][Y] - half, sw, std::min(sw, rect.height() + half));
1598 ctx->set_source(grad_top_right);
1599 ctx->fill();
1600
1601 ctx->rectangle(corners[0][X] - half, corners[0][Y] - half, half, half);
1602 ctx->set_source(grad_top_left);
1603 ctx->fill();
1604}
1605
1617{
1618 guchar *pixels = cairo_image_surface_get_data(s);
1619 int w = cairo_image_surface_get_width(s);
1620 int h = cairo_image_surface_get_height(s);
1621 int rs = cairo_image_surface_get_stride(s);
1622
1624
1625 GdkPixbuf *pb = gdk_pixbuf_new_from_data(
1626 pixels, GDK_COLORSPACE_RGB, TRUE, 8,
1627 w, h, rs, ink_cairo_pixbuf_cleanup, s);
1628
1629 return pb;
1630}
1631
1638void ink_cairo_pixbuf_cleanup(guchar * /*pixels*/, void *data)
1639{
1640 cairo_surface_t *surface = static_cast<cairo_surface_t*>(data);
1641 cairo_surface_destroy(surface);
1642}
1643
1644/* The following two functions use "from" instead of "to", because when you write:
1645 val1 = argb32_from_pixbuf(val1);
1646 the name of the format is closer to the value in that format. */
1647
1649{
1650 uint32_t a;
1651 if constexpr (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
1652 a = (c & 0xff000000) >> 24;
1653 } else {
1654 a = (c & 0x000000ff);
1655 }
1656
1657 if (a == 0) {
1658 return 0;
1659 }
1660
1661 // extract color components
1662 uint32_t r, g, b;
1663 if constexpr (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
1664 r = (c & 0x000000ff);
1665 g = (c & 0x0000ff00) >> 8;
1666 b = (c & 0x00ff0000) >> 16;
1667 } else {
1668 r = (c & 0xff000000) >> 24;
1669 g = (c & 0x00ff0000) >> 16;
1670 b = (c & 0x0000ff00) >> 8;
1671 }
1672
1673 // premultiply
1674 r = premul_alpha(r, a);
1675 b = premul_alpha(b, a);
1676 g = premul_alpha(g, a);
1677
1678 // combine into output
1679 return (a << 24) | (r << 16) | (g << 8) | b;
1680}
1681
1689{
1690 guint32 a = (c & 0xff000000) >> 24;
1691 if (a == 0) {
1692 assert(c == 0);
1693 c = bgcolor;
1694 }
1695
1696 // extract color components
1697 guint32 r = (c & 0x00ff0000) >> 16;
1698 guint32 g = (c & 0x0000ff00) >> 8;
1699 guint32 b = (c & 0x000000ff);
1700
1701 if (a != 0) {
1702 r = unpremul_alpha(r, a);
1703 g = unpremul_alpha(g, a);
1704 b = unpremul_alpha(b, a);
1705 }
1706
1707 // combine into output
1708 if constexpr (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
1709 return r | (g << 8) | (b << 16) | (a << 24);
1710 } else {
1711 return (r << 24) | (g << 16) | (b << 8) | a;
1712 }
1713}
1714
1722void
1724{
1725 if (!data || w < 1 || h < 1 || stride < 1) {
1726 return;
1727 }
1728
1729 for (size_t i = 0; i < h; ++i) {
1730 guint32 *px = reinterpret_cast<guint32*>(data + i*stride);
1731 for (size_t j = 0; j < w; ++j) {
1732 *px = argb32_from_pixbuf(*px);
1733 ++px;
1734 }
1735 }
1736}
1737
1743void
1744convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int stride, guint32 bgcolor)
1745{
1746 if (!data || w < 1 || h < 1 || stride < 1) {
1747 return;
1748 }
1749 for (size_t i = 0; i < h; ++i) {
1750 guint32 *px = reinterpret_cast<guint32*>(data + i*stride);
1751 for (size_t j = 0; j < w; ++j) {
1752 *px = pixbuf_from_argb32(*px, bgcolor);
1753 ++px;
1754 }
1755 }
1756}
1757
1759{
1760 guint32 r, g, b, a;
1761 a = (in & 0x000000ff);
1762 r = premul_alpha((in & 0xff000000) >> 24, a);
1763 g = premul_alpha((in & 0x00ff0000) >> 16, a);
1764 b = premul_alpha((in & 0x0000ff00) >> 8, a);
1765 ASSEMBLE_ARGB32(px, a, r, g, b)
1766 return px;
1767}
1768
1769
1776{
1777 guint32 a = (c & 0xff000000) >> 24;
1778 guint32 r = (c & 0x00ff0000) >> 16;
1779 guint32 g = (c & 0x0000ff00) >> 8;
1780 guint32 b = (c & 0x000000ff);
1781
1782 if (a != 0) {
1783 r = unpremul_alpha(r, a);
1784 g = unpremul_alpha(g, a);
1785 b = unpremul_alpha(b, a);
1786 }
1787
1788 // combine into output
1789 guint32 o = (r << 24) | (g << 16) | (b << 8) | (a);
1790
1791 return o;
1792}
1793
1794static constexpr uint16_t get_luminance(uint32_t r, uint32_t g, uint32_t b)
1795{
1796 return ((1063 * r + 3576 * g + 361 * b) * 257 + 2500) / 5000;
1797}
1798
1799static_assert(
1800 [] {
1801 for (int x = 0; x < 256; x++) {
1802 const uint16_t hex = 0x101 * x;
1803 assert(get_luminance(x, x, x) == hex);
1804 }
1805 return true;
1806 }(),
1807 "get_luminance function doesn't produce expected luminance values");
1808
1814const guchar* pixbuf_to_png(guchar const**rows, guchar* px, int num_rows, int num_cols, int stride, int color_type, int bit_depth)
1815{
1816 int n_fields = 1 + (color_type&2) + (color_type&4)/4;
1817 const guchar* new_data = (const guchar*)malloc(((n_fields * bit_depth * num_cols + 7)/8) * num_rows);
1818 char* ptr = (char*) new_data;
1819 // Used when we write image data smaller than one byte (for instance in
1820 // black and white images where 1px = 1bit). Only possible with greyscale.
1821 int pad = 0;
1822 for (int row = 0; row < num_rows; ++row) {
1823 rows[row] = (const guchar*)ptr;
1824 for (int col = 0; col < num_cols; ++col) {
1825 guint32 *pixel = reinterpret_cast<guint32*>(px + row*stride)+col;
1826
1827 guint64 pix3 = (*pixel & 0xff000000) >> 24;
1828 guint64 pix2 = (*pixel & 0x00ff0000) >> 16;
1829 guint64 pix1 = (*pixel & 0x0000ff00) >> 8;
1830 guint64 pix0 = (*pixel & 0x000000ff);
1831
1832 uint64_t a, r, g, b;
1833 if constexpr (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
1834 a = pix3;
1835 b = pix2;
1836 g = pix1;
1837 r = pix0;
1838 } else {
1839 r = pix3;
1840 g = pix2;
1841 b = pix1;
1842 a = pix0;
1843 }
1844
1845 // One of possible rgb to greyscale formulas. This one is called "luminance", "luminosity" or "luma"
1846 uint16_t const gray = get_luminance(r, g, b);
1847
1848 if (color_type & 2) { // RGB or RGBA
1849 // for 8bit->16bit transition, I take the FF -> FFFF convention (multiplication by 0x101).
1850 // If you prefer FF -> FF00 (multiplication by 0x100), remove the <<8, <<24, <<40 and <<56
1851 // for little-endian, and remove the <<0, <<16, <<32 and <<48 for big-endian.
1852 if (color_type & 4) { // RGBA
1853 if (bit_depth == 8)
1854 *((guint32*)ptr) = *pixel;
1855 else
1856 // This uses the samples in the order they appear in pixel rather than
1857 // normalised to abgr or rgba in order to make it endian agnostic,
1858 // exploiting the symmetry of the expression (0x101 is the same in both
1859 // endiannesses and each sample is multiplied by that).
1860 *((guint64*)ptr) = (guint64)((pix3<<56)+(pix3<<48)+(pix2<<40)+(pix2<<32)+(pix1<<24)+(pix1<<16)+(pix0<<8)+(pix0));
1861 } else { // RGB
1862 if (bit_depth == 8) {
1863 *ptr = r;
1864 *(ptr+1) = g;
1865 *(ptr+2) = b;
1866 } else {
1867 *((guint16*)ptr) = (r<<8)+r;
1868 *((guint16*)(ptr+2)) = (g<<8)+g;
1869 *((guint16*)(ptr+4)) = (b<<8)+b;
1870 }
1871 }
1872 } else { // Grayscale
1873 if (bit_depth == 16) {
1874 if constexpr (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
1875 *(guint16*)ptr = ((gray & 0xff00)>>8) + ((gray & 0x00ff)<<8);
1876 } else {
1877 *(guint16*)ptr = gray;
1878 }
1879 // For 8bit->16bit this mirrors RGB(A), multiplying by
1880 // 0x101; if you prefer multiplying by 0x100, remove the
1881 // <<8 for little-endian, and remove the unshifted value
1882 // for big-endian.
1883 if (color_type & 4) // Alpha channel
1884 *((guint16*)(ptr+2)) = a + (a<<8);
1885 } else if (bit_depth == 8) {
1886 *ptr = guint8(gray >> 8);
1887 if (color_type & 4) // Alpha channel
1888 *((guint8*)(ptr+1)) = a;
1889 } else {
1890 if (!pad) *ptr=0;
1891 // In PNG numbers are stored left to right, but in most significant bits first, so the first one processed is the ``big'' mask, etc.
1892 int realpad = 8 - bit_depth - pad;
1893 *ptr += guint8((gray >> (16-bit_depth))<<realpad); // Note the "+="
1894 if (color_type & 4) // Alpha channel
1895 *(ptr+1) += guint8((a >> (8-bit_depth))<<(bit_depth + realpad));
1896 }
1897 }
1898
1899 pad += bit_depth*n_fields;
1900 ptr += pad/8;
1901 pad %= 8;
1902 }
1903 // Align bytes on rows
1904 if (pad) {
1905 pad = 0;
1906 ptr++;
1907 }
1908 }
1909 return new_data;
1910}
1911
1912/*
1913 Local Variables:
1914 mode:c++
1915 c-file-style:"stroustrup"
1916 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1917 indent-tabs-mode:nil
1918 fill-column:99
1919 End:
1920*/
1921// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Path - a sequence of contiguous curves.
Cartesian point / 2D vector and related operations.
3x3 affine transformation matrix.
Cairo software blending templates.
void ink_cairo_surface_filter(cairo_surface_t *in, cairo_surface_t *out, Filter &&filter)
static uint32_t srgb_to_linear_argb32(uint32_t in)
static cairo_user_data_key_t ink_color_interpolation_key
Key for cairo_surface_t to keep track of current color interpolation value Only the address of the st...
SPColorInterpolation get_cairo_surface_ci(cairo_surface_t *surface)
void ink_cairo_draw_drop_shadow(const Cairo::RefPtr< Cairo::Context > &ctx, const Geom::Rect &rect, double size, guint32 color, double color_alpha)
Draw drop shadow around the 'rect' with given 'size' and 'color'; shadow extends to the right and bot...
SPBlendMode ink_cairo_operator_to_css_blend(cairo_operator_t cairo_operator)
static void feed_path_to_cairo(cairo_t *ct, Geom::Path const &path)
Feeds path-creating calls to the cairo context translating them from the Path.
void copy_cairo_surface_ci(cairo_surface_t *in, cairo_surface_t *out)
void convert_pixels_pixbuf_to_argb32(guchar *data, int w, int h, int stride)
Convert pixel data from GdkPixbuf format to ARGB.
static double ink_cairo_surface_average_color_internal(cairo_surface_t *surface, cairo_surface_t *mask, double &rf, double &gf, double &bf, double &af)
int ink_cairo_surface_linear_to_srgb(cairo_surface_t *surface)
Cairo::RefPtr< Cairo::Pattern > ink_cairo_pattern_create_slanting_stripes(uint32_t color)
cairo_surface_t * ink_cairo_extract_alpha(cairo_surface_t *s)
Extract the alpha channel into a new surface.
int ink_cairo_surface_srgb_to_linear(cairo_surface_t *surface)
void ink_cairo_set_source_color(Cairo::RefPtr< Cairo::Context > ctx, Inkscape::Colors::Color const &color, double opacity)
The following functions interact between Inkscape color model, and cairo surface rendering.
cairo_surface_t * ink_cairo_surface_create_same_size(cairo_surface_t *s, cairo_content_t c)
cairo_surface_t * ink_cairo_surface_create_identical(cairo_surface_t *s)
Create a surface that differs only in pixel content.
static void feed_curve_to_cairo(cairo_t *cr, Geom::Curve const &c, Geom::Affine const &trans, Geom::Rect const &view, bool optimize_stroke)
void ink_cairo_transform(cairo_t *ct, Geom::Affine const &m)
cairo_surface_t * ink_cairo_surface_copy(cairo_surface_t *s)
Create an exact copy of a surface.
void ink_cairo_pattern_add_color_stop(cairo_pattern_t *ptn, double offset, Inkscape::Colors::Color const &color, double opacity)
const guchar * pixbuf_to_png(guchar const **rows, guchar *px, int num_rows, int num_cols, int stride, int color_type, int bit_depth)
Converts a pixbuf to a PNG data structure.
void ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m)
void feed_pathvector_to_cairo(cairo_t *ct, Geom::PathVector const &pathv, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width)
Feeds path-creating calls to the cairo context translating them from the PathVector,...
guint32 rgba_from_argb32(guint32 c)
Convert one pixel from ARGB to GdkPixbuf format.
void ink_cairo_pattern_set_dither(cairo_pattern_t *pattern, bool enabled)
int ink_cairo_surface_get_width(cairo_surface_t *surface)
Return width in pixels.
guint32 argb32_from_rgba(guint32 in)
Convert a pixel in 0xRRGGBBAA format to Cairo ARGB32 format.
cairo_pattern_t * ink_cairo_pattern_create(Inkscape::Colors::Color const &color, double opacity)
std::optional< Geom::PathVector > extract_pathvector_from_cairo(cairo_t *ct)
void ink_cairo_surface_blit(cairo_surface_t *src, cairo_surface_t *dest)
static guint32 linear_to_srgb(const guint32 c, const guint32 a)
static uint32_t linear_to_srgb_argb32(uint32_t in)
guint32 pixbuf_from_argb32(guint32 c, guint32 bgcolor)
Convert one pixel from ARGB to GdkPixbuf format.
int ink_cairo_surface_get_height(cairo_surface_t *surface)
Return height in pixels.
Colors::Color ink_cairo_surface_average_color(cairo_surface_t *surface, cairo_surface_t *mask)
Get the average color from the given surface.
void convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int stride, guint32 bgcolor)
Convert pixel data from ARGB to GdkPixbuf format.
cairo_pattern_t * ink_cairo_pattern_create_checkerboard(guint32 rgba, bool use_alpha)
void ink_cairo_set_hairline(cairo_t *ct)
guint32 ink_cairo_pattern_get_argb32(cairo_pattern_t *pattern)
void ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba)
guint32 argb32_from_pixbuf(guint32 c)
void ink_cairo_pixbuf_cleanup(guchar *, void *data)
Cleanup function for GdkPixbuf.
static constexpr uint16_t get_luminance(uint32_t r, uint32_t g, uint32_t b)
void ink_matrix_to_cairo(cairo_matrix_t &cm, Geom::Affine const &m)
static guint32 srgb_to_linear(const guint32 c, const guint32 a)
void ink_matrix_to_2geom(Geom::Affine &m, cairo_matrix_t const &cm)
cairo_surface_t * ink_cairo_surface_create_output(cairo_surface_t *image, cairo_surface_t *bg)
cairo_operator_t ink_css_blend_to_cairo_operator(SPBlendMode css_blend)
GdkPixbuf * ink_pixbuf_create_from_cairo_surface(cairo_surface_t *s)
Converts the Cairo surface to a GdkPixbuf pixel format, without allocating extra memory.
void set_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation ci)
Set the color_interpolation_value for a Cairo surface.
Cairo integration helpers.
G_GNUC_CONST guint32 premul_alpha(const guint32 color, const guint32 alpha)
G_GNUC_CONST guint32 unpremul_alpha(const guint32 color, const guint32 alpha)
struct _GdkPixbuf GdkPixbuf
Definition cairo-utils.h:20
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
3x3 matrix representing an affine transformation.
Definition affine.h:70
Bezier curve with compile-time specified order.
Two-dimensional Bezier curve of arbitrary order.
Point controlPoint(unsigned ix) const
Access control points of the curve.
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
Elliptical arc curve.
Axis aligned, non-empty, generic rectangle.
bool intersects(GenericRect< C > const &r) const
Check whether the rectangles have any common points.
C top() const
Return top coordinate of the rectangle (+Y is downwards).
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
void expandTo(CPoint const &p)
Enlarge the rectangle to contain the given point.
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
CPoint corner(unsigned i) const
Return the n-th corner of the rectangle.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Store paths to a PathVector.
Definition path-sink.h:226
Sequence of subpaths.
Definition pathvector.h:122
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
Sequence of contiguous curves, aka spline.
Definition path.h:353
bool closed() const
Check whether the path is closed.
Definition path.h:503
bool empty() const
Check whether path is empty.
Definition path.h:500
const_iterator end_open() const
Definition path.h:467
Point initialPoint() const
Get the first point in the path.
Definition path.h:705
const_iterator begin() const
Definition path.h:464
Two-dimensional point that doubles as a vector.
Definition point.h:66
Coord length() const
Compute the distance from origin.
Definition point.h:118
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
Definition color.cpp:117
Class to hold image data for raster images.
Definition cairo-utils.h:31
guchar const * getMimeData(gsize &len, std::string &mimetype) const
Retrieves the original compressed data for the surface, if any.
Pixbuf(cairo_surface_t *s)
Create a pixbuf from a Cairo surface.
static GdkPixbuf * apply_embedded_orientation(GdkPixbuf *buf)
Pixbuf * cropTo(const Geom::IntRect &area) const
Create a new Pixbuf with the image cropped to the given area.
std::string _path
Definition cairo-utils.h:87
PixelFormat _pixel_format
Definition cairo-utils.h:88
int rowstride() const
guchar const * pixels() const
Cairo::RefPtr< Cairo::Surface > getSurface()
GdkPixbuf * getPixbufRaw(bool convert_format=true)
Converts the pixbuf to GdkPixbuf pixel format.
int width() const
GdkPixbuf * getPixbufRaw() const
void ensurePixelFormat(PixelFormat fmt)
Convert the internal pixel format between CAIRO and GDK formats.
cairo_surface_t * getSurfaceRaw()
Converts the pixbuf to Cairo pixel format and returns an image surface which can be used as a source.
static Pixbuf * create_from_file(std::string const &fn, double svgddpi=0)
static void ensure_argb32(GdkPixbuf *pb)
Converts GdkPixbuf's data to premultiplied ARGB.
static Geom::Affine get_embedded_orientation(GdkPixbuf *buf)
Gets any available orientation data and returns it as an affine.
static void ensure_pixbuf(GdkPixbuf *pb)
Converts GdkPixbuf's data back to its native format.
void _setMimeData(guchar *data, gsize len, Glib::ustring const &format)
cairo_surface_t * _surface
Definition cairo-utils.h:85
static Pixbuf * create_from_buffer(std::string const &, double svgddpi=0, std::string const &fn="")
int height() const
static Pixbuf * create_from_data_uri(gchar const *uri, double svgdpi=0)
GdkPixbuf * _pixbuf
Definition cairo-utils.h:84
Preference storage class.
Definition preferences.h:66
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
static Preferences * get()
Access the singleton Preferences object.
double value(Unit const *u) const
Return the quantity's value in the specified unit.
Definition units.cpp:565
static std::unique_ptr< SPDocument > createNewDocFromMem(std::span< char const > buffer, bool keepalive, std::string const &filename="")
Definition document.cpp:703
constexpr double SP_RGBA32_G_F(uint32_t v)
Definition utils.h:47
constexpr double SP_RGBA32_R_F(uint32_t v)
Definition utils.h:43
constexpr double SP_RGBA32_A_F(uint32_t v)
Definition utils.h:55
constexpr uint32_t SP_RGBA32_F_COMPOSE(double r, double g, double b, double a)
Definition utils.h:64
constexpr double SP_RGBA32_B_F(uint32_t v)
Definition utils.h:51
const double w
Definition conic-4.cpp:19
vector< vpsc::Rectangle * > rs
Css & result
Include all curve types.
Geom::Point corners[8]
double c[8][4]
const unsigned order
unsigned int guint32
_cairo_pattern cairo_pattern_t
struct _cairo_surface cairo_surface_t
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
std::unique_ptr< Magick::Image > image
void shift(T &a, T &b, T const &c)
double offset
Geom::Point end
Path cubicbezierpath_from_sbasis(D2< SBasis > const &B, double tol)
Affine identity()
Create an identity matrix.
Definition affine.h:210
Color make_contrasted_color(Color const &orig, double amount)
Make a darker or lighter version of the color, useful for making checkerboards.
Definition utils.cpp:134
Helper class to stream background task notifications as a series of messages.
static bool _workaround_issue_70__gdk_pixbuf_loader_write(GdkPixbufLoader *loader, guchar *decoded, gsize decoded_len, GError **error)
Incremental file read introduced to workaround https://gitlab.gnome.org/GNOME/gdk-pixbuf/issues/70.
void cairo_rectangle(cairo_t *cr, Geom::Rect const &r)
void cairo_line_to(cairo_t *cr, Geom::Point p1)
struct _cairo cairo_t
Definition path-cairo.h:16
void cairo_move_to(cairo_t *cr, Geom::Point p1)
void cairo_curve_to(cairo_t *cr, Geom::Point p1, Geom::Point p2, Geom::Point p3)
callback interface for SVG path data
PathVector - a sequence of subpaths.
Inkscape::Pixbuf * sp_generate_internal_bitmap(SPDocument *document, Geom::Rect const &area, double dpi, std::vector< SPItem const * > items, bool opaque, uint32_t const *checkerboard_color, double device_scale, std::optional< Antialiasing > antialias)
Generates a bitmap from given items.
int stride
int size
int buf
Singleton class to access the preferences file in a convenient way.
auto len
Definition safe-printf.h:21
int const char * fmt
Definition safe-printf.h:18
Conversion between SBasis and Bezier basis polynomials.
Run code on scope exit.
size_t N
static const Point data[]
SPBlendMode
@ SP_CSS_BLEND_LUMINOSITY
@ SP_CSS_BLEND_DARKEN
@ SP_CSS_BLEND_LIGHTEN
@ SP_CSS_BLEND_DIFFERENCE
@ SP_CSS_BLEND_COLORBURN
@ SP_CSS_BLEND_HARDLIGHT
@ SP_CSS_BLEND_EXCLUSION
@ SP_CSS_BLEND_COLORDODGE
@ SP_CSS_BLEND_SOFTLIGHT
@ SP_CSS_BLEND_SATURATION
@ SP_CSS_BLEND_SCREEN
@ SP_CSS_BLEND_OVERLAY
@ SP_CSS_BLEND_NORMAL
@ SP_CSS_BLEND_HUE
@ SP_CSS_BLEND_COLOR
@ SP_CSS_BLEND_MULTIPLY
SPColorInterpolation
@ SP_CSS_COLOR_INTERPOLATION_SRGB
@ SP_CSS_COLOR_INTERPOLATION_AUTO
@ SP_CSS_COLOR_INTERPOLATION_LINEARRGB
double height
double width
void cairo_set_source_rgba(cairo_t *cr, colour c)
Affine transformation classes.
std::tuple< char const *, Base64Data > extract_uri_data(char const *uri_data)
Attempt to extract the data in a data uri, but does not decode the base64.
Definition uri.cpp:117
URI functions as per 4.3.4 of CSS 2.1 http://www.w3.org/TR/CSS21/syndata.html#uri.
Cairo::RectangleInt geom_to_cairo(const Geom::IntRect &rect)
Definition util.cpp:353