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