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 <stdexcept>
15 
16 #include <glib/gstdio.h>
17 #include <glibmm/fileutils.h>
18 #include <gdk-pixbuf/gdk-pixbuf.h>
19 
20 #include <2geom/pathvector.h>
21 #include <2geom/curves.h>
22 #include <2geom/affine.h>
23 #include <2geom/point.h>
24 #include <2geom/path.h>
25 #include <2geom/transforms.h>
26 #include <2geom/sbasis-to-bezier.h>
27 
28 #include <boost/algorithm/string.hpp>
29 #include <boost/operators.hpp>
30 #include <boost/optional/optional.hpp>
31 
32 #include "color.h"
33 #include "cairo-templates.h"
34 #include "document.h"
35 #include "preferences.h"
36 #include "util/units.h"
37 #include "helper/pixbuf-ops.h"
38 
39 
40 /**
41  * Key for cairo_surface_t to keep track of current color interpolation value
42  * Only the address of the structure is used, it is never initialized. See:
43  * http://www.cairographics.org/manual/cairo-Types.html#cairo-user-data-key-t
44  */
45 cairo_user_data_key_t ink_color_interpolation_key;
46 cairo_user_data_key_t ink_pixbuf_key;
47 
48 namespace Inkscape {
49 
CairoGroup(cairo_t * _ct)50 CairoGroup::CairoGroup(cairo_t *_ct) : ct(_ct), pushed(false) {}
~CairoGroup()51 CairoGroup::~CairoGroup() {
52     if (pushed) {
53         cairo_pattern_t *p = cairo_pop_group(ct);
54         cairo_pattern_destroy(p);
55     }
56 }
push()57 void CairoGroup::push() {
58     cairo_push_group(ct);
59     pushed = true;
60 }
push_with_content(cairo_content_t content)61 void CairoGroup::push_with_content(cairo_content_t content) {
62     cairo_push_group_with_content(ct, content);
63     pushed = true;
64 }
pop()65 cairo_pattern_t *CairoGroup::pop() {
66     if (pushed) {
67         cairo_pattern_t *ret = cairo_pop_group(ct);
68         pushed = false;
69         return ret;
70     } else {
71         throw std::logic_error("Cairo group popped without pushing it first");
72     }
73 }
popmm()74 Cairo::RefPtr<Cairo::Pattern> CairoGroup::popmm() {
75     if (pushed) {
76         cairo_pattern_t *ret = cairo_pop_group(ct);
77         Cairo::RefPtr<Cairo::Pattern> retmm(new Cairo::Pattern(ret, true));
78         pushed = false;
79         return retmm;
80     } else {
81         throw std::logic_error("Cairo group popped without pushing it first");
82     }
83 }
pop_to_source()84 void CairoGroup::pop_to_source() {
85     if (pushed) {
86         cairo_pop_group_to_source(ct);
87         pushed = false;
88     }
89 }
90 
CairoContext(cairo_t * obj,bool ref)91 CairoContext::CairoContext(cairo_t *obj, bool ref)
92     : Cairo::Context(obj, ref)
93 {}
94 
transform(Geom::Affine const & m)95 void CairoContext::transform(Geom::Affine const &m)
96 {
97     cairo_matrix_t cm;
98     cm.xx = m[0];
99     cm.xy = m[2];
100     cm.x0 = m[4];
101     cm.yx = m[1];
102     cm.yy = m[3];
103     cm.y0 = m[5];
104     cairo_transform(cobj(), &cm);
105 }
106 
set_source_rgba32(guint32 color)107 void CairoContext::set_source_rgba32(guint32 color)
108 {
109     double red = SP_RGBA32_R_F(color);
110     double gre = SP_RGBA32_G_F(color);
111     double blu = SP_RGBA32_B_F(color);
112     double alp = SP_RGBA32_A_F(color);
113     cairo_set_source_rgba(cobj(), red, gre, blu, alp);
114 }
115 
append_path(Geom::PathVector const & pv)116 void CairoContext::append_path(Geom::PathVector const &pv)
117 {
118     feed_pathvector_to_cairo(cobj(), pv);
119 }
120 
create(Cairo::RefPtr<Cairo::Surface> const & target)121 Cairo::RefPtr<CairoContext> CairoContext::create(Cairo::RefPtr<Cairo::Surface> const &target)
122 {
123     cairo_t *ct = cairo_create(target->cobj());
124     Cairo::RefPtr<CairoContext> ret(new CairoContext(ct, true));
125     return ret;
126 }
127 
128 
129 /* The class below implement the following hack:
130  *
131  * The pixels formats of Cairo and GdkPixbuf are different.
132  * GdkPixbuf accesses pixels as bytes, alpha is not premultiplied,
133  * and successive bytes of a single pixel contain R, G, B and A components.
134  * Cairo accesses pixels as 32-bit ints, alpha is premultiplied,
135  * and each int contains as 0xAARRGGBB, accessed with bitwise operations.
136  *
137  * In other words, on a little endian system, a GdkPixbuf will contain:
138  *   char *data = "rgbargbargba...."
139  *   int *data = { 0xAABBGGRR, 0xAABBGGRR, 0xAABBGGRR, ... }
140  * while a Cairo image surface will contain:
141  *   char *data = "bgrabgrabgra...."
142  *   int *data = { 0xAARRGGBB, 0xAARRGGBB, 0xAARRGGBB, ... }
143  *
144  * It is possible to convert between these two formats (almost) losslessly.
145  * Some color information from partially transparent regions of the image
146  * is lost, but the result when displaying this image will remain the same.
147  *
148  * The class allows interoperation between GdkPixbuf
149  * and Cairo surfaces without creating a copy of the image.
150  * This is implemented by creating a GdkPixbuf and a Cairo image surface
151  * which share their data. Depending on what is needed at a given time,
152  * the pixels are converted in place to the Cairo or the GdkPixbuf format.
153  */
154 
155 /** Create a pixbuf from a Cairo surface.
156  * The constructor takes ownership of the passed surface,
157  * so it should not be destroyed. */
Pixbuf(cairo_surface_t * s)158 Pixbuf::Pixbuf(cairo_surface_t *s)
159     : _pixbuf(gdk_pixbuf_new_from_data(
160         cairo_image_surface_get_data(s), GDK_COLORSPACE_RGB, TRUE, 8,
161         cairo_image_surface_get_width(s), cairo_image_surface_get_height(s),
162         cairo_image_surface_get_stride(s),
163         ink_cairo_pixbuf_cleanup, s))
164     , _surface(s)
165     , _mod_time(0)
166     , _pixel_format(PF_CAIRO)
167     , _cairo_store(true)
168 {}
169 
170 /** Create a pixbuf from a GdkPixbuf.
171  * The constructor takes ownership of the passed GdkPixbuf reference,
172  * so it should not be unrefed. */
Pixbuf(GdkPixbuf * pb)173 Pixbuf::Pixbuf(GdkPixbuf *pb)
174     : _pixbuf(pb)
175     , _surface(nullptr)
176     , _mod_time(0)
177     , _pixel_format(PF_GDK)
178     , _cairo_store(false)
179 {
180     _forceAlpha();
181     _surface = cairo_image_surface_create_for_data(
182         gdk_pixbuf_get_pixels(_pixbuf), CAIRO_FORMAT_ARGB32,
183         gdk_pixbuf_get_width(_pixbuf), gdk_pixbuf_get_height(_pixbuf), gdk_pixbuf_get_rowstride(_pixbuf));
184 }
185 
Pixbuf(Inkscape::Pixbuf const & other)186 Pixbuf::Pixbuf(Inkscape::Pixbuf const &other)
187     : _pixbuf(gdk_pixbuf_copy(other._pixbuf))
188     , _surface(cairo_image_surface_create_for_data(
189         gdk_pixbuf_get_pixels(_pixbuf), CAIRO_FORMAT_ARGB32,
190         gdk_pixbuf_get_width(_pixbuf), gdk_pixbuf_get_height(_pixbuf), gdk_pixbuf_get_rowstride(_pixbuf)))
191     , _mod_time(other._mod_time)
192     , _path(other._path)
193     , _pixel_format(other._pixel_format)
194     , _cairo_store(false)
195 {}
196 
~Pixbuf()197 Pixbuf::~Pixbuf()
198 {
199     if (_cairo_store) {
200         g_object_unref(_pixbuf);
201     } else {
202         cairo_surface_destroy(_surface);
203         g_object_unref(_pixbuf);
204     }
205 }
206 
207 #if !GDK_PIXBUF_CHECK_VERSION(2, 41, 0)
208 /**
209  * Incremental file read introduced to workaround
210  * https://gitlab.gnome.org/GNOME/gdk-pixbuf/issues/70
211  */
_workaround_issue_70__gdk_pixbuf_loader_write(GdkPixbufLoader * loader,guchar * decoded,gsize decoded_len,GError ** error)212 static bool _workaround_issue_70__gdk_pixbuf_loader_write( //
213     GdkPixbufLoader *loader, guchar *decoded, gsize decoded_len, GError **error)
214 {
215     bool success = true;
216     gsize bytes_left = decoded_len;
217     gsize secret_limit = 0xffff;
218     guchar *decoded_head = decoded;
219     while (bytes_left && success) {
220         gsize bytes = (bytes_left > secret_limit) ? secret_limit : bytes_left;
221         success = gdk_pixbuf_loader_write(loader, decoded_head, bytes, error);
222         decoded_head += bytes;
223         bytes_left -= bytes;
224     }
225 
226     return success;
227 }
228 #define gdk_pixbuf_loader_write _workaround_issue_70__gdk_pixbuf_loader_write
229 #endif
230 
create_from_data_uri(gchar const * uri_data,double svgdpi)231 Pixbuf *Pixbuf::create_from_data_uri(gchar const *uri_data, double svgdpi)
232 {
233     Pixbuf *pixbuf = nullptr;
234 
235     bool data_is_image = false;
236     bool data_is_svg = false;
237     bool data_is_base64 = false;
238 
239     gchar const *data = uri_data;
240 
241     while (*data) {
242         if (strncmp(data,"base64",6) == 0) {
243             /* base64-encoding */
244             data_is_base64 = true;
245             data_is_image = true; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
246             data += 6;
247         }
248         else if (strncmp(data,"image/png",9) == 0) {
249             /* PNG image */
250             data_is_image = true;
251             data += 9;
252         }
253         else if (strncmp(data,"image/jpg",9) == 0) {
254             /* JPEG image */
255             data_is_image = true;
256             data += 9;
257         }
258         else if (strncmp(data,"image/jpeg",10) == 0) {
259             /* JPEG image */
260             data_is_image = true;
261             data += 10;
262         }
263         else if (strncmp(data,"image/jp2",9) == 0) {
264             /* JPEG2000 image */
265             data_is_image = true;
266             data += 9;
267         }
268         else if (strncmp(data,"image/svg+xml",13) == 0) {
269             /* JPEG2000 image */
270             data_is_svg = true;
271             data_is_image = true;
272             data += 13;
273         }
274         else { /* unrecognized option; skip it */
275             while (*data) {
276                 if (((*data) == ';') || ((*data) == ',')) {
277                     break;
278                 }
279                 data++;
280             }
281         }
282         if ((*data) == ';') {
283             data++;
284             continue;
285         }
286         if ((*data) == ',') {
287             data++;
288             break;
289         }
290     }
291 
292     if ((*data) && data_is_image && !data_is_svg && data_is_base64) {
293         GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
294 
295         if (!loader) return nullptr;
296 
297         gsize decoded_len = 0;
298         guchar *decoded = g_base64_decode(data, &decoded_len);
299 
300         if (gdk_pixbuf_loader_write(loader, decoded, decoded_len, nullptr)) {
301             gdk_pixbuf_loader_close(loader, nullptr);
302             GdkPixbuf *buf = gdk_pixbuf_loader_get_pixbuf(loader);
303             if (buf) {
304                 g_object_ref(buf);
305                 pixbuf = new Pixbuf(buf);
306 
307                 GdkPixbufFormat *fmt = gdk_pixbuf_loader_get_format(loader);
308                 gchar *fmt_name = gdk_pixbuf_format_get_name(fmt);
309                 pixbuf->_setMimeData(decoded, decoded_len, fmt_name);
310                 g_free(fmt_name);
311             } else {
312                 g_free(decoded);
313             }
314         } else {
315             g_free(decoded);
316         }
317         g_object_unref(loader);
318     }
319 
320     if ((*data) && data_is_image && data_is_svg && data_is_base64) {
321         gsize decoded_len = 0;
322         guchar *decoded = g_base64_decode(data, &decoded_len);
323         std::unique_ptr<SPDocument> svgDoc(
324             SPDocument::createNewDocFromMem(reinterpret_cast<gchar const *>(decoded), decoded_len, false));
325         // Check the document loaded properly
326         if (svgDoc == nullptr) {
327             return nullptr;
328         }
329         if (svgDoc->getRoot() == nullptr)
330         {
331             return nullptr;
332         }
333         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
334         double dpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", 96.0);
335         if (svgdpi && svgdpi > 0) {
336             dpi = svgdpi;
337         }
338         std::cout << dpi << "dpi" << std::endl;
339         // Get the size of the document
340         Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
341         Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
342         const double svgWidth_px = svgWidth.value("px");
343         const double svgHeight_px = svgHeight.value("px");
344         if (svgWidth_px < 0 || svgHeight_px < 0) {
345             g_warning("create_from_data_uri: malformed document: svgWidth_px=%f, svgHeight_px=%f", svgWidth_px,
346                       svgHeight_px);
347             return nullptr;
348         }
349 
350         // Now get the resized values
351         const int scaledSvgWidth  = round(svgWidth_px/(96.0/dpi));
352         const int scaledSvgHeight = round(svgHeight_px/(96.0/dpi));
353 
354         assert(!pixbuf);
355         pixbuf = sp_generate_internal_bitmap(svgDoc.get(), nullptr, 0, 0, svgWidth_px, svgHeight_px, scaledSvgWidth,
356                                              scaledSvgHeight, dpi, dpi, 0xffffff00, nullptr);
357         GdkPixbuf const *buf = pixbuf->getPixbufRaw();
358 
359         // Tidy up
360         if (buf == nullptr) {
361             std::cerr << "Pixbuf::create_from_data: failed to load contents: " << std::endl;
362             delete pixbuf;
363             g_free(decoded);
364             return nullptr;
365         } else {
366             pixbuf->_setMimeData(decoded, decoded_len, "svg+xml");
367         }
368     }
369 
370     return pixbuf;
371 }
372 
create_from_file(std::string const & fn,double svgdpi)373 Pixbuf *Pixbuf::create_from_file(std::string const &fn, double svgdpi)
374 {
375     Pixbuf *pb = nullptr;
376     // test correctness of filename
377     if (!g_file_test(fn.c_str(), G_FILE_TEST_EXISTS)) {
378         return nullptr;
379     }
380     GStatBuf stdir;
381     int val = g_stat(fn.c_str(), &stdir);
382     if (val == 0 && stdir.st_mode & S_IFDIR){
383         return nullptr;
384     }
385     // we need to load the entire file into memory,
386     // since we'll store it as MIME data
387     gchar *data = nullptr;
388     gsize len = 0;
389     GError *error = nullptr;
390 
391     if (g_file_get_contents(fn.c_str(), &data, &len, &error)) {
392 
393         if (error != nullptr) {
394             std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
395             std::cerr << "   (" << fn << ")" << std::endl;
396             return nullptr;
397         }
398 
399         pb = Pixbuf::create_from_buffer(std::move(data), len, svgdpi, fn);
400 
401         if (pb) {
402             pb->_mod_time = stdir.st_mtime;
403         }
404     } else {
405         std::cerr << "Pixbuf::create_from_file: failed to get contents: " << fn << std::endl;
406         return nullptr;
407     }
408 
409     return pb;
410 }
411 
create_from_buffer(std::string const & buffer,double svgdpi,std::string const & fn)412 Pixbuf *Pixbuf::create_from_buffer(std::string const &buffer, double svgdpi, std::string const &fn)
413 {
414     auto datacopy = (gchar *)g_memdup(buffer.data(), buffer.size());
415     return Pixbuf::create_from_buffer(std::move(datacopy), buffer.size(), svgdpi, fn);
416 }
417 
create_from_buffer(gchar * && data,gsize len,double svgdpi,std::string const & fn)418 Pixbuf *Pixbuf::create_from_buffer(gchar *&&data, gsize len, double svgdpi, std::string const &fn)
419 {
420     Pixbuf *pb = nullptr;
421     GError *error = nullptr;
422     {
423         GdkPixbuf *buf = nullptr;
424         GdkPixbufLoader *loader = nullptr;
425         std::string::size_type idx;
426         idx = fn.rfind('.');
427         bool is_svg = false;
428         if(idx != std::string::npos)
429         {
430             if (boost::iequals(fn.substr(idx+1).c_str(), "svg")) {
431 
432                 std::unique_ptr<SPDocument> svgDoc(SPDocument::createNewDocFromMem(data, len, true));
433 
434                 // Check the document loaded properly
435                 if (svgDoc == nullptr) {
436                     return nullptr;
437                 }
438                 if (svgDoc->getRoot() == nullptr)
439                 {
440                     return nullptr;
441                 }
442 
443                 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
444                 double dpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", 96.0);
445                 if (svgdpi && svgdpi > 0) {
446                     dpi = svgdpi;
447                 }
448 
449                 // Get the size of the document
450                 Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
451                 Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
452                 const double svgWidth_px = svgWidth.value("px");
453                 const double svgHeight_px = svgHeight.value("px");
454                 if (svgWidth_px < 0 || svgHeight_px < 0) {
455                     g_warning("create_from_buffer: malformed document: svgWidth_px=%f, svgHeight_px=%f", svgWidth_px,
456                               svgHeight_px);
457                     return nullptr;
458                 }
459 
460                 // Now get the resized values
461                 const int scaledSvgWidth  = round(svgWidth_px/(96.0/dpi));
462                 const int scaledSvgHeight = round(svgHeight_px/(96.0/dpi));
463 
464                 pb = sp_generate_internal_bitmap(svgDoc.get(), nullptr, 0, 0, svgWidth_px, svgHeight_px, scaledSvgWidth,
465                                                  scaledSvgHeight, dpi, dpi, 0xffffff00);
466                 buf = pb->getPixbufRaw();
467 
468                 // Tidy up
469                 if (buf == nullptr) {
470                     delete pb;
471                     return nullptr;
472                 }
473                 is_svg = true;
474             }
475         }
476         if (!is_svg) {
477             loader = gdk_pixbuf_loader_new();
478             gdk_pixbuf_loader_write(loader, (guchar *) data, len, &error);
479             if (error != nullptr) {
480                 std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
481                 std::cerr << "   (" << fn << ")" << std::endl;
482                 g_free(data);
483                 g_object_unref(loader);
484                 return nullptr;
485             }
486 
487             gdk_pixbuf_loader_close(loader, &error);
488             if (error != nullptr) {
489                 std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
490                 std::cerr << "   (" << fn << ")" << std::endl;
491                 g_free(data);
492                 g_object_unref(loader);
493                 return nullptr;
494             }
495 
496             buf = gdk_pixbuf_loader_get_pixbuf(loader);
497             if (buf) {
498                 // gdk_pixbuf_loader_get_pixbuf returns a borrowed reference
499                 g_object_ref(buf);
500                 pb = new Pixbuf(buf);
501             }
502         }
503 
504         if (pb) {
505             pb->_path = fn;
506             if (!is_svg) {
507                 GdkPixbufFormat *fmt = gdk_pixbuf_loader_get_format(loader);
508                 gchar *fmt_name = gdk_pixbuf_format_get_name(fmt);
509                 pb->_setMimeData((guchar *) data, len, fmt_name);
510                 g_free(fmt_name);
511                 g_object_unref(loader);
512             } else {
513                 pb->_setMimeData((guchar *) data, len, "svg");
514             }
515         } else {
516             std::cerr << "Pixbuf::create_from_file: failed to load contents: " << fn << std::endl;
517             g_free(data);
518         }
519 
520         // TODO: we could also read DPI, ICC profile, gamma correction, and other information
521         // from the file. This can be done by using format-specific libraries e.g. libpng.
522     }
523 
524     return pb;
525 }
526 
527 /**
528  * Converts the pixbuf to GdkPixbuf pixel format.
529  * The returned pixbuf can be used e.g. in calls to gdk_pixbuf_save().
530  */
getPixbufRaw(bool convert_format)531 GdkPixbuf *Pixbuf::getPixbufRaw(bool convert_format)
532 {
533     if (convert_format) {
534         ensurePixelFormat(PF_GDK);
535     }
536     return _pixbuf;
537 }
538 
539 /**
540  * Converts the pixbuf to Cairo pixel format and returns an image surface
541  * which can be used as a source.
542  *
543  * The returned surface is owned by the GdkPixbuf and should not be freed.
544  * Calling this function causes the pixbuf to be unsuitable for use
545  * with GTK drawing functions until ensurePixelFormat(Pixbuf::PIXEL_FORMAT_PIXBUF) is called.
546  */
getSurfaceRaw(bool convert_format)547 cairo_surface_t *Pixbuf::getSurfaceRaw(bool convert_format)
548 {
549     if (convert_format) {
550         ensurePixelFormat(PF_CAIRO);
551     }
552     return _surface;
553 }
554 
555 /* Declaring this function in the header requires including <gdkmm/pixbuf.h>,
556  * which stupidly includes <glibmm.h> which in turn pulls in <glibmm/threads.h>.
557  * However, since glib 2.32, <glibmm/threads.h> has to be included before <glib.h>
558  * when compiling with G_DISABLE_DEPRECATED, as we do in non-release builds.
559  * This necessitates spamming a lot of files with #include <glibmm/threads.h>
560  * at the top.
561  *
562  * Since we don't really use gdkmm, do not define this function for now. */
563 
564 /*
565 Glib::RefPtr<Gdk::Pixbuf> Pixbuf::getPixbuf(bool convert_format = true)
566 {
567     g_object_ref(_pixbuf);
568     Glib::RefPtr<Gdk::Pixbuf> p(getPixbuf(convert_format));
569     return p;
570 }
571 */
572 
getSurface(bool convert_format)573 Cairo::RefPtr<Cairo::Surface> Pixbuf::getSurface(bool convert_format)
574 {
575     Cairo::RefPtr<Cairo::Surface> p(new Cairo::Surface(getSurfaceRaw(convert_format), false));
576     return p;
577 }
578 
579 /** Retrieves the original compressed data for the surface, if any.
580  * The returned data belongs to the object and should not be freed. */
getMimeData(gsize & len,std::string & mimetype) const581 guchar const *Pixbuf::getMimeData(gsize &len, std::string &mimetype) const
582 {
583     static gchar const *mimetypes[] = {
584         CAIRO_MIME_TYPE_JPEG, CAIRO_MIME_TYPE_JP2, CAIRO_MIME_TYPE_PNG, nullptr };
585     static guint mimetypes_len = g_strv_length(const_cast<gchar**>(mimetypes));
586 
587     guchar const *data = nullptr;
588 
589     for (guint i = 0; i < mimetypes_len; ++i) {
590         unsigned long len_long = 0;
591         cairo_surface_get_mime_data(const_cast<cairo_surface_t*>(_surface), mimetypes[i], &data, &len_long);
592         if (data != nullptr) {
593 			len = len_long;
594             mimetype = mimetypes[i];
595             break;
596         }
597     }
598 
599     return data;
600 }
601 
width() const602 int Pixbuf::width() const {
603     return gdk_pixbuf_get_width(const_cast<GdkPixbuf*>(_pixbuf));
604 }
height() const605 int Pixbuf::height() const {
606     return gdk_pixbuf_get_height(const_cast<GdkPixbuf*>(_pixbuf));
607 }
rowstride() const608 int Pixbuf::rowstride() const {
609     return gdk_pixbuf_get_rowstride(const_cast<GdkPixbuf*>(_pixbuf));
610 }
pixels() const611 guchar const *Pixbuf::pixels() const {
612     return gdk_pixbuf_get_pixels(const_cast<GdkPixbuf*>(_pixbuf));
613 }
pixels()614 guchar *Pixbuf::pixels() {
615     return gdk_pixbuf_get_pixels(_pixbuf);
616 }
markDirty()617 void Pixbuf::markDirty() {
618     cairo_surface_mark_dirty(_surface);
619 }
620 
_forceAlpha()621 void Pixbuf::_forceAlpha()
622 {
623     if (gdk_pixbuf_get_has_alpha(_pixbuf)) return;
624 
625     GdkPixbuf *old = _pixbuf;
626     _pixbuf = gdk_pixbuf_add_alpha(old, FALSE, 0, 0, 0);
627     g_object_unref(old);
628 }
629 
_setMimeData(guchar * data,gsize len,Glib::ustring const & format)630 void Pixbuf::_setMimeData(guchar *data, gsize len, Glib::ustring const &format)
631 {
632     gchar const *mimetype = nullptr;
633 
634     if (format == "jpeg") {
635         mimetype = CAIRO_MIME_TYPE_JPEG;
636     } else if (format == "jpeg2000") {
637         mimetype = CAIRO_MIME_TYPE_JP2;
638     } else if (format == "png") {
639         mimetype = CAIRO_MIME_TYPE_PNG;
640     }
641 
642     if (mimetype != nullptr) {
643         cairo_surface_set_mime_data(_surface, mimetype, data, len, g_free, data);
644         //g_message("Setting Cairo MIME data: %s", mimetype);
645     } else {
646         g_free(data);
647         //g_message("Not setting Cairo MIME data: unknown format %s", name.c_str());
648     }
649 }
650 
ensurePixelFormat(PixelFormat fmt)651 void Pixbuf::ensurePixelFormat(PixelFormat fmt)
652 {
653     if (_pixel_format == PF_GDK) {
654         if (fmt == PF_GDK) {
655             return;
656         }
657         if (fmt == PF_CAIRO) {
658             convert_pixels_pixbuf_to_argb32(
659                 gdk_pixbuf_get_pixels(_pixbuf),
660                 gdk_pixbuf_get_width(_pixbuf),
661                 gdk_pixbuf_get_height(_pixbuf),
662                 gdk_pixbuf_get_rowstride(_pixbuf));
663             _pixel_format = fmt;
664             return;
665         }
666         g_assert_not_reached();
667     }
668     if (_pixel_format == PF_CAIRO) {
669         if (fmt == PF_GDK) {
670             convert_pixels_argb32_to_pixbuf(
671                 gdk_pixbuf_get_pixels(_pixbuf),
672                 gdk_pixbuf_get_width(_pixbuf),
673                 gdk_pixbuf_get_height(_pixbuf),
674                 gdk_pixbuf_get_rowstride(_pixbuf));
675             _pixel_format = fmt;
676             return;
677         }
678         if (fmt == PF_CAIRO) {
679             return;
680         }
681         g_assert_not_reached();
682     }
683     g_assert_not_reached();
684 }
685 
686 } // namespace Inkscape
687 
688 /*
689  * Can be called recursively.
690  * If optimize_stroke == false, the view Rect is not used.
691  */
692 static void
feed_curve_to_cairo(cairo_t * cr,Geom::Curve const & c,Geom::Affine const & trans,Geom::Rect view,bool optimize_stroke)693 feed_curve_to_cairo(cairo_t *cr, Geom::Curve const &c, Geom::Affine const & trans, Geom::Rect view, bool optimize_stroke)
694 {
695     using Geom::X;
696     using Geom::Y;
697 
698     unsigned order = 0;
699     if (Geom::BezierCurve const* b = dynamic_cast<Geom::BezierCurve const*>(&c)) {
700         order = b->order();
701     }
702 
703     // handle the three typical curve cases
704     switch (order) {
705     case 1:
706     {
707         Geom::Point end_tr = c.finalPoint() * trans;
708         if (!optimize_stroke) {
709             cairo_line_to(cr, end_tr[0], end_tr[1]);
710         } else {
711             Geom::Rect swept(c.initialPoint()*trans, end_tr);
712             if (swept.intersects(view)) {
713                 cairo_line_to(cr, end_tr[0], end_tr[1]);
714             } else {
715                 cairo_move_to(cr, end_tr[0], end_tr[1]);
716             }
717         }
718     }
719     break;
720     case 2:
721     {
722         Geom::QuadraticBezier const *quadratic_bezier = static_cast<Geom::QuadraticBezier const*>(&c);
723         std::vector<Geom::Point> points = quadratic_bezier->controlPoints();
724         points[0] *= trans;
725         points[1] *= trans;
726         points[2] *= trans;
727         // degree-elevate to cubic Bezier, since Cairo doesn't do quadratic Beziers
728         Geom::Point b1 = points[0] + (2./3) * (points[1] - points[0]);
729         Geom::Point b2 = b1 + (1./3) * (points[2] - points[0]);
730         if (!optimize_stroke) {
731             cairo_curve_to(cr, b1[X], b1[Y], b2[X], b2[Y], points[2][X], points[2][Y]);
732         } else {
733             Geom::Rect swept(points[0], points[2]);
734             swept.expandTo(points[1]);
735             if (swept.intersects(view)) {
736                 cairo_curve_to(cr, b1[X], b1[Y], b2[X], b2[Y], points[2][X], points[2][Y]);
737             } else {
738                 cairo_move_to(cr, points[2][X], points[2][Y]);
739             }
740         }
741     }
742     break;
743     case 3:
744     {
745         Geom::CubicBezier const *cubic_bezier = static_cast<Geom::CubicBezier const*>(&c);
746         std::vector<Geom::Point> points = cubic_bezier->controlPoints();
747         //points[0] *= trans; // don't do this one here for fun: it is only needed for optimized strokes
748         points[1] *= trans;
749         points[2] *= trans;
750         points[3] *= trans;
751         if (!optimize_stroke) {
752             cairo_curve_to(cr, points[1][X], points[1][Y], points[2][X], points[2][Y], points[3][X], points[3][Y]);
753         } else {
754             points[0] *= trans;  // didn't transform this point yet
755             Geom::Rect swept(points[0], points[3]);
756             swept.expandTo(points[1]);
757             swept.expandTo(points[2]);
758             if (swept.intersects(view)) {
759                 cairo_curve_to(cr, points[1][X], points[1][Y], points[2][X], points[2][Y], points[3][X], points[3][Y]);
760             } else {
761                 cairo_move_to(cr, points[3][X], points[3][Y]);
762             }
763         }
764     }
765     break;
766     default:
767     {
768         if (Geom::EllipticalArc const *arc = dynamic_cast<Geom::EllipticalArc const*>(&c)) {
769             if (arc->isChord()) {
770                 Geom::Point endPoint(arc->finalPoint());
771                 cairo_line_to(cr, endPoint[0], endPoint[1]);
772             } else {
773                 Geom::Affine xform = arc->unitCircleTransform() * trans;
774                 // Don't draw anything if the angle is borked
775                 if(isnan(arc->initialAngle()) || isnan(arc->finalAngle())) {
776                     g_warning("Bad angle while drawing EllipticalArc");
777                     break;
778                 }
779 
780                 // Apply the transformation to the current context
781                 cairo_matrix_t cm;
782                 cm.xx = xform[0];
783                 cm.xy = xform[2];
784                 cm.x0 = xform[4];
785                 cm.yx = xform[1];
786                 cm.yy = xform[3];
787                 cm.y0 = xform[5];
788 
789                 cairo_save(cr);
790                 cairo_transform(cr, &cm);
791 
792                 // Draw the circle
793                 if (arc->sweep()) {
794                     cairo_arc(cr, 0, 0, 1, arc->initialAngle(), arc->finalAngle());
795                 } else {
796                     cairo_arc_negative(cr, 0, 0, 1, arc->initialAngle(), arc->finalAngle());
797                 }
798                 // Revert the current context
799                 cairo_restore(cr);
800             }
801         } else {
802             // handles sbasis as well as all other curve types
803             // this is very slow
804             Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
805 
806             // recurse to convert the new path resulting from the sbasis to svgd
807             for (const auto & iter : sbasis_path) {
808                 feed_curve_to_cairo(cr, iter, trans, view, optimize_stroke);
809             }
810         }
811     }
812     break;
813     }
814 }
815 
816 
817 /** Feeds path-creating calls to the cairo context translating them from the Path */
818 static void
feed_path_to_cairo(cairo_t * ct,Geom::Path const & path)819 feed_path_to_cairo (cairo_t *ct, Geom::Path const &path)
820 {
821     if (path.empty())
822         return;
823 
824     cairo_move_to(ct, path.initialPoint()[0], path.initialPoint()[1] );
825 
826     for (Geom::Path::const_iterator cit = path.begin(); cit != path.end_open(); ++cit) {
827         feed_curve_to_cairo(ct, *cit, Geom::identity(), Geom::Rect(), false); // optimize_stroke is false, so the view rect is not used
828     }
829 
830     if (path.closed()) {
831         cairo_close_path(ct);
832     }
833 }
834 
835 /** Feeds path-creating calls to the cairo context translating them from the Path, with the given transform and shift */
836 static void
feed_path_to_cairo(cairo_t * ct,Geom::Path const & path,Geom::Affine trans,Geom::OptRect area,bool optimize_stroke,double stroke_width)837 feed_path_to_cairo (cairo_t *ct, Geom::Path const &path, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width)
838 {
839     if (!area)
840         return;
841     if (path.empty())
842         return;
843 
844     // Transform all coordinates to coords within "area"
845     Geom::Point shift = area->min();
846     Geom::Rect view = *area;
847     view.expandBy (stroke_width);
848     view = view * (Geom::Affine)Geom::Translate(-shift);
849     //  Pass transformation to feed_curve, so that we don't need to create a whole new path.
850     Geom::Affine transshift(trans * Geom::Translate(-shift));
851 
852     Geom::Point initial = path.initialPoint() * transshift;
853     cairo_move_to(ct, initial[0], initial[1] );
854 
855     for(Geom::Path::const_iterator cit = path.begin(); cit != path.end_open(); ++cit) {
856         feed_curve_to_cairo(ct, *cit, transshift, view, optimize_stroke);
857     }
858 
859     if (path.closed()) {
860         if (!optimize_stroke) {
861             cairo_close_path(ct);
862         } else {
863             cairo_line_to(ct, initial[0], initial[1]);
864             /* We cannot use cairo_close_path(ct) here because some parts of the path may have been
865                clipped and not drawn (maybe the before last segment was outside view area), which
866                would result in closing the "subpath" after the last interruption, not the entire path.
867 
868                However, according to cairo documentation:
869                The behavior of cairo_close_path() is distinct from simply calling cairo_line_to() with the equivalent coordinate
870                in the case of stroking. When a closed sub-path is stroked, there are no caps on the ends of the sub-path. Instead,
871                there is a line join connecting the final and initial segments of the sub-path.
872 
873                The correct fix will be possible when cairo introduces methods for moving without
874                ending/starting subpaths, which we will use for skipping invisible segments; then we
875                will be able to use cairo_close_path here. This issue also affects ps/eps/pdf export,
876                see bug 168129
877             */
878         }
879     }
880 }
881 
882 /** Feeds path-creating calls to the cairo context translating them from the PathVector, with the given transform and shift
883  *  One must have done cairo_new_path(ct); before calling this function. */
884 void
feed_pathvector_to_cairo(cairo_t * ct,Geom::PathVector const & pathv,Geom::Affine trans,Geom::OptRect area,bool optimize_stroke,double stroke_width)885 feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width)
886 {
887     if (!area)
888         return;
889     if (pathv.empty())
890         return;
891 
892     for(const auto & it : pathv) {
893         feed_path_to_cairo(ct, it, trans, area, optimize_stroke, stroke_width);
894     }
895 }
896 
897 /** Feeds path-creating calls to the cairo context translating them from the PathVector
898  *  One must have done cairo_new_path(ct); before calling this function. */
899 void
feed_pathvector_to_cairo(cairo_t * ct,Geom::PathVector const & pathv)900 feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv)
901 {
902     if (pathv.empty())
903         return;
904 
905     for(const auto & it : pathv) {
906         feed_path_to_cairo(ct, it);
907     }
908 }
909 
910 SPColorInterpolation
get_cairo_surface_ci(cairo_surface_t * surface)911 get_cairo_surface_ci(cairo_surface_t *surface) {
912     void* data = cairo_surface_get_user_data( surface, &ink_color_interpolation_key );
913     if( data != nullptr ) {
914         return (SPColorInterpolation)GPOINTER_TO_INT( data );
915     } else {
916         return SP_CSS_COLOR_INTERPOLATION_AUTO;
917     }
918 }
919 
920 /** Set the color_interpolation_value for a Cairo surface.
921  *  Transform the surface between sRGB and linearRGB if necessary. */
922 void
set_cairo_surface_ci(cairo_surface_t * surface,SPColorInterpolation ci)923 set_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation ci) {
924 
925     if( cairo_surface_get_content( surface ) != CAIRO_CONTENT_ALPHA ) {
926 
927         SPColorInterpolation ci_in = get_cairo_surface_ci( surface );
928 
929         if( ci_in == SP_CSS_COLOR_INTERPOLATION_SRGB &&
930             ci    == SP_CSS_COLOR_INTERPOLATION_LINEARRGB ) {
931             ink_cairo_surface_srgb_to_linear( surface );
932         }
933         if( ci_in == SP_CSS_COLOR_INTERPOLATION_LINEARRGB &&
934             ci    == SP_CSS_COLOR_INTERPOLATION_SRGB ) {
935             ink_cairo_surface_linear_to_srgb( surface );
936         }
937 
938         cairo_surface_set_user_data(surface, &ink_color_interpolation_key, GINT_TO_POINTER (ci), nullptr);
939     }
940 }
941 
942 void
copy_cairo_surface_ci(cairo_surface_t * in,cairo_surface_t * out)943 copy_cairo_surface_ci(cairo_surface_t *in, cairo_surface_t *out) {
944     cairo_surface_set_user_data(out, &ink_color_interpolation_key, cairo_surface_get_user_data(in, &ink_color_interpolation_key), nullptr);
945 }
946 
947 void
ink_cairo_set_source_rgba32(cairo_t * ct,guint32 rgba)948 ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba)
949 {
950     cairo_set_source_rgba(ct, SP_RGBA32_R_F(rgba), SP_RGBA32_G_F(rgba), SP_RGBA32_B_F(rgba), SP_RGBA32_A_F(rgba));
951 }
952 
953 void
ink_cairo_set_source_color(cairo_t * ct,SPColor const & c,double opacity)954 ink_cairo_set_source_color(cairo_t *ct, SPColor const &c, double opacity)
955 {
956     cairo_set_source_rgba(ct, c.v.c[0], c.v.c[1], c.v.c[2], opacity);
957 }
958 
ink_matrix_to_2geom(Geom::Affine & m,cairo_matrix_t const & cm)959 void ink_matrix_to_2geom(Geom::Affine &m, cairo_matrix_t const &cm)
960 {
961     m[0] = cm.xx;
962     m[2] = cm.xy;
963     m[4] = cm.x0;
964     m[1] = cm.yx;
965     m[3] = cm.yy;
966     m[5] = cm.y0;
967 }
968 
ink_matrix_to_cairo(cairo_matrix_t & cm,Geom::Affine const & m)969 void ink_matrix_to_cairo(cairo_matrix_t &cm, Geom::Affine const &m)
970 {
971     cm.xx = m[0];
972     cm.xy = m[2];
973     cm.x0 = m[4];
974     cm.yx = m[1];
975     cm.yy = m[3];
976     cm.y0 = m[5];
977 }
978 
979 void
ink_cairo_transform(cairo_t * ct,Geom::Affine const & m)980 ink_cairo_transform(cairo_t *ct, Geom::Affine const &m)
981 {
982     cairo_matrix_t cm;
983     ink_matrix_to_cairo(cm, m);
984     cairo_transform(ct, &cm);
985 }
986 
987 void
ink_cairo_pattern_set_matrix(cairo_pattern_t * cp,Geom::Affine const & m)988 ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m)
989 {
990     cairo_matrix_t cm;
991     ink_matrix_to_cairo(cm, m);
992     cairo_pattern_set_matrix(cp, &cm);
993 }
994 
995 void
ink_cairo_set_hairline(cairo_t * ct)996 ink_cairo_set_hairline(cairo_t *ct)
997 {
998 #ifdef CAIRO_HAS_HAIRLINE
999     cairo_set_hairline(ct);
1000 #else
1001     // As a backup, use a device unit of 1
1002     double x = 1, y = 1;
1003     cairo_device_to_user_distance(ct, &x, &y);
1004     cairo_set_line_width(ct, std::min(x, y));
1005 #endif
1006 }
1007 
1008 /**
1009  * Create an exact copy of a surface.
1010  * Creates a surface that has the same type, content type, dimensions and contents
1011  * as the specified surface.
1012  */
1013 cairo_surface_t *
ink_cairo_surface_copy(cairo_surface_t * s)1014 ink_cairo_surface_copy(cairo_surface_t *s)
1015 {
1016     cairo_surface_t *ns = ink_cairo_surface_create_identical(s);
1017 
1018     if (cairo_surface_get_type(s) == CAIRO_SURFACE_TYPE_IMAGE) {
1019         // use memory copy instead of using a Cairo context
1020         cairo_surface_flush(s);
1021         int stride = cairo_image_surface_get_stride(s);
1022         int h = cairo_image_surface_get_height(s);
1023         memcpy(cairo_image_surface_get_data(ns), cairo_image_surface_get_data(s), stride * h);
1024         cairo_surface_mark_dirty(ns);
1025     } else {
1026         // generic implementation
1027         cairo_t *ct = cairo_create(ns);
1028         cairo_set_source_surface(ct, s, 0, 0);
1029         cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
1030         cairo_paint(ct);
1031         cairo_destroy(ct);
1032     }
1033 
1034     return ns;
1035 }
1036 
1037 /**
1038  * Create an exact copy of an image surface.
1039  */
1040 Cairo::RefPtr<Cairo::ImageSurface>
ink_cairo_surface_copy(Cairo::RefPtr<Cairo::ImageSurface> surface)1041 ink_cairo_surface_copy(Cairo::RefPtr<Cairo::ImageSurface> surface )
1042 {
1043     int width  = surface->get_width();
1044     int height = surface->get_height();
1045     int stride = surface->get_stride();
1046     auto new_surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width, height); // device scale?
1047 
1048     surface->flush();
1049     memcpy(new_surface->get_data(), surface->get_data(), stride * height);
1050     new_surface->mark_dirty(); // Clear caches. Mandatory after messing directly with contents.
1051 
1052     return new_surface;
1053 }
1054 
1055 /**
1056  * Create a surface that differs only in pixel content.
1057  * Creates a surface that has the same type, content type and dimensions
1058  * as the specified surface. Pixel contents are not copied.
1059  */
1060 cairo_surface_t *
ink_cairo_surface_create_identical(cairo_surface_t * s)1061 ink_cairo_surface_create_identical(cairo_surface_t *s)
1062 {
1063     cairo_surface_t *ns = ink_cairo_surface_create_same_size(s, cairo_surface_get_content(s));
1064     cairo_surface_set_user_data(ns, &ink_color_interpolation_key, cairo_surface_get_user_data(s, &ink_color_interpolation_key), nullptr);
1065     return ns;
1066 }
1067 
1068 cairo_surface_t *
ink_cairo_surface_create_same_size(cairo_surface_t * s,cairo_content_t c)1069 ink_cairo_surface_create_same_size(cairo_surface_t *s, cairo_content_t c)
1070 {
1071     // ink_cairo_surface_get_width()/height() returns value in pixels
1072     // cairo_surface_create_similar() uses device units
1073     double x_scale = 0;
1074     double y_scale = 0;
1075     cairo_surface_get_device_scale( s, &x_scale, &y_scale );
1076 
1077     assert (x_scale > 0);
1078     assert (y_scale > 0);
1079 
1080     cairo_surface_t *ns =
1081         cairo_surface_create_similar(s, c,
1082                                      ink_cairo_surface_get_width(s)/x_scale,
1083                                      ink_cairo_surface_get_height(s)/y_scale);
1084     return ns;
1085 }
1086 
1087 /**
1088  * Extract the alpha channel into a new surface.
1089  * Creates a surface with a content type of CAIRO_CONTENT_ALPHA that contains
1090  * the alpha values of pixels from @a s.
1091  */
1092 cairo_surface_t *
ink_cairo_extract_alpha(cairo_surface_t * s)1093 ink_cairo_extract_alpha(cairo_surface_t *s)
1094 {
1095     cairo_surface_t *alpha = ink_cairo_surface_create_same_size(s, CAIRO_CONTENT_ALPHA);
1096 
1097     cairo_t *ct = cairo_create(alpha);
1098     cairo_set_source_surface(ct, s, 0, 0);
1099     cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
1100     cairo_paint(ct);
1101     cairo_destroy(ct);
1102 
1103     return alpha;
1104 }
1105 
1106 cairo_surface_t *
ink_cairo_surface_create_output(cairo_surface_t * image,cairo_surface_t * bg)1107 ink_cairo_surface_create_output(cairo_surface_t *image, cairo_surface_t *bg)
1108 {
1109     cairo_content_t imgt = cairo_surface_get_content(image);
1110     cairo_content_t bgt = cairo_surface_get_content(bg);
1111     cairo_surface_t *out = nullptr;
1112 
1113     if (bgt == CAIRO_CONTENT_ALPHA && imgt == CAIRO_CONTENT_ALPHA) {
1114         out = ink_cairo_surface_create_identical(bg);
1115     } else {
1116         out = ink_cairo_surface_create_same_size(bg, CAIRO_CONTENT_COLOR_ALPHA);
1117     }
1118 
1119     return out;
1120 }
1121 
1122 void
ink_cairo_surface_blit(cairo_surface_t * src,cairo_surface_t * dest)1123 ink_cairo_surface_blit(cairo_surface_t *src, cairo_surface_t *dest)
1124 {
1125     if (cairo_surface_get_type(src) == CAIRO_SURFACE_TYPE_IMAGE &&
1126         cairo_surface_get_type(dest) == CAIRO_SURFACE_TYPE_IMAGE &&
1127         cairo_image_surface_get_format(src) == cairo_image_surface_get_format(dest) &&
1128         cairo_image_surface_get_height(src) == cairo_image_surface_get_height(dest) &&
1129         cairo_image_surface_get_width(src) == cairo_image_surface_get_width(dest) &&
1130         cairo_image_surface_get_stride(src) == cairo_image_surface_get_stride(dest))
1131     {
1132         // use memory copy instead of using a Cairo context
1133         cairo_surface_flush(src);
1134         int stride = cairo_image_surface_get_stride(src);
1135         int h = cairo_image_surface_get_height(src);
1136         memcpy(cairo_image_surface_get_data(dest), cairo_image_surface_get_data(src), stride * h);
1137         cairo_surface_mark_dirty(dest);
1138     } else {
1139         // generic implementation
1140         cairo_t *ct = cairo_create(dest);
1141         cairo_set_source_surface(ct, src, 0, 0);
1142         cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
1143         cairo_paint(ct);
1144         cairo_destroy(ct);
1145     }
1146 }
1147 
1148 /**
1149  * Return width in pixels.
1150  */
1151 int
ink_cairo_surface_get_width(cairo_surface_t * surface)1152 ink_cairo_surface_get_width(cairo_surface_t *surface)
1153 {
1154     // For now only image surface is handled.
1155     // Later add others, e.g. cairo-gl
1156     assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
1157     return cairo_image_surface_get_width(surface);
1158 }
1159 
1160 /**
1161  * Return height in pixels.
1162  */
1163 int
ink_cairo_surface_get_height(cairo_surface_t * surface)1164 ink_cairo_surface_get_height(cairo_surface_t *surface)
1165 {
1166     assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
1167     return cairo_image_surface_get_height(surface);
1168 }
1169 
ink_cairo_surface_average_color_internal(cairo_surface_t * surface,double & rf,double & gf,double & bf,double & af)1170 static int ink_cairo_surface_average_color_internal(cairo_surface_t *surface, double &rf, double &gf, double &bf, double &af)
1171 {
1172     rf = gf = bf = af = 0.0;
1173     cairo_surface_flush(surface);
1174     int width = cairo_image_surface_get_width(surface);
1175     int height = cairo_image_surface_get_height(surface);
1176     int stride = cairo_image_surface_get_stride(surface);
1177     unsigned char *data = cairo_image_surface_get_data(surface);
1178 
1179     /* TODO convert this to OpenMP somehow */
1180     for (int y = 0; y < height; ++y, data += stride) {
1181         for (int x = 0; x < width; ++x) {
1182             guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
1183             EXTRACT_ARGB32(px, a,r,g,b)
1184             rf += r / 255.0;
1185             gf += g / 255.0;
1186             bf += b / 255.0;
1187             af += a / 255.0;
1188         }
1189     }
1190     return width * height;
1191 }
1192 
ink_cairo_surface_average_color(cairo_surface_t * surface)1193 guint32 ink_cairo_surface_average_color(cairo_surface_t *surface)
1194 {
1195     double rf,gf,bf,af;
1196     ink_cairo_surface_average_color_premul(surface, rf,gf,bf,af);
1197     guint32 r = round(rf * 255);
1198     guint32 g = round(gf * 255);
1199     guint32 b = round(bf * 255);
1200     guint32 a = round(af * 255);
1201     ASSEMBLE_ARGB32(px, a,r,g,b);
1202     return px;
1203 }
1204 // We extract colors from pattern background, if we need to extract sometimes from a gradient we can add
1205 // a extra parameter with the spot number and use cairo_pattern_get_color_stop_rgba
1206 // 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
ink_cairo_pattern_get_argb32(cairo_pattern_t * pattern)1207 guint32 ink_cairo_pattern_get_argb32(cairo_pattern_t *pattern)
1208 {
1209     double red = 0;
1210     double green = 0;
1211     double blue = 0;
1212     double alpha = 0;
1213     auto status = cairo_pattern_get_rgba(pattern, &red, &green, &blue, &alpha);
1214     if (status != CAIRO_STATUS_PATTERN_TYPE_MISMATCH) {
1215         // in ARGB32 format
1216         return SP_RGBA32_F_COMPOSE(alpha, red, green, blue);
1217     }
1218 
1219     cairo_surface_t *surface;
1220     status = cairo_pattern_get_surface (pattern, &surface);
1221     if (status != CAIRO_STATUS_PATTERN_TYPE_MISMATCH) {
1222         // first pixel only
1223         auto *pxbsurface =  cairo_image_surface_get_data(surface);
1224         return *reinterpret_cast<guint32 const *>(pxbsurface);
1225     }
1226     return 0;
1227 }
1228 
ink_cairo_surface_average_color(cairo_surface_t * surface,double & r,double & g,double & b,double & a)1229 void ink_cairo_surface_average_color(cairo_surface_t *surface, double &r, double &g, double &b, double &a)
1230 {
1231     int count = ink_cairo_surface_average_color_internal(surface, r,g,b,a);
1232 
1233     r /= a;
1234     g /= a;
1235     b /= a;
1236     a /= count;
1237 
1238     r = CLAMP(r, 0.0, 1.0);
1239     g = CLAMP(g, 0.0, 1.0);
1240     b = CLAMP(b, 0.0, 1.0);
1241     a = CLAMP(a, 0.0, 1.0);
1242 }
1243 
ink_cairo_surface_average_color_premul(cairo_surface_t * surface,double & r,double & g,double & b,double & a)1244 void ink_cairo_surface_average_color_premul(cairo_surface_t *surface, double &r, double &g, double &b, double &a)
1245 {
1246     int count = ink_cairo_surface_average_color_internal(surface, r,g,b,a);
1247 
1248     r /= count;
1249     g /= count;
1250     b /= count;
1251     a /= count;
1252 
1253     r = CLAMP(r, 0.0, 1.0);
1254     g = CLAMP(g, 0.0, 1.0);
1255     b = CLAMP(b, 0.0, 1.0);
1256     a = CLAMP(a, 0.0, 1.0);
1257 }
1258 
srgb_to_linear(const guint32 c,const guint32 a)1259 static guint32 srgb_to_linear( const guint32 c, const guint32 a ) {
1260 
1261     const guint32 c1 = unpremul_alpha( c, a );
1262 
1263     double cc = c1/255.0;
1264 
1265     if( cc < 0.04045 ) {
1266         cc /= 12.92;
1267     } else {
1268         cc = pow( (cc+0.055)/1.055, 2.4 );
1269     }
1270     cc *= 255.0;
1271 
1272     const guint32 c2 = (int)cc;
1273 
1274     return premul_alpha( c2, a );
1275 }
1276 
linear_to_srgb(const guint32 c,const guint32 a)1277 static guint32 linear_to_srgb( const guint32 c, const guint32 a ) {
1278 
1279     const guint32 c1 = unpremul_alpha( c, a );
1280 
1281     double cc = c1/255.0;
1282 
1283     if( cc < 0.0031308 ) {
1284         cc *= 12.92;
1285     } else {
1286         cc = pow( cc, 1.0/2.4 )*1.055-0.055;
1287     }
1288     cc *= 255.0;
1289 
1290     const guint32 c2 = (int)cc;
1291 
1292     return premul_alpha( c2, a );
1293 }
1294 
1295 struct SurfaceSrgbToLinear {
1296 
operator ()SurfaceSrgbToLinear1297     guint32 operator()(guint32 in) {
1298         EXTRACT_ARGB32(in, a,r,g,b)    ; // Unneeded semi-colon for indenting
1299         if( a != 0 ) {
1300             r = srgb_to_linear( r, a );
1301             g = srgb_to_linear( g, a );
1302             b = srgb_to_linear( b, a );
1303         }
1304         ASSEMBLE_ARGB32(out, a,r,g,b);
1305         return out;
1306     }
1307 private:
1308     /* None */
1309 };
1310 
ink_cairo_surface_srgb_to_linear(cairo_surface_t * surface)1311 int ink_cairo_surface_srgb_to_linear(cairo_surface_t *surface)
1312 {
1313     cairo_surface_flush(surface);
1314     int width = cairo_image_surface_get_width(surface);
1315     int height = cairo_image_surface_get_height(surface);
1316     // int stride = cairo_image_surface_get_stride(surface);
1317     // unsigned char *data = cairo_image_surface_get_data(surface);
1318 
1319     ink_cairo_surface_filter( surface, surface, SurfaceSrgbToLinear() );
1320 
1321     /* TODO convert this to OpenMP somehow */
1322     // for (int y = 0; y < height; ++y, data += stride) {
1323     //     for (int x = 0; x < width; ++x) {
1324     //         guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
1325     //         EXTRACT_ARGB32(px, a,r,g,b)    ; // Unneeded semi-colon for indenting
1326     //         if( a != 0 ) {
1327     //             r = srgb_to_linear( r, a );
1328     //             g = srgb_to_linear( g, a );
1329     //             b = srgb_to_linear( b, a );
1330     //         }
1331     //         ASSEMBLE_ARGB32(px2, a,r,g,b);
1332     //         *reinterpret_cast<guint32*>(data + 4*x) = px2;
1333     //     }
1334     // }
1335     return width * height;
1336 }
1337 
1338 struct SurfaceLinearToSrgb {
1339 
operator ()SurfaceLinearToSrgb1340     guint32 operator()(guint32 in) {
1341         EXTRACT_ARGB32(in, a,r,g,b)    ; // Unneeded semi-colon for indenting
1342         if( a != 0 ) {
1343             r = linear_to_srgb( r, a );
1344             g = linear_to_srgb( g, a );
1345             b = linear_to_srgb( b, a );
1346         }
1347         ASSEMBLE_ARGB32(out, a,r,g,b);
1348         return out;
1349     }
1350 private:
1351     /* None */
1352 };
1353 
ink_cairo_operator_to_css_blend(cairo_operator_t cairo_operator)1354 SPBlendMode ink_cairo_operator_to_css_blend(cairo_operator_t cairo_operator)
1355 {
1356     // All of the blend modes are implemented in Cairo as of 1.10.
1357     // For a detailed description, see:
1358     // http://cairographics.org/operators/
1359     auto res = SP_CSS_BLEND_NORMAL;
1360     switch (cairo_operator) {
1361         case CAIRO_OPERATOR_MULTIPLY:
1362             res = SP_CSS_BLEND_MULTIPLY;
1363             break;
1364         case CAIRO_OPERATOR_SCREEN:
1365             res = SP_CSS_BLEND_SCREEN;
1366             break;
1367         case CAIRO_OPERATOR_DARKEN:
1368             res = SP_CSS_BLEND_DARKEN;
1369             break;
1370         case CAIRO_OPERATOR_LIGHTEN:
1371             res = SP_CSS_BLEND_LIGHTEN;
1372             break;
1373         case CAIRO_OPERATOR_OVERLAY:
1374             res = SP_CSS_BLEND_OVERLAY;
1375             break;
1376         case CAIRO_OPERATOR_COLOR_DODGE:
1377             res = SP_CSS_BLEND_COLORDODGE;
1378             break;
1379         case CAIRO_OPERATOR_COLOR_BURN:
1380             res = SP_CSS_BLEND_COLORBURN;
1381             break;
1382         case CAIRO_OPERATOR_HARD_LIGHT:
1383             res = SP_CSS_BLEND_HARDLIGHT;
1384             break;
1385         case CAIRO_OPERATOR_SOFT_LIGHT:
1386             res = SP_CSS_BLEND_SOFTLIGHT;
1387             break;
1388         case CAIRO_OPERATOR_DIFFERENCE:
1389             res = SP_CSS_BLEND_DIFFERENCE;
1390             break;
1391         case CAIRO_OPERATOR_EXCLUSION:
1392             res = SP_CSS_BLEND_EXCLUSION;
1393             break;
1394         case CAIRO_OPERATOR_HSL_HUE:
1395             res = SP_CSS_BLEND_HUE;
1396             break;
1397         case CAIRO_OPERATOR_HSL_SATURATION:
1398             res = SP_CSS_BLEND_SATURATION;
1399             break;
1400         case CAIRO_OPERATOR_HSL_COLOR:
1401             res = SP_CSS_BLEND_COLOR;
1402             break;
1403         case CAIRO_OPERATOR_HSL_LUMINOSITY:
1404             res = SP_CSS_BLEND_LUMINOSITY;
1405             break;
1406         case CAIRO_OPERATOR_OVER:
1407         default:
1408             res = SP_CSS_BLEND_NORMAL;
1409             break;
1410     }
1411     return res;
1412 }
1413 
ink_css_blend_to_cairo_operator(SPBlendMode css_blend)1414 cairo_operator_t ink_css_blend_to_cairo_operator(SPBlendMode css_blend)
1415 {
1416     // All of the blend modes are implemented in Cairo as of 1.10.
1417     // For a detailed description, see:
1418     // http://cairographics.org/operators/
1419 
1420     cairo_operator_t res = CAIRO_OPERATOR_OVER;
1421     switch (css_blend) {
1422         case SP_CSS_BLEND_MULTIPLY:
1423             res = CAIRO_OPERATOR_MULTIPLY;
1424             break;
1425         case SP_CSS_BLEND_SCREEN:
1426             res = CAIRO_OPERATOR_SCREEN;
1427             break;
1428         case SP_CSS_BLEND_DARKEN:
1429             res = CAIRO_OPERATOR_DARKEN;
1430             break;
1431         case SP_CSS_BLEND_LIGHTEN:
1432             res = CAIRO_OPERATOR_LIGHTEN;
1433             break;
1434         case SP_CSS_BLEND_OVERLAY:
1435             res = CAIRO_OPERATOR_OVERLAY;
1436             break;
1437         case SP_CSS_BLEND_COLORDODGE:
1438             res = CAIRO_OPERATOR_COLOR_DODGE;
1439             break;
1440         case SP_CSS_BLEND_COLORBURN:
1441             res = CAIRO_OPERATOR_COLOR_BURN;
1442             break;
1443         case SP_CSS_BLEND_HARDLIGHT:
1444             res = CAIRO_OPERATOR_HARD_LIGHT;
1445             break;
1446         case SP_CSS_BLEND_SOFTLIGHT:
1447             res = CAIRO_OPERATOR_SOFT_LIGHT;
1448             break;
1449         case SP_CSS_BLEND_DIFFERENCE:
1450             res = CAIRO_OPERATOR_DIFFERENCE;
1451             break;
1452         case SP_CSS_BLEND_EXCLUSION:
1453             res = CAIRO_OPERATOR_EXCLUSION;
1454             break;
1455         case SP_CSS_BLEND_HUE:
1456             res = CAIRO_OPERATOR_HSL_HUE;
1457             break;
1458         case SP_CSS_BLEND_SATURATION:
1459             res = CAIRO_OPERATOR_HSL_SATURATION;
1460             break;
1461         case SP_CSS_BLEND_COLOR:
1462             res = CAIRO_OPERATOR_HSL_COLOR;
1463             break;
1464         case SP_CSS_BLEND_LUMINOSITY:
1465             res = CAIRO_OPERATOR_HSL_LUMINOSITY;
1466             break;
1467         case SP_CSS_BLEND_NORMAL:
1468             res = CAIRO_OPERATOR_OVER;
1469             break;
1470         default:
1471             g_error("Invalid SPBlendMode %d", css_blend);
1472     }
1473     return res;
1474 }
1475 
1476 
1477 
ink_cairo_surface_linear_to_srgb(cairo_surface_t * surface)1478 int ink_cairo_surface_linear_to_srgb(cairo_surface_t *surface)
1479 {
1480     cairo_surface_flush(surface);
1481     int width = cairo_image_surface_get_width(surface);
1482     int height = cairo_image_surface_get_height(surface);
1483     // int stride = cairo_image_surface_get_stride(surface);
1484     // unsigned char *data = cairo_image_surface_get_data(surface);
1485 
1486     ink_cairo_surface_filter( surface, surface, SurfaceLinearToSrgb() );
1487 
1488     // /* TODO convert this to OpenMP somehow */
1489     // for (int y = 0; y < height; ++y, data += stride) {
1490     //     for (int x = 0; x < width; ++x) {
1491     //         guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
1492     //         EXTRACT_ARGB32(px, a,r,g,b)    ; // Unneeded semi-colon for indenting
1493     //         if( a != 0 ) {
1494     //             r = linear_to_srgb( r, a );
1495     //             g = linear_to_srgb( g, a );
1496     //             b = linear_to_srgb( b, a );
1497     //         }
1498     //         ASSEMBLE_ARGB32(px2, a,r,g,b);
1499     //         *reinterpret_cast<guint32*>(data + 4*x) = px2;
1500     //     }
1501     // }
1502     return width * height;
1503 }
1504 
1505 cairo_pattern_t *
ink_cairo_pattern_create_checkerboard(guint32 rgba)1506 ink_cairo_pattern_create_checkerboard(guint32 rgba)
1507 {
1508     int const w = 6;
1509     int const h = 6;
1510 
1511     double r = SP_RGBA32_R_F(rgba);
1512     double g = SP_RGBA32_G_F(rgba);
1513     double b = SP_RGBA32_B_F(rgba);
1514 
1515     float hsl[3];
1516     SPColor::rgb_to_hsl_floatv(hsl, r, g, b);
1517     hsl[2] += hsl[2] < 0.08 ? 0.08 : -0.08; // 0.08 = 0.77-0.69, the original checkerboard colors.
1518 
1519     float rgb2[3];
1520     SPColor::hsl_to_rgb_floatv(rgb2, hsl[0], hsl[1], hsl[2]);
1521 
1522     cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2*w, 2*h);
1523 
1524     cairo_t *ct = cairo_create(s);
1525     cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
1526     cairo_set_source_rgb(ct, r, g, b);
1527     cairo_paint(ct);
1528     cairo_set_source_rgb(ct, rgb2[0], rgb2[1], rgb2[2]);
1529     cairo_rectangle(ct, 0, 0, w, h);
1530     cairo_rectangle(ct, w, h, w, h);
1531     cairo_fill(ct);
1532     cairo_destroy(ct);
1533 
1534     cairo_pattern_t *p = cairo_pattern_create_for_surface(s);
1535     cairo_pattern_set_extend(p, CAIRO_EXTEND_REPEAT);
1536     cairo_pattern_set_filter(p, CAIRO_FILTER_NEAREST);
1537 
1538     cairo_surface_destroy(s);
1539     return p;
1540 }
1541 
1542 /**
1543  * Converts the Cairo surface to a GdkPixbuf pixel format,
1544  * without allocating extra memory.
1545  *
1546  * This function is intended mainly for creating previews displayed by GTK.
1547  * For loading images for display on the canvas, use the Inkscape::Pixbuf object.
1548  *
1549  * The returned GdkPixbuf takes ownership of the passed surface reference,
1550  * so it should NOT be freed after calling this function.
1551  */
ink_pixbuf_create_from_cairo_surface(cairo_surface_t * s)1552 GdkPixbuf *ink_pixbuf_create_from_cairo_surface(cairo_surface_t *s)
1553 {
1554     guchar *pixels = cairo_image_surface_get_data(s);
1555     int w = cairo_image_surface_get_width(s);
1556     int h = cairo_image_surface_get_height(s);
1557     int rs = cairo_image_surface_get_stride(s);
1558 
1559     convert_pixels_argb32_to_pixbuf(pixels, w, h, rs);
1560 
1561     GdkPixbuf *pb = gdk_pixbuf_new_from_data(
1562         pixels, GDK_COLORSPACE_RGB, TRUE, 8,
1563         w, h, rs, ink_cairo_pixbuf_cleanup, s);
1564 
1565     return pb;
1566 }
1567 
1568 /**
1569  * Cleanup function for GdkPixbuf.
1570  * This function should be passed as the GdkPixbufDestroyNotify parameter
1571  * to gdk_pixbuf_new_from_data when creating a GdkPixbuf backed by
1572  * a Cairo surface.
1573  */
ink_cairo_pixbuf_cleanup(guchar *,void * data)1574 void ink_cairo_pixbuf_cleanup(guchar * /*pixels*/, void *data)
1575 {
1576     cairo_surface_t *surface = static_cast<cairo_surface_t*>(data);
1577     cairo_surface_destroy(surface);
1578 }
1579 
1580 /* The following two functions use "from" instead of "to", because when you write:
1581    val1 = argb32_from_pixbuf(val1);
1582    the name of the format is closer to the value in that format. */
1583 
argb32_from_pixbuf(guint32 c)1584 guint32 argb32_from_pixbuf(guint32 c)
1585 {
1586     guint32 o = 0;
1587 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
1588     guint32 a = (c & 0xff000000) >> 24;
1589 #else
1590     guint32 a = (c & 0x000000ff);
1591 #endif
1592     if (a != 0) {
1593         // extract color components
1594 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
1595         guint32 r = (c & 0x000000ff);
1596         guint32 g = (c & 0x0000ff00) >> 8;
1597         guint32 b = (c & 0x00ff0000) >> 16;
1598 #else
1599         guint32 r = (c & 0xff000000) >> 24;
1600         guint32 g = (c & 0x00ff0000) >> 16;
1601         guint32 b = (c & 0x0000ff00) >> 8;
1602 #endif
1603         // premultiply
1604         r = premul_alpha(r, a);
1605         b = premul_alpha(b, a);
1606         g = premul_alpha(g, a);
1607         // combine into output
1608         o = (a << 24) | (r << 16) | (g << 8) | (b);
1609     }
1610     return o;
1611 }
1612 
pixbuf_from_argb32(guint32 c)1613 guint32 pixbuf_from_argb32(guint32 c)
1614 {
1615     guint32 a = (c & 0xff000000) >> 24;
1616     if (a == 0) return 0;
1617 
1618     // extract color components
1619     guint32 r = (c & 0x00ff0000) >> 16;
1620     guint32 g = (c & 0x0000ff00) >> 8;
1621     guint32 b = (c & 0x000000ff);
1622     // unpremultiply; adding a/2 gives correct rounding
1623     // (taken from Cairo sources)
1624     r = (r * 255 + a/2) / a;
1625     b = (b * 255 + a/2) / a;
1626     g = (g * 255 + a/2) / a;
1627     // combine into output
1628 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
1629     guint32 o = (r) | (g << 8) | (b << 16) | (a << 24);
1630 #else
1631     guint32 o = (r << 24) | (g << 16) | (b << 8) | (a);
1632 #endif
1633     return o;
1634 }
1635 
1636 /**
1637  * Convert pixel data from GdkPixbuf format to ARGB.
1638  * This will convert pixel data from GdkPixbuf format to Cairo's native pixel format.
1639  * This involves premultiplying alpha and shuffling around the channels.
1640  * Pixbuf data must have an alpha channel, otherwise the results are undefined
1641  * (usually a segfault).
1642  */
1643 void
convert_pixels_pixbuf_to_argb32(guchar * data,int w,int h,int stride)1644 convert_pixels_pixbuf_to_argb32(guchar *data, int w, int h, int stride)
1645 {
1646     if (!data || w < 1 || h < 1 || stride < 1) {
1647         return;
1648     }
1649 
1650     for (size_t i = 0; i < h; ++i) {
1651         guint32 *px = reinterpret_cast<guint32*>(data + i*stride);
1652         for (size_t j = 0; j < w; ++j) {
1653             *px = argb32_from_pixbuf(*px);
1654             ++px;
1655         }
1656     }
1657 }
1658 
1659 /**
1660  * Convert pixel data from ARGB to GdkPixbuf format.
1661  * This will convert pixel data from GdkPixbuf format to Cairo's native pixel format.
1662  * This involves premultiplying alpha and shuffling around the channels.
1663  */
1664 void
convert_pixels_argb32_to_pixbuf(guchar * data,int w,int h,int stride)1665 convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int stride)
1666 {
1667     if (!data || w < 1 || h < 1 || stride < 1) {
1668         return;
1669     }
1670     for (size_t i = 0; i < h; ++i) {
1671         guint32 *px = reinterpret_cast<guint32*>(data + i*stride);
1672         for (size_t j = 0; j < w; ++j) {
1673             *px = pixbuf_from_argb32(*px);
1674             ++px;
1675         }
1676     }
1677 }
1678 
1679 /**
1680  * Converts GdkPixbuf's data to premultiplied ARGB.
1681  * This function will convert a GdkPixbuf in place into Cairo's native pixel format.
1682  * Note that this is a hack intended to save memory. When the pixbuf is in Cairo's format,
1683  * using it with GTK will result in corrupted drawings.
1684  */
1685 void
ink_pixbuf_ensure_argb32(GdkPixbuf * pb)1686 ink_pixbuf_ensure_argb32(GdkPixbuf *pb)
1687 {
1688     gchar *pixel_format = reinterpret_cast<gchar*>(g_object_get_data(G_OBJECT(pb), "pixel_format"));
1689     if (pixel_format != nullptr && strcmp(pixel_format, "argb32") == 0) {
1690         // nothing to do
1691         return;
1692     }
1693 
1694     convert_pixels_pixbuf_to_argb32(
1695         gdk_pixbuf_get_pixels(pb),
1696         gdk_pixbuf_get_width(pb),
1697         gdk_pixbuf_get_height(pb),
1698         gdk_pixbuf_get_rowstride(pb));
1699     g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("argb32"), g_free);
1700 }
1701 
1702 /**
1703  * Converts GdkPixbuf's data back to its native format.
1704  * Once this is done, the pixbuf can be used with GTK again.
1705  */
1706 void
ink_pixbuf_ensure_normal(GdkPixbuf * pb)1707 ink_pixbuf_ensure_normal(GdkPixbuf *pb)
1708 {
1709     gchar *pixel_format = reinterpret_cast<gchar*>(g_object_get_data(G_OBJECT(pb), "pixel_format"));
1710     if (pixel_format == nullptr || strcmp(pixel_format, "pixbuf") == 0) {
1711         // nothing to do
1712         return;
1713     }
1714 
1715     convert_pixels_argb32_to_pixbuf(
1716         gdk_pixbuf_get_pixels(pb),
1717         gdk_pixbuf_get_width(pb),
1718         gdk_pixbuf_get_height(pb),
1719         gdk_pixbuf_get_rowstride(pb));
1720     g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("pixbuf"), g_free);
1721 }
1722 
argb32_from_rgba(guint32 in)1723 guint32 argb32_from_rgba(guint32 in)
1724 {
1725     guint32 r, g, b, a;
1726     a = (in & 0x000000ff);
1727     r = premul_alpha((in & 0xff000000) >> 24, a);
1728     g = premul_alpha((in & 0x00ff0000) >> 16, a);
1729     b = premul_alpha((in & 0x0000ff00) >> 8,  a);
1730     ASSEMBLE_ARGB32(px, a, r, g, b)
1731     return px;
1732 }
1733 
1734 
1735 /**
1736  * Converts a pixbuf to a PNG data structure.
1737  * For 8-but RGBA png, this is like copying.
1738  *
1739  */
pixbuf_to_png(guchar const ** rows,guchar * px,int num_rows,int num_cols,int stride,int color_type,int bit_depth)1740 const guchar* pixbuf_to_png(guchar const**rows, guchar* px, int num_rows, int num_cols, int stride, int color_type, int bit_depth)
1741 {
1742     int n_fields = 1 + (color_type&2) + (color_type&4)/4;
1743     const guchar* new_data = (const guchar*)malloc((n_fields * bit_depth * num_rows * num_cols)/8 + 64);
1744     char* ptr = (char*) new_data;
1745     int pad=0; //used when we write image data smaller than one byte (for instance in black and white images where 1px = 1bit)
1746     for(int row = 0; row < num_rows; ++row){
1747         rows[row] = (const guchar*)ptr;
1748         for(int col = 0; col < num_cols; ++col){
1749             guint32 *pixel = reinterpret_cast<guint32*>(px + row*stride)+col;
1750 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
1751             //this part should probably be rewritten as (a tested, working) big endian, using htons() and ntohs()
1752             guint64 a = (*pixel & 0xff000000) >> 24;
1753             guint64 b = (*pixel & 0x00ff0000) >> 16;
1754             guint64 g = (*pixel & 0x0000ff00) >> 8;
1755             guint64 r = (*pixel & 0x000000ff);
1756 
1757             //one of possible rgb to greyscale formulas. This one is called "luminance, "luminosity" or "luma"
1758             guint16 gray = (guint16)((guint32)((0.2126*(r<<24) + 0.7152*(g<<24) + 0.0722*(b<<24)))>>16);
1759 
1760             if (!pad) *((guint64*)ptr)=0;
1761             if (color_type & 2) { //RGB
1762                 // for 8bit->16bit transition, I take the FF -> FFFF convention (multiplication by 0x101).
1763                 // If you prefer FF -> FF00 (multiplication by 0x100), remove the <<8, <<24, <<40  and <<56
1764                 if (color_type & 4) { //RGBA
1765                     if (bit_depth==8)
1766                         *((guint32*)ptr) = *pixel;
1767                     else
1768                         *((guint64*)ptr) = (guint64)((a<<56)+(a<<48)+(b<<40)+(b<<32)+(g<<24)+(g<<16)+(r<<8)+(r));
1769                 }
1770                 else{ //no alpha
1771                     if(bit_depth==8)
1772                         *((guint32*)ptr) = ((*pixel)<<8)>>8;
1773                     else
1774                         *((guint64*)ptr) = (guint64)((b<<40)+(b<<32)+(g<<24)+(g<<16)+(r<<8)+r);
1775                 }
1776             } else { //Grayscale
1777 		int realpad = 8 - bit_depth - pad; // in PNG numbers are stored left to right, but in most significant bits first, so the first one processed is the ``big'' mask, etc.
1778                 if(bit_depth==16)
1779                     *(guint16*)ptr= ((gray & 0xff00)>>8) + ((gray &0x00ff)<<8);
1780                 else *((guint16*)ptr) += guint16(((gray >> (16-bit_depth))<<realpad) ); //note the "+="
1781 
1782                 if(color_type & 4) { //Alpha channel
1783                     if (bit_depth == 16)
1784                         *((guint32*)(ptr+2)) = a + (a<<8);
1785                     else
1786                         *((guint32*)(ptr)) += guint32((a << 8) >> (16 - bit_depth))<<(bit_depth + realpad);
1787                 }
1788             }
1789 
1790 #else
1791             // I don't have any access to a big-endian machine to test this. It should still work with default export settings.
1792             guint64 r = (*pixel & 0xff000000) >> 24;
1793             guint64 g = (*pixel & 0x00ff0000) >> 16;
1794             guint64 b = (*pixel & 0x0000ff00) >> 8;
1795             guint64 a = (*pixel & 0x000000ff);
1796             guint32 gray = (guint32)(0.2126*(r<<24) + 0.7152*(g<<24) + 0.0722*(b<<24));
1797             if(color_type & 2){
1798                 if(bit_depth==8)*ptr = *pixel; else *ptr = (guint64)((r<<56)+(g<<40)+(b<<24)+(a<<8));
1799             } else {
1800                 *((guint32*)ptr) += guint32(gray>>pad);
1801                 if(color_type & 4) *((guint32*)ptr) += guint32((a>>pad)>> bit_depth);
1802             }
1803 #endif
1804             pad+=bit_depth*n_fields;
1805             ptr+=(pad/8);
1806             pad%=8;
1807         }
1808         if(pad){pad=0;ptr++;}//align bytes on rows
1809     }
1810     return new_data;
1811 }
1812 
1813 
1814 
1815 
1816 
1817 
1818 
1819 
1820 /*
1821   Local Variables:
1822   mode:c++
1823   c-file-style:"stroustrup"
1824   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1825   indent-tabs-mode:nil
1826   fill-column:99
1827   End:
1828 */
1829 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1830