summaryrefslogtreecommitdiffstats
path: root/src/display/cairo-utils.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/display/cairo-utils.cpp
parentInitial commit. (diff)
downloadinkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz
inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/display/cairo-utils.cpp')
-rw-r--r--src/display/cairo-utils.cpp1962
1 files changed, 1962 insertions, 0 deletions
diff --git a/src/display/cairo-utils.cpp b/src/display/cairo-utils.cpp
new file mode 100644
index 0000000..68a7e4a
--- /dev/null
+++ b/src/display/cairo-utils.cpp
@@ -0,0 +1,1962 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Helper functions to use cairo with inkscape
+ *
+ * Copyright (C) 2007 bulia byak
+ * Copyright (C) 2008 Johan Engelen
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#include "display/cairo-utils.h"
+
+#include <stdexcept>
+
+#include <glib/gstdio.h>
+#include <glibmm/fileutils.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include <2geom/pathvector.h>
+#include <2geom/curves.h>
+#include <2geom/affine.h>
+#include <2geom/point.h>
+#include <2geom/path.h>
+#include <2geom/transforms.h>
+#include <2geom/sbasis-to-bezier.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/operators.hpp>
+#include <boost/optional/optional.hpp>
+
+#include "color.h"
+#include "cairo-templates.h"
+#include "document.h"
+#include "preferences.h"
+#include "util/units.h"
+#include "helper/pixbuf-ops.h"
+
+
+/**
+ * Key for cairo_surface_t to keep track of current color interpolation value
+ * Only the address of the structure is used, it is never initialized. See:
+ * http://www.cairographics.org/manual/cairo-Types.html#cairo-user-data-key-t
+ */
+cairo_user_data_key_t ink_color_interpolation_key;
+cairo_user_data_key_t ink_pixbuf_key;
+
+namespace Inkscape {
+
+CairoGroup::CairoGroup(cairo_t *_ct) : ct(_ct), pushed(false) {}
+CairoGroup::~CairoGroup() {
+ if (pushed) {
+ cairo_pattern_t *p = cairo_pop_group(ct);
+ cairo_pattern_destroy(p);
+ }
+}
+void CairoGroup::push() {
+ cairo_push_group(ct);
+ pushed = true;
+}
+void CairoGroup::push_with_content(cairo_content_t content) {
+ cairo_push_group_with_content(ct, content);
+ pushed = true;
+}
+cairo_pattern_t *CairoGroup::pop() {
+ if (pushed) {
+ cairo_pattern_t *ret = cairo_pop_group(ct);
+ pushed = false;
+ return ret;
+ } else {
+ throw std::logic_error("Cairo group popped without pushing it first");
+ }
+}
+Cairo::RefPtr<Cairo::Pattern> CairoGroup::popmm() {
+ if (pushed) {
+ cairo_pattern_t *ret = cairo_pop_group(ct);
+ Cairo::RefPtr<Cairo::Pattern> retmm(new Cairo::Pattern(ret, true));
+ pushed = false;
+ return retmm;
+ } else {
+ throw std::logic_error("Cairo group popped without pushing it first");
+ }
+}
+void CairoGroup::pop_to_source() {
+ if (pushed) {
+ cairo_pop_group_to_source(ct);
+ pushed = false;
+ }
+}
+
+CairoContext::CairoContext(cairo_t *obj, bool ref)
+ : Cairo::Context(obj, ref)
+{}
+
+void CairoContext::transform(Geom::Affine const &m)
+{
+ cairo_matrix_t cm;
+ cm.xx = m[0];
+ cm.xy = m[2];
+ cm.x0 = m[4];
+ cm.yx = m[1];
+ cm.yy = m[3];
+ cm.y0 = m[5];
+ cairo_transform(cobj(), &cm);
+}
+
+void CairoContext::set_source_rgba32(guint32 color)
+{
+ double red = SP_RGBA32_R_F(color);
+ double gre = SP_RGBA32_G_F(color);
+ double blu = SP_RGBA32_B_F(color);
+ double alp = SP_RGBA32_A_F(color);
+ cairo_set_source_rgba(cobj(), red, gre, blu, alp);
+}
+
+void CairoContext::append_path(Geom::PathVector const &pv)
+{
+ feed_pathvector_to_cairo(cobj(), pv);
+}
+
+Cairo::RefPtr<CairoContext> CairoContext::create(Cairo::RefPtr<Cairo::Surface> const &target)
+{
+ cairo_t *ct = cairo_create(target->cobj());
+ Cairo::RefPtr<CairoContext> ret(new CairoContext(ct, true));
+ return ret;
+}
+
+
+/* The class below implement the following hack:
+ *
+ * The pixels formats of Cairo and GdkPixbuf are different.
+ * GdkPixbuf accesses pixels as bytes, alpha is not premultiplied,
+ * and successive bytes of a single pixel contain R, G, B and A components.
+ * Cairo accesses pixels as 32-bit ints, alpha is premultiplied,
+ * and each int contains as 0xAARRGGBB, accessed with bitwise operations.
+ *
+ * In other words, on a little endian system, a GdkPixbuf will contain:
+ * char *data = "rgbargbargba...."
+ * int *data = { 0xAABBGGRR, 0xAABBGGRR, 0xAABBGGRR, ... }
+ * while a Cairo image surface will contain:
+ * char *data = "bgrabgrabgra...."
+ * int *data = { 0xAARRGGBB, 0xAARRGGBB, 0xAARRGGBB, ... }
+ *
+ * It is possible to convert between these two formats (almost) losslessly.
+ * Some color information from partially transparent regions of the image
+ * is lost, but the result when displaying this image will remain the same.
+ *
+ * The class allows interoperation between GdkPixbuf
+ * and Cairo surfaces without creating a copy of the image.
+ * This is implemented by creating a GdkPixbuf and a Cairo image surface
+ * which share their data. Depending on what is needed at a given time,
+ * the pixels are converted in place to the Cairo or the GdkPixbuf format.
+ */
+
+/** Create a pixbuf from a Cairo surface.
+ * The constructor takes ownership of the passed surface,
+ * so it should not be destroyed. */
+Pixbuf::Pixbuf(cairo_surface_t *s)
+ : _pixbuf(gdk_pixbuf_new_from_data(
+ cairo_image_surface_get_data(s), GDK_COLORSPACE_RGB, TRUE, 8,
+ cairo_image_surface_get_width(s), cairo_image_surface_get_height(s),
+ cairo_image_surface_get_stride(s),
+ ink_cairo_pixbuf_cleanup, s))
+ , _surface(s)
+ , _mod_time(0)
+ , _pixel_format(PF_CAIRO)
+ , _cairo_store(true)
+{}
+
+/** Create a pixbuf from a GdkPixbuf.
+ * The constructor takes ownership of the passed GdkPixbuf reference,
+ * so it should not be unrefed. */
+Pixbuf::Pixbuf(GdkPixbuf *pb)
+ : _pixbuf(pb)
+ , _surface(nullptr)
+ , _mod_time(0)
+ , _pixel_format(PF_GDK)
+ , _cairo_store(false)
+{
+ _forceAlpha();
+ _surface = cairo_image_surface_create_for_data(
+ gdk_pixbuf_get_pixels(_pixbuf), CAIRO_FORMAT_ARGB32,
+ gdk_pixbuf_get_width(_pixbuf), gdk_pixbuf_get_height(_pixbuf), gdk_pixbuf_get_rowstride(_pixbuf));
+}
+
+Pixbuf::Pixbuf(Inkscape::Pixbuf const &other)
+ : _pixbuf(gdk_pixbuf_copy(other._pixbuf))
+ , _surface(cairo_image_surface_create_for_data(
+ gdk_pixbuf_get_pixels(_pixbuf), CAIRO_FORMAT_ARGB32,
+ gdk_pixbuf_get_width(_pixbuf), gdk_pixbuf_get_height(_pixbuf), gdk_pixbuf_get_rowstride(_pixbuf)))
+ , _mod_time(other._mod_time)
+ , _path(other._path)
+ , _pixel_format(other._pixel_format)
+ , _cairo_store(false)
+{}
+
+Pixbuf::~Pixbuf()
+{
+ if (_cairo_store) {
+ g_object_unref(_pixbuf);
+ } else {
+ cairo_surface_destroy(_surface);
+ g_object_unref(_pixbuf);
+ }
+}
+
+#if !GDK_PIXBUF_CHECK_VERSION(2, 41, 0)
+/**
+ * Incremental file read introduced to workaround
+ * https://gitlab.gnome.org/GNOME/gdk-pixbuf/issues/70
+ */
+static bool _workaround_issue_70__gdk_pixbuf_loader_write( //
+ GdkPixbufLoader *loader, guchar *decoded, gsize decoded_len, GError **error)
+{
+ bool success = true;
+ gsize bytes_left = decoded_len;
+ gsize secret_limit = 0xffff;
+ guchar *decoded_head = decoded;
+ while (bytes_left && success) {
+ gsize bytes = (bytes_left > secret_limit) ? secret_limit : bytes_left;
+ success = gdk_pixbuf_loader_write(loader, decoded_head, bytes, error);
+ decoded_head += bytes;
+ bytes_left -= bytes;
+ }
+
+ return success;
+}
+#define gdk_pixbuf_loader_write _workaround_issue_70__gdk_pixbuf_loader_write
+#endif
+
+Pixbuf *Pixbuf::create_from_data_uri(gchar const *uri_data, double svgdpi)
+{
+ Pixbuf *pixbuf = nullptr;
+
+ bool data_is_image = false;
+ bool data_is_svg = false;
+ bool data_is_base64 = false;
+
+ gchar const *data = uri_data;
+
+ while (*data) {
+ if (strncmp(data,"base64",6) == 0) {
+ /* base64-encoding */
+ data_is_base64 = true;
+ data_is_image = true; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
+ data += 6;
+ }
+ else if (strncmp(data,"image/png",9) == 0) {
+ /* PNG image */
+ data_is_image = true;
+ data += 9;
+ }
+ else if (strncmp(data,"image/jpg",9) == 0) {
+ /* JPEG image */
+ data_is_image = true;
+ data += 9;
+ }
+ else if (strncmp(data,"image/jpeg",10) == 0) {
+ /* JPEG image */
+ data_is_image = true;
+ data += 10;
+ }
+ else if (strncmp(data,"image/jp2",9) == 0) {
+ /* JPEG2000 image */
+ data_is_image = true;
+ data += 9;
+ }
+ else if (strncmp(data,"image/svg+xml",13) == 0) {
+ /* JPEG2000 image */
+ data_is_svg = true;
+ data_is_image = true;
+ data += 13;
+ }
+ else { /* unrecognized option; skip it */
+ while (*data) {
+ if (((*data) == ';') || ((*data) == ',')) {
+ break;
+ }
+ data++;
+ }
+ }
+ if ((*data) == ';') {
+ data++;
+ continue;
+ }
+ if ((*data) == ',') {
+ data++;
+ break;
+ }
+ }
+
+ if ((*data) && data_is_image && !data_is_svg && data_is_base64) {
+ GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
+
+ if (!loader) return nullptr;
+
+ gsize decoded_len = 0;
+ guchar *decoded = g_base64_decode(data, &decoded_len);
+
+ if (gdk_pixbuf_loader_write(loader, decoded, decoded_len, nullptr)) {
+ gdk_pixbuf_loader_close(loader, nullptr);
+ GdkPixbuf *buf = gdk_pixbuf_loader_get_pixbuf(loader);
+ if (buf) {
+ g_object_ref(buf);
+ buf = Pixbuf::apply_embedded_orientation(buf);
+ pixbuf = new Pixbuf(buf);
+
+ GdkPixbufFormat *fmt = gdk_pixbuf_loader_get_format(loader);
+ gchar *fmt_name = gdk_pixbuf_format_get_name(fmt);
+ pixbuf->_setMimeData(decoded, decoded_len, fmt_name);
+ g_free(fmt_name);
+ } else {
+ g_free(decoded);
+ }
+ } else {
+ g_free(decoded);
+ }
+ g_object_unref(loader);
+ }
+
+ if ((*data) && data_is_image && data_is_svg && data_is_base64) {
+ gsize decoded_len = 0;
+ guchar *decoded = g_base64_decode(data, &decoded_len);
+ std::unique_ptr<SPDocument> svgDoc(
+ SPDocument::createNewDocFromMem(reinterpret_cast<gchar const *>(decoded), decoded_len, false));
+ // Check the document loaded properly
+ if (svgDoc == nullptr) {
+ return nullptr;
+ }
+ if (svgDoc->getRoot() == nullptr)
+ {
+ return nullptr;
+ }
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double dpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", 96.0);
+ if (svgdpi && svgdpi > 0) {
+ dpi = svgdpi;
+ }
+
+ // Get the size of the document
+ Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
+ Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
+ const double svgWidth_px = svgWidth.value("px");
+ const double svgHeight_px = svgHeight.value("px");
+ if (svgWidth_px < 0 || svgHeight_px < 0) {
+ g_warning("create_from_data_uri: malformed document: svgWidth_px=%f, svgHeight_px=%f", svgWidth_px,
+ svgHeight_px);
+ return nullptr;
+ }
+
+ assert(!pixbuf);
+ Geom::Rect area(0, 0, svgWidth_px, svgHeight_px);
+ pixbuf = sp_generate_internal_bitmap(svgDoc.get(), area, dpi);
+ GdkPixbuf const *buf = pixbuf->getPixbufRaw();
+
+ // Tidy up
+ if (buf == nullptr) {
+ std::cerr << "Pixbuf::create_from_data: failed to load contents: " << std::endl;
+ delete pixbuf;
+ g_free(decoded);
+ return nullptr;
+ } else {
+ pixbuf->_setMimeData(decoded, decoded_len, "svg+xml");
+ }
+ }
+
+ return pixbuf;
+}
+
+Pixbuf *Pixbuf::create_from_file(std::string const &fn, double svgdpi)
+{
+ Pixbuf *pb = nullptr;
+ // test correctness of filename
+ if (!g_file_test(fn.c_str(), G_FILE_TEST_EXISTS)) {
+ return nullptr;
+ }
+ GStatBuf stdir;
+ int val = g_stat(fn.c_str(), &stdir);
+ if (val == 0 && stdir.st_mode & S_IFDIR){
+ return nullptr;
+ }
+ // we need to load the entire file into memory,
+ // since we'll store it as MIME data
+ gchar *data = nullptr;
+ gsize len = 0;
+ GError *error = nullptr;
+
+ if (g_file_get_contents(fn.c_str(), &data, &len, &error)) {
+
+ if (error != nullptr) {
+ std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
+ std::cerr << " (" << fn << ")" << std::endl;
+ return nullptr;
+ }
+
+ pb = Pixbuf::create_from_buffer(std::move(data), len, svgdpi, fn);
+
+ if (pb) {
+ pb->_mod_time = stdir.st_mtime;
+ }
+ } else {
+ std::cerr << "Pixbuf::create_from_file: failed to get contents: " << fn << std::endl;
+ return nullptr;
+ }
+
+ return pb;
+}
+
+GdkPixbuf *Pixbuf::apply_embedded_orientation(GdkPixbuf *buf)
+{
+ GdkPixbuf *old = buf;
+ buf = gdk_pixbuf_apply_embedded_orientation(buf);
+ g_object_unref(old);
+ return buf;
+}
+
+Pixbuf *Pixbuf::create_from_buffer(std::string const &buffer, double svgdpi, std::string const &fn)
+{
+#if GLIB_CHECK_VERSION(2,67,3)
+ auto datacopy = (gchar *)g_memdup2(buffer.data(), buffer.size());
+#else
+ auto datacopy = (gchar *)g_memdup(buffer.data(), buffer.size());
+#endif
+ return Pixbuf::create_from_buffer(std::move(datacopy), buffer.size(), svgdpi, fn);
+}
+
+Pixbuf *Pixbuf::create_from_buffer(gchar *&&data, gsize len, double svgdpi, std::string const &fn)
+{
+ Pixbuf *pb = nullptr;
+ GError *error = nullptr;
+ {
+ GdkPixbuf *buf = nullptr;
+ GdkPixbufLoader *loader = nullptr;
+ std::string::size_type idx;
+ idx = fn.rfind('.');
+ bool is_svg = false;
+ if(idx != std::string::npos)
+ {
+ if (boost::iequals(fn.substr(idx+1).c_str(), "svg")) {
+
+ std::unique_ptr<SPDocument> svgDoc(SPDocument::createNewDocFromMem(data, len, true));
+
+ // Check the document loaded properly
+ if (svgDoc == nullptr) {
+ return nullptr;
+ }
+ if (svgDoc->getRoot() == nullptr)
+ {
+ return nullptr;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double dpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", 96.0);
+ if (svgdpi && svgdpi > 0) {
+ dpi = svgdpi;
+ }
+
+ // Get the size of the document
+ Inkscape::Util::Quantity svgWidth = svgDoc->getWidth();
+ Inkscape::Util::Quantity svgHeight = svgDoc->getHeight();
+ const double svgWidth_px = svgWidth.value("px");
+ const double svgHeight_px = svgHeight.value("px");
+ if (svgWidth_px < 0 || svgHeight_px < 0) {
+ g_warning("create_from_buffer: malformed document: svgWidth_px=%f, svgHeight_px=%f", svgWidth_px,
+ svgHeight_px);
+ return nullptr;
+ }
+
+ Geom::Rect area(0, 0, svgWidth_px, svgHeight_px);
+ pb = sp_generate_internal_bitmap(svgDoc.get(), area, dpi);
+ buf = pb->getPixbufRaw();
+
+ // Tidy up
+ if (buf == nullptr) {
+ delete pb;
+ return nullptr;
+ }
+ buf = Pixbuf::apply_embedded_orientation(buf);
+ is_svg = true;
+ }
+ }
+ if (!is_svg) {
+ loader = gdk_pixbuf_loader_new();
+ gdk_pixbuf_loader_write(loader, (guchar *) data, len, &error);
+ if (error != nullptr) {
+ std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
+ std::cerr << " (" << fn << ")" << std::endl;
+ g_free(data);
+ g_object_unref(loader);
+ return nullptr;
+ }
+
+ gdk_pixbuf_loader_close(loader, &error);
+ if (error != nullptr) {
+ std::cerr << "Pixbuf::create_from_file: " << error->message << std::endl;
+ std::cerr << " (" << fn << ")" << std::endl;
+ g_free(data);
+ g_object_unref(loader);
+ return nullptr;
+ }
+
+ buf = gdk_pixbuf_loader_get_pixbuf(loader);
+ if (buf) {
+ // gdk_pixbuf_loader_get_pixbuf returns a borrowed reference
+ g_object_ref(buf);
+ buf = Pixbuf::apply_embedded_orientation(buf);
+ pb = new Pixbuf(buf);
+ }
+ }
+
+ if (pb) {
+ pb->_path = fn;
+ if (!is_svg) {
+ GdkPixbufFormat *fmt = gdk_pixbuf_loader_get_format(loader);
+ gchar *fmt_name = gdk_pixbuf_format_get_name(fmt);
+ pb->_setMimeData((guchar *) data, len, fmt_name);
+ g_free(fmt_name);
+ g_object_unref(loader);
+ } else {
+ pb->_setMimeData((guchar *) data, len, "svg");
+ }
+ } else {
+ std::cerr << "Pixbuf::create_from_file: failed to load contents: " << fn << std::endl;
+ g_free(data);
+ }
+
+ // TODO: we could also read DPI, ICC profile, gamma correction, and other information
+ // from the file. This can be done by using format-specific libraries e.g. libpng.
+ }
+
+ return pb;
+}
+
+/**
+ * Converts the pixbuf to GdkPixbuf pixel format.
+ * The returned pixbuf can be used e.g. in calls to gdk_pixbuf_save().
+ */
+GdkPixbuf *Pixbuf::getPixbufRaw(bool convert_format)
+{
+ if (convert_format) {
+ ensurePixelFormat(PF_GDK);
+ }
+ return _pixbuf;
+}
+
+/**
+ * Converts the pixbuf to Cairo pixel format and returns an image surface
+ * which can be used as a source.
+ *
+ * The returned surface is owned by the GdkPixbuf and should not be freed.
+ * Calling this function causes the pixbuf to be unsuitable for use
+ * with GTK drawing functions until ensurePixelFormat(Pixbuf::PIXEL_FORMAT_PIXBUF) is called.
+ */
+cairo_surface_t *Pixbuf::getSurfaceRaw(bool convert_format)
+{
+ if (convert_format) {
+ ensurePixelFormat(PF_CAIRO);
+ }
+ return _surface;
+}
+
+/* Declaring this function in the header requires including <gdkmm/pixbuf.h>,
+ * which stupidly includes <glibmm.h> which in turn pulls in <glibmm/threads.h>.
+ * However, since glib 2.32, <glibmm/threads.h> has to be included before <glib.h>
+ * when compiling with G_DISABLE_DEPRECATED, as we do in non-release builds.
+ * This necessitates spamming a lot of files with #include <glibmm/threads.h>
+ * at the top.
+ *
+ * Since we don't really use gdkmm, do not define this function for now. */
+
+/*
+Glib::RefPtr<Gdk::Pixbuf> Pixbuf::getPixbuf(bool convert_format = true)
+{
+ g_object_ref(_pixbuf);
+ Glib::RefPtr<Gdk::Pixbuf> p(getPixbuf(convert_format));
+ return p;
+}
+*/
+
+Cairo::RefPtr<Cairo::Surface> Pixbuf::getSurface(bool convert_format)
+{
+ Cairo::RefPtr<Cairo::Surface> p(new Cairo::Surface(getSurfaceRaw(convert_format), false));
+ return p;
+}
+
+/** Retrieves the original compressed data for the surface, if any.
+ * The returned data belongs to the object and should not be freed. */
+guchar const *Pixbuf::getMimeData(gsize &len, std::string &mimetype) const
+{
+ static gchar const *mimetypes[] = {
+ CAIRO_MIME_TYPE_JPEG, CAIRO_MIME_TYPE_JP2, CAIRO_MIME_TYPE_PNG, nullptr };
+ static guint mimetypes_len = g_strv_length(const_cast<gchar**>(mimetypes));
+
+ guchar const *data = nullptr;
+
+ for (guint i = 0; i < mimetypes_len; ++i) {
+ unsigned long len_long = 0;
+ cairo_surface_get_mime_data(const_cast<cairo_surface_t*>(_surface), mimetypes[i], &data, &len_long);
+ if (data != nullptr) {
+ len = len_long;
+ mimetype = mimetypes[i];
+ break;
+ }
+ }
+
+ return data;
+}
+
+int Pixbuf::width() const {
+ return gdk_pixbuf_get_width(const_cast<GdkPixbuf*>(_pixbuf));
+}
+int Pixbuf::height() const {
+ return gdk_pixbuf_get_height(const_cast<GdkPixbuf*>(_pixbuf));
+}
+int Pixbuf::rowstride() const {
+ return gdk_pixbuf_get_rowstride(const_cast<GdkPixbuf*>(_pixbuf));
+}
+guchar const *Pixbuf::pixels() const {
+ return gdk_pixbuf_get_pixels(const_cast<GdkPixbuf*>(_pixbuf));
+}
+guchar *Pixbuf::pixels() {
+ return gdk_pixbuf_get_pixels(_pixbuf);
+}
+void Pixbuf::markDirty() {
+ cairo_surface_mark_dirty(_surface);
+}
+
+void Pixbuf::_forceAlpha()
+{
+ if (gdk_pixbuf_get_has_alpha(_pixbuf)) return;
+
+ GdkPixbuf *old = _pixbuf;
+ _pixbuf = gdk_pixbuf_add_alpha(old, FALSE, 0, 0, 0);
+ g_object_unref(old);
+}
+
+void Pixbuf::_setMimeData(guchar *data, gsize len, Glib::ustring const &format)
+{
+ gchar const *mimetype = nullptr;
+
+ if (format == "jpeg") {
+ mimetype = CAIRO_MIME_TYPE_JPEG;
+ } else if (format == "jpeg2000") {
+ mimetype = CAIRO_MIME_TYPE_JP2;
+ } else if (format == "png") {
+ mimetype = CAIRO_MIME_TYPE_PNG;
+ }
+
+ if (mimetype != nullptr) {
+ cairo_surface_set_mime_data(_surface, mimetype, data, len, g_free, data);
+ //g_message("Setting Cairo MIME data: %s", mimetype);
+ } else {
+ g_free(data);
+ //g_message("Not setting Cairo MIME data: unknown format %s", name.c_str());
+ }
+}
+
+void Pixbuf::ensurePixelFormat(PixelFormat fmt)
+{
+ if (_pixel_format == PF_GDK) {
+ if (fmt == PF_GDK) {
+ return;
+ }
+ if (fmt == PF_CAIRO) {
+ convert_pixels_pixbuf_to_argb32(
+ gdk_pixbuf_get_pixels(_pixbuf),
+ gdk_pixbuf_get_width(_pixbuf),
+ gdk_pixbuf_get_height(_pixbuf),
+ gdk_pixbuf_get_rowstride(_pixbuf));
+ _pixel_format = fmt;
+ return;
+ }
+ g_assert_not_reached();
+ }
+ if (_pixel_format == PF_CAIRO) {
+ if (fmt == PF_GDK) {
+ convert_pixels_argb32_to_pixbuf(
+ gdk_pixbuf_get_pixels(_pixbuf),
+ gdk_pixbuf_get_width(_pixbuf),
+ gdk_pixbuf_get_height(_pixbuf),
+ gdk_pixbuf_get_rowstride(_pixbuf));
+ _pixel_format = fmt;
+ return;
+ }
+ if (fmt == PF_CAIRO) {
+ return;
+ }
+ g_assert_not_reached();
+ }
+ g_assert_not_reached();
+}
+
+} // namespace Inkscape
+
+/*
+ * Can be called recursively.
+ * If optimize_stroke == false, the view Rect is not used.
+ */
+static void
+feed_curve_to_cairo(cairo_t *cr, Geom::Curve const &c, Geom::Affine const & trans, Geom::Rect view, bool optimize_stroke)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ unsigned order = 0;
+ if (Geom::BezierCurve const* b = dynamic_cast<Geom::BezierCurve const*>(&c)) {
+ order = b->order();
+ }
+
+ // handle the three typical curve cases
+ switch (order) {
+ case 1:
+ {
+ Geom::Point end_tr = c.finalPoint() * trans;
+ if (!optimize_stroke) {
+ cairo_line_to(cr, end_tr[0], end_tr[1]);
+ } else {
+ Geom::Rect swept(c.initialPoint()*trans, end_tr);
+ if (swept.intersects(view)) {
+ cairo_line_to(cr, end_tr[0], end_tr[1]);
+ } else {
+ cairo_move_to(cr, end_tr[0], end_tr[1]);
+ }
+ }
+ }
+ break;
+ case 2:
+ {
+ Geom::QuadraticBezier const *quadratic_bezier = static_cast<Geom::QuadraticBezier const*>(&c);
+ std::vector<Geom::Point> points = quadratic_bezier->controlPoints();
+ points[0] *= trans;
+ points[1] *= trans;
+ points[2] *= trans;
+ // degree-elevate to cubic Bezier, since Cairo doesn't do quadratic Beziers
+ Geom::Point b1 = points[0] + (2./3) * (points[1] - points[0]);
+ Geom::Point b2 = b1 + (1./3) * (points[2] - points[0]);
+ if (!optimize_stroke) {
+ cairo_curve_to(cr, b1[X], b1[Y], b2[X], b2[Y], points[2][X], points[2][Y]);
+ } else {
+ Geom::Rect swept(points[0], points[2]);
+ swept.expandTo(points[1]);
+ if (swept.intersects(view)) {
+ cairo_curve_to(cr, b1[X], b1[Y], b2[X], b2[Y], points[2][X], points[2][Y]);
+ } else {
+ cairo_move_to(cr, points[2][X], points[2][Y]);
+ }
+ }
+ }
+ break;
+ case 3:
+ {
+ Geom::CubicBezier const *cubic_bezier = static_cast<Geom::CubicBezier const*>(&c);
+ std::vector<Geom::Point> points = cubic_bezier->controlPoints();
+ //points[0] *= trans; // don't do this one here for fun: it is only needed for optimized strokes
+ points[1] *= trans;
+ points[2] *= trans;
+ points[3] *= trans;
+ if (!optimize_stroke) {
+ cairo_curve_to(cr, points[1][X], points[1][Y], points[2][X], points[2][Y], points[3][X], points[3][Y]);
+ } else {
+ points[0] *= trans; // didn't transform this point yet
+ Geom::Rect swept(points[0], points[3]);
+ swept.expandTo(points[1]);
+ swept.expandTo(points[2]);
+ if (swept.intersects(view)) {
+ cairo_curve_to(cr, points[1][X], points[1][Y], points[2][X], points[2][Y], points[3][X], points[3][Y]);
+ } else {
+ cairo_move_to(cr, points[3][X], points[3][Y]);
+ }
+ }
+ }
+ break;
+ default:
+ {
+ if (Geom::EllipticalArc const *arc = dynamic_cast<Geom::EllipticalArc const*>(&c)) {
+ if (arc->isChord()) {
+ Geom::Point endPoint(arc->finalPoint());
+ cairo_line_to(cr, endPoint[0], endPoint[1]);
+ } else {
+ Geom::Affine xform = arc->unitCircleTransform() * trans;
+ // Don't draw anything if the angle is borked
+ if(std::isnan(arc->initialAngle()) || std::isnan(arc->finalAngle())) {
+ g_warning("Bad angle while drawing EllipticalArc");
+ break;
+ }
+
+ // Apply the transformation to the current context
+ cairo_matrix_t cm;
+ cm.xx = xform[0];
+ cm.xy = xform[2];
+ cm.x0 = xform[4];
+ cm.yx = xform[1];
+ cm.yy = xform[3];
+ cm.y0 = xform[5];
+
+ cairo_save(cr);
+ cairo_transform(cr, &cm);
+
+ // Draw the circle
+ if (arc->sweep()) {
+ cairo_arc(cr, 0, 0, 1, arc->initialAngle(), arc->finalAngle());
+ } else {
+ cairo_arc_negative(cr, 0, 0, 1, arc->initialAngle(), arc->finalAngle());
+ }
+ // Revert the current context
+ cairo_restore(cr);
+ }
+ } else {
+ // handles sbasis as well as all other curve types
+ // this is very slow
+ Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
+
+ // recurse to convert the new path resulting from the sbasis to svgd
+ for (const auto & iter : sbasis_path) {
+ feed_curve_to_cairo(cr, iter, trans, view, optimize_stroke);
+ }
+ }
+ }
+ break;
+ }
+}
+
+
+/** Feeds path-creating calls to the cairo context translating them from the Path */
+static void
+feed_path_to_cairo (cairo_t *ct, Geom::Path const &path)
+{
+ if (path.empty())
+ return;
+
+ cairo_move_to(ct, path.initialPoint()[0], path.initialPoint()[1] );
+
+ for (Geom::Path::const_iterator cit = path.begin(); cit != path.end_open(); ++cit) {
+ feed_curve_to_cairo(ct, *cit, Geom::identity(), Geom::Rect(), false); // optimize_stroke is false, so the view rect is not used
+ }
+
+ if (path.closed()) {
+ cairo_close_path(ct);
+ }
+}
+
+/** Feeds path-creating calls to the cairo context translating them from the Path, with the given transform and shift */
+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)
+{
+ if (!area)
+ return;
+ if (path.empty())
+ return;
+
+ // Transform all coordinates to coords within "area"
+ Geom::Point shift = area->min();
+ Geom::Rect view = *area;
+ view.expandBy (stroke_width);
+ view = view * (Geom::Affine)Geom::Translate(-shift);
+ // Pass transformation to feed_curve, so that we don't need to create a whole new path.
+ Geom::Affine transshift(trans * Geom::Translate(-shift));
+
+ Geom::Point initial = path.initialPoint() * transshift;
+ cairo_move_to(ct, initial[0], initial[1] );
+
+ for(Geom::Path::const_iterator cit = path.begin(); cit != path.end_open(); ++cit) {
+ feed_curve_to_cairo(ct, *cit, transshift, view, optimize_stroke);
+ }
+
+ if (path.closed()) {
+ if (!optimize_stroke) {
+ cairo_close_path(ct);
+ } else {
+ cairo_line_to(ct, initial[0], initial[1]);
+ /* We cannot use cairo_close_path(ct) here because some parts of the path may have been
+ clipped and not drawn (maybe the before last segment was outside view area), which
+ would result in closing the "subpath" after the last interruption, not the entire path.
+
+ However, according to cairo documentation:
+ The behavior of cairo_close_path() is distinct from simply calling cairo_line_to() with the equivalent coordinate
+ in the case of stroking. When a closed sub-path is stroked, there are no caps on the ends of the sub-path. Instead,
+ there is a line join connecting the final and initial segments of the sub-path.
+
+ The correct fix will be possible when cairo introduces methods for moving without
+ ending/starting subpaths, which we will use for skipping invisible segments; then we
+ will be able to use cairo_close_path here. This issue also affects ps/eps/pdf export,
+ see bug 168129
+ */
+ }
+ }
+}
+
+/** Feeds path-creating calls to the cairo context translating them from the PathVector, with the given transform and shift
+ * One must have done cairo_new_path(ct); before calling this function. */
+void
+feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv, Geom::Affine trans, Geom::OptRect area, bool optimize_stroke, double stroke_width)
+{
+ if (!area)
+ return;
+ if (pathv.empty())
+ return;
+
+ for(const auto & it : pathv) {
+ feed_path_to_cairo(ct, it, trans, area, optimize_stroke, stroke_width);
+ }
+}
+
+/** Feeds path-creating calls to the cairo context translating them from the PathVector
+ * One must have done cairo_new_path(ct); before calling this function. */
+void
+feed_pathvector_to_cairo (cairo_t *ct, Geom::PathVector const &pathv)
+{
+ if (pathv.empty())
+ return;
+
+ for(const auto & it : pathv) {
+ feed_path_to_cairo(ct, it);
+ }
+}
+
+SPColorInterpolation
+get_cairo_surface_ci(cairo_surface_t *surface) {
+ void* data = cairo_surface_get_user_data( surface, &ink_color_interpolation_key );
+ if( data != nullptr ) {
+ return (SPColorInterpolation)GPOINTER_TO_INT( data );
+ } else {
+ return SP_CSS_COLOR_INTERPOLATION_AUTO;
+ }
+}
+
+/** Set the color_interpolation_value for a Cairo surface.
+ * Transform the surface between sRGB and linearRGB if necessary. */
+void
+set_cairo_surface_ci(cairo_surface_t *surface, SPColorInterpolation ci) {
+
+ if( cairo_surface_get_content( surface ) != CAIRO_CONTENT_ALPHA ) {
+
+ SPColorInterpolation ci_in = get_cairo_surface_ci( surface );
+
+ if( ci_in == SP_CSS_COLOR_INTERPOLATION_SRGB &&
+ ci == SP_CSS_COLOR_INTERPOLATION_LINEARRGB ) {
+ ink_cairo_surface_srgb_to_linear( surface );
+ }
+ if( ci_in == SP_CSS_COLOR_INTERPOLATION_LINEARRGB &&
+ ci == SP_CSS_COLOR_INTERPOLATION_SRGB ) {
+ ink_cairo_surface_linear_to_srgb( surface );
+ }
+
+ cairo_surface_set_user_data(surface, &ink_color_interpolation_key, GINT_TO_POINTER (ci), nullptr);
+ }
+}
+
+void
+copy_cairo_surface_ci(cairo_surface_t *in, cairo_surface_t *out) {
+ cairo_surface_set_user_data(out, &ink_color_interpolation_key, cairo_surface_get_user_data(in, &ink_color_interpolation_key), nullptr);
+}
+
+void
+ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba)
+{
+ 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));
+}
+
+void
+ink_cairo_set_source_color(cairo_t *ct, SPColor const &c, double opacity)
+{
+ cairo_set_source_rgba(ct, c.v.c[0], c.v.c[1], c.v.c[2], opacity);
+}
+
+void ink_matrix_to_2geom(Geom::Affine &m, cairo_matrix_t const &cm)
+{
+ m[0] = cm.xx;
+ m[2] = cm.xy;
+ m[4] = cm.x0;
+ m[1] = cm.yx;
+ m[3] = cm.yy;
+ m[5] = cm.y0;
+}
+
+void ink_matrix_to_cairo(cairo_matrix_t &cm, Geom::Affine const &m)
+{
+ cm.xx = m[0];
+ cm.xy = m[2];
+ cm.x0 = m[4];
+ cm.yx = m[1];
+ cm.yy = m[3];
+ cm.y0 = m[5];
+}
+
+void
+ink_cairo_transform(cairo_t *ct, Geom::Affine const &m)
+{
+ cairo_matrix_t cm;
+ ink_matrix_to_cairo(cm, m);
+ cairo_transform(ct, &cm);
+}
+
+void
+ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m)
+{
+ cairo_matrix_t cm;
+ ink_matrix_to_cairo(cm, m);
+ cairo_pattern_set_matrix(cp, &cm);
+}
+
+void
+ink_cairo_set_hairline(cairo_t *ct)
+{
+#ifdef CAIRO_HAS_HAIRLINE
+ cairo_set_hairline(ct);
+#else
+ // As a backup, use a device unit of 1
+ double x = 1, y = 1;
+ cairo_device_to_user_distance(ct, &x, &y);
+ cairo_set_line_width(ct, std::min(x, y));
+#endif
+}
+
+/**
+ * Create an exact copy of a surface.
+ * Creates a surface that has the same type, content type, dimensions and contents
+ * as the specified surface.
+ */
+cairo_surface_t *
+ink_cairo_surface_copy(cairo_surface_t *s)
+{
+ cairo_surface_t *ns = ink_cairo_surface_create_identical(s);
+
+ if (cairo_surface_get_type(s) == CAIRO_SURFACE_TYPE_IMAGE) {
+ // use memory copy instead of using a Cairo context
+ cairo_surface_flush(s);
+ int stride = cairo_image_surface_get_stride(s);
+ int h = cairo_image_surface_get_height(s);
+ memcpy(cairo_image_surface_get_data(ns), cairo_image_surface_get_data(s), stride * h);
+ cairo_surface_mark_dirty(ns);
+ } else {
+ // generic implementation
+ cairo_t *ct = cairo_create(ns);
+ cairo_set_source_surface(ct, s, 0, 0);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ }
+
+ return ns;
+}
+
+/**
+ * Create an exact copy of an image surface.
+ */
+Cairo::RefPtr<Cairo::ImageSurface>
+ink_cairo_surface_copy(Cairo::RefPtr<Cairo::ImageSurface> surface )
+{
+ int width = surface->get_width();
+ int height = surface->get_height();
+ int stride = surface->get_stride();
+ auto new_surface = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, width, height); // device scale?
+
+ surface->flush();
+ memcpy(new_surface->get_data(), surface->get_data(), stride * height);
+ new_surface->mark_dirty(); // Clear caches. Mandatory after messing directly with contents.
+
+ return new_surface;
+}
+
+/**
+ * Create a surface that differs only in pixel content.
+ * Creates a surface that has the same type, content type and dimensions
+ * as the specified surface. Pixel contents are not copied.
+ */
+cairo_surface_t *
+ink_cairo_surface_create_identical(cairo_surface_t *s)
+{
+ cairo_surface_t *ns = ink_cairo_surface_create_same_size(s, cairo_surface_get_content(s));
+ cairo_surface_set_user_data(ns, &ink_color_interpolation_key, cairo_surface_get_user_data(s, &ink_color_interpolation_key), nullptr);
+ return ns;
+}
+
+cairo_surface_t *
+ink_cairo_surface_create_same_size(cairo_surface_t *s, cairo_content_t c)
+{
+ // ink_cairo_surface_get_width()/height() returns value in pixels
+ // cairo_surface_create_similar() uses device units
+ double x_scale = 0;
+ double y_scale = 0;
+ cairo_surface_get_device_scale( s, &x_scale, &y_scale );
+
+ assert (x_scale > 0);
+ assert (y_scale > 0);
+
+ cairo_surface_t *ns =
+ cairo_surface_create_similar(s, c,
+ ink_cairo_surface_get_width(s)/x_scale,
+ ink_cairo_surface_get_height(s)/y_scale);
+ return ns;
+}
+
+/**
+ * Extract the alpha channel into a new surface.
+ * Creates a surface with a content type of CAIRO_CONTENT_ALPHA that contains
+ * the alpha values of pixels from @a s.
+ */
+cairo_surface_t *
+ink_cairo_extract_alpha(cairo_surface_t *s)
+{
+ cairo_surface_t *alpha = ink_cairo_surface_create_same_size(s, CAIRO_CONTENT_ALPHA);
+
+ cairo_t *ct = cairo_create(alpha);
+ cairo_set_source_surface(ct, s, 0, 0);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+
+ return alpha;
+}
+
+cairo_surface_t *
+ink_cairo_surface_create_output(cairo_surface_t *image, cairo_surface_t *bg)
+{
+ cairo_content_t imgt = cairo_surface_get_content(image);
+ cairo_content_t bgt = cairo_surface_get_content(bg);
+ cairo_surface_t *out = nullptr;
+
+ if (bgt == CAIRO_CONTENT_ALPHA && imgt == CAIRO_CONTENT_ALPHA) {
+ out = ink_cairo_surface_create_identical(bg);
+ } else {
+ out = ink_cairo_surface_create_same_size(bg, CAIRO_CONTENT_COLOR_ALPHA);
+ }
+
+ return out;
+}
+
+void
+ink_cairo_surface_blit(cairo_surface_t *src, cairo_surface_t *dest)
+{
+ if (cairo_surface_get_type(src) == CAIRO_SURFACE_TYPE_IMAGE &&
+ cairo_surface_get_type(dest) == CAIRO_SURFACE_TYPE_IMAGE &&
+ cairo_image_surface_get_format(src) == cairo_image_surface_get_format(dest) &&
+ cairo_image_surface_get_height(src) == cairo_image_surface_get_height(dest) &&
+ cairo_image_surface_get_width(src) == cairo_image_surface_get_width(dest) &&
+ cairo_image_surface_get_stride(src) == cairo_image_surface_get_stride(dest))
+ {
+ // use memory copy instead of using a Cairo context
+ cairo_surface_flush(src);
+ int stride = cairo_image_surface_get_stride(src);
+ int h = cairo_image_surface_get_height(src);
+ memcpy(cairo_image_surface_get_data(dest), cairo_image_surface_get_data(src), stride * h);
+ cairo_surface_mark_dirty(dest);
+ } else {
+ // generic implementation
+ cairo_t *ct = cairo_create(dest);
+ cairo_set_source_surface(ct, src, 0, 0);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_paint(ct);
+ cairo_destroy(ct);
+ }
+}
+
+/**
+ * Return width in pixels.
+ */
+int
+ink_cairo_surface_get_width(cairo_surface_t *surface)
+{
+ // For now only image surface is handled.
+ // Later add others, e.g. cairo-gl
+ assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
+ return cairo_image_surface_get_width(surface);
+}
+
+/**
+ * Return height in pixels.
+ */
+int
+ink_cairo_surface_get_height(cairo_surface_t *surface)
+{
+ assert(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
+ return cairo_image_surface_get_height(surface);
+}
+
+static int ink_cairo_surface_average_color_internal(cairo_surface_t *surface, double &rf, double &gf, double &bf, double &af)
+{
+ rf = gf = bf = af = 0.0;
+ cairo_surface_flush(surface);
+ int width = cairo_image_surface_get_width(surface);
+ int height = cairo_image_surface_get_height(surface);
+ int stride = cairo_image_surface_get_stride(surface);
+ unsigned char *data = cairo_image_surface_get_data(surface);
+
+ /* TODO convert this to OpenMP somehow */
+ for (int y = 0; y < height; ++y, data += stride) {
+ for (int x = 0; x < width; ++x) {
+ guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
+ EXTRACT_ARGB32(px, a,r,g,b)
+ rf += r / 255.0;
+ gf += g / 255.0;
+ bf += b / 255.0;
+ af += a / 255.0;
+ }
+ }
+ return width * height;
+}
+
+guint32 ink_cairo_surface_average_color(cairo_surface_t *surface)
+{
+ double rf,gf,bf,af;
+ ink_cairo_surface_average_color_premul(surface, rf,gf,bf,af);
+ guint32 r = round(rf * 255);
+ guint32 g = round(gf * 255);
+ guint32 b = round(bf * 255);
+ guint32 a = round(af * 255);
+ ASSEMBLE_ARGB32(px, a,r,g,b);
+ return px;
+}
+// We extract colors from pattern background, if we need to extract sometimes from a gradient we can add
+// a extra parameter with the spot number and use cairo_pattern_get_color_stop_rgba
+// 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
+guint32 ink_cairo_pattern_get_argb32(cairo_pattern_t *pattern)
+{
+ double red = 0;
+ double green = 0;
+ double blue = 0;
+ double alpha = 0;
+ auto status = cairo_pattern_get_rgba(pattern, &red, &green, &blue, &alpha);
+ if (status != CAIRO_STATUS_PATTERN_TYPE_MISMATCH) {
+ // in ARGB32 format
+ return SP_RGBA32_F_COMPOSE(alpha, red, green, blue);
+ }
+
+ cairo_surface_t *surface;
+ status = cairo_pattern_get_surface (pattern, &surface);
+ if (status != CAIRO_STATUS_PATTERN_TYPE_MISMATCH) {
+ // first pixel only
+ auto *pxbsurface = cairo_image_surface_get_data(surface);
+ return *reinterpret_cast<guint32 const *>(pxbsurface);
+ }
+ return 0;
+}
+
+void ink_cairo_surface_average_color(cairo_surface_t *surface, double &r, double &g, double &b, double &a)
+{
+ int count = ink_cairo_surface_average_color_internal(surface, r,g,b,a);
+
+ r /= a;
+ g /= a;
+ b /= a;
+ a /= count;
+
+ r = CLAMP(r, 0.0, 1.0);
+ g = CLAMP(g, 0.0, 1.0);
+ b = CLAMP(b, 0.0, 1.0);
+ a = CLAMP(a, 0.0, 1.0);
+}
+
+void ink_cairo_surface_average_color_premul(cairo_surface_t *surface, double &r, double &g, double &b, double &a)
+{
+ int count = ink_cairo_surface_average_color_internal(surface, r,g,b,a);
+
+ r /= count;
+ g /= count;
+ b /= count;
+ a /= count;
+
+ r = CLAMP(r, 0.0, 1.0);
+ g = CLAMP(g, 0.0, 1.0);
+ b = CLAMP(b, 0.0, 1.0);
+ a = CLAMP(a, 0.0, 1.0);
+}
+
+static guint32 srgb_to_linear( const guint32 c, const guint32 a ) {
+
+ const guint32 c1 = unpremul_alpha( c, a );
+
+ double cc = c1/255.0;
+
+ if( cc < 0.04045 ) {
+ cc /= 12.92;
+ } else {
+ cc = pow( (cc+0.055)/1.055, 2.4 );
+ }
+ cc *= 255.0;
+
+ const guint32 c2 = (int)cc;
+
+ return premul_alpha( c2, a );
+}
+
+static guint32 linear_to_srgb( const guint32 c, const guint32 a ) {
+
+ const guint32 c1 = unpremul_alpha( c, a );
+
+ double cc = c1/255.0;
+
+ if( cc < 0.0031308 ) {
+ cc *= 12.92;
+ } else {
+ cc = pow( cc, 1.0/2.4 )*1.055-0.055;
+ }
+ cc *= 255.0;
+
+ const guint32 c2 = (int)cc;
+
+ return premul_alpha( c2, a );
+}
+
+struct SurfaceSrgbToLinear {
+
+ guint32 operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a,r,g,b) ; // Unneeded semi-colon for indenting
+ if( a != 0 ) {
+ r = srgb_to_linear( r, a );
+ g = srgb_to_linear( g, a );
+ b = srgb_to_linear( b, a );
+ }
+ ASSEMBLE_ARGB32(out, a,r,g,b);
+ return out;
+ }
+private:
+ /* None */
+};
+
+int ink_cairo_surface_srgb_to_linear(cairo_surface_t *surface)
+{
+ cairo_surface_flush(surface);
+ int width = cairo_image_surface_get_width(surface);
+ int height = cairo_image_surface_get_height(surface);
+ // int stride = cairo_image_surface_get_stride(surface);
+ // unsigned char *data = cairo_image_surface_get_data(surface);
+
+ ink_cairo_surface_filter( surface, surface, SurfaceSrgbToLinear() );
+
+ /* TODO convert this to OpenMP somehow */
+ // for (int y = 0; y < height; ++y, data += stride) {
+ // for (int x = 0; x < width; ++x) {
+ // guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
+ // EXTRACT_ARGB32(px, a,r,g,b) ; // Unneeded semi-colon for indenting
+ // if( a != 0 ) {
+ // r = srgb_to_linear( r, a );
+ // g = srgb_to_linear( g, a );
+ // b = srgb_to_linear( b, a );
+ // }
+ // ASSEMBLE_ARGB32(px2, a,r,g,b);
+ // *reinterpret_cast<guint32*>(data + 4*x) = px2;
+ // }
+ // }
+ return width * height;
+}
+
+struct SurfaceLinearToSrgb {
+
+ guint32 operator()(guint32 in) {
+ EXTRACT_ARGB32(in, a,r,g,b) ; // Unneeded semi-colon for indenting
+ if( a != 0 ) {
+ r = linear_to_srgb( r, a );
+ g = linear_to_srgb( g, a );
+ b = linear_to_srgb( b, a );
+ }
+ ASSEMBLE_ARGB32(out, a,r,g,b);
+ return out;
+ }
+private:
+ /* None */
+};
+
+SPBlendMode ink_cairo_operator_to_css_blend(cairo_operator_t cairo_operator)
+{
+ // All of the blend modes are implemented in Cairo as of 1.10.
+ // For a detailed description, see:
+ // http://cairographics.org/operators/
+ auto res = SP_CSS_BLEND_NORMAL;
+ switch (cairo_operator) {
+ case CAIRO_OPERATOR_MULTIPLY:
+ res = SP_CSS_BLEND_MULTIPLY;
+ break;
+ case CAIRO_OPERATOR_SCREEN:
+ res = SP_CSS_BLEND_SCREEN;
+ break;
+ case CAIRO_OPERATOR_DARKEN:
+ res = SP_CSS_BLEND_DARKEN;
+ break;
+ case CAIRO_OPERATOR_LIGHTEN:
+ res = SP_CSS_BLEND_LIGHTEN;
+ break;
+ case CAIRO_OPERATOR_OVERLAY:
+ res = SP_CSS_BLEND_OVERLAY;
+ break;
+ case CAIRO_OPERATOR_COLOR_DODGE:
+ res = SP_CSS_BLEND_COLORDODGE;
+ break;
+ case CAIRO_OPERATOR_COLOR_BURN:
+ res = SP_CSS_BLEND_COLORBURN;
+ break;
+ case CAIRO_OPERATOR_HARD_LIGHT:
+ res = SP_CSS_BLEND_HARDLIGHT;
+ break;
+ case CAIRO_OPERATOR_SOFT_LIGHT:
+ res = SP_CSS_BLEND_SOFTLIGHT;
+ break;
+ case CAIRO_OPERATOR_DIFFERENCE:
+ res = SP_CSS_BLEND_DIFFERENCE;
+ break;
+ case CAIRO_OPERATOR_EXCLUSION:
+ res = SP_CSS_BLEND_EXCLUSION;
+ break;
+ case CAIRO_OPERATOR_HSL_HUE:
+ res = SP_CSS_BLEND_HUE;
+ break;
+ case CAIRO_OPERATOR_HSL_SATURATION:
+ res = SP_CSS_BLEND_SATURATION;
+ break;
+ case CAIRO_OPERATOR_HSL_COLOR:
+ res = SP_CSS_BLEND_COLOR;
+ break;
+ case CAIRO_OPERATOR_HSL_LUMINOSITY:
+ res = SP_CSS_BLEND_LUMINOSITY;
+ break;
+ case CAIRO_OPERATOR_OVER:
+ default:
+ res = SP_CSS_BLEND_NORMAL;
+ break;
+ }
+ return res;
+}
+
+cairo_operator_t ink_css_blend_to_cairo_operator(SPBlendMode css_blend)
+{
+ // All of the blend modes are implemented in Cairo as of 1.10.
+ // For a detailed description, see:
+ // http://cairographics.org/operators/
+
+ cairo_operator_t res = CAIRO_OPERATOR_OVER;
+ switch (css_blend) {
+ case SP_CSS_BLEND_MULTIPLY:
+ res = CAIRO_OPERATOR_MULTIPLY;
+ break;
+ case SP_CSS_BLEND_SCREEN:
+ res = CAIRO_OPERATOR_SCREEN;
+ break;
+ case SP_CSS_BLEND_DARKEN:
+ res = CAIRO_OPERATOR_DARKEN;
+ break;
+ case SP_CSS_BLEND_LIGHTEN:
+ res = CAIRO_OPERATOR_LIGHTEN;
+ break;
+ case SP_CSS_BLEND_OVERLAY:
+ res = CAIRO_OPERATOR_OVERLAY;
+ break;
+ case SP_CSS_BLEND_COLORDODGE:
+ res = CAIRO_OPERATOR_COLOR_DODGE;
+ break;
+ case SP_CSS_BLEND_COLORBURN:
+ res = CAIRO_OPERATOR_COLOR_BURN;
+ break;
+ case SP_CSS_BLEND_HARDLIGHT:
+ res = CAIRO_OPERATOR_HARD_LIGHT;
+ break;
+ case SP_CSS_BLEND_SOFTLIGHT:
+ res = CAIRO_OPERATOR_SOFT_LIGHT;
+ break;
+ case SP_CSS_BLEND_DIFFERENCE:
+ res = CAIRO_OPERATOR_DIFFERENCE;
+ break;
+ case SP_CSS_BLEND_EXCLUSION:
+ res = CAIRO_OPERATOR_EXCLUSION;
+ break;
+ case SP_CSS_BLEND_HUE:
+ res = CAIRO_OPERATOR_HSL_HUE;
+ break;
+ case SP_CSS_BLEND_SATURATION:
+ res = CAIRO_OPERATOR_HSL_SATURATION;
+ break;
+ case SP_CSS_BLEND_COLOR:
+ res = CAIRO_OPERATOR_HSL_COLOR;
+ break;
+ case SP_CSS_BLEND_LUMINOSITY:
+ res = CAIRO_OPERATOR_HSL_LUMINOSITY;
+ break;
+ case SP_CSS_BLEND_NORMAL:
+ res = CAIRO_OPERATOR_OVER;
+ break;
+ default:
+ g_error("Invalid SPBlendMode %d", css_blend);
+ }
+ return res;
+}
+
+
+
+int ink_cairo_surface_linear_to_srgb(cairo_surface_t *surface)
+{
+ cairo_surface_flush(surface);
+ int width = cairo_image_surface_get_width(surface);
+ int height = cairo_image_surface_get_height(surface);
+ // int stride = cairo_image_surface_get_stride(surface);
+ // unsigned char *data = cairo_image_surface_get_data(surface);
+
+ ink_cairo_surface_filter( surface, surface, SurfaceLinearToSrgb() );
+
+ // /* TODO convert this to OpenMP somehow */
+ // for (int y = 0; y < height; ++y, data += stride) {
+ // for (int x = 0; x < width; ++x) {
+ // guint32 px = *reinterpret_cast<guint32*>(data + 4*x);
+ // EXTRACT_ARGB32(px, a,r,g,b) ; // Unneeded semi-colon for indenting
+ // if( a != 0 ) {
+ // r = linear_to_srgb( r, a );
+ // g = linear_to_srgb( g, a );
+ // b = linear_to_srgb( b, a );
+ // }
+ // ASSEMBLE_ARGB32(px2, a,r,g,b);
+ // *reinterpret_cast<guint32*>(data + 4*x) = px2;
+ // }
+ // }
+ return width * height;
+}
+
+cairo_pattern_t *
+ink_cairo_pattern_create_checkerboard(guint32 rgba, bool use_alpha)
+{
+ int const w = 6;
+ int const h = 6;
+
+ double r = SP_RGBA32_R_F(rgba);
+ double g = SP_RGBA32_G_F(rgba);
+ double b = SP_RGBA32_B_F(rgba);
+
+ float hsl[3];
+ SPColor::rgb_to_hsl_floatv(hsl, r, g, b);
+ hsl[2] += hsl[2] < 0.08 ? 0.08 : -0.08; // 0.08 = 0.77-0.69, the original checkerboard colors.
+
+ float rgb2[3];
+ SPColor::hsl_to_rgb_floatv(rgb2, hsl[0], hsl[1], hsl[2]);
+
+ cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 2*w, 2*h);
+
+ cairo_t *ct = cairo_create(s);
+ cairo_set_operator(ct, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_rgb(ct, r, g, b);
+ cairo_paint(ct);
+ cairo_set_source_rgb(ct, rgb2[0], rgb2[1], rgb2[2]);
+ cairo_rectangle(ct, 0, 0, w, h);
+ cairo_rectangle(ct, w, h, w, h);
+ cairo_fill(ct);
+ if (use_alpha) {
+ // use alpha to show opacity cover checkerboard
+ double a = SP_RGBA32_A_F(rgba);
+ if (a > 0.0) {
+ cairo_set_operator(ct, CAIRO_OPERATOR_OVER);
+ cairo_rectangle(ct, 0, 0, 2 * w, 2 * h);
+ cairo_set_source_rgba(ct, r, g, b, a);
+ cairo_fill(ct);
+ }
+ }
+ cairo_destroy(ct);
+
+ cairo_pattern_t *p = cairo_pattern_create_for_surface(s);
+ cairo_pattern_set_extend(p, CAIRO_EXTEND_REPEAT);
+ cairo_pattern_set_filter(p, CAIRO_FILTER_NEAREST);
+
+ cairo_surface_destroy(s);
+ return p;
+}
+
+
+/**
+ * Draw drop shadow around the 'rect' with given 'size' and 'color'; shadow extends to the right and bottom of rect.
+ */
+void ink_cairo_draw_drop_shadow(Cairo::RefPtr<Cairo::Context> ctx, const Geom::Rect& rect, double size, guint32 color, double color_alpha) {
+ // draw fake drop shadow built from gradients
+ const auto r = SP_RGBA32_R_F(color);
+ const auto g = SP_RGBA32_G_F(color);
+ const auto b = SP_RGBA32_B_F(color);
+ const auto a = color_alpha;
+ const Geom::Point corners[] = { rect.corner(0), rect.corner(1), rect.corner(2), rect.corner(3) };
+ // space for gradient shadow
+ double sw = size;
+ double half = sw / 2;
+ using Geom::X;
+ using Geom::Y;
+ // 8 gradients total: 4 sides + 4 corners
+ auto grad_top = Cairo::LinearGradient::create(0, corners[0][Y] + half, 0, corners[0][Y] - half);
+ auto grad_right = Cairo::LinearGradient::create(corners[1][X], 0, corners[1][X] + sw, 0);
+ auto grad_bottom = Cairo::LinearGradient::create(0, corners[2][Y], 0, corners[2][Y] + sw);
+ auto grad_left = Cairo::LinearGradient::create(corners[0][X] + half, 0, corners[0][X] - half, 0);
+ auto grad_btm_right = Cairo::RadialGradient::create(corners[2][X], corners[2][Y], 0, corners[2][X], corners[2][Y], sw);
+ auto grad_top_right = Cairo::RadialGradient::create(corners[1][X], corners[1][Y] + half, 0, corners[1][X], corners[1][Y] + half, sw);
+ auto grad_btm_left = Cairo::RadialGradient::create(corners[3][X] + half, corners[3][Y], 0, corners[3][X] + half, corners[3][Y], sw);
+ auto grad_top_left = Cairo::RadialGradient::create(corners[0][X], corners[0][Y], 0, corners[0][X], corners[0][Y], half);
+ const int N = 15; // number of gradient stops; stops used to make it non-linear
+ // using easing function here: (exp(a*(1-t)) - 1) / (exp(a) - 1);
+ // it has a nice property of growing from 0 to 1 for t in [0..1]
+ const auto A = 4.0; // this coefficient changes how steep the curve is and controls shadow drop-off
+ const auto denominator = exp(A) - 1;
+ for (int i = 0; i <= N; ++i) {
+ auto pos = static_cast<double>(i) / N;
+ // exponential decay for drop shadow - long tail, with values from 100% down to 0% opacity
+ auto t = 1 - pos; // reverse 't' so alpha drops from 1 to 0
+ auto alpha = (exp(A * t) - 1) / denominator;
+ grad_top->add_color_stop_rgba(pos, r, g, b, alpha * a);
+ grad_bottom->add_color_stop_rgba(pos, r, g, b, alpha * a);
+ grad_right->add_color_stop_rgba(pos, r, g, b, alpha * a);
+ grad_left->add_color_stop_rgba(pos, r, g, b, alpha * a);
+ grad_btm_right->add_color_stop_rgba(pos, r, g, b, alpha * a);
+ grad_top_right->add_color_stop_rgba(pos, r, g, b, alpha * a);
+ grad_btm_left->add_color_stop_rgba(pos, r, g, b, alpha * a);
+ // this left/top corner is just a silver of the shadow: half of it is "hidden" beneath the page
+ if (pos >= 0.5) {
+ grad_top_left->add_color_stop_rgba(2 * (pos - 0.5), r, g, b, alpha * a);
+ }
+ }
+
+ // shadow at the top (faint)
+ ctx->rectangle(corners[0][X], corners[0][Y] - half, std::max(corners[1][X] - corners[0][X], 0.0), half);
+ ctx->set_source(grad_top);
+ ctx->fill();
+
+ // right side
+ ctx->rectangle(corners[1][X], corners[1][Y] + half, sw, std::max(corners[2][Y] - corners[1][Y] - half, 0.0));
+ ctx->set_source(grad_right);
+ ctx->fill();
+
+ // bottom side
+ ctx->rectangle(corners[0][X] + half, corners[2][Y], std::max(corners[1][X] - corners[0][X] - half, 0.0), sw);
+ ctx->set_source(grad_bottom);
+ ctx->fill();
+
+ // left side (faint)
+ ctx->rectangle(corners[0][X] - half, corners[0][Y], half, std::max(corners[2][Y] - corners[1][Y], 0.0));
+ ctx->set_source(grad_left);
+ ctx->fill();
+
+ // bottom corners
+ ctx->rectangle(corners[2][X], corners[2][Y], sw, sw);
+ ctx->set_source(grad_btm_right);
+ ctx->fill();
+
+ ctx->rectangle(corners[3][X] - half, corners[3][Y], std::min(sw, rect.width() + half), sw);
+ ctx->set_source(grad_btm_left);
+ ctx->fill();
+
+ // top corners
+ ctx->rectangle(corners[1][X], corners[1][Y] - half, sw, std::min(sw, rect.height() + half));
+ ctx->set_source(grad_top_right);
+ ctx->fill();
+
+ ctx->rectangle(corners[0][X] - half, corners[0][Y] - half, half, half);
+ ctx->set_source(grad_top_left);
+ ctx->fill();
+}
+
+/**
+ * Converts the Cairo surface to a GdkPixbuf pixel format,
+ * without allocating extra memory.
+ *
+ * This function is intended mainly for creating previews displayed by GTK.
+ * For loading images for display on the canvas, use the Inkscape::Pixbuf object.
+ *
+ * The returned GdkPixbuf takes ownership of the passed surface reference,
+ * so it should NOT be freed after calling this function.
+ */
+GdkPixbuf *ink_pixbuf_create_from_cairo_surface(cairo_surface_t *s)
+{
+ guchar *pixels = cairo_image_surface_get_data(s);
+ int w = cairo_image_surface_get_width(s);
+ int h = cairo_image_surface_get_height(s);
+ int rs = cairo_image_surface_get_stride(s);
+
+ convert_pixels_argb32_to_pixbuf(pixels, w, h, rs);
+
+ GdkPixbuf *pb = gdk_pixbuf_new_from_data(
+ pixels, GDK_COLORSPACE_RGB, TRUE, 8,
+ w, h, rs, ink_cairo_pixbuf_cleanup, s);
+
+ return pb;
+}
+
+/**
+ * Cleanup function for GdkPixbuf.
+ * This function should be passed as the GdkPixbufDestroyNotify parameter
+ * to gdk_pixbuf_new_from_data when creating a GdkPixbuf backed by
+ * a Cairo surface.
+ */
+void ink_cairo_pixbuf_cleanup(guchar * /*pixels*/, void *data)
+{
+ cairo_surface_t *surface = static_cast<cairo_surface_t*>(data);
+ cairo_surface_destroy(surface);
+}
+
+/* The following two functions use "from" instead of "to", because when you write:
+ val1 = argb32_from_pixbuf(val1);
+ the name of the format is closer to the value in that format. */
+
+guint32 argb32_from_pixbuf(guint32 c)
+{
+ guint32 o = 0;
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ guint32 a = (c & 0xff000000) >> 24;
+#else
+ guint32 a = (c & 0x000000ff);
+#endif
+ if (a != 0) {
+ // extract color components
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ guint32 r = (c & 0x000000ff);
+ guint32 g = (c & 0x0000ff00) >> 8;
+ guint32 b = (c & 0x00ff0000) >> 16;
+#else
+ guint32 r = (c & 0xff000000) >> 24;
+ guint32 g = (c & 0x00ff0000) >> 16;
+ guint32 b = (c & 0x0000ff00) >> 8;
+#endif
+ // premultiply
+ r = premul_alpha(r, a);
+ b = premul_alpha(b, a);
+ g = premul_alpha(g, a);
+ // combine into output
+ o = (a << 24) | (r << 16) | (g << 8) | (b);
+ }
+ return o;
+}
+
+/**
+ * Convert one pixel from ARGB to GdkPixbuf format.
+ *
+ * @param c ARGB color
+ * @param bgcolor Color to use if c.alpha is zero (bgcolor.alpha is ignored)
+ */
+guint32 pixbuf_from_argb32(guint32 c, guint32 bgcolor)
+{
+ guint32 a = (c & 0xff000000) >> 24;
+ if (a == 0) {
+ assert(c == 0);
+ c = bgcolor;
+ }
+
+ // extract color components
+ guint32 r = (c & 0x00ff0000) >> 16;
+ guint32 g = (c & 0x0000ff00) >> 8;
+ guint32 b = (c & 0x000000ff);
+
+ if (a != 0) {
+ r = unpremul_alpha(r, a);
+ g = unpremul_alpha(g, a);
+ b = unpremul_alpha(b, a);
+ }
+
+ // combine into output
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ guint32 o = (r) | (g << 8) | (b << 16) | (a << 24);
+#else
+ guint32 o = (r << 24) | (g << 16) | (b << 8) | (a);
+#endif
+ return o;
+}
+
+/**
+ * Convert pixel data from GdkPixbuf format to ARGB.
+ * This will convert pixel data from GdkPixbuf format to Cairo's native pixel format.
+ * This involves premultiplying alpha and shuffling around the channels.
+ * Pixbuf data must have an alpha channel, otherwise the results are undefined
+ * (usually a segfault).
+ */
+void
+convert_pixels_pixbuf_to_argb32(guchar *data, int w, int h, int stride)
+{
+ if (!data || w < 1 || h < 1 || stride < 1) {
+ return;
+ }
+
+ for (size_t i = 0; i < h; ++i) {
+ guint32 *px = reinterpret_cast<guint32*>(data + i*stride);
+ for (size_t j = 0; j < w; ++j) {
+ *px = argb32_from_pixbuf(*px);
+ ++px;
+ }
+ }
+}
+
+/**
+ * Convert pixel data from ARGB to GdkPixbuf format.
+ * This will convert pixel data from GdkPixbuf format to Cairo's native pixel format.
+ * This involves premultiplying alpha and shuffling around the channels.
+ */
+void
+convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int stride, guint32 bgcolor)
+{
+ if (!data || w < 1 || h < 1 || stride < 1) {
+ return;
+ }
+ for (size_t i = 0; i < h; ++i) {
+ guint32 *px = reinterpret_cast<guint32*>(data + i*stride);
+ for (size_t j = 0; j < w; ++j) {
+ *px = pixbuf_from_argb32(*px, bgcolor);
+ ++px;
+ }
+ }
+}
+
+/**
+ * Converts GdkPixbuf's data to premultiplied ARGB.
+ * This function will convert a GdkPixbuf in place into Cairo's native pixel format.
+ * Note that this is a hack intended to save memory. When the pixbuf is in Cairo's format,
+ * using it with GTK will result in corrupted drawings.
+ */
+void
+ink_pixbuf_ensure_argb32(GdkPixbuf *pb)
+{
+ gchar *pixel_format = reinterpret_cast<gchar*>(g_object_get_data(G_OBJECT(pb), "pixel_format"));
+ if (pixel_format != nullptr && strcmp(pixel_format, "argb32") == 0) {
+ // nothing to do
+ return;
+ }
+
+ convert_pixels_pixbuf_to_argb32(
+ gdk_pixbuf_get_pixels(pb),
+ gdk_pixbuf_get_width(pb),
+ gdk_pixbuf_get_height(pb),
+ gdk_pixbuf_get_rowstride(pb));
+ g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("argb32"), g_free);
+}
+
+/**
+ * Converts GdkPixbuf's data back to its native format.
+ * Once this is done, the pixbuf can be used with GTK again.
+ */
+void
+ink_pixbuf_ensure_normal(GdkPixbuf *pb)
+{
+ gchar *pixel_format = reinterpret_cast<gchar*>(g_object_get_data(G_OBJECT(pb), "pixel_format"));
+ if (pixel_format == nullptr || strcmp(pixel_format, "pixbuf") == 0) {
+ // nothing to do
+ return;
+ }
+
+ convert_pixels_argb32_to_pixbuf(
+ gdk_pixbuf_get_pixels(pb),
+ gdk_pixbuf_get_width(pb),
+ gdk_pixbuf_get_height(pb),
+ gdk_pixbuf_get_rowstride(pb));
+ g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("pixbuf"), g_free);
+}
+
+guint32 argb32_from_rgba(guint32 in)
+{
+ guint32 r, g, b, a;
+ a = (in & 0x000000ff);
+ r = premul_alpha((in & 0xff000000) >> 24, a);
+ g = premul_alpha((in & 0x00ff0000) >> 16, a);
+ b = premul_alpha((in & 0x0000ff00) >> 8, a);
+ ASSEMBLE_ARGB32(px, a, r, g, b)
+ return px;
+}
+
+
+/**
+ * Converts a pixbuf to a PNG data structure.
+ * For 8-but RGBA png, this is like copying.
+ *
+ */
+const guchar* pixbuf_to_png(guchar const**rows, guchar* px, int num_rows, int num_cols, int stride, int color_type, int bit_depth)
+{
+ int n_fields = 1 + (color_type&2) + (color_type&4)/4;
+ const guchar* new_data = (const guchar*)malloc(((n_fields * bit_depth * num_cols + 7)/8) * num_rows);
+ char* ptr = (char*) new_data;
+ // Used when we write image data smaller than one byte (for instance in
+ // black and white images where 1px = 1bit). Only possible with greyscale.
+ int pad = 0;
+ for (int row = 0; row < num_rows; ++row) {
+ rows[row] = (const guchar*)ptr;
+ for (int col = 0; col < num_cols; ++col) {
+ guint32 *pixel = reinterpret_cast<guint32*>(px + row*stride)+col;
+
+ guint64 pix3 = (*pixel & 0xff000000) >> 24;
+ guint64 pix2 = (*pixel & 0x00ff0000) >> 16;
+ guint64 pix1 = (*pixel & 0x0000ff00) >> 8;
+ guint64 pix0 = (*pixel & 0x000000ff);
+
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ guint64 a = pix3, b = pix2, g = pix1, r = pix0;
+#else
+ guint64 r = pix3, g = pix2, b = pix1, a = pix0;
+#endif
+
+ // One of possible rgb to greyscale formulas. This one is called "luminance", "luminosity" or "luma"
+ guint16 gray = (guint16)((guint32)((0.2126*(r<<24) + 0.7152*(g<<24) + 0.0722*(b<<24)))>>16);
+
+ if (color_type & 2) { // RGB or RGBA
+ // for 8bit->16bit transition, I take the FF -> FFFF convention (multiplication by 0x101).
+ // If you prefer FF -> FF00 (multiplication by 0x100), remove the <<8, <<24, <<40 and <<56
+ // for little-endian, and remove the <<0, <<16, <<32 and <<48 for big-endian.
+ if (color_type & 4) { // RGBA
+ if (bit_depth == 8)
+ *((guint32*)ptr) = *pixel;
+ else
+ // This uses the samples in the order they appear in pixel rather than
+ // normalised to abgr or rgba in order to make it endian agnostic,
+ // exploiting the symmetry of the expression (0x101 is the same in both
+ // endiannesses and each sample is multiplied by that).
+ *((guint64*)ptr) = (guint64)((pix3<<56)+(pix3<<48)+(pix2<<40)+(pix2<<32)+(pix1<<24)+(pix1<<16)+(pix0<<8)+(pix0));
+ } else { // RGB
+ if (bit_depth == 8) {
+ *ptr = r;
+ *(ptr+1) = g;
+ *(ptr+2) = b;
+ } else {
+ *((guint16*)ptr) = (r<<8)+r;
+ *((guint16*)(ptr+2)) = (g<<8)+g;
+ *((guint16*)(ptr+4)) = (b<<8)+b;
+ }
+ }
+ } else { // Grayscale
+ if (bit_depth == 16) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+ *(guint16*)ptr = ((gray & 0xff00)>>8) + ((gray & 0x00ff)<<8);
+#else
+ *(guint16*)ptr = gray;
+#endif
+ // For 8bit->16bit this mirrors RGB(A), multiplying by
+ // 0x101; if you prefer multiplying by 0x100, remove the
+ // <<8 for little-endian, and remove the unshifted value
+ // for big-endian.
+ if (color_type & 4) // Alpha channel
+ *((guint16*)(ptr+2)) = a + (a<<8);
+ } else if (bit_depth == 8) {
+ *ptr = guint8(gray >> 8);
+ if (color_type & 4) // Alpha channel
+ *((guint8*)(ptr+1)) = a;
+ } else {
+ if (!pad) *ptr=0;
+ // In PNG numbers are stored left to right, but in most significant bits first, so the first one processed is the ``big'' mask, etc.
+ int realpad = 8 - bit_depth - pad;
+ *ptr += guint8((gray >> (16-bit_depth))<<realpad); // Note the "+="
+ if (color_type & 4) // Alpha channel
+ *(ptr+1) += guint8((a >> (8-bit_depth))<<(bit_depth + realpad));
+ }
+ }
+
+ pad += bit_depth*n_fields;
+ ptr += pad/8;
+ pad %= 8;
+ }
+ // Align bytes on rows
+ if (pad) {
+ pad = 0;
+ ptr++;
+ }
+ }
+ return new_data;
+}
+
+
+
+
+
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :