diff options
| author | Markus Engel <markus.engel@tum.de> | 2013-09-19 17:32:15 +0000 |
|---|---|---|
| committer | Markus Engel <markus.engel@tum.de> | 2013-09-19 17:32:15 +0000 |
| commit | 658a60b2ffdb7ed361b3c3b57d62efd419f7ba47 (patch) | |
| tree | 7f0f76347c27e59a784b4a3af990af7b247c5b41 /src | |
| parent | Added gpl notice (diff) | |
| parent | Do not require a new layer for clipping paths in the Cairo renderer. (diff) | |
| download | inkscape-658a60b2ffdb7ed361b3c3b57d62efd419f7ba47.tar.gz inkscape-658a60b2ffdb7ed361b3c3b57d62efd419f7ba47.zip | |
Merged from trunk (r12544).
(bzr r11608.1.126)
Diffstat (limited to 'src')
70 files changed, 5000 insertions, 1010 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 42e091f43..a09bceb06 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -61,7 +61,6 @@ set(sp_SRC sp-root.cpp sp-script.cpp sp-shape.cpp - # sp-skeleton.cpp sp-spiral.cpp sp-star.cpp sp-stop.cpp @@ -153,7 +152,6 @@ set(sp_SRC sp-root.h sp-script.h sp-shape.h - # sp-skeleton.h sp-spiral.h sp-star.h sp-stop.h @@ -560,6 +558,7 @@ add_subdirectory(libuemf) add_subdirectory(libvpsc) add_subdirectory(livarot) add_subdirectory(libnrtype) +add_subdirectory(libdepixelize) get_property(inkscape_global_SRC GLOBAL PROPERTY inkscape_global_SRC) diff --git a/src/Makefile.am b/src/Makefile.am index 6fcfa421d..a0c240252 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,15 +23,16 @@ endif noinst_LIBRARIES = \ - dom/libdom.a \ - libcroco/libcroco.a \ - libavoid/libavoid.a \ - $(internal_GDL) \ - libuemf/libuemf.a \ - libcola/libcola.a \ - libvpsc/libvpsc.a \ - livarot/libvarot.a \ - 2geom/lib2geom.a \ + dom/libdom.a \ + libcroco/libcroco.a \ + libavoid/libavoid.a \ + $(internal_GDL) \ + libuemf/libuemf.a \ + libcola/libcola.a \ + libvpsc/libvpsc.a \ + livarot/libvarot.a \ + 2geom/lib2geom.a \ + libdepixelize/libdepixelize.a \ libinkversion.a all_libs = \ @@ -138,6 +139,7 @@ include ui/widget/Makefile_insert include util/Makefile_insert include trace/Makefile_insert include 2geom/Makefile_insert +include libdepixelize/Makefile_insert # Extra files not mentioned as sources to include in the source tarball EXTRA_DIST += \ @@ -181,8 +183,8 @@ EXTRA_DIST += \ io/crystalegg.xml \ io/doc2html.xsl \ show-preview.bmp \ - sp-skeleton.cpp sp-skeleton.h \ winconsole.cpp \ + libdepixelize/makefile.in \ $(CXXTEST_TEMPLATE) # Extra files to remove when doing "make distclean" diff --git a/src/desktop.cpp b/src/desktop.cpp index cb56669a8..69d83d8da 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -1923,6 +1923,7 @@ SPDesktop::show_dialogs() mapVerbPreference.insert(std::make_pair ((int)SP_VERB_DIALOG_DISPLAY, "/dialogs/preferences") ); mapVerbPreference.insert(std::make_pair ((int)SP_VERB_SELECTION_GRIDTILE, "/dialogs/gridtiler") ); mapVerbPreference.insert(std::make_pair ((int)SP_VERB_SELECTION_TRACE, "/dialogs/trace") ); + mapVerbPreference.insert(std::make_pair ((int)SP_VERB_SELECTION_PIXEL_ART, "/dialogs/pixelart") ); mapVerbPreference.insert(std::make_pair ((int)SP_VERB_DIALOG_TEXT, "/dialogs/textandfont") ); mapVerbPreference.insert(std::make_pair ((int)SP_VERB_DIALOG_EXPORT, "/dialogs/export") ); mapVerbPreference.insert(std::make_pair ((int)SP_VERB_DIALOG_XML_EDITOR, "/dialogs/xml") ); diff --git a/src/display/cairo-utils.cpp b/src/display/cairo-utils.cpp index 755553033..451f0b509 100644 --- a/src/display/cairo-utils.cpp +++ b/src/display/cairo-utils.cpp @@ -15,6 +15,8 @@ #include "display/cairo-utils.h" #include <stdexcept> +#include <glib/gstdio.h> +#include <glibmm/fileutils.h> #include <2geom/pathvector.h> #include <2geom/bezier-curve.h> #include <2geom/hvlinesegment.h> @@ -28,6 +30,8 @@ #include "helper/geom-curves.h" #include "display/cairo-templates.h" +static void ink_cairo_pixbuf_cleanup(guchar *, void *); + /** * 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: @@ -116,6 +120,380 @@ Cairo::RefPtr<CairoContext> CairoContext::create(Cairo::RefPtr<Cairo::Surface> c 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), NULL, NULL)) + , _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(0) + , _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); + cairo_surface_destroy(_surface); + } else { + cairo_surface_destroy(_surface); + g_object_unref(_pixbuf); + } +} + +Pixbuf *Pixbuf::create_from_data_uri(gchar const *uri_data) +{ + Pixbuf *pixbuf = NULL; + + bool data_is_image = 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 { /* 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_base64) { + GdkPixbuf *buf = NULL; + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + + if (!loader) return NULL; + + gsize decoded_len = 0; + guchar *decoded = g_base64_decode(data, &decoded_len); + + if (gdk_pixbuf_loader_write(loader, decoded, decoded_len, NULL)) { + gdk_pixbuf_loader_close(loader, NULL); + buf = gdk_pixbuf_loader_get_pixbuf(loader); + if (buf) { + g_object_ref(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); + } + + return pixbuf; +} + +Pixbuf *Pixbuf::create_from_file(std::string const &fn) +{ + Pixbuf *pb = NULL; + // test correctness of filename + if (!g_file_test(fn.c_str(), G_FILE_TEST_EXISTS)) { + return NULL; + } + struct stat stdir; + int val = g_stat(fn.c_str(), &stdir); + if (val == 0 && stdir.st_mode & S_IFDIR){ + return NULL; + } + + // we need to load the entire file into memory, + // since we'll store it as MIME data + gchar *data = NULL; + gsize len = 0; + GError *error; + + if (g_file_get_contents(fn.c_str(), &data, &len, &error)) { + + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + gdk_pixbuf_loader_write(loader, (guchar *) data, len, NULL); + gdk_pixbuf_loader_close(loader, NULL); + + GdkPixbuf *buf = gdk_pixbuf_loader_get_pixbuf(loader); + if (buf) { + g_object_ref(buf); + pb = new Pixbuf(buf); + pb->_mod_time = stdir.st_mtime; + pb->_path = fn; + + 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); + } else { + g_free(data); + } + g_object_unref(loader); + + // 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. + } else { + return NULL; + } + + 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, NULL }; + static guint mimetypes_len = g_strv_length(const_cast<gchar**>(mimetypes)); + + guchar const *data = NULL; + + 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 != NULL) { + 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 = NULL; + + 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 != NULL) { + 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 /* @@ -371,129 +749,6 @@ ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m) cairo_pattern_set_matrix(cp, &cm); } -void -ink_cairo_set_source_pixbuf(cairo_t *ct, GdkPixbuf *pb, double x, double y) -{ - cairo_surface_t *pbs = ink_cairo_surface_get_for_pixbuf(pb); - cairo_set_source_surface(ct, pbs, x, y); -} - -/* The functions 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 functions below allow interoperation between GdkPixbuf - * and Cairo surfaces, allowing pixbufs to be used as Cairo sources, - * and saving Cairo surfaces using GdkPixbuf APIs. - * 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. - * In this way, only one copy of the image data is needed. - * - * Given either a GdkPixbuf or a Cairo surface, these functions create - * the other object and convert to its format. The returned object should be - * freed using cairo_surface_destroy or g_object_unref when it's no longer - * needed. - * - * Memory ownership semantics: - * Regardless of whether the pixels are stored in memory originally belonging - * to Cairo surface or to GdkPixbuf, the GdkPixbuf is the master object. - * To free the memory, unref the GdkPixbuf ONLY. - */ - -/** - * 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 ink_pixbuf_ensure_normal() is called. - * - * @bug You have to call g_object_set_data(G_OBJECT(pb), "cairo_surface", NULL) - * when unrefing the last reference to the pixbuf. Otherwise there will be - * crashes, because cairo_surface_destroy is called after the pixbuf data - * is already freed. - */ -cairo_surface_t * -ink_cairo_surface_get_for_pixbuf(GdkPixbuf *pb) -{ - cairo_surface_t *pbs = - reinterpret_cast<cairo_surface_t*>(g_object_get_data(G_OBJECT(pb), "cairo_surface")); - - ink_pixbuf_ensure_argb32(pb); - - if (pbs == NULL) { - guchar *data = gdk_pixbuf_get_pixels(pb); - int w = gdk_pixbuf_get_width(pb); - int h = gdk_pixbuf_get_height(pb); - int stride = gdk_pixbuf_get_rowstride(pb); - - // create a surface that stores the data - pbs = cairo_image_surface_create_for_data( - data, CAIRO_FORMAT_ARGB32, w, h, stride); - - g_object_set_data_full(G_OBJECT(pb), "cairo_surface", pbs, (GDestroyNotify) cairo_surface_destroy); - cairo_surface_set_user_data(pbs, &ink_pixbuf_key, pb, NULL); - } - - return pbs; -} - -/** - * Converts the Cairo surface to GdkPixbuf pixel format. - * 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) -{ - GdkPixbuf *pb = reinterpret_cast<GdkPixbuf*>(cairo_surface_get_user_data(s, &ink_pixbuf_key)); - if (pb == NULL) { - pb = 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), NULL, NULL); - - g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("argb32"), g_free); - g_object_set_data_full(G_OBJECT(pb), "cairo_surface", s, (GDestroyNotify) cairo_surface_destroy); - - cairo_surface_set_user_data(s, &ink_pixbuf_key, pb, NULL); - } else { - g_warning("Received surface which is already owned by GdkPixbuf"); - g_object_ref(pb); - } - - ink_pixbuf_ensure_normal(pb); - - 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 = reinterpret_cast<cairo_surface_t*>(data); - cairo_surface_destroy(surface); -} - /** * Create an exact copy of a surface. * Creates a surface that has the same type, content type, dimensions and contents @@ -833,6 +1088,44 @@ ink_cairo_pattern_create_checkerboard() return p; } +/** + * 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. + */ +static 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. */ diff --git a/src/display/cairo-utils.h b/src/display/cairo-utils.h index 289d4e01f..505e2ca77 100644 --- a/src/display/cairo-utils.h +++ b/src/display/cairo-utils.h @@ -12,14 +12,15 @@ #ifndef SEEN_INKSCAPE_DISPLAY_CAIRO_UTILS_H #define SEEN_INKSCAPE_DISPLAY_CAIRO_UTILS_H +#include <boost/noncopyable.hpp> +//#include <glibmm/threads.h> // workaround #include <glib.h> #include <cairomm/cairomm.h> +//#include <gdkmm/pixbuf.h> #include <2geom/forward.h> #include "style.h" struct SPColor; -struct _GdkPixbuf; -typedef struct _GdkPixbuf GdkPixbuf; namespace Inkscape { @@ -80,15 +81,61 @@ public: static Cairo::RefPtr<CairoContext> create(Cairo::RefPtr<Cairo::Surface> const &target); }; -} // namespace Inkscape +/** Class to hold image data for raster images. + * Allows easy interoperation with GdkPixbuf and Cairo. */ +class Pixbuf { +public: + enum PixelFormat { + PF_CAIRO = 1, + PF_GDK = 2, + PF_LAST + }; + + explicit Pixbuf(cairo_surface_t *s); + explicit Pixbuf(GdkPixbuf *pb); + Pixbuf(Inkscape::Pixbuf const &other); + ~Pixbuf(); + + GdkPixbuf *getPixbufRaw(bool convert_format = true); + //Glib::RefPtr<Gdk::Pixbuf> getPixbuf(bool convert_format = true); + + cairo_surface_t *getSurfaceRaw(bool convert_format = true); + Cairo::RefPtr<Cairo::Surface> getSurface(bool convert_format = true); + + int width() const; + int height() const; + int rowstride() const; + guchar const *pixels() const; + guchar *pixels(); + void markDirty(); + + bool hasMimeData() const; + guchar const *getMimeData(gsize &len, std::string &mimetype) const; + std::string const &originalPath() const { return _path; } + time_t modificationTime() const { return _mod_time; } -enum InkPixelFormat { - INK_PIXEL_FORMAT_NONE, - INK_PIXEL_FORMAT_CAIRO, - INK_PIXEL_FORMAT_PIXBUF, - INK_PIXEL_FORMAT_LAST + PixelFormat pixelFormat() const { return _pixel_format; } + void ensurePixelFormat(PixelFormat fmt); + + static Pixbuf *create_from_data_uri(gchar const *uri); + static Pixbuf *create_from_file(std::string const &fn); + +private: + void _ensurePixelsARGB32(); + void _ensurePixelsPixbuf(); + void _forceAlpha(); + void _setMimeData(guchar *data, gsize len, Glib::ustring const &format); + + GdkPixbuf *_pixbuf; + cairo_surface_t *_surface; + time_t _mod_time; + std::string _path; + PixelFormat _pixel_format; + bool _cairo_store; }; +} // namespace Inkscape + // TODO: these declarations may not be needed in the header extern cairo_user_data_key_t ink_color_interpolation_key; extern cairo_user_data_key_t ink_pixbuf_key; @@ -102,7 +149,6 @@ void ink_cairo_set_source_color(cairo_t *ct, SPColor const &color, double opacit void ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba); void ink_cairo_transform(cairo_t *ct, Geom::Affine const &m); void ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m); -void ink_cairo_set_source_pixbuf(cairo_t *ct, GdkPixbuf *pb, double x, double y); void ink_matrix_to_2geom(Geom::Affine &, cairo_matrix_t const &); void ink_matrix_to_cairo(cairo_matrix_t &, Geom::Affine const &); @@ -125,13 +171,9 @@ int ink_cairo_surface_linear_to_srgb(cairo_surface_t *surface); cairo_pattern_t *ink_cairo_pattern_create_checkerboard(); +GdkPixbuf *ink_pixbuf_create_from_cairo_surface(cairo_surface_t *s); void convert_pixels_pixbuf_to_argb32(guchar *data, int w, int h, int rs); void convert_pixels_argb32_to_pixbuf(guchar *data, int w, int h, int rs); -void ink_pixbuf_ensure_argb32(GdkPixbuf *); -void ink_pixbuf_ensure_normal(GdkPixbuf *); -cairo_surface_t *ink_cairo_surface_get_for_pixbuf(GdkPixbuf *pb); -GdkPixbuf *ink_pixbuf_create_from_cairo_surface(cairo_surface_t *s); -void ink_cairo_pixbuf_cleanup(guchar *pixels, void *surface); G_GNUC_CONST guint32 argb32_from_pixbuf(guint32 in); G_GNUC_CONST guint32 pixbuf_from_argb32(guint32 in); diff --git a/src/display/drawing-image.cpp b/src/display/drawing-image.cpp index 46f066b8e..0b661a450 100644 --- a/src/display/drawing-image.cpp +++ b/src/display/drawing-image.cpp @@ -22,34 +22,23 @@ namespace Inkscape { DrawingImage::DrawingImage(Drawing &drawing) : DrawingItem(drawing) , _pixbuf(NULL) - , _surface(NULL) // this is owned by _pixbuf! , _style(NULL) , _new_surface(NULL) {} DrawingImage::~DrawingImage() { - if (_style) + if (_style) { sp_style_unref(_style); - if (_pixbuf) { - if (_new_surface) cairo_surface_destroy(_new_surface); - g_object_unref(_pixbuf); } + + // _pixbuf is owned by SPImage - do not delete it } void -DrawingImage::setARGB32Pixbuf(GdkPixbuf *pb) +DrawingImage::setPixbuf(Inkscape::Pixbuf *pb) { - // when done in this order, it won't break if pb == image->pixbuf and the refcount is 1 - if (pb != NULL) { - g_object_ref (pb); - } - if (_pixbuf != NULL) { - g_object_unref(_pixbuf); - // unrefing the pixbuf also destroys surface - } _pixbuf = pb; - _surface = pb ? ink_cairo_surface_get_for_pixbuf(pb) : NULL; _markForUpdate(STATE_ALL, false); } @@ -86,8 +75,8 @@ DrawingImage::bounds() const { if (!_pixbuf) return _clipbox; - double pw = gdk_pixbuf_get_width(_pixbuf); - double ph = gdk_pixbuf_get_height(_pixbuf); + double pw = _pixbuf->width(); + double ph = _pixbuf->height(); double vw = pw * _scale[Geom::X]; double vh = ph * _scale[Geom::Y]; Geom::Point wh(vw, vh); @@ -143,14 +132,16 @@ unsigned DrawingImage::_renderItem(DrawingContext &ct, Geom::IntRect const &/*ar // See https://bugs.launchpad.net/inkscape/+bug/804162 Geom::Scale expansion(_ctm.expansion()); - int orgwidth = cairo_image_surface_get_width(_surface); - int orgheight = cairo_image_surface_get_height(_surface); + int orgwidth = _pixbuf->width(); + int orgheight = _pixbuf->height(); if (_scale[Geom::X]*expansion[Geom::X]*orgwidth*255.0<1.0 || _scale[Geom::Y]*expansion[Geom::Y]*orgheight*255.0<1.0) { // Resized image too small to actually see anything return RENDER_OK; } - + + _pixbuf->ensurePixelFormat(Inkscape::Pixbuf::PF_CAIRO); + // Split scale*expansion in a part that is <= 1.0 and a part that is >= 1.0. We only take care of the part <= 1.0. Geom::Scale scaleExpansionSmall(std::min<Geom::Coord>(fabs(_scale[Geom::X]*expansion[Geom::X]),1),std::min<Geom::Coord>(fabs(_scale[Geom::Y]*expansion[Geom::Y]),1)); Geom::Scale scaleExpansionLarge(_scale[Geom::X]*expansion[Geom::X]/scaleExpansionSmall[Geom::X],_scale[Geom::Y]*expansion[Geom::Y]/scaleExpansionSmall[Geom::Y]); @@ -161,7 +152,7 @@ unsigned DrawingImage::_renderItem(DrawingContext &ct, Geom::IntRect const &/*ar ct.scale(expansion.inverse()); // This should not include scale (see derivation above) ct.translate(_origin*expansion); ct.scale(scaleExpansionLarge); - ct.setSource(_surface, 0, 0); + ct.setSource(_pixbuf->getSurfaceRaw(), 0, 0); } else if (!_new_surface || (newSize-_rescaledSize).length()>0.1) { // Rescaled image is sufficiently different from cached image to recompute if (_new_surface) cairo_surface_destroy(_new_surface); @@ -200,13 +191,13 @@ unsigned DrawingImage::_renderItem(DrawingContext &ct, Geom::IntRect const &/*ar } } + cairo_surface_t *surface = _pixbuf->getSurfaceRaw(); _new_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, newwidth,newheight); - unsigned char * orgdata = cairo_image_surface_get_data(_surface); + unsigned char * orgdata = cairo_image_surface_get_data(surface); unsigned char * newdata = cairo_image_surface_get_data(_new_surface); - int orgstride = cairo_image_surface_get_stride(_surface); + int orgstride = cairo_image_surface_get_stride(surface); int newstride = cairo_image_surface_get_stride(_new_surface); - - //cairo_surface_flush(_surface); + cairo_surface_flush(_new_surface); for(int y=0; y<newheight; y++) { @@ -245,7 +236,7 @@ unsigned DrawingImage::_renderItem(DrawingContext &ct, Geom::IntRect const &/*ar // TODO: If Cairo's problems are gone, uncomment the following: //ct.translate(_origin); //ct.scale(_scale); - //ct.setSource(_surface, 0, 0); + //ct.setSource(_pixbuf->getSurfaceRaw(), 0, 0); //ct.paint(_opacity); ct.paint(); @@ -315,10 +306,10 @@ DrawingImage::_pickItem(Geom::Point const &p, double delta, unsigned /*sticky*/) return NULL; } else { - unsigned char *const pixels = gdk_pixbuf_get_pixels(_pixbuf); - int width = gdk_pixbuf_get_width(_pixbuf); - int height = gdk_pixbuf_get_height(_pixbuf); - int rowstride = gdk_pixbuf_get_rowstride(_pixbuf); + unsigned char *const pixels = _pixbuf->pixels(); + int width = _pixbuf->width(); + int height = _pixbuf->height(); + int rowstride = _pixbuf->rowstride(); Geom::Point tp = p * _ctm.inverse(); Geom::Rect r = bounds(); @@ -336,8 +327,17 @@ DrawingImage::_pickItem(Geom::Point const &p, double delta, unsigned /*sticky*/) unsigned char *pix_ptr = pixels + iy * rowstride + ix * 4; // pick if the image is less than 99% transparent - float alpha = (pix_ptr[3] / 255.0f) * _opacity; - return alpha > 0.01 ? this : NULL; + guint32 alpha = 0; + if (_pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { + guint32 px = *reinterpret_cast<guint32 const *>(pix_ptr); + alpha = (px & 0xff000000) >> 24; + } else if (_pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_GDK) { + alpha = pix_ptr[3]; + } else { + throw std::runtime_error("Unrecognized pixel format"); + } + float alpha_f = (alpha / 255.0f) * _opacity; + return alpha_f > 0.01 ? this : NULL; } } diff --git a/src/display/drawing-image.h b/src/display/drawing-image.h index 593185c97..58e6de72e 100644 --- a/src/display/drawing-image.h +++ b/src/display/drawing-image.h @@ -19,6 +19,7 @@ #include "display/drawing-item.h" namespace Inkscape { +class Pixbuf; class DrawingImage : public DrawingItem @@ -27,7 +28,7 @@ public: DrawingImage(Drawing &drawing); ~DrawingImage(); - void setARGB32Pixbuf(GdkPixbuf *pb); + void setPixbuf(Inkscape::Pixbuf *pb); void setStyle(SPStyle *style); void setScale(double sx, double sy); void setOrigin(Geom::Point const &o); @@ -41,8 +42,7 @@ protected: DrawingItem *stop_at); virtual DrawingItem *_pickItem(Geom::Point const &p, double delta, unsigned flags); - GdkPixbuf *_pixbuf; - cairo_surface_t *_surface; + Inkscape::Pixbuf *_pixbuf; SPStyle *_style; cairo_surface_t *_new_surface; // Part of hack around Cairo bug diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 80664d822..a9836a9e3 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -284,7 +284,7 @@ DrawingItem::setZOrder(unsigned z) void DrawingItem::setItemBounds(Geom::OptRect const &bounds) { - _item_bbox = bounds; + if (bounds) _filter_bbox = bounds; } /** @@ -352,8 +352,10 @@ DrawingItem::update(Geom::IntRect const &area, UpdateContext const &ctx, unsigne if (to_update & STATE_BBOX) { // compute drawbox - if (_filter && render_filters) { - _drawbox = _filter->compute_drawbox(this, _item_bbox); + if (_filter && render_filters && _bbox) { + Geom::IntRect newbox(*_bbox); + _filter->area_enlarge(newbox, this); + _drawbox = Geom::OptIntRect(newbox); } else { _drawbox = _bbox; } diff --git a/src/display/drawing-item.h b/src/display/drawing-item.h index 4a516512b..8020659db 100644 --- a/src/display/drawing-item.h +++ b/src/display/drawing-item.h @@ -89,7 +89,7 @@ public: Geom::OptIntRect geometricBounds() const { return _bbox; } Geom::OptIntRect visualBounds() const { return _drawbox; } - Geom::OptRect itemBounds() const { return _item_bbox; } + Geom::OptRect filterBounds() const { return _filter_bbox; } Geom::Affine ctm() const { return _ctm; } Geom::Affine transform() const { return _transform ? *_transform : Geom::identity(); } Drawing &drawing() const { return _drawing; } @@ -175,7 +175,7 @@ protected: Geom::Affine _ctm; ///< Total transform from item coords to display coords Geom::OptIntRect _bbox; ///< Bounding box in display (pixel) coords including stroke Geom::OptIntRect _drawbox; ///< Full visual bounding box - enlarged by filters, shrunk by clips and masks - Geom::OptRect _item_bbox; ///< Geometric bounding box in item coordinates + Geom::OptRect _filter_bbox; ///< Used by filters when settings bounds DrawingItem *_clip; DrawingItem *_mask; diff --git a/src/display/drawing-shape.cpp b/src/display/drawing-shape.cpp index e80f12486..e689d0755 100644 --- a/src/display/drawing-shape.cpp +++ b/src/display/drawing-shape.cpp @@ -179,8 +179,8 @@ DrawingShape::_renderItem(DrawingContext &ct, Geom::IntRect const &area, unsigne // update fill and stroke paints. // this cannot be done during nr_arena_shape_update, because we need a Cairo context // to render svg:pattern - has_fill = _nrstyle.prepareFill(ct, _item_bbox); - has_stroke = _nrstyle.prepareStroke(ct, _item_bbox); + has_fill = _nrstyle.prepareFill(ct, _bbox); + has_stroke = _nrstyle.prepareStroke(ct, _bbox); has_stroke &= (_nrstyle.stroke_width != 0); if (has_fill || has_stroke) { diff --git a/src/display/drawing-text.cpp b/src/display/drawing-text.cpp index 234006983..fa9ce4ff8 100644 --- a/src/display/drawing-text.cpp +++ b/src/display/drawing-text.cpp @@ -398,8 +398,8 @@ unsigned DrawingText::_renderItem(DrawingContext &ct, Geom::IntRect const &/*are using Geom::X; using Geom::Y; - has_fill = _nrstyle.prepareFill( ct, _item_bbox); - has_stroke = _nrstyle.prepareStroke(ct, _item_bbox); + has_fill = _nrstyle.prepareFill( ct, _bbox); + has_stroke = _nrstyle.prepareStroke(ct, _bbox); if (has_fill || has_stroke) { Geom::Affine rotinv; @@ -441,7 +441,7 @@ unsigned DrawingText::_renderItem(DrawingContext &ct, Geom::IntRect const &/*are } Inkscape::DrawingContext::Save save(ct); -// ct.transform(_ctm); // Seems to work fine without this line, which was in the original. + ct.transform(_ctm); // For one thing, this is needed to scale a fill-pattern when zooming in if (has_fill) { _nrstyle.applyFill(ct); ct.fillPreserve(); diff --git a/src/display/nr-filter-image.cpp b/src/display/nr-filter-image.cpp index b9d73f0ad..4ca4cd07c 100644 --- a/src/display/nr-filter-image.cpp +++ b/src/display/nr-filter-image.cpp @@ -30,6 +30,7 @@ FilterImage::FilterImage() : SVGElem(0) , document(0) , feImageHref(0) + , image(0) , broken_ref(false) { } @@ -41,7 +42,7 @@ FilterImage::~FilterImage() { if (feImageHref) g_free(feImageHref); - g_object_set_data(G_OBJECT(image->gobj()), "cairo_surface", NULL); + delete image; } void FilterImage::render_cairo(FilterSlot &slot) @@ -131,50 +132,38 @@ void FilterImage::render_cairo(FilterSlot &slot) // External image, like <image> if (!image && !broken_ref) { broken_ref = true; - try { - /* TODO: If feImageHref is absolute, then use that (preferably handling the - * case that it's not a file URI). Otherwise, go up the tree looking - * for an xml:base attribute, and use that as the base URI for resolving - * the relative feImageHref URI. Otherwise, if document->base is valid, - * then use that as the base URI. Otherwise, use feImageHref directly - * (i.e. interpreting it as relative to our current working directory). - * (See http://www.w3.org/TR/xmlbase/#resolution .) */ - gchar *fullname = feImageHref; - if ( !g_file_test( fullname, G_FILE_TEST_EXISTS ) ) { - // Try to load from relative postion combined with document base - if( document ) { - fullname = g_build_filename( document->getBase(), feImageHref, NULL ); - } - } - if ( !g_file_test( fullname, G_FILE_TEST_EXISTS ) ) { - // Should display Broken Image png. - g_warning("FilterImage::render: Can not find: %s", feImageHref ); - return; + + /* TODO: If feImageHref is absolute, then use that (preferably handling the + * case that it's not a file URI). Otherwise, go up the tree looking + * for an xml:base attribute, and use that as the base URI for resolving + * the relative feImageHref URI. Otherwise, if document->base is valid, + * then use that as the base URI. Otherwise, use feImageHref directly + * (i.e. interpreting it as relative to our current working directory). + * (See http://www.w3.org/TR/xmlbase/#resolution .) */ + gchar *fullname = feImageHref; + if ( !g_file_test( fullname, G_FILE_TEST_EXISTS ) ) { + // Try to load from relative postion combined with document base + if( document ) { + fullname = g_build_filename( document->getBase(), feImageHref, NULL ); } - image = Gdk::Pixbuf::create_from_file(fullname); - if( fullname != feImageHref ) g_free( fullname ); } - catch (const Glib::FileError & e) - { - g_warning("caught Glib::FileError in FilterImage::render: %s", e.what().data() ); + if ( !g_file_test( fullname, G_FILE_TEST_EXISTS ) ) { + // Should display Broken Image png. + g_warning("FilterImage::render: Can not find: %s", feImageHref ); return; } - catch (const Gdk::PixbufError & e) - { - g_warning("Gdk::PixbufError in FilterImage::render: %s", e.what().data() ); + image = Inkscape::Pixbuf::create_from_file(fullname); + if( fullname != feImageHref ) g_free( fullname ); + + if ( !image ) { + g_warning("FilterImage::render: failed to load image: %s", feImageHref); return; } - if ( !image ) return; broken_ref = false; - - bool has_alpha = image->get_has_alpha(); - if (!has_alpha) { - image = image->add_alpha(false, 0, 0, 0); - } } - cairo_surface_t *image_surface = ink_cairo_surface_get_for_pixbuf(image->gobj()); + cairo_surface_t *image_surface = image->getSurfaceRaw(); Geom::Rect sa = slot.get_slot_area(); cairo_surface_t *out = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, @@ -199,7 +188,7 @@ void FilterImage::render_cairo(FilterSlot &slot) // Check aspect ratio of image vs. viewport double feAspect = feImageHeight/feImageWidth; - double aspect = (double)image->get_height()/(double)image->get_width(); + double aspect = (double)image->height()/(double)image->width(); bool ratio = (feAspect < aspect); double ax, ay; // Align side @@ -274,8 +263,8 @@ void FilterImage::render_cairo(FilterSlot &slot) } } - double scaleX = feImageWidth / image->get_width(); - double scaleY = feImageHeight / image->get_height(); + double scaleX = feImageWidth / image->width(); + double scaleY = feImageHeight / image->height(); cairo_translate(ct, feImageX, feImageY); cairo_scale(ct, scaleX, scaleY); @@ -302,8 +291,8 @@ void FilterImage::set_href(const gchar *href){ if (feImageHref) g_free (feImageHref); feImageHref = (href) ? g_strdup (href) : NULL; - g_object_set_data(G_OBJECT(image->gobj()), "cairo_surface", NULL); - image.reset(); + delete image; + image = NULL; broken_ref = false; } diff --git a/src/display/nr-filter-image.h b/src/display/nr-filter-image.h index f45f42265..69691ac99 100644 --- a/src/display/nr-filter-image.h +++ b/src/display/nr-filter-image.h @@ -12,14 +12,14 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#include <gdkmm/pixbuf.h> #include "display/nr-filter-primitive.h" -#include <glibmm/refptr.h> class SPDocument; class SPItem; namespace Inkscape { +class Pixbuf; + namespace Filters { class FilterSlot; @@ -43,7 +43,7 @@ public: private: SPDocument *document; gchar *feImageHref; - Glib::RefPtr<Gdk::Pixbuf> image; + Inkscape::Pixbuf *image; float feImageX, feImageY, feImageWidth, feImageHeight; unsigned int aspect_align, aspect_clip; bool broken_ref; diff --git a/src/display/nr-filter.cpp b/src/display/nr-filter.cpp index f0965c460..a0103cbb0 100644 --- a/src/display/nr-filter.cpp +++ b/src/display/nr-filter.cpp @@ -114,13 +114,16 @@ int Filter::render(Inkscape::DrawingItem const *item, DrawingContext &graphic, D Geom::Affine trans = item->ctm(); -// Geom::OptRect filter_area = filter_effect_area(item->itemBounds()); // disabled, already done in visualBounds - Geom::OptRect filter_area = item->itemBounds(); // see LP Bug 1188336 - if (!filter_area) return 1; + // Get filter are, the filter_effect_area is already done in visualBounds + Geom::OptRect filter_area = item->filterBounds(); + // Use the geometricBounds as a backup solution + if (!filter_area || (filter_area->hasZeroArea() && + filter_area->min()[Geom::X] == 0 && filter_area->min()[Geom::Y] == 0)) + filter_area = item->geometricBounds(); FilterUnits units(_filter_units, _primitive_units); units.set_ctm(trans); - units.set_item_bbox(item->itemBounds()); + units.set_item_bbox(filter_area); units.set_filter_area(*filter_area); std::pair<double,double> resolution @@ -200,7 +203,7 @@ void Filter::area_enlarge(Geom::IntRect &bbox, Inkscape::DrawingItem const *item } Geom::Rect item_bbox; - Geom::OptRect maybe_bbox = item->itemBounds(); + Geom::OptRect maybe_bbox = item->geometricBounds(); if (maybe_bbox.isEmpty()) { // Code below needs a bounding box return; @@ -220,20 +223,6 @@ void Filter::area_enlarge(Geom::IntRect &bbox, Inkscape::DrawingItem const *item */ } -Geom::OptIntRect Filter::compute_drawbox(Inkscape::DrawingItem const *item, Geom::OptRect const &item_bbox) { - -// Geom::OptRect enlarged = filter_effect_area(item_bbox); // disabled, already done in visualBounds - Geom::OptRect enlarged = item_bbox; // see LP Bug 1188336 - if (enlarged) { - *enlarged *= item->ctm(); - - Geom::OptIntRect ret(enlarged->roundOutwards()); - return ret; - } else { - return Geom::OptIntRect(); - } -} - Geom::OptRect Filter::filter_effect_area(Geom::OptRect const &bbox) { Geom::Point minp, maxp; diff --git a/src/display/nr-filter.h b/src/display/nr-filter.h index d53005c5d..5df38ffe9 100644 --- a/src/display/nr-filter.h +++ b/src/display/nr-filter.h @@ -151,12 +151,6 @@ public: */ void area_enlarge(Geom::IntRect &area, Inkscape::DrawingItem const *item) const; /** - * Given an item bounding box (in user coords), this function enlarges it - * to contain the filter effects region and transforms it to screen - * coordinates - */ - Geom::OptIntRect compute_drawbox(Inkscape::DrawingItem const *item, Geom::OptRect const &item_bbox); - /** * Returns the filter effects area in user coordinate system. * The given bounding box should be a bounding box as specified in * SVG standard and in user coordinate system. diff --git a/src/doxygen-main.cpp b/src/doxygen-main.cpp index a1d3f3604..e581b8708 100644 --- a/src/doxygen-main.cpp +++ b/src/doxygen-main.cpp @@ -245,7 +245,6 @@ namespace XML {} * - SPLinearGradient * - SPRadialGradient * - SPPattern [\ref sp-pattern.cpp, \ref sp-pattern.h] - * - SPSkeleton [\ref sp-skeleton.cpp, \ref sp-skeleton.h] * - SPStop [\ref sp-stop.h] * - SPString [\ref sp-string.cpp, \ref sp-string.h] * - SPStyleElem [\ref sp-style-elem.cpp, \ref sp-style-elem.h] diff --git a/src/extension/internal/cairo-ps-out.cpp b/src/extension/internal/cairo-ps-out.cpp index e06c9f30d..5f535dc64 100644 --- a/src/extension/internal/cairo-ps-out.cpp +++ b/src/extension/internal/cairo-ps-out.cpp @@ -340,7 +340,7 @@ CairoPsOutput::init (void) "<_option value=\"page\">" N_("Use document's page size") "</_option>" "<_option value=\"drawing\">" N_("Use exported object's size") "</_option>" "</param>" - "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm)") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n" + "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm):") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n" "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID:") "\" type=\"string\"></param>\n" "<output>\n" "<extension>.ps</extension>\n" diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp index a950fa177..4f9273cbb 100644 --- a/src/extension/internal/cairo-render-context.cpp +++ b/src/extension/internal/cairo-render-context.cpp @@ -1436,7 +1436,7 @@ CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle con return true; } -bool CairoRenderContext::renderImage(GdkPixbuf *pb, +bool CairoRenderContext::renderImage(Inkscape::Pixbuf *pb, Geom::Affine const &image_transform, SPStyle const * /*style*/) { g_assert( _is_valid ); @@ -1447,13 +1447,13 @@ bool CairoRenderContext::renderImage(GdkPixbuf *pb, _prepareRenderGraphic(); - int w = gdk_pixbuf_get_width (pb); - int h = gdk_pixbuf_get_height (pb); + int w = pb->width(); + int h = pb->height(); // TODO: reenable merge_opacity if useful float opacity = _state->opacity; - cairo_surface_t *image_surface = ink_cairo_surface_get_for_pixbuf(pb); + cairo_surface_t *image_surface = pb->getSurfaceRaw(); if (cairo_surface_status(image_surface)) { TRACE(("Image surface creation failed:\n%s\n", cairo_status_to_string(cairo_surface_status(image_surface)))); return false; diff --git a/src/extension/internal/cairo-render-context.h b/src/extension/internal/cairo-render-context.h index 91e1cdf7d..8d3e63775 100644 --- a/src/extension/internal/cairo-render-context.h +++ b/src/extension/internal/cairo-render-context.h @@ -6,7 +6,7 @@ */ /* * Authors: - * Miklos Erdelyi <erdelyim@gmail.com> + * Miklos Erdelyi <erdelyim@gmail.com> * * Copyright (C) 2006 Miklos Erdelyi * @@ -32,6 +32,8 @@ class SPClipPath; class SPMask; namespace Inkscape { +class Pixbuf; + namespace Extension { namespace Internal { @@ -144,7 +146,7 @@ public: /* Rendering methods */ bool renderPathVector(Geom::PathVector const &pathv, SPStyle const *style, Geom::OptRect const &pbox); - bool renderImage(GdkPixbuf *pb, + bool renderImage(Inkscape::Pixbuf *pb, Geom::Affine const &image_transform, SPStyle const *style); bool renderGlyphtext(PangoFont *font, Geom::Affine const &font_matrix, std::vector<CairoGlyphInfo> const &glyphtext, SPStyle const *style); diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp index 3463925b6..cace251cf 100644 --- a/src/extension/internal/cairo-renderer.cpp +++ b/src/extension/internal/cairo-renderer.cpp @@ -27,6 +27,7 @@ #include <signal.h> #include <errno.h> +#include <boost/scoped_ptr.hpp> #include "libnrtype/Layout-TNG.h" #include <2geom/transforms.h> @@ -347,8 +348,8 @@ static void sp_image_render(SPItem *item, CairoRenderContext *ctx) if (!image->pixbuf) return; if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) return; - w = gdk_pixbuf_get_width (image->pixbuf); - h = gdk_pixbuf_get_height (image->pixbuf); + w = image->pixbuf->width(); + h = image->pixbuf->height(); double x = image->x.computed; double y = image->y.computed; @@ -497,22 +498,15 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) GSList *items = NULL; items = g_slist_append(items, item); - GdkPixbuf *pb = sp_generate_internal_bitmap(document, NULL, - bbox->min()[Geom::X], bbox->min()[Geom::Y], bbox->max()[Geom::X], bbox->max()[Geom::Y], - width, height, res, res, (guint32) 0xffffff00, items ); + boost::scoped_ptr<Inkscape::Pixbuf> pb( + sp_generate_internal_bitmap(document, NULL, + bbox->min()[Geom::X], bbox->min()[Geom::Y], bbox->max()[Geom::X], bbox->max()[Geom::Y], + width, height, res, res, (guint32) 0xffffff00, items )); if (pb) { - TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL )); - - /* TODO: find a way to avoid a duplicate conversion between - * Cairo and GdkPixbuf pixel formats here. - * Internally, generate_internal_bitmap creates a Cairo surface, - * but then converts it to pixbuf format. In turn, renderImage() - * below converts back to Cairo format. - */ - ctx->renderImage(pb, t, item->style); - g_object_unref(pb); - pb = 0; + //TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL )); + + ctx->renderImage(pb.get(), t, item->style); } g_slist_free (items); } @@ -586,9 +580,9 @@ void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item) setStateForItem(ctx, item); CairoRenderState *state = ctx->getCurrentState(); - state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 ); + state->need_layer = ( state->mask || state->opacity != 1.0 ); - // Draw item on a temporary surface so a mask, clip path, or opacity can be applied to it. + // Draw item on a temporary surface so a mask or opacity can be applied to it. if (state->need_layer) { state->merge_opacity = FALSE; ctx->pushLayer(); diff --git a/src/extension/internal/emf-print.cpp b/src/extension/internal/emf-print.cpp index 826a52ade..770257978 100644 --- a/src/extension/internal/emf-print.cpp +++ b/src/extension/internal/emf-print.cpp @@ -53,6 +53,7 @@ #include "sp-gradient.h" #include "sp-radial-gradient.h" #include "sp-linear-gradient.h" +#include "display/cairo-utils.h" #include "splivarot.h" // pieces for union on shapes #include "2geom/svg-path-parser.h" // to get from SVG text to Geom::Path @@ -333,7 +334,7 @@ int PrintEmf::create_brush(SPStyle const *style, PU_COLORREF fcolor) U_LOGBRUSH lb; uint32_t brush, fmode; MFDrawMode fill_mode; - GdkPixbuf *pixbuf; + Inkscape::Pixbuf *pixbuf; uint32_t brushStyle; int hatchType; U_COLORREF hatchColor; @@ -462,7 +463,7 @@ int PrintEmf::create_brush(SPStyle const *style, PU_COLORREF fcolor) int numCt; U_BITMAPINFOHEADER Bmih; PU_BITMAPINFO Bmi; - rgba_px = (char *) gdk_pixbuf_get_pixels(pixbuf); // Do NOT free this!!! + rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!! colortype = U_BCBM_COLOR32; (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1); // Not sure why the next swap is needed because the preceding does it, and the code is identical @@ -528,7 +529,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) int linejoin = 0; uint32_t pen; uint32_t brushStyle; - GdkPixbuf *pixbuf; + Inkscape::Pixbuf *pixbuf; int hatchType; U_COLORREF hatchColor; U_COLORREF bkColor; @@ -565,7 +566,7 @@ int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) brush_classify(pat, 0, &pixbuf, &hatchType, &hatchColor, &bkColor); if (pixbuf) { brushStyle = U_BS_DIBPATTERN; - rgba_px = (char *) gdk_pixbuf_get_pixels(pixbuf); // Do NOT free this!!! + rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!! colortype = U_BCBM_COLOR32; (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1); // Not sure why the next swap is needed because the preceding does it, and the code is identical diff --git a/src/extension/internal/gdkpixbuf-input.cpp b/src/extension/internal/gdkpixbuf-input.cpp index 117c2fe39..87cf8a9cc 100644 --- a/src/extension/internal/gdkpixbuf-input.cpp +++ b/src/extension/internal/gdkpixbuf-input.cpp @@ -1,6 +1,7 @@ #ifdef HAVE_CONFIG_H # include <config.h> #endif +#include <boost/scoped_ptr.hpp> #include <glib/gprintf.h> #include <glibmm/i18n.h> #include "document-private.h" @@ -14,15 +15,11 @@ #include "document-undo.h" #include "util/units.h" #include "image-resolution.h" +#include "display/cairo-utils.h" #include <set> namespace Inkscape { -namespace IO { -// this is defined in sp-image.cpp -GdkPixbuf* pixbuf_new_from_file(char const *filename, time_t &modTime, gchar*& pixPath); -} - namespace Extension { namespace Internal { @@ -47,9 +44,7 @@ GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) } SPDocument *doc = NULL; - gchar *pixpath = NULL; - time_t dummy; - GdkPixbuf *pb = Inkscape::IO::pixbuf_new_from_file(uri, dummy, pixpath); + boost::scoped_ptr<Inkscape::Pixbuf> pb(Inkscape::Pixbuf::create_from_file(uri)); // TODO: the pixbuf is created again from the base64-encoded attribute in SPImage. // Find a way to create the pixbuf only once. @@ -59,8 +54,8 @@ GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) bool saved = DocumentUndo::getUndoSensitive(doc); DocumentUndo::setUndoSensitive(doc, false); // no need to undo in this temporary document - double width = gdk_pixbuf_get_width(pb); - double height = gdk_pixbuf_get_height(pb); + double width = pb->width(); + double height = pb->height(); double defaultxdpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", Inkscape::Util::Quantity::convert(1, "in", "px")); bool forcexdpi = prefs->getBool("/dialogs/import/forcexdpi"); ImageResolution *ir = 0; @@ -91,7 +86,7 @@ GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) sp_repr_set_svg_double(image_node, "height", height); if (embed) { - sp_embed_image(image_node, pb); + sp_embed_image(image_node, pb.get()); } else { // convert filename to uri gchar* _uri = g_filename_to_uri(uri, NULL, NULL); @@ -103,9 +98,6 @@ GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) } } - g_object_set_data(G_OBJECT(pb), "cairo_surface", NULL); - g_object_unref(pb); - // Add it to the current layer doc->getRoot()->appendChildRepr(image_node); Inkscape::GC::release(image_node); diff --git a/src/extension/internal/metafile-print.cpp b/src/extension/internal/metafile-print.cpp index 9d080bd96..1e7735410 100644 --- a/src/extension/internal/metafile-print.cpp +++ b/src/extension/internal/metafile-print.cpp @@ -266,7 +266,7 @@ void PrintMetafile::hatch_classify(char *name, int *hatchType, U_COLORREF *hatch // otherwise hatchType is set to -1 and hatchColor is not defined. // -void PrintMetafile::brush_classify(SPObject *parent, int depth, GdkPixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor) +void PrintMetafile::brush_classify(SPObject *parent, int depth, Inkscape::Pixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor) { if (depth == 0) { *epixbuf = NULL; diff --git a/src/extension/internal/metafile-print.h b/src/extension/internal/metafile-print.h index e64ba92f3..cba4d564d 100644 --- a/src/extension/internal/metafile-print.h +++ b/src/extension/internal/metafile-print.h @@ -30,6 +30,8 @@ struct SPGradient; struct SPObject; namespace Inkscape { +class Pixbuf; + namespace Extension { namespace Internal { @@ -93,7 +95,7 @@ protected: U_COLORREF weight_colors(U_COLORREF c1, U_COLORREF c2, double t); void hatch_classify(char *name, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor); - void brush_classify(SPObject *parent, int depth, GdkPixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor); + void brush_classify(SPObject *parent, int depth, Inkscape::Pixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor); static void swapRBinRGBA(char *px, int pixels); int hold_gradient(void *gr, int mode); diff --git a/src/extension/internal/wmf-print.cpp b/src/extension/internal/wmf-print.cpp index e5816073e..99262b109 100644 --- a/src/extension/internal/wmf-print.cpp +++ b/src/extension/internal/wmf-print.cpp @@ -56,6 +56,7 @@ #include "sp-gradient.h" #include "sp-radial-gradient.h" #include "sp-linear-gradient.h" +#include "display/cairo-utils.h" #include "splivarot.h" // pieces for union on shapes #include "2geom/svg-path-parser.h" // to get from SVG text to Geom::Path @@ -336,7 +337,7 @@ int PrintWmf::create_brush(SPStyle const *style, PU_COLORREF fcolor) U_WLOGBRUSH lb; uint32_t brush, fmode; MFDrawMode fill_mode; - GdkPixbuf *pixbuf; + Inkscape::Pixbuf *pixbuf; uint32_t brushStyle; int hatchType; U_COLORREF hatchColor; @@ -464,7 +465,7 @@ int PrintWmf::create_brush(SPStyle const *style, PU_COLORREF fcolor) int numCt; U_BITMAPINFOHEADER Bmih; PU_BITMAPINFO Bmi; - rgba_px = (char *) gdk_pixbuf_get_pixels(pixbuf); // Do NOT free this!!! + rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!! colortype = U_BCBM_COLOR32; (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1); // Not sure why the next swap is needed because the preceding does it, and the code is identical @@ -1112,10 +1113,10 @@ unsigned int PrintWmf::image( g_error("Fatal programming error in PrintWmf::image at EMRHEADER"); } - x1 = atof(style->object->getAttribute("x")); - y1 = atof(style->object->getAttribute("y")); - dw = atof(style->object->getAttribute("width")); - dh = atof(style->object->getAttribute("height")); + x1 = g_ascii_strtod(style->object->getAttribute("x"), NULL); + y1 = g_ascii_strtod(style->object->getAttribute("y"), NULL); + dw = g_ascii_strtod(style->object->getAttribute("width"), NULL); + dh = g_ascii_strtod(style->object->getAttribute("height"), NULL); Geom::Point pLL(x1, y1); Geom::Point pLL2 = pLL * tf; //location of LL corner in Inkscape coordinates diff --git a/src/filter-chemistry.cpp b/src/filter-chemistry.cpp index be030e12f..0f9138560 100644 --- a/src/filter-chemistry.cpp +++ b/src/filter-chemistry.cpp @@ -208,6 +208,15 @@ new_filter_gaussian_blur (SPDocument *document, gdouble radius, double expansion set_filter_area(repr, radius, expansion, expansionX, expansionY, width, height); + /* Inkscape now supports both sRGB and linear color-interpolation-filters. + * But, for the moment, keep sRGB as default value for new filters. + * historically set to sRGB and doesn't require conversion between + * filter cairo surfaces and other types of cairo surfaces. lp:1127103 */ + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "color-interpolation-filters", "sRGB"); + sp_repr_css_change(repr, css, "style"); + sp_repr_css_attr_unref(css); + //create feGaussianBlur node Inkscape::XML::Node *b_repr; b_repr = xml_doc->createElement("svg:feGaussianBlur"); @@ -260,6 +269,15 @@ new_filter_blend_gaussian_blur (SPDocument *document, const char *blendmode, gdo repr = xml_doc->createElement("svg:filter"); repr->setAttribute("inkscape:collect", "always"); + /* Inkscape now supports both sRGB and linear color-interpolation-filters. + * But, for the moment, keep sRGB as default value for new filters. + * historically set to sRGB and doesn't require conversion between + * filter cairo surfaces and other types of cairo surfaces. lp:1127103 */ + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "color-interpolation-filters", "sRGB"); + sp_repr_css_change(repr, css, "style"); + sp_repr_css_attr_unref(css); + // Append the new filter node to defs defs->appendChild(repr); Inkscape::GC::release(repr); diff --git a/src/filters/image.cpp b/src/filters/image.cpp index a454061c9..9a1b85911 100644 --- a/src/filters/image.cpp +++ b/src/filters/image.cpp @@ -17,6 +17,8 @@ #ifdef HAVE_CONFIG_H # include "config.h" #endif + +#include <sigc++/bind.h> #include "display/nr-filter-image.h" #include "uri.h" #include "uri-references.h" diff --git a/src/helper/pixbuf-ops.cpp b/src/helper/pixbuf-ops.cpp index a51a62f42..8e611d197 100644 --- a/src/helper/pixbuf-ops.cpp +++ b/src/helper/pixbuf-ops.cpp @@ -16,6 +16,7 @@ #endif #include <png.h> +#include <boost/scoped_ptr.hpp> #include <2geom/transforms.h> #include "interface.h" @@ -67,16 +68,14 @@ bool sp_export_jpg_file(SPDocument *doc, gchar const *filename, unsigned width, unsigned height, double xdpi, double ydpi, unsigned long bgcolor, double quality,GSList *items) { - GdkPixbuf* pixbuf = 0; - pixbuf = sp_generate_internal_bitmap(doc, filename, x0, y0, x1, y1, - width, height, xdpi, ydpi, - bgcolor, items ); + boost::scoped_ptr<Inkscape::Pixbuf> pixbuf( + sp_generate_internal_bitmap(doc, filename, x0, y0, x1, y1, + width, height, xdpi, ydpi, bgcolor, items)); gchar c[32]; g_snprintf(c, 32, "%f", quality); - gboolean saved = gdk_pixbuf_save (pixbuf, filename, "jpeg", NULL, "quality", c, NULL); + gboolean saved = gdk_pixbuf_save(pixbuf->getPixbufRaw(), filename, "jpeg", NULL, "quality", c, NULL); g_free(c); - g_object_unref (pixbuf); return saved; } @@ -94,7 +93,7 @@ bool sp_export_jpg_file(SPDocument *doc, gchar const *filename, @param ydpi @return the created GdkPixbuf structure or NULL if no memory is allocable */ -GdkPixbuf *sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename*/, +Inkscape::Pixbuf *sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename*/, double x0, double y0, double x1, double y1, unsigned width, unsigned height, double xdpi, double ydpi, unsigned long /*bgcolor*/, @@ -103,7 +102,7 @@ GdkPixbuf *sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename* { if (width == 0 || height == 0) return NULL; - GdkPixbuf* pixbuf = NULL; + Inkscape::Pixbuf *inkpb = NULL; /* Create new drawing for offscreen rendering*/ Inkscape::Drawing drawing; drawing.setExact(true); @@ -146,7 +145,7 @@ GdkPixbuf *sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename* // render items drawing.render(ct, final_bbox, Inkscape::DrawingItem::RENDER_BYPASS_CACHE); - pixbuf = ink_pixbuf_create_from_cairo_surface(surface); + inkpb = new Inkscape::Pixbuf(surface); } else { @@ -158,7 +157,7 @@ GdkPixbuf *sp_generate_internal_bitmap(SPDocument *doc, gchar const */*filename* // gdk_pixbuf_save (pixbuf, "C:\\temp\\internal.jpg", "jpeg", NULL, "quality","100", NULL); - return pixbuf; + return inkpb; } /* diff --git a/src/helper/pixbuf-ops.h b/src/helper/pixbuf-ops.h index 44851d388..61a879f9b 100644 --- a/src/helper/pixbuf-ops.h +++ b/src/helper/pixbuf-ops.h @@ -15,11 +15,12 @@ #include <glib.h> class SPDocument; +namespace Inkscape { class Pixbuf; } bool sp_export_jpg_file (SPDocument *doc, gchar const *filename, double x0, double y0, double x1, double y1, unsigned int width, unsigned int height, double xdpi, double ydpi, unsigned long bgcolor, double quality, GSList *items_only = NULL); -GdkPixbuf* sp_generate_internal_bitmap(SPDocument *doc, gchar const *filename, +Inkscape::Pixbuf *sp_generate_internal_bitmap(SPDocument *doc, gchar const *filename, double x0, double y0, double x1, double y1, unsigned width, unsigned height, double xdpi, double ydpi, unsigned long bgcolor, GSList *items_only = NULL); diff --git a/src/interface.cpp b/src/interface.cpp index 63d507f3e..ea5eaf16a 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -2009,6 +2009,15 @@ void ContextMenu::MakeImageMenu (void) mi->set_sensitive(FALSE); } + /* Trace Pixel Art */ + mi = manage(new Gtk::MenuItem(_("Trace Pixel Art"),1)); + mi->signal_activate().connect(sigc::mem_fun(*this, &ContextMenu::ImageTracePixelArt)); + mi->show(); + insert(*mi,positionOfLastDialog++); + if (_desktop->selection->isEmpty()) { + mi->set_sensitive(FALSE); + } + /* Embed image */ if (Inkscape::Verb::getbyid( "org.ekips.filter.embedselectedimages" )) { mi = manage(new Gtk::MenuItem(C_("Context menu", "Embed Image"))); @@ -2126,6 +2135,12 @@ void ContextMenu::ImageTraceBitmap(void) _desktop->_dlg_mgr->showDialog("Trace"); } +void ContextMenu::ImageTracePixelArt(void) +{ + inkscape_dialogs_unhide(); + _desktop->_dlg_mgr->showDialog("PixelArt"); +} + void ContextMenu::ImageEmbed(void) { if (_desktop->selection->isEmpty()) { diff --git a/src/interface.h b/src/interface.h index 13fbaf9ac..215a3bfc9 100644 --- a/src/interface.h +++ b/src/interface.h @@ -239,6 +239,11 @@ class ContextMenu : public Gtk::Menu void ImageTraceBitmap(void); /** + * callback, is executed on clicking the "Trace Pixel Art" menu entry + */ + void ImageTracePixelArt(void); + + /** * callback, is executed on clicking the "Extract Image" menu entry */ void ImageExtract(void); diff --git a/src/libdepixelize/CMakeLists.txt b/src/libdepixelize/CMakeLists.txt new file mode 100644 index 000000000..64a72f9d9 --- /dev/null +++ b/src/libdepixelize/CMakeLists.txt @@ -0,0 +1,11 @@ + +set(libdepixelize_SRC + kopftracer2011.cpp + + # ------- + # Headers + kopftracer2011.h + splines.h +) + +add_inkscape_lib(depixelize_LIB "${libdepixelize_SRC}") diff --git a/src/libdepixelize/Makefile_insert b/src/libdepixelize/Makefile_insert new file mode 100644 index 000000000..75b19bf5c --- /dev/null +++ b/src/libdepixelize/Makefile_insert @@ -0,0 +1,11 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +libdepixelize/all: libdepixelize/libdepixelize.a + +libdepixelize/clean: + rm -f libdepixelize/libdepixelize.a $(libdepixelize_libdepixelize_a_OBJECTS) + +libdepixelize_libdepixelize_a_SOURCES = \ + libdepixelize/kopftracer2011.cpp \ + libdepixelize/kopftracer2011.h \ + libdepixelize/splines.h diff --git a/src/libdepixelize/kopftracer2011.cpp b/src/libdepixelize/kopftracer2011.cpp new file mode 100644 index 000000000..26ad8863b --- /dev/null +++ b/src/libdepixelize/kopftracer2011.cpp @@ -0,0 +1,470 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +// Build fix under Inkscape build tree +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include <glibmm/threads.h> +#endif + +#include <utility> +#include <algorithm> +#include "kopftracer2011.h" +#include "priv/colorspace.h" +#include "priv/homogeneoussplines.h" +#include "priv/branchless.h" +#include "priv/splines.h" +#include "priv/iterator.h" + +namespace Tracer { +namespace Heuristics { + +int curves(const PixelGraph &graph, PixelGraph::const_iterator a, + PixelGraph::const_iterator b); +bool islands(PixelGraph::const_iterator a, PixelGraph::const_iterator b); + +struct SparsePixels +{ + enum Diagonal { + /** + * From (first) the top left corner to (second) the bottom right. + */ + MAIN_DIAGONAL = 0, + /** + * From (first) the top right to (second) the bottom left. + */ + SECONDARY_DIAGONAL = 1 + }; + + typedef std::pair<PixelGraph::const_iterator, PixelGraph::const_iterator> + Edge; + typedef std::pair<Edge, int> EdgeWeight; + + void operator()(const PixelGraph &graph, unsigned radius); + + static bool similar_colors(PixelGraph::const_iterator n, + const guint8 (&a)[4], const guint8 (&b)[4]); + + /* + * Precondition: Must be filled according to Diagonal enum. + */ + EdgeWeight diagonals[2]; +}; + +} // namespace Heuristics + +Splines Kopf2011::to_voronoi(const std::string &filename, + const Options &options) +{ + return to_voronoi(Gdk::Pixbuf::create_from_file(filename), options); +} + +Splines Kopf2011::to_voronoi(const Glib::RefPtr<Gdk::Pixbuf const> &buf, + const Options &options) +{ + return Splines(_voronoi<Precision>(buf, options)); +} + +Splines Kopf2011::to_splines(const std::string &filename, + const Options &options) +{ + return to_splines(Gdk::Pixbuf::create_from_file(filename), options); +} + +Splines Kopf2011::to_splines(const Glib::RefPtr<Gdk::Pixbuf const> &buf, + const Options &options) +{ + HomogeneousSplines<Precision> splines(_voronoi<Precision>(buf, options)); + return Splines(splines, options.optimize, options.nthreads); +} + +template<class T> +SimplifiedVoronoi<T> Kopf2011::_voronoi(const Glib::RefPtr<Gdk::Pixbuf const> &buf, + const Options &options) +{ + PixelGraph graph(buf); + + /*if ( !graph.width() || !graph.height() ) + return;*/ + +#ifndef NDEBUG + graph.checkConsistency(); +#endif + + // This step could be part of the initialization of PixelGraph + // and decrease the necessary number of passes + graph.connectAllNeighbors(); + +#ifndef NDEBUG + graph.checkConsistency(); +#endif + + // This step can't be part of PixelGraph initilization without adding some + // cache misses due to random access patterns that might be injected + _disconnect_neighbors_with_dissimilar_colors(graph); + +#ifndef NDEBUG + graph.checkConsistency(); +#endif + + // This and below steps must be executed in separate. + // Otherwise, there will be colateral effects due to misassumption about the + // data being read. + _remove_crossing_edges_safe(graph); + +#ifndef NDEBUG + graph.checkConsistency(); +#endif + + _remove_crossing_edges_unsafe(graph, options); + +#ifndef NDEBUG + graph.checkConsistency(); +#endif + + return SimplifiedVoronoi<T>(graph); +} + +// TODO: move this function (plus connectAllNeighbors) to PixelGraph constructor +inline void +Kopf2011::_disconnect_neighbors_with_dissimilar_colors(PixelGraph &graph) +{ + using colorspace::similar_colors; + for ( PixelGraph::iterator it = graph.begin(), end = graph.end() ; it != end + ; ++it ) { + if ( it->adj.top ) + it->adj.top = similar_colors(it->rgba, (it - graph.width())->rgba); + if ( it->adj.topright ) { + it->adj.topright + = similar_colors(it->rgba, (it - graph.width() + 1)->rgba); + } + if ( it->adj.right ) + it->adj.right = similar_colors(it->rgba, (it + 1)->rgba); + if ( it->adj.bottomright ) { + it->adj.bottomright + = similar_colors(it->rgba, (it + graph.width() + 1)->rgba); + } + if ( it->adj.bottom ) { + it->adj.bottom + = similar_colors(it->rgba, (it + graph.width())->rgba); + } + if ( it->adj.bottomleft ) { + it->adj.bottomleft + = similar_colors(it->rgba, (it + graph.width() - 1)->rgba); + } + if ( it->adj.left ) + it->adj.left = similar_colors(it->rgba, (it - 1)->rgba); + if ( it->adj.topleft ) { + it->adj.topleft = similar_colors(it->rgba, + (it - graph.width() - 1)->rgba); + } + } +} + +/** + * This method removes crossing edges if the 2x2 block is fully connected. + * + * In this case the two diagonal connections can be safely removed without + * affecting the final result. + * + * \TODO: It should remember/cache who are the unsafe crossing edges? + */ +inline void Kopf2011::_remove_crossing_edges_safe(PixelGraph &graph) +{ + if ( graph.width() < 2 || graph.height() < 2 ) + return; + + PixelGraph::iterator it = graph.begin(); + for ( int i = 0 ; i != graph.height() - 1 ; ++i, ++it ) { + for ( int j = 0 ; j != graph.width() - 1 ; ++j, ++it ) { + // this <-> right + if ( !it->adj.right ) + continue; + + // this <-> down + if ( !it->adj.bottom ) + continue; + + PixelGraph::iterator down_right = it + graph.width() + 1; + + // down_right <-> right + if ( !down_right->adj.top ) + continue; + + // down_right <-> down + if ( !down_right->adj.left ) + continue; + + // main diagonal + // this <-> down_right + it->adj.bottomright = 0; + down_right->adj.topleft = 0; + + // secondary diagonal + // right <-> down + (it + 1)->adj.bottomleft = 0; + (it + graph.width())->adj.topright = 0; + } + } +} + +/** + * This method removes crossing edges using the heuristics. + */ +inline +void Kopf2011::_remove_crossing_edges_unsafe(PixelGraph &graph, + const Options &options) +{ + if ( graph.width() < 2 || graph.height() < 2 ) + return; + + // Iterate over the graph, 2x2 blocks at time + PixelGraph::iterator it = graph.begin(); + for (int i = 0 ; i != graph.height() - 1 ; ++i, ++it ) { + for ( int j = 0 ; j != graph.width() - 1 ; ++j, ++it ) { + using std::pair; + using std::make_pair; + + typedef pair<PixelGraph::iterator, PixelGraph::iterator> Edge; + typedef pair<Edge, int> EdgeWeight; + + EdgeWeight diagonals[2] = { + make_pair(make_pair(it, graph.nodeBottomRight(it)), 0), + make_pair(make_pair(graph.nodeRight(it), graph.nodeBottom(it)), + 0) + }; + + // Check if there are crossing edges + if ( !diagonals[0].first.first->adj.bottomright + || !diagonals[1].first.first->adj.bottomleft ) { + continue; + } + + // Compute weights + for ( int i = 0 ; i != 2 ; ++i ) { + // Curves and islands heuristics + PixelGraph::const_iterator a = diagonals[i].first.first; + PixelGraph::const_iterator b = diagonals[i].first.second; + + diagonals[i].second += Heuristics::curves(graph, a, b) + * options.curvesMultiplier; + + diagonals[i].second += Heuristics::islands(a, b) + * options.islandsWeight; + } + + { + // Sparse pixels heuristic + Heuristics::SparsePixels sparse_pixels; + + for ( int i = 0 ; i != 2 ; ++i ) + sparse_pixels.diagonals[i] = diagonals[i]; + + sparse_pixels(graph, options.sparsePixelsRadius); + + for ( int i = 0 ; i != 2 ; ++i ) { + diagonals[i].second += sparse_pixels.diagonals[i].second + * options.sparsePixelsMultiplier; + } + } + + // Remove edges with lower weight + if ( diagonals[0].second > diagonals[1].second ) { + diagonals[1].first.first->adj.bottomleft = 0; + diagonals[1].first.second->adj.topright = 0; + } else if ( diagonals[0].second < diagonals[1].second ) { + diagonals[0].first.first->adj.bottomright = 0; + diagonals[0].first.second->adj.topleft = 0; + } else { + diagonals[0].first.first->adj.bottomright = 0; + diagonals[0].first.second->adj.topleft = 0; + diagonals[1].first.first->adj.bottomleft = 0; + diagonals[1].first.second->adj.topright = 0; + } + } + } +} + +inline int Heuristics::curves(const PixelGraph &graph, + PixelGraph::const_iterator a, + PixelGraph::const_iterator b) +{ + int count = 1; + ToPtr<PixelGraph::Node> to_ptr; + ToIter<PixelGraph::Node> to_iter(graph.begin()); + + // b -> a + // and then a -> b + for ( int i = 0 ; i != 2 ; ++i ) { + PixelGraph::const_iterator it = i ? a : b; + PixelGraph::const_iterator prev = i ? b : a; + int local_count = 0; + + // Used to avoid inifinite loops in circular-like edges + const PixelGraph::const_iterator initial = it; + + while ( it->adjsize() == 2 ) { + ++local_count; + + // Iterate to next + { + // There are only two values that won't be zero'ed + // and one of them has the same value of prev + guintptr aux = guintptr(to_ptr(it)); + aux = (it->adj.top + * guintptr(to_ptr(graph.nodeTop(it)))) + + (it->adj.topright + * guintptr(to_ptr(graph.nodeTopRight(it)))) + + (it->adj.right + * guintptr(to_ptr(graph.nodeRight(it)))) + + (it->adj.bottomright + * guintptr(to_ptr(graph.nodeBottomRight(it)))) + + (it->adj.bottom + * guintptr(to_ptr(graph.nodeBottom(it)))) + + (it->adj.bottomleft + * guintptr(to_ptr(graph.nodeBottomLeft(it)))) + + (it->adj.left + * guintptr(to_ptr(graph.nodeLeft(it)))) + + (it->adj.topleft + * guintptr(to_ptr(graph.nodeTopLeft(it)))) + - guintptr(to_ptr(prev)); + prev = it; + it = to_iter(reinterpret_cast<PixelGraph::Node const*>(aux)); + } + + // Break infinite loops + if ( it == initial ) + return local_count; + } + count += local_count; + } + + return count; +} + +inline void Heuristics::SparsePixels::operator ()(const PixelGraph &graph, + unsigned radius) +{ + if ( !graph.width() || !graph.height() ) + return; + + // Clear weights + for ( int i = 0 ; i != 2 ; ++i ) + diagonals[i].second = 0; + + if ( !radius ) + return; + + // Fix radius/bounds + { + unsigned x = graph.toX(diagonals[MAIN_DIAGONAL].first.first); + unsigned y = graph.toY(diagonals[MAIN_DIAGONAL].first.first); + unsigned minor = std::min(x, y); + unsigned displace = radius - 1; + + if ( displace > minor ) { + displace = minor; + radius = displace + 1; + } + + displace = radius; + + if ( x + displace >= graph.width() ) { + displace = unsigned(graph.width()) - x - 1; + radius = displace; + } + + if ( y + displace >= graph.height() ) { + displace = unsigned(graph.height()) - y - 1; + radius = displace; + } + } + + if ( !radius ) + return; + + // Iterate over nodes and count them + { + PixelGraph::const_iterator it = diagonals[MAIN_DIAGONAL].first.first; + for ( unsigned i = radius - 1 ; i ; --i ) + it = graph.nodeTopLeft(it); + + bool invert = false; + for ( unsigned i = 0 ; i != 2 * radius ; ++i ) { + for ( unsigned j = 0 ; j != 2 * radius ; ++j ) { + for ( int k = 0 ; k != 2 ; ++k ) { + diagonals[k].second + += similar_colors(it, diagonals[k].first.first->rgba, + diagonals[k].first.second->rgba); + } + it = (invert ? graph.nodeLeft(it) : graph.nodeRight(it)); + } + it = (invert ? graph.nodeRight(it) : graph.nodeLeft(it)); + + + invert = !invert; + it = graph.nodeBottom(it); + } + } + + int minor = std::min(diagonals[0].second, diagonals[1].second); + for ( int i = 0 ; i != 2 ; ++i ) + diagonals[i].second -= minor; + + std::swap(diagonals[0].second, diagonals[1].second); +} + +inline bool +Heuristics::SparsePixels::similar_colors(PixelGraph::const_iterator n, + const guint8 (&a)[4], + const guint8 (&b)[4]) +{ + using colorspace::similar_colors; + return similar_colors(n->rgba, a) || similar_colors(n->rgba, b); +} + +inline bool Heuristics::islands(PixelGraph::const_iterator a, + PixelGraph::const_iterator b) +{ + if ( a->adjsize() == 1 || b->adjsize() == 1 ) + return true; + + return false; +} + +} // namespace Tracer + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/kopftracer2011.h b/src/libdepixelize/kopftracer2011.h new file mode 100644 index 000000000..73f3b7274 --- /dev/null +++ b/src/libdepixelize/kopftracer2011.h @@ -0,0 +1,123 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_KOPFTRACER2011_H +#define LIBDEPIXELIZE_TRACER_KOPFTRACER2011_H + +#include <string> + +// Contains exception definitions +#include <glibmm/fileutils.h> +#include <gdkmm/pixbuf.h> +#include "splines.h" + +namespace Tracer { + +class PixelGraph; +template<typename T> class SimplifiedVoronoi; +template<typename T> class HomogeneousSplines; + +class Kopf2011 +{ +public: + struct Options + { + enum Defaults { + CURVES_MULTIPLIER = 1, + ISLANDS_WEIGHT = 5, + SPARSE_PIXELS_MULTIPLIER = 1, + SPARSE_PIXELS_RADIUS = 4 + }; + + Options() : + curvesMultiplier(CURVES_MULTIPLIER), + islandsWeight(ISLANDS_WEIGHT), + sparsePixelsMultiplier(SPARSE_PIXELS_MULTIPLIER), + sparsePixelsRadius(SPARSE_PIXELS_RADIUS), + optimize(true), + nthreads(1) + {} + + // Heuristics + double curvesMultiplier; + int islandsWeight; + double sparsePixelsMultiplier; + unsigned sparsePixelsRadius; + + // Other options + bool optimize; + int nthreads; + }; + + /** + * # Exceptions + * + * Glib::FileError + * Gdk::PixbufError + */ + static Splines to_voronoi(const std::string &filename, + const Options &options = Options()); + + static Splines to_voronoi(const Glib::RefPtr<Gdk::Pixbuf const> &buf, + const Options &options = Options()); + + /** + * # Exceptions + * + * Glib::FileError + * Gdk::PixbufError + */ + static Splines to_splines(const std::string &filename, + const Options &options = Options()); + + static Splines to_splines(const Glib::RefPtr<Gdk::Pixbuf const> &buf, + const Options &options = Options()); + +private: + typedef double Precision; + + template<class T> + static SimplifiedVoronoi<T> _voronoi(const Glib::RefPtr<Gdk::Pixbuf const> &buf, + const Options &options); + + static void _disconnect_neighbors_with_dissimilar_colors(PixelGraph &graph); + static void _remove_crossing_edges_safe(PixelGraph &graph); + static void _remove_crossing_edges_unsafe(PixelGraph &graph, + const Options &options); +}; + +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_KOPFTRACER2011_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/makefile.in b/src/libdepixelize/makefile.in new file mode 100644 index 000000000..51d020db1 --- /dev/null +++ b/src/libdepixelize/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +OBJEXT = @OBJEXT@ + +# Explicit so that it's the default rule. +all: + cd .. && $(MAKE) libdepixelize/all + +clean %.a %.$(OBJEXT): + cd .. && $(MAKE) libdepixelize/$@ + +.PHONY: all clean + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/libdepixelize/priv/branchless.h b/src/libdepixelize/priv/branchless.h new file mode 100644 index 000000000..487a9688d --- /dev/null +++ b/src/libdepixelize/priv/branchless.h @@ -0,0 +1,58 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_BRANCHLESS_H +#define LIBDEPIXELIZE_TRACER_BRANCHLESS_H + +namespace Tracer { + +/** + * Branch misprediction proof operations + */ +namespace branchless { + +/* + * All modern processors optimize the branch to a conditional move + */ +template<class T> +T first_if(T first, T second, bool cond) +{ + return cond ? first : second; +} + +} // namespace branchless +} // namespace Tracer { + +#endif // LIBDEPIXELIZE_TRACER_BRANCHLESS_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/priv/colorspace.h b/src/libdepixelize/priv/colorspace.h new file mode 100644 index 000000000..4982630ad --- /dev/null +++ b/src/libdepixelize/priv/colorspace.h @@ -0,0 +1,111 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_YUV_H +#define LIBDEPIXELIZE_TRACER_YUV_H + +#include <glib.h> + +namespace Tracer { +namespace colorspace { + +/** + * The same algorithm used in hqx filter + */ +inline void rgb2yuv(guint8 r, guint8 g, guint8 b, + guint8 &y, guint8 &u, guint8 &v) +{ + y = 0.299 * r + 0.587 * g + 0.114 * b; + u = guint16(-0.169 * r - 0.331 * g + 0.5 * b) + 128; + v = guint16(0.5 * r - 0.419 * g - 0.081 * b) + 128; +} + +inline void rgb2yuv(const guint8 rgb[], guint8 yuv[]) +{ + rgb2yuv(rgb[0], rgb[1], rgb[2], yuv[0], yuv[1], yuv[2]); +} + +inline bool same_color(const guint8 (&a)[4], const guint8 (&b)[4]) +{ + return (a[0] == b[0] + && a[1] == b[1] + && a[2] == b[2] + && a[3] == b[3]); +} + +inline bool dissimilar_colors(const guint8 a[], const guint8 b[]) +{ + // C uses row-major order, so + // A[2][3] = { {1, 2, 3}, {4, 5, 6} } = {1, 2, 3, 4, 5, 6} + guint8 yuv[2][3]; + rgb2yuv(a, yuv[0]); + rgb2yuv(b, yuv[1]); + + // Magic numbers taken from hqx algorithm + // Only used to describe the level of tolerance + return abs(yuv[0][0] - yuv[1][0]) > 0x30 + || abs(yuv[0][1] - yuv[1][1]) > 7 + || abs(yuv[0][2] - yuv[1][2]) > 6; +} + +inline bool similar_colors(const guint8 a[], const guint8 b[]) +{ + return !dissimilar_colors(a, b); +} + +inline bool shading_edge(const guint8 a[], const guint8 b[]) +{ + // C uses row-major order, so + // A[2][3] = { {1, 2, 3}, {4, 5, 6} } = {1, 2, 3, 4, 5, 6} + guint8 yuv[2][3]; + rgb2yuv(a, yuv[0]); + rgb2yuv(b, yuv[1]); + + // Magic numbers taken from Kopf-Lischinski algorithm + // Only used to describe the level of tolerance + return abs(yuv[0][0] - yuv[1][0]) <= 100 + && abs(yuv[0][1] - yuv[1][1]) <= 100 + && abs(yuv[0][2] - yuv[1][2]) <= 100; +} + +inline bool contour_edge(const guint8 a[], const guint8 b[]) +{ + return !shading_edge(a, b); +} + +} // namespace colorspace +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_YUV_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/priv/homogeneoussplines.h b/src/libdepixelize/priv/homogeneoussplines.h new file mode 100644 index 000000000..57c77a163 --- /dev/null +++ b/src/libdepixelize/priv/homogeneoussplines.h @@ -0,0 +1,465 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_HOMOGENEOUSSPLINES_H +#define LIBDEPIXELIZE_TRACER_HOMOGENEOUSSPLINES_H + +#include "simplifiedvoronoi.h" +#include "point.h" +#include <algorithm> +#include <utility> + +namespace Tracer { + +template<typename T> +class HomogeneousSplines +{ +public: + struct Polygon + { + Polygon() {} + Polygon(const guint8 (&rgba)[4]) + { + for ( int i = 0 ; i != 4 ; ++i ) + this->rgba[i] = rgba[i]; + } + + std::vector< Point<T> > vertices; + + /** + * It may be benefited from C++11 move references. + */ + std::vector< std::vector< Point<T> > > holes; + + guint8 rgba[4]; + }; + + typedef typename std::vector<Polygon>::iterator iterator; + typedef typename std::vector<Polygon>::const_iterator const_iterator; + typedef typename std::vector<Polygon>::size_type size_type; + + HomogeneousSplines(const SimplifiedVoronoi<T> &voronoi); + + // Iterators + iterator begin() + { + return _polygons.begin(); + } + + const_iterator begin() const + { + return _polygons.begin(); + } + + iterator end() + { + return _polygons.end(); + } + + const_iterator end() const + { + return _polygons.end(); + } + + size_type size() const + { + return _polygons.size(); + } + + int width() const + { + return _width; + } + + int height() const + { + return _height; + } + +private: + typedef typename SimplifiedVoronoi<T>::Cell Cell; + typedef std::vector< Point<T> > Points; + + typedef typename SimplifiedVoronoi<T>::iterator voronoi_iter; + typedef typename SimplifiedVoronoi<T>::const_iterator voronoi_citer; + + typedef typename Points::iterator points_iter; + typedef typename Points::const_iterator points_citer; + typedef typename Points::reverse_iterator points_riter; + typedef typename Points::const_reverse_iterator points_criter; + + typedef std::pair<points_iter, points_iter> points_range; + typedef std::pair<points_citer, points_citer> points_crange; + + struct CommonEdge + { + bool ok; //< share an edge + Points *dst; + const Points *src; + + // the interval is closed on both ends + // different from [begin, end) STL style + points_iter dst_begin, dst_end; + points_citer src_begin, src_end; + }; + + struct SelfCommonEdge + { + bool ok; //< share an edge + + // Greater range. The one that should be erased from the vector. + points_riter grt_begin, grt_end; + + // Smaller range. The one that should be used to create a new vector. + points_riter sml_begin, sml_end; + }; + + /** + * Return ok == true if they share an edge (more than one point). + */ + CommonEdge _common_edge(Points &dst, const Points &src); + + /** + * Return ok == true if they share an edge (more than one point). + * + * - [dst_begin, dst_end) will contain the hole polygon + * - [src_begin, src_end) will contain the range to be erased + * + * It's required to do the search in backward order. + */ + SelfCommonEdge _common_edge(Points &points, points_riter it); + + /*! + * Add polygon represented by \p common_edge.src to \p common_edge.dst. + */ + void _polygon_union(CommonEdge common_edge); + + /** + * Weird recursive function created to solve the complex problem to fill + * polygons holes without the need to store temporaries on the heap nor + * changing requirements to some data type that don't invalidate iterators + * that point before the current element (maybe I'll write some poetry about + * the problem someday). + */ + void _fill_holes(std::vector<Points> &holes, points_iter region_begin, + points_iter region_end); + + std::vector<Polygon> _polygons; + int _width; + int _height; +}; + +template<class T> +HomogeneousSplines<T>::HomogeneousSplines(const SimplifiedVoronoi<T> &voronoi) : + _width(voronoi.width()), + _height(voronoi.height()) +{ + //if (!voronoi.size()) + // return; + using colorspace::same_color; + + // Identify visible edges (group polygons with the same color) + for ( voronoi_citer cell_it = voronoi.begin(), cell_end = voronoi.end() + ; cell_it != cell_end ; ++cell_it ) { + bool found = false; + for ( iterator polygon_it = _polygons.begin(), + polygon_end = _polygons.end() + ; polygon_it != polygon_end ; ++polygon_it ) { + if ( same_color(polygon_it->rgba, cell_it->rgba) ) { + CommonEdge common_edge = _common_edge(polygon_it->vertices, + cell_it->vertices); + if ( common_edge.ok ) { + _polygon_union(common_edge); + found = true; + + for ( iterator polygon2_it = polygon_it + 1 + ; polygon2_it != polygon_end ; ++polygon2_it ) { + if ( same_color(polygon_it->rgba, polygon2_it->rgba) ) { + CommonEdge common_edge2 + = _common_edge(polygon_it->vertices, + polygon2_it->vertices); + if ( common_edge2.ok ) { + _polygon_union(common_edge2); + _polygons.erase(polygon2_it); + break; + } + } + } + + break; + } + } + } + if ( !found ) { + Polygon polygon(cell_it->rgba); + polygon.vertices = cell_it->vertices; + _polygons.insert(_polygons.end(), polygon); + } + } + + // Find polygons with holes and fix them + // This iteration runs such complex time-consuming algorithm, but each + // polygon has an independent result. They wouldn't even need to share/sync + // results and the only waste would be a join at the end of the for. + for ( typename std::vector<Polygon>::iterator it = _polygons.begin(), + end = _polygons.end() ; it != end ; ++it ) { + SelfCommonEdge ce = _common_edge(it->vertices, it->vertices.rbegin()); + while ( ce.ok ) { + _fill_holes(it->holes, ce.sml_end.base(), ce.sml_begin.base()); + it->vertices.erase(ce.grt_end.base() + 1, ce.grt_begin.base()); + ce = _common_edge(it->vertices, ce.grt_end); + } + } +} + +// it can infinite loop if points of both entities are equal, +// but this shouldn't happen if user has only access to Kopf2011 interface +template<class T> +typename HomogeneousSplines<T>::CommonEdge +HomogeneousSplines<T>::_common_edge(Points &dst, const Points &src) +{ + // It's an edge, then the points are closer together. After we find the + // first point, there is no need for check against all points of the src + // a second time + + const points_iter dst_begin = dst.begin(); + const points_iter dst_end = dst.end(); + + const points_citer src_begin = src.begin(); + const points_citer src_end = src.end(); + + for ( points_iter it = dst_begin ; it != dst_end ; ++it ) { + points_citer src_it = std::find(src_begin, src_end, *it); + + if ( src_it == src_end ) + continue; + + points_iter dst_common_edge_begin = it; + points_citer src_common_edge_end = src_it; + + // iterate until find the beginning of the common edge range + while ( *dst_common_edge_begin == *src_common_edge_end ) { + if ( dst_common_edge_begin == dst_begin ) + dst_common_edge_begin = dst_end - 1; + else + --dst_common_edge_begin; + + ++src_common_edge_end; + if ( src_common_edge_end == src_end ) + src_common_edge_end = src_begin; + } + + // fix {dst_begin, src_end} range + ++dst_common_edge_begin; + if ( dst_common_edge_begin == dst_end ) + dst_common_edge_begin = dst_begin; + + if ( src_common_edge_end == src_begin ) + src_common_edge_end = src_end - 1; + else + --src_common_edge_end; + + points_iter dst_common_edge_end = it; + points_citer src_common_edge_begin = src_it; + + // find the end of the common edge range + while ( *dst_common_edge_end == *src_common_edge_begin ) { + ++dst_common_edge_end; + if ( dst_common_edge_end == dst_end ) + dst_common_edge_end = dst_begin; + + if ( src_common_edge_begin == src_begin ) + src_common_edge_begin = src_end - 1; + else + --src_common_edge_begin; + } + + // fix {dst_end, src_begin} range + if ( dst_common_edge_end == dst_begin ) + dst_common_edge_end = dst_end - 1; + else + --dst_common_edge_end; + + ++src_common_edge_begin; + if ( src_common_edge_begin == src_end ) + src_common_edge_begin = src_begin; + + CommonEdge ret; + + // if only one point in common + if ( dst_common_edge_begin == dst_common_edge_end ) + continue; + + ret.ok = true; + + ret.dst = &dst; + ret.dst_begin = dst_common_edge_begin; + ret.dst_end = dst_common_edge_end; + + ret.src = &src; + ret.src_begin = src_common_edge_begin; + ret.src_end = src_common_edge_end; + + return ret; + } + + CommonEdge ret; + ret.ok = false; + return ret; +} + +template<class T> +typename HomogeneousSplines<T>::SelfCommonEdge +HomogeneousSplines<T>::_common_edge(Points &points, points_riter it) +{ + SelfCommonEdge ret; + + ret.grt_end = points.rend(); + + for ( ; it != ret.grt_end ; ++it ) { + ret.sml_end = std::find(it + 1, ret.grt_end, *it); + + if ( ret.sml_end == ret.grt_end ) + continue; + + ret.grt_begin = it; + ret.grt_end = ret.sml_end + 1; + + ret.sml_begin = it; + + while ( *ret.sml_begin == *ret.sml_end ) { + ++ret.sml_begin; + --ret.sml_end; + } + + --ret.sml_begin; + ++ret.sml_end; + ++ret.sml_end; + + ret.ok = true; + return ret; + } + + ret.ok = false; + return ret; +} + +template<class T> +void HomogeneousSplines<T>::_polygon_union(CommonEdge common_edge) +{ + Points &dst = *common_edge.dst; + const Points &src = *common_edge.src; + + // the rotated cell must be inserted before (dst.begin() + index) + typename Points::difference_type index; + + // first, we remove the common edge in dst + if ( common_edge.dst_begin < common_edge.dst_end ) { + // common edge is in the middle of dst + + index = dst.erase(common_edge.dst_begin, + common_edge.dst_end + 1) - dst.begin(); + } else { + // common edge cross the end of dst + + dst.erase(common_edge.dst_begin, dst.end()); + dst.erase(dst.begin(), common_edge.dst_end); + index = dst.end() - dst.begin(); + } + + // second, we copy src points to polygon + if ( common_edge.src_begin < common_edge.src_end ) { + // common edge is in the middle of src + + const typename Points::difference_type nfirstinserted + = src.end() - common_edge.src_end; + const typename Points::difference_type nsecondinserted + = 1 + (common_edge.src_begin - src.begin()); + + dst.reserve(dst.size() + nfirstinserted + nsecondinserted); + + dst.insert(dst.begin() + index, common_edge.src_end, src.end()); + + dst.insert(dst.begin() + index + nfirstinserted, + src.begin(), common_edge.src_begin + 1); + } else { + // common edge cross the end of src + + dst.reserve(dst.size() + 1 + + (common_edge.src_begin - common_edge.src_end)); + + dst.insert(dst.begin() + index, + common_edge.src_end, common_edge.src_begin + 1); + } +} + +// The following piece of code is so evil that you could end up invoking an +// ancient beast if you proceed to read it, but I'll be able to explain it in +// the form of some video (text is not so representative as an image). +template<class T> +void HomogeneousSplines<T>::_fill_holes(std::vector<Points> &holes, + points_iter region_begin, + points_iter region_end) +{ + // the exact location might not always be back and iterators will be + // invalidated after some insertions, then the index is required + const typename std::vector<Points>::size_type hole_index = holes.size(); + holes.resize(hole_index + 1); + + for ( points_iter it = region_begin + 1 ; it != region_end ; ++it ) { + points_iter res = std::find(it + 1, region_end, *it); + if ( res == region_end ) + continue; + + holes[hole_index].insert(holes[hole_index].end(), region_begin, + it); + region_begin = res; + + do { + ++it; + --res; + } while ( *it == *res ); + _fill_holes(holes, it - 1, res + 2); + + it = region_begin; + } + + holes[hole_index].insert(holes[hole_index].end(), region_begin, + region_end - 1); +} + +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_HOMOGENEOUSSPLINES_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/priv/iterator.h b/src/libdepixelize/priv/iterator.h new file mode 100644 index 000000000..7caa9bfa9 --- /dev/null +++ b/src/libdepixelize/priv/iterator.h @@ -0,0 +1,123 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_ITERATOR_H +#define LIBDEPIXELIZE_TRACER_ITERATOR_H + +#include <vector> +#include <iterator> + +namespace Tracer { + +template<typename T> +const T *to_ptr(typename std::vector<T>::const_iterator it) +{ + return &*it; +} + +template<typename T> +T *to_ptr(typename std::vector<T>::iterator it) +{ + return &*it; +} + +template<typename T> +typename std::vector<T>::const_iterator to_iterator(T const *ptr, + typename std::vector<T> + ::const_iterator begin) +{ + typedef typename std::vector<T>::const_iterator It; + typedef typename std::iterator_traits<It>::difference_type difference_type; + difference_type idx = ptr - to_ptr<T>(begin); + return begin + idx; +} + +template<typename T> +typename std::vector<T>::iterator to_iterator(T *ptr, + typename std::vector<T>::iterator + begin) +{ + typedef typename std::vector<T>::iterator It; + typedef typename std::iterator_traits<It>::difference_type difference_type; + difference_type idx = ptr - to_ptr<T>(begin); + return begin + idx; +} + +template<typename T> +class ToIter +{ +public: + typedef typename std::vector<T>::const_iterator const_iterator; + typedef typename std::vector<T>::iterator iterator; + + ToIter(const_iterator begin) : + begin(begin) + {} + + const_iterator operator()(T const *ptr) const + { + return to_iterator<T>(ptr, begin); + } + + iterator operator()(T *ptr) const + { + return to_iterator<T>(ptr, begin); + } + +private: + typename std::vector<T>::const_iterator begin; +}; + +template<typename T> +class ToPtr +{ +public: + typedef typename std::vector<T>::const_iterator const_iterator; + typedef typename std::vector<T>::iterator iterator; + + const T *operator()(const_iterator it) const + { + return to_ptr<T>(it); + } + + T *operator()(iterator it) const + { + return to_ptr<T>(it); + } +}; + +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_ITERATOR_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/priv/pixelgraph.h b/src/libdepixelize/priv/pixelgraph.h new file mode 100644 index 000000000..32523d8ee --- /dev/null +++ b/src/libdepixelize/priv/pixelgraph.h @@ -0,0 +1,490 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_PIXELGRAPH_H +#define LIBDEPIXELIZE_TRACER_PIXELGRAPH_H + +#include <gdkmm/pixbuf.h> +#include <vector> +#include <cassert> + +namespace Tracer { + +class PixelGraph +{ +public: + class Node + { + public: + /* + * Hamming weight of \p adj + */ + unsigned adjsize() const + { + unsigned all[8] = { + adj.top, + adj.topright, + adj.right, + adj.bottomright, + adj.bottom, + adj.bottomleft, + adj.left, + adj.topleft + }; + return all[0] + all[1] + all[2] + all[3] + + all[4] + all[5] + all[6] + all[7]; + } + + guint8 rgba[4]; + // Nodes pointing from this + struct Adj + { + unsigned top: 1; + unsigned topright: 1; + unsigned right: 1; + unsigned bottomright: 1; + unsigned bottom: 1; + unsigned bottomleft: 1; + unsigned left: 1; + unsigned topleft: 1; + } adj; + }; + + typedef std::vector<Node>::iterator iterator; + typedef std::vector<Node>::const_iterator const_iterator; + typedef std::vector<Node>::reverse_iterator reverse_iterator; + typedef std::vector<Node>::const_reverse_iterator const_reverse_iterator; + + class ColumnView + { + public: + ColumnView(std::vector<Node> &nodes, int width, int column) : + _nodes(nodes), _width(width), _column(column) + {} + + Node &operator[](int line); + + private: + std::vector<Node> &_nodes; + const int _width; + const int _column; + }; + + PixelGraph(Glib::RefPtr<Gdk::Pixbuf const> pixbuf); + + void checkConsistency(); + + /** + * It'll let you access the nodes using the syntax: + * + * graph[x][y] + * + * Where x is the column and y is the line. + */ + ColumnView operator[](int column); + + // Iterators + iterator begin() + { + return _nodes.begin(); + } + + const_iterator begin() const + { + return _nodes.begin(); + } + + iterator end() + { + return _nodes.end(); + } + + const_iterator end() const + { + return _nodes.end(); + } + + reverse_iterator rbegin() + { + return _nodes.rbegin(); + } + + const_reverse_iterator rbegin() const + { + return _nodes.rbegin(); + } + + reverse_iterator rend() + { + return _nodes.rend(); + } + + const_reverse_iterator rend() const + { + return _nodes.rend(); + } + + size_t size() const + { + return _nodes.size(); + } + + int width() const + { + return _width; + } + + int height() const + { + return _height; + } + + // Algorithms + void connectAllNeighbors(); + + int toX(const_iterator n) const + { + return (&*n - &_nodes[0]) % _width; + } + + int toY(const_iterator n) const + { + return (&*n - &_nodes[0]) / _width; + } + + iterator nodeTop(iterator n) + { + return n - _width; + } + + iterator nodeBottom(iterator n) + { + return n + _width; + } + + iterator nodeLeft(iterator n) + { + return n - 1; + } + + iterator nodeRight(iterator n) + { + return n + 1; + } + + iterator nodeTopLeft(iterator n) + { + return n - _width - 1; + } + + iterator nodeTopRight(iterator n) + { + return n - _width + 1; + } + + iterator nodeBottomLeft(iterator n) + { + return n + _width - 1; + } + + iterator nodeBottomRight(iterator n) + { + return n + _width + 1; + } + + const_iterator nodeTop(const_iterator n) const + { + return n - _width; + } + + const_iterator nodeBottom(const_iterator n) const + { + return n + _width; + } + + const_iterator nodeLeft(const_iterator n) const + { + return n - 1; + } + + const_iterator nodeRight(const_iterator n) const + { + return n + 1; + } + + const_iterator nodeTopLeft(const_iterator n) const + { + return n - _width - 1; + } + + const_iterator nodeTopRight(const_iterator n) const + { + return n - _width + 1; + } + + const_iterator nodeBottomLeft(const_iterator n) const + { + return n + _width - 1; + } + + const_iterator nodeBottomRight(const_iterator n) const + { + return n + _width + 1; + } + +private: + PixelGraph(const PixelGraph&); + + int _width; + int _height; + + // The data representation follows the image data pattern from gdk-pixbuf. + // + // Quoting: + // "Image data in a pixbuf is stored in memory in uncompressed, packed + // format. Rows in the image are stored top to bottom, and in each row + // pixels are stored from left to right. There may be padding at the end of + // a row." + // + // Differently, _nodes don't put padding among rows. + std::vector<Node> _nodes; +}; + +inline PixelGraph::PixelGraph(Glib::RefPtr<Gdk::Pixbuf const> pixbuf) : + _width(pixbuf->get_width()), + _height(pixbuf->get_height()), + _nodes(size_t(_width) * _height) +{ + if ( !_width || !_height ) + return; + + // Initialize the graph using the pixels' color data + guint8 *pixels = pixbuf->get_pixels(); + Node *dest = &_nodes[0]; + const int n_channels = pixbuf->get_n_channels(); + const int rowpadding = pixbuf->get_rowstride() - _width * n_channels; + + if ( n_channels == 4 ) { + for ( int i = 0 ; i != _height ; ++i ) { + for ( int j = 0 ; j != _width ; ++j ) { + for ( int k = 0 ; k != 4 ; ++k ) + dest->rgba[k] = pixels[k]; + { + dest->adj.top = 0; + dest->adj.topright = 0; + dest->adj.right = 0; + dest->adj.bottomright = 0; + dest->adj.bottom = 0; + dest->adj.bottomleft = 0; + dest->adj.left = 0; + dest->adj.topleft = 0; + } + pixels += n_channels; + ++dest; + } + pixels += rowpadding; + } + } else { + assert(n_channels == 3); + for ( int i = 0 ; i != _height ; ++i ) { + for ( int j = 0 ; j != _width ; ++j ) { + for ( int k = 0 ; k != 3 ; ++k ) + dest->rgba[k] = pixels[k]; + dest->rgba[3] = '\xFF'; + { + dest->adj.top = 0; + dest->adj.topright = 0; + dest->adj.right = 0; + dest->adj.bottomright = 0; + dest->adj.bottom = 0; + dest->adj.bottomleft = 0; + dest->adj.left = 0; + dest->adj.topleft = 0; + } + pixels += n_channels; + ++dest; + } + pixels += rowpadding; + } + } +} + +inline void PixelGraph::checkConsistency() +{ + PixelGraph::Node *it = &_nodes.front(); + for ( int i = 0 ; i != _height ; ++i ) { + for ( int j = 0 ; j != _width ; ++j, ++it ) { + if ( it->adj.top ) + assert((it - _width)->adj.bottom); + if ( it->adj.topright ) + assert((it - _width + 1)->adj.bottomleft); + if ( it->adj.right ) + assert((it + 1)->adj.left); + if ( it->adj.bottomright ) + assert((it + _width + 1)->adj.topleft); + if ( it->adj.bottom ) + assert((it + _width)->adj.top); + if ( it->adj.bottomleft ) + assert((it + _width - 1)->adj.topright); + if ( it->adj.left ) + assert((it - 1)->adj.right); + if ( it->adj.topleft ) + assert((it - _width - 1)->adj.bottomright); + } + } +} + +inline PixelGraph::ColumnView PixelGraph::operator[](int column) +{ + return ColumnView(_nodes, _width, column); +} + +inline void PixelGraph::connectAllNeighbors() +{ + // ...the "center" nodes first... + if ( _width > 2 && _height > 2 ) { + iterator it = nodeBottomRight(begin()); // [1][1] + for ( int i = 1 ; i != _height - 1 ; ++i ) { + for ( int j = 1 ; j != _width - 1 ; ++j ) { + it->adj.top = 1; + it->adj.topright = 1; + it->adj.right = 1; + it->adj.bottomright = 1; + it->adj.bottom = 1; + it->adj.bottomleft = 1; + it->adj.left = 1; + it->adj.topleft = 1; + + it = nodeRight(it); + } + // After the previous loop, 'it' is poiting to the last node from + // the row. + // Go south, then first node in the row (increment 'it' by 1) + // Go to the second node in the line (increment 'it' by 1) + it += 2; + } + } + + // ...then the "top" nodes... + if ( _width > 2 ) { + Node *it = &_nodes[1]; + for ( int i = 1 ; i != _width - 1 ; ++i ) { + it->adj.right = 1; + it->adj.bottomright = 1; + it->adj.bottom = 1; + it->adj.bottomleft = 1; + it->adj.left = 1; + + ++it; + } + } + + // ...then the "bottom" nodes... + if ( _width > 2 && _height > 1 ) { + Node *it = &((*this)[1][_height - 1]); + for ( int i = 1 ; i != _width - 1 ; ++i ) { + it->adj.left = 1; + it->adj.topleft = 1; + it->adj.top = 1; + it->adj.topright = 1; + it->adj.right = 1; + + ++it; + } + } + + // ...then the "left" nodes... + if ( _height > 2 ) { + iterator it = nodeBottom(begin()); // [0][1] + for ( int i = 1 ; i != _height - 1 ; ++i ) { + it->adj.top = 1; + it->adj.topright = 1; + it->adj.right = 1; + it->adj.bottomright = 1; + it->adj.bottom = 1; + + it = nodeBottom(it); + } + } + + // ...then the "right" nodes... + if ( _height > 2 && _width > 1 ) { + iterator it = nodeBottom(begin() + _width - 1);// [_width - 1][1] + for ( int i = 1 ; i != _height - 1 ; ++i ) { + it->adj.bottom = 1; + it->adj.bottomleft = 1; + it->adj.left = 1; + it->adj.topleft = 1; + it->adj.top = 1; + + it = nodeBottom(it); + } + } + + // ...and the 4 corner nodes + { + Node *const top_left = &(*this)[0][0]; + top_left->adj.right = 1; + top_left->adj.bottomright = 1; + top_left->adj.bottom = 1; + } + if ( _width > 1 ) { + Node *const top_right = &(*this)[_width - 1][0]; + top_right->adj.bottom = 1; + top_right->adj.bottomleft = 1; + top_right->adj.left = 1; + } + if ( _height > 1 ) { + Node *const down_left = &(*this)[0][_height - 1]; + down_left->adj.top = 1; + down_left->adj.topright = 1; + down_left->adj.right = 1; + } + if ( _width > 1 && _height > 1 ) { + Node *const down_right = &(*this)[_width - 1][_height - 1]; + down_right->adj.left = 1; + down_right->adj.topleft = 1; + down_right->adj.top = 1; + } +} + +inline PixelGraph::Node &PixelGraph::ColumnView::operator[](int line) +{ + return _nodes[line * _width + _column]; +} + +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_PIXELGRAPH_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/priv/point.h b/src/libdepixelize/priv/point.h new file mode 100644 index 000000000..6bea752ed --- /dev/null +++ b/src/libdepixelize/priv/point.h @@ -0,0 +1,75 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_POINT_H +#define LIBDEPIXELIZE_TRACER_POINT_H + +namespace Tracer { + +template<class T> +struct Point +{ + Point() {} + Point(T x, T y) : x(x), y(y) {} + Point(T x, T y, bool smooth) : smooth(smooth), x(x), y(y) {} + + Point operator+(const Point &rhs) const + { + return Point(x + rhs.x, y + rhs.y); + } + + Point operator/(T foo) const + { + return Point(x / foo, y / foo); + } + + bool smooth; + + T x, y; +}; + +template<class T> +inline bool operator==(const Point<T> &lhs, const Point<T> &rhs) +{ + return +#ifndef LIBDEPIXELIZE_IS_VERY_WELL_TESTED + lhs.smooth == rhs.smooth && +#endif // LIBDEPIXELIZE_IS_VERY_WELL_TESTED + lhs.x == rhs.x && lhs.y == rhs.y; +} + +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_POINT_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/priv/simplifiedvoronoi.h b/src/libdepixelize/priv/simplifiedvoronoi.h new file mode 100644 index 000000000..a33695ff7 --- /dev/null +++ b/src/libdepixelize/priv/simplifiedvoronoi.h @@ -0,0 +1,1324 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_SIMPLIFIEDVORONOI_H +#define LIBDEPIXELIZE_TRACER_SIMPLIFIEDVORONOI_H + +#include "pixelgraph.h" +#include "colorspace.h" +#include "point.h" +#include "branchless.h" + +namespace Tracer { + +template<typename T> +class SimplifiedVoronoi +{ +public: + /** + * The "smooth" attribute of each vertex is only accurate if edge is + * visible. This decision was made because invisible edges will disappear + * during polygon-union, the next phase of Kopf-Lischinski. + */ + struct Cell + { + // There may not exist more than 8 vertices per cell and a + // "small vector optimization" could improve the performance + // by avoiding memory fragmentation. Serious testing is needed. + + // The vertices are filled in clockwise order + std::vector< Point<T> > vertices; + guint8 rgba[4]; + }; + + typedef typename std::vector<Cell>::iterator iterator; + typedef typename std::vector<Cell>::const_iterator const_iterator; + typedef typename std::vector<Cell>::reverse_iterator reverse_iterator; + + typedef typename std::vector<Cell>::const_reverse_iterator + const_reverse_iterator; + + /* + It will work correctly if no crossing-edges are present. + */ + SimplifiedVoronoi(const PixelGraph &graph); + + // Iterators + iterator begin() + { + return _cells.begin(); + } + + const_iterator begin() const + { + return _cells.begin(); + } + + iterator end() + { + return _cells.end(); + } + + const_iterator end() const + { + return _cells.end(); + } + + reverse_iterator rbegin() + { + return _cells.rbegin(); + } + + const_reverse_iterator rbegin() const + { + return _cells.rbegin(); + } + + reverse_iterator rend() + { + return _cells.rend(); + } + + const_reverse_iterator rend() const + { + return _cells.rend(); + } + + size_t size() const + { + return _cells.size(); + } + + int width() const + { + return _width; + } + + int height() const + { + return _height; + } + +private: + typedef void (*PointTransform)(Point<T> &p, T dx, T dy); + typedef bool (*NodeTransform)(PixelGraph::const_iterator); + + static Point<T> _midpoint(const Point<T> &p1, const Point<T> p2) + { + return Point<T>((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); + } + + /** + * Output is translated by -.5 in each axis. This function fixes this error. + */ + static Point<T> _adjust(Point<T> p) + { + return Point<T>(p.x + .5, p.y + .5); + } + + /** + * Output is translated by -.5 in each axis. This function fixes this error. + */ + static Point<T> _adjust(Point<T> p, bool smooth) + { + return Point<T>(p.x + .5, p.y + .5, smooth); + } + + void _complexTopLeft(const PixelGraph &graph, + PixelGraph::const_iterator graph_it, + Cell *const cells_it, int x, int y); + void _complexTopRight(const PixelGraph &graph, + PixelGraph::const_iterator graph_it, + Cell *const cells_it, int x, int y); + void _complexBottomRight(const PixelGraph &graph, + PixelGraph::const_iterator graph_it, + Cell *const cells_it, int x, int y); + void _complexBottomLeft(const PixelGraph &graph, + PixelGraph::const_iterator graph_it, + Cell *const cells_it, int x, int y); + + static void _complexTopLeftTransform(Point<T> &p, T dx, T dy); + static void _complexTopRightTransform(Point<T> &p, T dx, T dy); + static void _complexBottomRightTransform(Point<T> &p, T dx, T dy); + static void _complexBottomLeftTransform(Point<T> &p, T dx, T dy); + + static bool _complexTopLeftTransformTop(PixelGraph::const_iterator graph_it); + static bool _complexTopLeftTransformTopRight(PixelGraph::const_iterator graph_it); + static bool _complexTopLeftTransformRight(PixelGraph::const_iterator graph_it); + static bool _complexTopLeftTransformBottomRight(PixelGraph::const_iterator graph_it); + static bool _complexTopLeftTransformBottom(PixelGraph::const_iterator graph_it); + static bool _complexTopLeftTransformBottomLeft(PixelGraph::const_iterator graph_it); + static bool _complexTopLeftTransformLeft(PixelGraph::const_iterator graph_it); + static bool _complexTopLeftTransformTopLeft(PixelGraph::const_iterator graph_it); + + static bool _complexTopRightTransformTop(PixelGraph::const_iterator graph_it); + static bool _complexTopRightTransformTopRight(PixelGraph::const_iterator graph_it); + static bool _complexTopRightTransformRight(PixelGraph::const_iterator graph_it); + static bool _complexTopRightTransformBottomRight(PixelGraph::const_iterator graph_it); + static bool _complexTopRightTransformBottom(PixelGraph::const_iterator graph_it); + static bool _complexTopRightTransformBottomLeft(PixelGraph::const_iterator graph_it); + static bool _complexTopRightTransformLeft(PixelGraph::const_iterator graph_it); + static bool _complexTopRightTransformTopLeft(PixelGraph::const_iterator graph_it); + + static bool _complexBottomRightTransformTop(PixelGraph::const_iterator graph_it); + static bool _complexBottomRightTransformTopRight(PixelGraph::const_iterator graph_it); + static bool _complexBottomRightTransformRight(PixelGraph::const_iterator graph_it); + static bool _complexBottomRightTransformBottomRight(PixelGraph::const_iterator graph_it); + static bool _complexBottomRightTransformBottom(PixelGraph::const_iterator graph_it); + static bool _complexBottomRightTransformBottomLeft(PixelGraph::const_iterator graph_it); + static bool _complexBottomRightTransformLeft(PixelGraph::const_iterator graph_it); + static bool _complexBottomRightTransformTopLeft(PixelGraph::const_iterator graph_it); + + static bool _complexBottomLeftTransformTop(PixelGraph::const_iterator graph_it); + static bool _complexBottomLeftTransformTopRight(PixelGraph::const_iterator graph_it); + static bool _complexBottomLeftTransformRight(PixelGraph::const_iterator graph_it); + static bool _complexBottomLeftTransformBottomRight(PixelGraph::const_iterator graph_it); + static bool _complexBottomLeftTransformBottom(PixelGraph::const_iterator graph_it); + static bool _complexBottomLeftTransformBottomLeft(PixelGraph::const_iterator graph_it); + static bool _complexBottomLeftTransformLeft(PixelGraph::const_iterator graph_it); + static bool _complexBottomLeftTransformTopLeft(PixelGraph::const_iterator graph_it); + + /* + * The memory layout assumed goes like this (with a_it being the current + * iterated element): + * + * (a_it) | (b_it) + * -------+------- + * (c_it) | (d_it) + * + * If you want to use it with another directions (topleft, topright, ...) + * **DO NOT** invert x or y axis, because the insertion order will go mad. + * + * The idea behind this abstraction is to rotate the iterators, then the + * insertion order will be preserved. + * + * The initial value of all nodes that will be inserted is {x, y}. All + * changes to this node **MUST** occur through \p transform or _adjust. + * + * Some maintainers may like this function because they will handle a + * code base 4 times smaller and bugs will be MUCH MUCH difficult to hide. + * + * Some maintainers may go mad because the level extra level of + * abstracation. + * + * "All problems in computer science can be solved by another level of + * indirection, except for the problem of too many layers of indirection." + * -- David J. Wheeler + */ + void _genericComplexBottomRight(PixelGraph::const_iterator a_it, + PixelGraph::const_iterator b_it, + PixelGraph::const_iterator c_it, + PixelGraph::const_iterator d_it, + Cell *const cells_it, int x, int y, + PointTransform transform, + NodeTransform top, + NodeTransform topright, + NodeTransform right, + NodeTransform bottomright, + NodeTransform bottom, + NodeTransform bottomleft, + NodeTransform left, + NodeTransform topleft); + + int _width; + int _height; + std::vector<Cell> _cells; +}; + +template<class T> +SimplifiedVoronoi<T>::SimplifiedVoronoi(const PixelGraph &graph) : + _width(graph.width()), + _height(graph.height()), + _cells(graph.size()) +{ + if (!graph.size()) + return; + + // ...the "center" cells first... + if ( _width > 2 && _height > 2 ) { + PixelGraph::const_iterator graph_it = graph.begin() + _width + 1; + Cell *cells_it = &_cells.front() + _width + 1; + + for ( int i = 1 ; i != _height - 1 ; ++i ) { + for ( int j = 1 ; j != _width - 1 ; ++j, ++graph_it, ++cells_it ) { + for ( int k = 0 ; k != 4 ; ++k ) + cells_it->rgba[k] = graph_it->rgba[k]; + // Top-left + _complexTopLeft(graph, graph_it, cells_it, j, i); + + // Top-right + _complexTopRight(graph, graph_it, cells_it, j, i); + + // Bottom-right + _complexBottomRight(graph, graph_it, cells_it, j, i); + + // Bottom-left + _complexBottomLeft(graph, graph_it, cells_it, j, i); + } + // After the previous loop, 'it' is poiting to the last cell from + // the row. + // Go south, then first node in the row (increment 'it' by 1) + // Go to the second node in the line (increment 'it' by 1) + graph_it += 2; + cells_it += 2; + } + } + + // ...then the "top" cells... + if ( _width > 2 ) { + PixelGraph::const_iterator graph_it = graph.begin() + 1; + Cell *cells_it = &_cells.front() + 1; + + for ( int i = 1 ; i != _width - 1 ; ++i, ++graph_it, ++cells_it ) { + for ( int j = 0 ; j != 4 ; ++j ) + cells_it->rgba[j] = graph_it->rgba[j]; + + // Top-left + cells_it->vertices.push_back(Point<T>(i, 0, false)); + + // Top-right + cells_it->vertices.push_back(Point<T>(i + 1, 0, false)); + + // Bottom-right + _complexBottomRight(graph, graph_it, cells_it, i, 0); + + // Bottom-left + _complexBottomLeft(graph, graph_it, cells_it, i, 0); + } + } + + // ...then the "bottom" cells... + if ( _width > 2 && _height > 1 ) { + // Node *it = &((*this)[1][_height - 1]); + PixelGraph::const_iterator graph_it + = graph.begin() + (_height - 1) * _width + 1; + Cell *cells_it = &_cells.front() + (_height - 1) * _width + 1; + + for ( int i = 1 ; i != _width - 1 ; ++i, ++graph_it, ++cells_it ) { + for ( int j = 0 ; j != 4 ; ++j ) + cells_it->rgba[j] = graph_it->rgba[j]; + + // Top-left + _complexTopLeft(graph, graph_it, cells_it, i, _height - 1); + + // Top-right + _complexTopRight(graph, graph_it, cells_it, i, _height - 1); + + // Bottom-right + cells_it->vertices.push_back(Point<T>(i + 1, _height, false)); + + // Bottom-left + cells_it->vertices.push_back(Point<T>(i, _height, false)); + } + } + + // ...then the "left" cells... + if ( _height > 2 ) { + PixelGraph::const_iterator graph_it = graph.begin() + _width; + Cell *cells_it = &_cells.front() + _width; + + for ( int i = 1 ; i != _height - 1 ; ++i) { + for ( int j = 0 ; j != 4 ; ++j ) + cells_it->rgba[j] = graph_it->rgba[j]; + + // Top-left + cells_it->vertices.push_back(Point<T>(0, i, false)); + + // Top-right + _complexTopRight(graph, graph_it, cells_it, 0, i); + + // Bottom-right + _complexBottomRight(graph, graph_it, cells_it, 0, i); + + // Bottom-left + cells_it->vertices.push_back(Point<T>(0, i + 1, false)); + + graph_it += _width; + cells_it += _width; + } + } + + // ...then the "right" cells... + if ( _height > 2 && _width > 1 ) { + PixelGraph::const_iterator graph_it = graph.begin() + 2 * _width - 1; + Cell *cells_it = &_cells.front() + 2 * _width - 1; + + for ( int i = 1 ; i != _height - 1 ; ++i ) { + for ( int j = 0 ; j != 4 ; ++j ) + cells_it->rgba[j] = graph_it->rgba[j]; + + // Top-left + _complexTopLeft(graph, graph_it, cells_it, _width - 1, i); + + // Top-right + cells_it->vertices.push_back(Point<T>(_width, i, false)); + + // Bottom-right + cells_it->vertices.push_back(Point<T>(_width, i + 1, false)); + + // Bottom-left + _complexBottomLeft(graph, graph_it, cells_it, _width - 1, i); + + graph_it += _width; + cells_it += _width; + } + } + + // ...and the 4 corner nodes + // top-left + { + PixelGraph::const_iterator graph_it = graph.begin(); + Cell *cells_it = &_cells.front(); + + for ( int i = 0 ; i != 4 ; ++i ) + cells_it->rgba[i] = graph_it->rgba[i]; + + // Top-left + cells_it->vertices.push_back(Point<T>(0, 0, false)); + + // Top-right + cells_it->vertices.push_back(Point<T>(1, 0, false)); + + // Bottom-right + if ( _width > 1 && _height > 1 ) + _complexBottomRight(graph, graph_it, cells_it, 0, 0); + else + cells_it->vertices.push_back(Point<T>(1, 1, false)); + + // Bottom-left + cells_it->vertices.push_back(Point<T>(0, 1, false)); + } + + // top-right + if ( _width > 1 ) { + PixelGraph::const_iterator graph_it = graph.begin() + _width - 1; + Cell *cells_it = &_cells.front() + _width - 1; + + for ( int i = 0 ; i != 4 ; ++i ) + cells_it->rgba[i] = graph_it->rgba[i]; + + // Top-left + cells_it->vertices.push_back(Point<T>(_width - 1, 0, false)); + + // Top-right + cells_it->vertices.push_back(Point<T>(_width, 0, false)); + + // Bottom-right + cells_it->vertices.push_back(Point<T>(_width, 1, false)); + + // Bottom-left + if ( _height > 1 ) + _complexBottomLeft(graph, graph_it, cells_it, _width - 1, 0); + else + cells_it->vertices.push_back(Point<T>(_width - 1, 1, false)); + } + + // bottom-left + if ( _height > 1 ) { + PixelGraph::const_iterator graph_it + = graph.begin() + (_height - 1) * _width; + Cell *cells_it = &_cells.front() + (_height - 1) * _width; + + for ( int i = 0 ; i != 4 ; ++i ) + cells_it->rgba[i] = graph_it->rgba[i]; + + // Top-left + cells_it->vertices.push_back(Point<T>(0, _height - 1, false)); + + // Top-right + if ( _width > 1) + _complexTopRight(graph, graph_it, cells_it, 0, _height - 1); + else + cells_it->vertices.push_back(Point<T>(1, _height - 1, false)); + + // Bottom-right + cells_it->vertices.push_back(Point<T>(1, _height, false)); + + // Bottom-left + cells_it->vertices.push_back(Point<T>(0, _height, false)); + } + + // bottom-right + if ( _width > 1 && _height > 1 ) { + PixelGraph::const_iterator graph_it = --graph.end(); + Cell *cells_it = &_cells.back(); + + for ( int i = 0 ; i != 4 ; ++i ) + cells_it->rgba[i] = graph_it->rgba[i]; + + // Top-left + _complexTopLeft(graph, graph_it, cells_it, _width - 1, _height - 1); + + // Top-right + cells_it->vertices.push_back(Point<T>(_width, _height - 1, false)); + + // Bottom-right + cells_it->vertices.push_back(Point<T>(_width, _height, false)); + + // Bottom-left + cells_it->vertices.push_back(Point<T>(_width - 1, _height, false)); + } +} + +template<class T> void +SimplifiedVoronoi<T>::_complexTopLeft(const PixelGraph &graph, + PixelGraph::const_iterator graph_it, + Cell *const cells_it, int x, int y) +{ + _genericComplexBottomRight(graph_it, + graph.nodeLeft(graph_it), + graph.nodeTop(graph_it), + graph.nodeTopLeft(graph_it), + cells_it, x, y, + &SimplifiedVoronoi::_complexTopLeftTransform, + &SimplifiedVoronoi::_complexTopLeftTransformTop, + &SimplifiedVoronoi::_complexTopLeftTransformTopRight, + &SimplifiedVoronoi::_complexTopLeftTransformRight, + &SimplifiedVoronoi::_complexTopLeftTransformBottomRight, + &SimplifiedVoronoi::_complexTopLeftTransformBottom, + &SimplifiedVoronoi::_complexTopLeftTransformBottomLeft, + &SimplifiedVoronoi::_complexTopLeftTransformLeft, + &SimplifiedVoronoi::_complexTopLeftTransformTopLeft); +} + +template<class T> void +SimplifiedVoronoi<T>::_complexTopRight(const PixelGraph &graph, + PixelGraph::const_iterator graph_it, + Cell *const cells_it, int x, int y) +{ + _genericComplexBottomRight(graph_it, + graph.nodeTop(graph_it), + graph.nodeRight(graph_it), + graph.nodeTopRight(graph_it), + cells_it, x, y, + &SimplifiedVoronoi::_complexTopRightTransform, + &SimplifiedVoronoi::_complexTopRightTransformTop, + &SimplifiedVoronoi::_complexTopRightTransformTopRight, + &SimplifiedVoronoi::_complexTopRightTransformRight, + &SimplifiedVoronoi::_complexTopRightTransformBottomRight, + &SimplifiedVoronoi::_complexTopRightTransformBottom, + &SimplifiedVoronoi::_complexTopRightTransformBottomLeft, + &SimplifiedVoronoi::_complexTopRightTransformLeft, + &SimplifiedVoronoi::_complexTopRightTransformTopLeft); +} + +template<class T> void +SimplifiedVoronoi<T>::_complexBottomRight(const PixelGraph &graph, + PixelGraph::const_iterator graph_it, + Cell *const cells_it, int x, int y) +{ + _genericComplexBottomRight(graph_it, + graph.nodeRight(graph_it), + graph.nodeBottom(graph_it), + graph.nodeBottomRight(graph_it), + cells_it, x, y, + &SimplifiedVoronoi::_complexBottomRightTransform, + &SimplifiedVoronoi::_complexBottomRightTransformTop, + &SimplifiedVoronoi::_complexBottomRightTransformTopRight, + &SimplifiedVoronoi::_complexBottomRightTransformRight, + &SimplifiedVoronoi::_complexBottomRightTransformBottomRight, + &SimplifiedVoronoi::_complexBottomRightTransformBottom, + &SimplifiedVoronoi::_complexBottomRightTransformBottomLeft, + &SimplifiedVoronoi::_complexBottomRightTransformLeft, + &SimplifiedVoronoi::_complexBottomRightTransformTopLeft); +} + +template<class T> void +SimplifiedVoronoi<T>::_complexBottomLeft(const PixelGraph &graph, + PixelGraph::const_iterator graph_it, + Cell *const cells_it, int x, int y) +{ + _genericComplexBottomRight(graph_it, + graph.nodeBottom(graph_it), + graph.nodeLeft(graph_it), + graph.nodeBottomLeft(graph_it), + cells_it, x, y, + &SimplifiedVoronoi::_complexBottomLeftTransform, + &SimplifiedVoronoi::_complexBottomLeftTransformTop, + &SimplifiedVoronoi::_complexBottomLeftTransformTopRight, + &SimplifiedVoronoi::_complexBottomLeftTransformRight, + &SimplifiedVoronoi::_complexBottomLeftTransformBottomRight, + &SimplifiedVoronoi::_complexBottomLeftTransformBottom, + &SimplifiedVoronoi::_complexBottomLeftTransformBottomLeft, + &SimplifiedVoronoi::_complexBottomLeftTransformLeft, + &SimplifiedVoronoi::_complexBottomLeftTransformTopLeft); +} + +template<class T> void +SimplifiedVoronoi<T>::_complexTopLeftTransform(Point<T> &p, T dx, T dy) +{ + p.x -= dx; + p.y -= dy; +} + +template<class T> void +SimplifiedVoronoi<T>::_complexTopRightTransform(Point<T> &p, T dx, T dy) +{ + p.x += dy; + p.y -= dx; +} + +template<class T> void +SimplifiedVoronoi<T>::_complexBottomRightTransform(Point<T> &p, T dx, T dy) +{ + p.x += dx; + p.y += dy; +} + +template<class T> void +SimplifiedVoronoi<T>::_complexBottomLeftTransform(Point<T> &p, T dx, T dy) +{ + p.x -= dy; + p.y += dx; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopLeftTransformTop(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottom; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopLeftTransformTopRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottomleft; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopLeftTransformRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.left; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopLeftTransformBottomRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.topleft; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopLeftTransformBottom(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.top; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopLeftTransformBottomLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.topright; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopLeftTransformLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.right; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopLeftTransformTopLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottomright; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopRightTransformTop(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.left; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopRightTransformTopRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.topleft; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopRightTransformRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.top; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopRightTransformBottomRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.topright; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopRightTransformBottom(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.right; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopRightTransformBottomLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottomright; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopRightTransformLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottom; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexTopRightTransformTopLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottomleft; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomRightTransformTop(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.top; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomRightTransformTopRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.topright; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomRightTransformRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.right; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomRightTransformBottomRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottomright; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomRightTransformBottom(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottom; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomRightTransformBottomLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottomleft; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomRightTransformLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.left; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomRightTransformTopLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.topleft; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomLeftTransformTop(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.right; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomLeftTransformTopRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottomright; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomLeftTransformRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottom; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomLeftTransformBottomRight(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.bottomleft; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomLeftTransformBottom(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.left; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomLeftTransformBottomLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.topleft; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomLeftTransformLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.top; +} + +template<class T> +bool SimplifiedVoronoi<T>::_complexBottomLeftTransformTopLeft(PixelGraph::const_iterator graph_it) +{ + return graph_it->adj.topright; +} + +template<class T> +void +SimplifiedVoronoi<T> +::_genericComplexBottomRight(PixelGraph::const_iterator a_it, + PixelGraph::const_iterator b_it, + PixelGraph::const_iterator c_it, + PixelGraph::const_iterator d_it, + Cell *const cells_it, int x, int y, + PointTransform transform, + NodeTransform top, + NodeTransform topright, + NodeTransform right, + NodeTransform bottomright, + NodeTransform bottom, + NodeTransform bottomleft, + NodeTransform left, + NodeTransform topleft) +{ + using colorspace::contour_edge; + using colorspace::same_color; + + const Point<T> initial(x, y); + + if ( bottomright(a_it) ) { + // this and bottom-right are connected + + bool smooth[2] = { + same_color(a_it->rgba, d_it->rgba) + right(a_it), + same_color(a_it->rgba, d_it->rgba) + bottom(a_it) + }; + + Point<T> borderMid = initial; + { + transform(borderMid, 1, 1); + borderMid = _midpoint(initial, borderMid); + } + + Point<T> vertices[2] = {initial, initial}; + { + transform(vertices[0], 1, 0); + vertices[0] = _adjust(_midpoint(borderMid, vertices[0]), smooth[0]); + + transform(vertices[1], 0, 1); + vertices[1] = _adjust(_midpoint(borderMid, vertices[1]), smooth[1]); + } + + if ( !smooth[0] ) { + cells_it->vertices.push_back(vertices[0]); + { + Point<T> another = vertices[0]; + transform(another, + - ( 0.1875 + - ( topright(a_it) - topleft(b_it) ) * 0.1875 ), + // y + - ( 0.5625 + - ( topright(a_it) + topleft(b_it) ) * 0.1875 )); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertices[0]; + transform(another, + - ( 0.0625 + - ( topright(a_it) - topleft(b_it) ) * 0.0625 ), + // y + - ( 0.1875 + - ( topright(a_it) + topleft(b_it) ) * 0.0625) ); + another.smooth = true; + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertices[0]; + transform(another, + 0.1875 + - ( bottomright(b_it) + topright(d_it) ) * 0.0625, + // y + 0.0625 + + ( bottomright(b_it) - topright(d_it) ) * 0.0625); + cells_it->vertices.push_back(another); + } + { + transform(vertices[0], + 0.0625 + + ( topright(a_it) - topright(d_it) - topleft(b_it) + - bottomright(b_it) ) * 0.03125, + // y + - ( 0.0625 + + ( topright(d_it) - topright(a_it) + - topleft(b_it) - bottomright(b_it) ) + * 0.03125 )); + } + } + + cells_it->vertices.push_back(vertices[0]); + + if ( !smooth[1] ) { + { + Point<T> another = vertices[1]; + transform(another, + - ( 0.0625 + + ( bottomleft(d_it) - bottomleft(a_it) + - topleft(c_it) - bottomright(c_it) ) + * 0.03125 ), + // y + 0.0625 + + ( bottomleft(a_it) - bottomleft(d_it) + - topleft(c_it) - bottomright(c_it) ) * 0.03125); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertices[1]; + transform(another, + 0.0625 + + ( bottomright(c_it) - bottomleft(d_it) ) * 0.0625, + // y + 0.1875 + - ( bottomright(c_it) + bottomleft(d_it) ) * 0.0625); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertices[1]; + transform(another, + - ( 0.1875 + - ( bottomleft(a_it) + topleft(c_it) ) + * 0.0625 ), + // y + - ( 0.0625 + - ( bottomleft(a_it) - topleft(c_it) ) + * 0.0625 )); + another.smooth = true; + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertices[1]; + transform(another, + - ( 0.5625 + - ( bottomleft(a_it) + topleft(c_it) ) + * 0.1875 ), + // y + - ( 0.1875 + - ( bottomleft(a_it) - topleft(c_it) ) + * 0.1875 )); + cells_it->vertices.push_back(another); + } + } + + cells_it->vertices.push_back(vertices[1]); + } else if ( bottomleft(b_it) ) { + // right and bottom are connected + + Point<T> vertex = initial; + transform(vertex, 1, 1); + vertex = _adjust(_midpoint(_midpoint(initial, vertex), initial), true); + cells_it->vertices.push_back(vertex); + } else { + // Connections don't affect the shape of this squared-like + // pixel + + Point<T> vertex = initial; + transform(vertex, 1, 1); + vertex = _adjust(_midpoint(initial, vertex)); + + // compute smoothness + if ( right(a_it) ) { + // this and right are connected + + if ( !right(c_it) && !( bottom(a_it) && bottom(b_it) ) ) { + // bottom and bottom-right are disconnected + + bool foreign_is_contour = contour_edge(c_it->rgba, d_it->rgba); + bool twin_is_contour = contour_edge(b_it->rgba, d_it->rgba); + bool another_is_contour = contour_edge(a_it->rgba, c_it->rgba); + + if ( another_is_contour + twin_is_contour + + foreign_is_contour == 2 ) { + vertex.smooth = !foreign_is_contour; + + if ( !vertex.smooth ) { + if ( another_is_contour ) { + { + Point<T> another = vertex; + T amount = 0.125 + - ( ( bottomright(c_it) + topleft(c_it) ) + * 0.03125 ); + transform(another, - amount, amount); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625 * bottomright(c_it); + transform(another, amount, 0.25 - amount); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625 * topleft(c_it); + transform(another, - ( 0.25 - amount ), + - amount); + another.smooth = true; + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.1875 * topleft(c_it); + transform(another, - ( 0.75 - amount ), + - amount); + cells_it->vertices.push_back(another); + } + } else if ( twin_is_contour ) { + T amount = 0.125 + - ( ( bottomleft(d_it) + topright(d_it) ) + * 0.03125 ); + transform(vertex, amount, amount); + } + } + } else { + // {this, right} is the pair with the angle + // closest to 180 degrees + vertex.smooth = true; + } + } else { + // there might be 2-color, then vertex.smooth = true + + // or it might be 1-color and doesn't matter, + // because the current node will disappear + vertex.smooth + = !( bottom(a_it) ^ bottom(b_it) ); + + if ( vertex.smooth ) { + vertex.smooth + = same_color(a_it->rgba, b_it->rgba) + + same_color(a_it->rgba, c_it->rgba) + + same_color(d_it->rgba, b_it->rgba) + + same_color(d_it->rgba, c_it->rgba) == 2; + } + } + } else if ( bottom(a_it) ) { + // this and bottom are connected + + if ( !bottom(b_it) && !( right(a_it) && right(c_it) ) ) { + // right and bottom-right are disconnected + + bool foreign_is_contour = contour_edge(b_it->rgba, d_it->rgba); + bool twin_is_contour = contour_edge(c_it->rgba, d_it->rgba); + bool another_is_contour = contour_edge(a_it->rgba, b_it->rgba); + + if ( another_is_contour + twin_is_contour + + foreign_is_contour == 2 ) { + vertex.smooth = !foreign_is_contour; + + if ( !vertex.smooth ) { + if ( another_is_contour ) { + cells_it->vertices.push_back(vertex); + { + Point<T> another = vertex; + T amount = 0.1875 * topleft(b_it); + transform(another, - amount, + - ( 0.75 - amount )); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625 * topleft(b_it); + transform(another, - amount, + - ( 0.25 - amount )); + another.smooth = true; + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625 * bottomright(b_it); + transform(another, 0.25 - amount, amount); + cells_it->vertices.push_back(another); + } + { + T amount = 0.125 + - (bottomright(b_it) + topleft(b_it)) + * 0.03125; + transform(vertex, amount, - amount); + } + } else if ( twin_is_contour ) { + T amount = 0.125 + - ( ( topright(d_it) + bottomleft(d_it) ) + * 0.03125 ); + transform(vertex, amount, amount); + } + } + } else { + // {this, bottom} is the pair with the angle + // closest to 180 degrees + vertex.smooth = true; + } + } else { + // there might be 2-color, then vertex.smooth = true + + // or it might be 1-color and doesn't matter, + // because the current node will disappear + vertex.smooth = !( right(a_it) ^ right(c_it) ); + + if ( vertex.smooth ) { + vertex.smooth + = same_color(a_it->rgba, c_it->rgba) + + same_color(a_it->rgba, b_it->rgba) + + same_color(d_it->rgba, b_it->rgba) + + same_color(d_it->rgba, c_it->rgba) == 2; + } + } + } else if ( bottom(b_it) ) { + // right and bottom-right are connected + + bool special = false; + + bool foreign_is_contour = contour_edge(c_it->rgba, d_it->rgba); + + // the neighbor similar in 90° feature + bool similar_neighbor_is_contour + = contour_edge(a_it->rgba, c_it->rgba); + + if ( contour_edge(a_it->rgba, b_it->rgba) + + similar_neighbor_is_contour + + foreign_is_contour == 2 ) { + vertex.smooth = !foreign_is_contour; + + if ( !vertex.smooth ) { + if ( similar_neighbor_is_contour ) { + { + Point<T> another = vertex; + T amount = 0.125 + - ( topleft(c_it) + bottomright(c_it) ) + * 0.03125; + transform(another, - amount, amount); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625 * bottomright(c_it); + transform(another, amount, 0.25 - amount); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625 * topleft(c_it); + transform(another, - ( 0.25 - amount ), - amount); + another.smooth = true; + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.1875 * topleft(c_it); + transform(another, - ( 0.75 - amount ), - amount); + cells_it->vertices.push_back(another); + } + } else { + special = true; + } + } + } else { + // {right, bottom-right} is the pair with the + // angle closest to 180 degrees + vertex.smooth = false; + + special = true; + } + + if ( special ) { + cells_it->vertices.push_back(vertex); + { + Point<T> another = vertex; + T amount = 0.1875; + transform(another, + - ( topleft(b_it) - topright(a_it) ) * amount, + // y + - ( 0.75 + - ( topleft(b_it) + topright(a_it) ) + * amount )); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625; + transform(another, + - ( topleft(b_it) - topright(a_it) ) * amount, + // y + - ( 0.25 + - ( topleft(b_it) + topright(a_it) ) + * amount )); + another.smooth = true; + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625; + transform(another, - amount + * ( bottomleft(d_it) - bottomright(c_it) ), + // y + 0.25 - amount + * ( bottomleft(d_it) + bottomright(c_it) )); + cells_it->vertices.push_back(another); + } + { + transform(vertex, + - ( topleft(b_it) + bottomleft(d_it) + - topright(a_it) - bottomright(c_it) ) + * 0.03125, + // y + ( topleft(b_it) - bottomleft(d_it) + + topright(a_it) - bottomright(c_it) ) + * 0.03125); + } + } + } else if ( right(c_it) ) { + // bottom and bottom-right are connected + + bool special = false; + + bool foreign_is_contour = contour_edge(b_it->rgba, d_it->rgba); + + // the neighbor similar in 90° feature + bool similar_neighbor_is_contour + = contour_edge(a_it->rgba, b_it->rgba); + + if ( contour_edge(a_it->rgba, c_it->rgba) + + similar_neighbor_is_contour + + foreign_is_contour == 2 ) { + vertex.smooth = !foreign_is_contour; + + if ( !vertex.smooth ) { + if ( similar_neighbor_is_contour ) { + cells_it->vertices.push_back(vertex); + { + Point<T> another = vertex; + T amount = 0.1875 * topleft(b_it); + transform(another, - amount, - ( 0.75 - amount )); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625 * topleft(b_it); + transform(another, - amount, - ( 0.25 - amount )); + another.smooth = true; + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625 * bottomright(b_it); + transform(another, 0.25 - amount, amount); + cells_it->vertices.push_back(another); + } + { + T amount = 0.125 + - 0.03125 * (topleft(b_it) + bottomright(b_it)); + transform(vertex, amount, - amount); + } + } else { + special = true; + } + } + } else { + // {bottom, bottom-right} is the pair with the + // angle closest to 180 degrees + vertex.smooth = false; + + special = true; + } + + if ( special ) { + { + Point<T> another = vertex; + T amount = 0.03125; + transform(another, + amount + * ( topleft(c_it) - topright(d_it) + + bottomleft(a_it) - bottomright(b_it) ), + // y + - amount + * ( topleft(c_it) + topright(d_it) + - bottomleft(a_it) - bottomright(b_it) )); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625; + transform(another, + 0.25 - amount + * ( topright(d_it) + bottomright(b_it) ), + // y + - amount + * ( topright(d_it) - bottomright(b_it) )); + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.0625; + transform(another, + - ( 0.25 - amount + * ( topleft(c_it) + bottomleft(a_it) ) ), + // y + - amount + * ( topleft(c_it) - bottomleft(a_it) )); + another.smooth = true; + cells_it->vertices.push_back(another); + } + { + Point<T> another = vertex; + T amount = 0.1875; + transform(another, + - ( 0.75 - amount + * ( topleft(c_it) + bottomleft(a_it) ) ), + // y + - amount + * ( topleft(c_it) - bottomleft(a_it) )); + cells_it->vertices.push_back(another); + } + } + } else { + // there is a 4-color pattern, where the current node + // won't be smooth + vertex.smooth = false; + } + + cells_it->vertices.push_back(vertex); + } +} + +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_SIMPLIFIEDVORONOI_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/priv/splines.h b/src/libdepixelize/priv/splines.h new file mode 100644 index 000000000..c7ef2b492 --- /dev/null +++ b/src/libdepixelize/priv/splines.h @@ -0,0 +1,155 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_SPLINES_PRIV_H +#define LIBDEPIXELIZE_TRACER_SPLINES_PRIV_H + +#include "../splines.h" +#include "homogeneoussplines.h" + +namespace Tracer { + +template<class T> +Geom::Point to_geom_point(Point<T> p) +{ + return Geom::Point(p.x, p.y); +} + +template<class T> +Geom::Path worker_helper(const std::vector< Point<T> > &source, bool optimize) +{ + typedef Geom::LineSegment Line; + typedef Geom::QuadraticBezier Quad; + typedef typename std::vector< Point<T> >::const_iterator iterator; + + iterator it = source.begin(); + Geom::Path ret(to_geom_point((source.back() + *it) / 2)); + + for ( iterator end = --source.end() ; it != end ; ++it ) { + Point<T> next = *(it + 1); + Point<T> middle = (*it + next) / 2; + + if ( !it->smooth ) { + // TODO: remove redundant colinear points + ret.appendNew<Line>(Geom::Point(it->x, it->y)); + ret.appendNew<Line>(Geom::Point(middle.x, middle.y)); + } else { + ret.appendNew<Quad>(Geom::Point(it->x, it->y), + Geom::Point(middle.x, middle.y)); + } + } + + { + Point<T> next = source.front(); + Point<T> middle = (*it + next) / 2; + + if ( !it->smooth ) { + ret.appendNew<Line>(Geom::Point(it->x, it->y)); + ret.appendNew<Line>(Geom::Point(middle.x, middle.y)); + } else { + ret.appendNew<Quad>(Geom::Point(it->x, it->y), + Geom::Point(middle.x, middle.y)); + } + } + + return ret; +} + +/** + * It should be used by worker threads. Convert only one object. + */ +template<class T> +void worker(const typename HomogeneousSplines<T>::Polygon &source, + Splines::Path &dest, bool optimize) +{ + dest.pathVector.reserve(source.holes.size() + 1); + + for ( int i = 0 ; i != 4 ; ++i ) + dest.rgba[i] = source.rgba[i]; + + dest.pathVector.push_back(worker_helper(source.vertices, optimize)); + + for ( typename std::vector< std::vector< Point<T> > >::const_iterator + it = source.holes.begin(), end = source.holes.end() + ; it != end ; ++it ) { + dest.pathVector.push_back(worker_helper(*it, optimize)); + } +} + +template<typename T> +Splines::Splines(const SimplifiedVoronoi<T> &diagram) +{ + _paths.reserve(diagram.size()); + + for ( typename SimplifiedVoronoi<T>::const_iterator it = diagram.begin() + , end = diagram.end() ; it != end ; ++it ) { + Path path; + + path.pathVector + .push_back(Geom::Path(to_geom_point(it->vertices.front()))); + + for ( typename std::vector< Point<T> >::const_iterator + it2 = it->vertices.begin(), end2 = it->vertices.end() + ; it2 != end2 ; ++it2 ) { + path.pathVector.back() + .appendNew<Geom::LineSegment>(Geom::Point(it2->x, it2->y)); + } + + for ( int i = 0 ; i != 4 ; ++i ) + path.rgba[i] = it->rgba[i]; + + _paths.push_back(path); + } +} + +template<class T> +Splines::Splines(const HomogeneousSplines<T> &homogeneousSplines, + bool optimize, int nthreads) : + _paths(homogeneousSplines.size()), + _width(homogeneousSplines.width()), + _height(homogeneousSplines.height()) +{ + // TODO: It should be threaded + iterator paths_it = begin(); + for ( typename HomogeneousSplines<T>::const_iterator + it = homogeneousSplines.begin(), end = homogeneousSplines.end() + ; it != end ; ++it, ++paths_it ) { + worker<T>(*it, *paths_it, optimize); + } +} + +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_SPLINES_PRIV_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libdepixelize/splines.h b/src/libdepixelize/splines.h new file mode 100644 index 000000000..c4b455aae --- /dev/null +++ b/src/libdepixelize/splines.h @@ -0,0 +1,120 @@ +/* This file is part of the libdepixelize project + Copyright (C) 2013 Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + + GNU Lesser General Public License Usage + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of the License, or (at your + option) any later version. + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see <http://www.gnu.org/licenses/>. + + GNU General Public License Usage + Alternatively, this library may be used under the terms of the GNU General + Public License as published by the Free Software Foundation, either version + 2 of the License, or (at your option) any later version. + You should have received a copy of the GNU General Public License along with + this library. If not, see <http://www.gnu.org/licenses/>. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. +*/ + +#ifndef LIBDEPIXELIZE_TRACER_SPLINES_H +#define LIBDEPIXELIZE_TRACER_SPLINES_H + +#include <2geom/pathvector.h> +#include <glib.h> + +namespace Tracer { + +template<typename T> +class SimplifiedVoronoi; + +template<typename T> +class HomogeneousSplines; + +class Splines +{ +public: + struct Path + { + /** + * It may be benefited from C++11 move references. + */ + Geom::PathVector pathVector; + + guint8 rgba[4]; + }; + + typedef std::vector<Path>::iterator iterator; + typedef std::vector<Path>::const_iterator const_iterator; + + Splines() /* = default */ {} + + template<typename T> + Splines(const SimplifiedVoronoi<T> &simplifiedVoronoi); + + /** + * There are two levels of optimization. The first level only removes + * redundant points of colinear points. The second level uses the + * Kopf-Lischinski optimization. The first level is always enabled. + * The second level is enabled using \p optimize. + */ + template<typename T> + Splines(const HomogeneousSplines<T> &homogeneousSplines, bool optimize, + int nthreads); + + // Iterators + iterator begin() + { + return _paths.begin(); + } + + const_iterator begin() const + { + return _paths.begin(); + } + + iterator end() + { + return _paths.end(); + } + + const_iterator end() const + { + return _paths.end(); + } + + int width() const + { + return _width; + } + + int height() const + { + return _height; + } + +private: + std::vector<Path> _paths; + int _width; + int _height; +}; + +} // namespace Tracer + +#endif // LIBDEPIXELIZE_TRACER_SPLINES_H + +/* + 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:encoding=utf-8:textwidth=99 : diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index 1b2704a7e..7ea089c93 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -706,7 +706,7 @@ static void dumpUnbrokenSpans(ParagraphInfo *para){ if (newcluster){ // find where the text ends for this log_cluster end_byte = it_span->start.iter_span->text_bytes; // Upper limit - for(unsigned next_glyph_index = glyph_index+1; next_glyph_index < it_span->end_glyph_index; next_glyph_index++){ + for(int next_glyph_index = glyph_index+1; next_glyph_index < unbroken_span.glyph_string->num_glyphs; next_glyph_index++){ if(unbroken_span.glyph_string->glyphs[next_glyph_index].attr.is_cluster_start){ end_byte = unbroken_span.glyph_string->log_clusters[next_glyph_index]; break; diff --git a/src/livarot/PathStroke.cpp b/src/livarot/PathStroke.cpp index cdd5cae6d..50c335176 100644 --- a/src/livarot/PathStroke.cpp +++ b/src/livarot/PathStroke.cpp @@ -456,19 +456,20 @@ Path::DoLeftJoin (Shape * dest, double width, JoinType join, Geom::Point pos, } else {*/ leftStNo = dest->AddPoint (pos + width * pnor); leftEnNo = dest->AddPoint (pos + width * nnor); - int midNo = dest->AddPoint (pos); - int nEdge=dest->AddEdge (leftEnNo, midNo); - if ( dest->hasBackData() ) { - dest->ebData[nEdge].pathID=pathID; - dest->ebData[nEdge].pieceID=pieceID; - dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; - } - nEdge=dest->AddEdge (midNo, leftStNo); +// int midNo = dest->AddPoint (pos); +// int nEdge=dest->AddEdge (leftEnNo, midNo); + int nEdge=dest->AddEdge (leftEnNo, leftStNo); if ( dest->hasBackData() ) { dest->ebData[nEdge].pathID=pathID; dest->ebData[nEdge].pieceID=pieceID; dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; } +// nEdge=dest->AddEdge (midNo, leftStNo); +// if ( dest->hasBackData() ) { +// dest->ebData[nEdge].pathID=pathID; +// dest->ebData[nEdge].pieceID=pieceID; +// dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; +// } // } } else @@ -678,19 +679,20 @@ Path::DoRightJoin (Shape * dest, double width, JoinType join, Geom::Point pos, } else {*/ rightStNo = dest->AddPoint (pos - width*pnor); rightEnNo = dest->AddPoint (pos - width*nnor); - int midNo = dest->AddPoint (pos); - int nEdge=dest->AddEdge (rightStNo, midNo); +// int midNo = dest->AddPoint (pos); +// int nEdge=dest->AddEdge (rightStNo, midNo); + int nEdge=dest->AddEdge (rightStNo, rightEnNo); if ( dest->hasBackData() ) { dest->ebData[nEdge].pathID=pathID; dest->ebData[nEdge].pieceID=pieceID; dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; } - nEdge=dest->AddEdge (midNo, rightEnNo); - if ( dest->hasBackData() ) { - dest->ebData[nEdge].pathID=pathID; - dest->ebData[nEdge].pieceID=pieceID; - dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; - } +// nEdge=dest->AddEdge (midNo, rightEnNo); +// if ( dest->hasBackData() ) { +// dest->ebData[nEdge].pathID=pathID; +// dest->ebData[nEdge].pieceID=pieceID; +// dest->ebData[nEdge].tSt=dest->ebData[nEdge].tEn=tID; +// } // } } } diff --git a/src/livarot/ShapeSweep.cpp b/src/livarot/ShapeSweep.cpp index ff58b4a71..b04b36bfd 100644 --- a/src/livarot/ShapeSweep.cpp +++ b/src/livarot/ShapeSweep.cpp @@ -2740,8 +2740,7 @@ Shape::CheckAdjacencies (int lastPointNo, int lastChgtPt, Shape * /*shapeHead*/, if (TesteAdjacency (lS, lB, getPoint(n).x, n, false) == false) break; - if (getPoint(lS->swsData[lB].leftRnd).x[0] > getPoint(n).x[0] + HalfRound (1)) // LP Bug 614577 - lS->swsData[lB].leftRnd = n; + lS->swsData[lB].leftRnd = n; } for (int n = rgtN + 1; n < lastPointNo; n++) { @@ -2767,8 +2766,7 @@ Shape::CheckAdjacencies (int lastPointNo, int lastChgtPt, Shape * /*shapeHead*/, if (TesteAdjacency (rS, rB, getPoint(n).x, n, false) == false) break; - if (getPoint(rS->swsData[rB].leftRnd).x[0] > getPoint(n).x[0] + HalfRound (1)) // LP Bug 614577 - rS->swsData[rB].leftRnd = n; + rS->swsData[rB].leftRnd = n; } for (int n = rgtN + 1; n < lastPointNo; n++) { diff --git a/src/menus-skeleton.h b/src/menus-skeleton.h index 5b141902b..2334a08c1 100644 --- a/src/menus-skeleton.h +++ b/src/menus-skeleton.h @@ -222,6 +222,7 @@ static char const menus_skeleton[] = " <verb verb-id=\"ObjectToPath\" />\n" " <verb verb-id=\"StrokeToPath\" />\n" " <verb verb-id=\"SelectionTrace\" />\n" +" <verb verb-id=\"SelectionPixelArt\" />\n" " <separator/>\n" " <verb verb-id=\"SelectionUnion\" />\n" " <verb verb-id=\"SelectionDiff\" />\n" diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index 6aacc3f15..91b99e3f4 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -96,6 +96,7 @@ SPCycleType SP_CYCLING = SP_CYCLE_FOCUS; #include "uri-references.h" #include "display/curve.h" #include "display/canvas-bpath.h" +#include "display/cairo-utils.h" #include "inkscape-private.h" #include "path-chemistry.h" #include "ui/tool/control-point-selection.h" @@ -3480,9 +3481,10 @@ void sp_selection_create_bitmap_copy(SPDesktop *desktop) } // Import the image back - GdkPixbuf *pb = gdk_pixbuf_new_from_file(filepath, NULL); + Inkscape::Pixbuf *pb = Inkscape::Pixbuf::create_from_file(filepath); if (pb) { // Create the repr for the image + // TODO: avoid unnecessary roundtrip between data URI and decoded pixbuf Inkscape::XML::Node * repr = xml_doc->createElement("svg:image"); sp_embed_image(repr, pb); if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) { // for default 90 dpi, snap it to pixel grid diff --git a/src/sp-image.cpp b/src/sp-image.cpp index b0a094950..80daf33c3 100644 --- a/src/sp-image.cpp +++ b/src/sp-image.cpp @@ -17,9 +17,6 @@ # include "config.h" #endif -// This has to be included prior to anything that includes setjmp.h, it croaks otherwise -#include <png.h> - #include <cstring> #include <algorithm> #include <string> @@ -43,8 +40,9 @@ #include "xml/repr.h" #include "snap-candidate.h" #include "preferences.h" - #include "io/sys.h" +#include "sp-factory.h" + #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) #include "cms-system.h" #include "color-profile.h" @@ -77,8 +75,7 @@ static void sp_image_set_curve(SPImage *image); -static GdkPixbuf *sp_image_repr_read_image( time_t& modTime, gchar*& pixPath, const gchar *href, const gchar *absref, const gchar *base ); -static GdkPixbuf *sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf); +static Inkscape::Pixbuf *sp_image_repr_read_image(gchar const *href, gchar const *absref, gchar const *base ); static void sp_image_update_arenaitem (SPImage *img, Inkscape::DrawingImage *ai); static void sp_image_update_canvas_image (SPImage *image); static GdkPixbuf * sp_image_repr_read_dataURI (const gchar * uri_data); @@ -117,78 +114,16 @@ extern guint update_in_progress; #define DEBUG_MESSAGE_SCISLAC(key, ...) #endif // DEBUG_LCMS -namespace Inkscape { -namespace IO { - -GdkPixbuf* pixbuf_new_from_file(const char *filename, time_t &modTime, gchar*& pixPath) -{ - GdkPixbuf* buf = NULL; - modTime = 0; - if ( pixPath ) { - g_free(pixPath); - pixPath = NULL; - } - - //test correctness of filename - if (!g_file_test (filename, G_FILE_TEST_EXISTS)){ - return NULL; - } - struct stat stdir; - int val = g_stat(filename, &stdir); - if (stdir.st_mode & S_IFDIR){ - g_warning("Linked image file %s is a directory", filename); - return NULL; - } - - // we need to load the entire pixbuf into memory - gchar *data = NULL; - gsize len = 0; - - if (g_file_get_contents(filename, &data, &len, NULL)) { - if (!val) { - modTime = stdir.st_mtime; - pixPath = g_strdup(filename); - } - - GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); - gdk_pixbuf_loader_write(loader, (guchar *) data, len, NULL); - gdk_pixbuf_loader_close(loader, NULL); - - buf = gdk_pixbuf_loader_get_pixbuf(loader); - if (buf) { - g_object_ref(buf); - buf = sp_image_pixbuf_force_rgba(buf); - pixbuf_set_mime_data(buf, (guchar *) data, len, gdk_pixbuf_loader_get_format(loader)); - } else { - g_free(data); - g_warning("Error loading pixbuf"); - } - - // 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. - } else { - g_warning("Unable to open linked file: %s", filename); - } - - return buf; -} - -} -} - - -#include "sp-factory.h" - namespace { - SPObject* createImage() { - return new SPImage(); - } +SPObject* createImage() { + return new SPImage(); +} - bool imageRegistered = SPFactory::instance().registerObject("svg:image", createImage); +bool imageRegistered = SPFactory::instance().registerObject("svg:image", createImage); } SPImage::SPImage() : SPItem() { - this->aspect_clip = 0; + this->aspect_clip = 0; this->x.unset(); this->y.unset(); @@ -206,15 +141,13 @@ SPImage::SPImage() : SPItem() { this->color_profile = 0; #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) this->pixbuf = 0; - this->pixPath = 0; - this->lastMod = 0; } SPImage::~SPImage() { } void SPImage::build(SPDocument *document, Inkscape::XML::Node *repr) { - SPItem::build(document, repr); + SPItem::build(document, repr); this->readAttr( "xlink:href" ); this->readAttr( "x" ); @@ -231,7 +164,7 @@ void SPImage::build(SPDocument *document, Inkscape::XML::Node *repr) { void SPImage::release() { if (this->document) { // Unregister ourselves - this->document->removeResource("image", this); + this->document->removeResource("image", this); } if (this->href) { @@ -239,11 +172,8 @@ void SPImage::release() { this->href = NULL; } - if (this->pixbuf) { - g_object_set_data(G_OBJECT(this->pixbuf), "cairo_surface", NULL); - g_object_unref (this->pixbuf); - this->pixbuf = NULL; - } + delete this->pixbuf; + this->pixbuf = NULL; #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) if (this->color_profile) { @@ -252,11 +182,6 @@ void SPImage::release() { } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) - if (this->pixPath) { - g_free(this->pixPath); - this->pixPath = 0; - } - if (this->curve) { this->curve = this->curve->unref(); } @@ -275,7 +200,7 @@ void SPImage::set(unsigned int key, const gchar* value) { case SP_ATTR_X: if (!this->x.readAbsolute(value)) { /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ - this->x.unset(); + this->x.unset(); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); @@ -284,7 +209,7 @@ void SPImage::set(unsigned int key, const gchar* value) { case SP_ATTR_Y: if (!this->y.readAbsolute(value)) { /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ - this->y.unset(); + this->y.unset(); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); @@ -293,7 +218,7 @@ void SPImage::set(unsigned int key, const gchar* value) { case SP_ATTR_WIDTH: if (!this->width.readAbsolute(value)) { /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ - this->width.unset(); + this->width.unset(); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); @@ -302,7 +227,7 @@ void SPImage::set(unsigned int key, const gchar* value) { case SP_ATTR_HEIGHT: if (!this->height.readAbsolute(value)) { /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ - this->height.unset(); + this->height.unset(); } this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); @@ -310,7 +235,7 @@ void SPImage::set(unsigned int key, const gchar* value) { case SP_ATTR_PRESERVEASPECTRATIO: /* Do setup before, so we can use break to escape */ - this->aspect_align = SP_ASPECT_NONE; + this->aspect_align = SP_ASPECT_NONE; this->aspect_clip = SP_ASPECT_MEET; this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); @@ -406,28 +331,13 @@ void SPImage::update(SPCtx *ctx, unsigned int flags) { SPItem::update(ctx, flags); if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) { - if (this->pixbuf) { - g_object_unref (this->pixbuf); - this->pixbuf = NULL; - } - - if ( this->pixPath ) { - g_free(this->pixPath); - this->pixPath = 0; - } - - this->lastMod = 0; - + delete this->pixbuf; + this->pixbuf = NULL; + if (this->href) { - GdkPixbuf *pixbuf; + Inkscape::Pixbuf *pixbuf = NULL; pixbuf = sp_image_repr_read_image ( - this->lastMod, - this->pixPath, - - //XML Tree being used directly while it shouldn't be. this->getRepr()->attribute("xlink:href"), - - //XML Tree being used directly while it shouldn't be. this->getRepr()->attribute("sodipodi:absref"), doc->getBase()); @@ -436,10 +346,13 @@ void SPImage::update(SPCtx *ctx, unsigned int flags) { #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) if ( this->color_profile ) { - int imagewidth = gdk_pixbuf_get_width( pixbuf ); - int imageheight = gdk_pixbuf_get_height( pixbuf ); - int rowstride = gdk_pixbuf_get_rowstride( pixbuf ); - guchar* px = gdk_pixbuf_get_pixels( pixbuf ); + // TODO: this will prevent using MIME data when exporting. + // Integrate color correction into loading. + pixbuf->ensurePixelFormat(Inkscape::Pixbuf::PF_GDK); + int imagewidth = pixbuf->width(); + int imageheight = pixbuf->height(); + int rowstride = pixbuf->rowstride();; + guchar* px = pixbuf->pixels(); if ( px ) { DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" ); @@ -508,23 +421,23 @@ void SPImage::update(SPCtx *ctx, unsigned int flags) { if (this->pixbuf) { /* fixme: We are slightly violating spec here (Lauris) */ if (!this->width._set) { - this->width.computed = gdk_pixbuf_get_width(this->pixbuf); + this->width.computed = this->pixbuf->width(); } - + if (!this->height._set) { - this->height.computed = gdk_pixbuf_get_height(this->pixbuf); + this->height.computed = this->pixbuf->height(); } } - Geom::Point p(this->x.computed, this->y.computed); - Geom::Point wh(this->width.computed, this->height.computed); - this->clipbox = Geom::Rect(p, p + wh); + this->clipbox = Geom::Rect::from_xywh( + this->x.computed, this->y.computed, + this->width.computed, this->height.computed); this->ox = this->x.computed; this->oy = this->y.computed; - int pixwidth = gdk_pixbuf_get_width (this->pixbuf); - int pixheight = gdk_pixbuf_get_height (this->pixbuf); + int pixwidth = this->pixbuf->width(); + int pixheight = this->pixbuf->height(); this->sx = this->width.computed / pixwidth; this->sy = this->height.computed / pixheight; @@ -656,16 +569,15 @@ Geom::OptRect SPImage::bbox(Geom::Affine const &transform, SPItem::BBoxType type void SPImage::print(SPPrintContext *ctx) { if (this->pixbuf && (this->width.computed > 0.0) && (this->height.computed > 0.0) ) { - GdkPixbuf *pb = gdk_pixbuf_copy(this->pixbuf); - // GObject data is not copied, so we have to set the pixel format explicitly - g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("argb32"), g_free); - ink_pixbuf_ensure_normal(pb); + Inkscape::Pixbuf *pb = new Inkscape::Pixbuf(*this->pixbuf); + pb->ensurePixelFormat(Inkscape::Pixbuf::PF_GDK); - guchar *px = gdk_pixbuf_get_pixels(pb); - int w = gdk_pixbuf_get_width(pb); - int h = gdk_pixbuf_get_height(pb); - int rs = gdk_pixbuf_get_rowstride(pb); - int pixskip = gdk_pixbuf_get_n_channels(pb) * gdk_pixbuf_get_bits_per_sample(pb) / 8; + guchar *px = pb->pixels(); + int w = pb->width(); + int h = pb->height(); + int rs = pb->rowstride(); + //int pixskip = gdk_pixbuf_get_n_channels(pb) * gdk_pixbuf_get_bits_per_sample(pb) / 8; + int pixskip = 4; if (this->aspect_align == SP_ASPECT_NONE) { Geom::Affine t; @@ -697,7 +609,7 @@ void SPImage::print(SPPrintContext *ctx) { t = ti * t; sp_print_image_R8G8B8A8_N(ctx, px + trimx*pixskip + trimy*rs, trimwidth, trimheight, rs, t, this->style); } - g_object_unref(pb); + delete pb; } } @@ -716,8 +628,8 @@ gchar* SPImage::description() { char *ret = ( this->pixbuf == NULL ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc) : g_strdup_printf(_("<b>Image</b> %d × %d: %s"), - gdk_pixbuf_get_width(this->pixbuf), - gdk_pixbuf_get_height(this->pixbuf), + this->pixbuf->width(), + this->pixbuf->height(), href_desc) ); g_free(href_desc); return ret; @@ -731,22 +643,9 @@ Inkscape::DrawingItem* SPImage::show(Inkscape::Drawing &drawing, unsigned int ke return ai; } -/* - * utility function to try loading image from href - * - * docbase/relative_src - * absolute_src - * - */ - -GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base ) +Inkscape::Pixbuf *sp_image_repr_read_image(gchar const *href, gchar const *absref, gchar const *base) { - GdkPixbuf *pixbuf = 0; - modTime = 0; - if ( pixPath ) { - g_free(pixPath); - pixPath = 0; - } + Inkscape::Pixbuf *inkpb = 0; gchar const *filename = href; @@ -754,18 +653,18 @@ GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gcha if (strncmp (filename,"file:",5) == 0) { gchar *fullname = g_filename_from_uri(filename, NULL, NULL); if (fullname) { - pixbuf = Inkscape::IO::pixbuf_new_from_file(fullname, modTime, pixPath); + inkpb = Inkscape::Pixbuf::create_from_file(fullname); g_free(fullname); - if (pixbuf != NULL) { - return pixbuf; + if (inkpb != NULL) { + return inkpb; } } } else if (strncmp (filename,"data:",5) == 0) { /* data URI - embedded image */ filename += 5; - pixbuf = sp_image_repr_read_dataURI (filename); - if (pixbuf != NULL) { - return pixbuf; + inkpb = Inkscape::Pixbuf::create_from_data_uri(filename); + if (inkpb != NULL) { + return inkpb; } } else { @@ -781,19 +680,19 @@ GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gcha // different dir) or unset (when doc is not saved yet), so we check for base+href existence first, // and if it fails, we also try to use bare href regardless of its g_path_is_absolute if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) { - pixbuf = Inkscape::IO::pixbuf_new_from_file(fullname, modTime, pixPath); + inkpb = Inkscape::Pixbuf::create_from_file(fullname); g_free (fullname); - if (pixbuf != NULL) { - return pixbuf; + if (inkpb != NULL) { + return inkpb; } } } /* try filename as absolute */ if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) { - pixbuf = Inkscape::IO::pixbuf_new_from_file(filename, modTime, pixPath); - if (pixbuf != NULL) { - return pixbuf; + inkpb = Inkscape::Pixbuf::create_from_file(filename); + if (inkpb != NULL) { + return inkpb; } } } @@ -809,31 +708,20 @@ GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gcha g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref); } - pixbuf = Inkscape::IO::pixbuf_new_from_file(filename, modTime, pixPath); - if (pixbuf != NULL) { - return pixbuf; + inkpb = Inkscape::Pixbuf::create_from_file(filename); + if (inkpb != NULL) { + return inkpb; } } /* Nope: We do not find any valid pixmap file :-( */ - pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **) brokenimage_xpm); + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **) brokenimage_xpm); + inkpb = new Inkscape::Pixbuf(pixbuf); /* It should be included xpm, so if it still does not does load, */ /* our libraries are broken */ - g_assert (pixbuf != NULL); + g_assert (inkpb != NULL); - return pixbuf; -} - -static GdkPixbuf *sp_image_pixbuf_force_rgba( GdkPixbuf * pixbuf ) -{ - GdkPixbuf* result; - if (gdk_pixbuf_get_has_alpha(pixbuf)) { - result = pixbuf; - } else { - result = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0); - g_object_unref(pixbuf); - } - return result; + return inkpb; } /* We assert that realpixbuf is either NULL or identical size to pixbuf */ @@ -841,7 +729,7 @@ static void sp_image_update_arenaitem (SPImage *image, Inkscape::DrawingImage *ai) { ai->setStyle(SP_OBJECT(image)->style); - ai->setARGB32Pixbuf(image->pixbuf); + ai->setPixbuf(image->pixbuf); ai->setOrigin(Geom::Point(image->ox, image->oy)); ai->setScale(image->sx, image->sy); ai->setClipbox(image->clipbox); @@ -928,113 +816,6 @@ Geom::Affine SPImage::set_transform(Geom::Affine const &xform) { return ret; } -static GdkPixbuf *sp_image_repr_read_dataURI( const gchar * uri_data ) -{ - GdkPixbuf * pixbuf = NULL; - - gint data_is_image = 0; - gint data_is_base64 = 0; - - const gchar * data = uri_data; - - while (*data) { - if (strncmp(data,"base64",6) == 0) { - /* base64-encoding */ - data_is_base64 = 1; - data_is_image = 1; // 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 = 1; - data += 9; - } - else if (strncmp(data,"image/jpg",9) == 0) { - /* JPEG image */ - data_is_image = 1; - data += 9; - } - else if (strncmp(data,"image/jpeg",10) == 0) { - /* JPEG image */ - data_is_image = 1; - data += 10; - } - 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_base64) { - pixbuf = sp_image_repr_read_b64(data); - } - - return pixbuf; -} - -static GdkPixbuf *sp_image_repr_read_b64(gchar const *uri_data) -{ - GdkPixbuf *pixbuf = NULL; - GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); - - if (!loader) return NULL; - - gsize decoded_len = 0; - guchar *decoded = g_base64_decode(uri_data, &decoded_len); - - if (gdk_pixbuf_loader_write(loader, decoded, decoded_len, NULL)) { - gdk_pixbuf_loader_close(loader, NULL); - pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); - g_object_ref(pixbuf); - pixbuf = sp_image_pixbuf_force_rgba(pixbuf); - pixbuf_set_mime_data(pixbuf, decoded, decoded_len, gdk_pixbuf_loader_get_format(loader)); - } else { - g_free(decoded); - } - g_object_unref(loader); - - return pixbuf; -} - -// takes ownership of passed data -static void pixbuf_set_mime_data(GdkPixbuf *pb, guchar *data, gsize len, GdkPixbufFormat *fmt) -{ - cairo_surface_t *s = ink_cairo_surface_get_for_pixbuf(pb); - - gchar const *mimetype = NULL; - gchar *fmt_name = gdk_pixbuf_format_get_name(fmt); - Glib::ustring name = fmt_name; - g_free(fmt_name); - - if (name == "jpeg") { - mimetype = CAIRO_MIME_TYPE_JPEG; - } else if (name == "jpeg2000") { - mimetype = CAIRO_MIME_TYPE_JP2; - } else if (name == "png") { - mimetype = CAIRO_MIME_TYPE_PNG; - } - - if (mimetype != NULL) { - cairo_surface_set_mime_data(s, 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()); - } -} - static void sp_image_set_curve( SPImage *image ) { //create a curve at the image's boundary for snapping @@ -1070,52 +851,32 @@ SPCurve *sp_image_get_curve( SPImage *image ) return result; } -void sp_embed_image(Inkscape::XML::Node *image_node, GdkPixbuf *pb) +void sp_embed_image(Inkscape::XML::Node *image_node, Inkscape::Pixbuf *pb) { - static gchar const *mimetypes[] = { - CAIRO_MIME_TYPE_JPEG, CAIRO_MIME_TYPE_JP2, CAIRO_MIME_TYPE_PNG, NULL }; - static guint mimetypes_len = g_strv_length(const_cast<gchar**>(mimetypes)); - bool free_data = false; // check whether the pixbuf has MIME data guchar *data = NULL; gsize len = 0; - gchar const *data_mimetype = NULL; - - cairo_surface_t *s = reinterpret_cast<cairo_surface_t*>(g_object_get_data(G_OBJECT(pb), "cairo_surface")); - if (s) { - for (guint i = 0; i < mimetypes_len; ++i) { - unsigned long len_long = 0; - cairo_surface_get_mime_data(s, mimetypes[i], const_cast<unsigned char const **>(&data), &len_long); - len = len_long; // this assumes that the added range of long is not needed. the code below assumes gsize range of values is sufficient. - if (data != NULL) { - data_mimetype = mimetypes[i]; - break; - } - } - } + std::string data_mimetype; - if (data == NULL) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - Glib::ustring quality = Glib::ustring::format(prefs->getInt("/dialogs/import/quality", 100)); + data = const_cast<guchar *>(pb->getMimeData(len, data_mimetype)); + if (data == NULL) { // if there is no supported MIME data, embed as PNG data_mimetype = "image/png"; - ink_pixbuf_ensure_normal(pb); - gdk_pixbuf_save_to_buffer(pb, reinterpret_cast<gchar**>(&data), &len, "png", NULL, - "quality", quality.c_str(), NULL); + gdk_pixbuf_save_to_buffer(pb->getPixbufRaw(), reinterpret_cast<gchar**>(&data), &len, "png", NULL, NULL); free_data = true; } // Save base64 encoded data in image node // this formula taken from Glib docs gsize needed_size = len * 4 / 3 + len * 4 / (3 * 72) + 7; - needed_size += 5 + 8 + strlen(data_mimetype); // 5 bytes for data: + 8 for ;base64, + needed_size += 5 + 8 + data_mimetype.size(); // 5 bytes for data: + 8 for ;base64, gchar *buffer = (gchar *) g_malloc(needed_size); gchar *buf_work = buffer; - buf_work += g_sprintf(buffer, "data:%s;base64,", data_mimetype); + buf_work += g_sprintf(buffer, "data:%s;base64,", data_mimetype.c_str()); gint state = 0; gint save = 0; @@ -1135,18 +896,18 @@ void sp_embed_image(Inkscape::XML::Node *image_node, GdkPixbuf *pb) void sp_image_refresh_if_outdated( SPImage* image ) { - if ( image->href && image->lastMod ) { + if ( image->href && image->pixbuf && image->pixbuf->modificationTime()) { // It *might* change struct stat st; memset(&st, 0, sizeof(st)); int val = 0; - if (g_file_test (image->pixPath, G_FILE_TEST_EXISTS)){ - val = g_stat(image->pixPath, &st); + if (g_file_test (image->pixbuf->originalPath().c_str(), G_FILE_TEST_EXISTS)){ + val = g_stat(image->pixbuf->originalPath().c_str(), &st); } if ( !val ) { // stat call worked. Check time now - if ( st.st_mtime != image->lastMod ) { + if ( st.st_mtime != image->pixbuf->modificationTime() ) { SPCtx *ctx = 0; unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG; image->update(ctx, flags); diff --git a/src/sp-image.h b/src/sp-image.h index 9a229e5f5..bfc10e7f2 100644 --- a/src/sp-image.h +++ b/src/sp-image.h @@ -1,9 +1,6 @@ -#ifndef __SP_IMAGE_H__ -#define __SP_IMAGE_H__ - -/* +/** @file * SVG <image> implementation - * + *//* * Authors: * Lauris Kaplinski <lauris@kaplinski.com> * Edward Flick (EAF) @@ -14,22 +11,24 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#define SP_IMAGE(obj) (dynamic_cast<SPImage*>((SPObject*)obj)) -#define SP_IS_IMAGE(obj) (dynamic_cast<const SPImage*>((SPObject*)obj) != NULL) - -/* SPImage */ +#ifndef SEEN_INKSCAPE_SP_IMAGE_H +#define SEEN_INKSCAPE_SP_IMAGE_H #include <gdk-pixbuf/gdk-pixbuf.h> #include <glibmm/ustring.h> #include "svg/svg-length.h" -#include "sp-item.h" +#include "sp-shape.h" + +#define SP_IMAGE(obj) (dynamic_cast<SPImage*>((SPObject*)obj)) +#define SP_IS_IMAGE(obj) (dynamic_cast<const SPImage*>((SPObject*)obj) != NULL) #define SP_IMAGE_HREF_MODIFIED_FLAG SP_OBJECT_USER_MODIFIED_FLAG_A +namespace Inkscape { class Pixbuf; } class SPImage : public SPItem { public: - SPImage(); - virtual ~SPImage(); + SPImage(); + virtual ~SPImage(); SVGLength x; SVGLength y; @@ -54,28 +53,26 @@ public: gchar *color_profile; #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) - GdkPixbuf *pixbuf; - gchar *pixPath; - time_t lastMod; + Inkscape::Pixbuf *pixbuf; - virtual void build(SPDocument *document, Inkscape::XML::Node *repr); - virtual void release(); - virtual void set(unsigned int key, gchar const* value); - virtual void update(SPCtx *ctx, guint flags); - virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags); - virtual void modified(unsigned int flags); + virtual void build(SPDocument *document, Inkscape::XML::Node *repr); + virtual void release(); + virtual void set(unsigned int key, gchar const* value); + virtual void update(SPCtx *ctx, guint flags); + virtual Inkscape::XML::Node* write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags); + virtual void modified(unsigned int flags); - virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type); - virtual void print(SPPrintContext *ctx); - virtual gchar* description(); - virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); + virtual Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType type); + virtual void print(SPPrintContext *ctx); + virtual gchar* description(); + virtual Inkscape::DrawingItem* show(Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); virtual void snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs); virtual Geom::Affine set_transform(Geom::Affine const &transform); }; /* Return duplicate of curve or NULL */ SPCurve *sp_image_get_curve (SPImage *image); -void sp_embed_image(Inkscape::XML::Node *imgnode, GdkPixbuf *pb); +void sp_embed_image(Inkscape::XML::Node *imgnode, Inkscape::Pixbuf *pb); void sp_image_refresh_if_outdated( SPImage* image ); #endif diff --git a/src/sp-item-group.cpp b/src/sp-item-group.cpp index 36a42f704..010cc5449 100644 --- a/src/sp-item-group.cpp +++ b/src/sp-item-group.cpp @@ -720,7 +720,9 @@ sp_group_perform_patheffect(SPGroup *group, SPGroup *topgroup, bool write) // only run LPEs when the shape has a curve defined if (c) { + c->transform(i2anc_affine(subitem, topgroup)); sp_lpe_item_perform_path_effect(SP_LPE_ITEM(topgroup), c); + c->transform(i2anc_affine(subitem, topgroup).inverse()); SP_SHAPE(subitem)->setCurve(c, TRUE); if (write) { diff --git a/src/sp-item.cpp b/src/sp-item.cpp index abff398e6..e6991a1fa 100644 --- a/src/sp-item.cpp +++ b/src/sp-item.cpp @@ -601,11 +601,9 @@ void SPItem::update(SPCtx *ctx, guint flags) { } } } - /* Update bounding box data used by filters */ if (item->style->filter.set && item->display) { Geom::OptRect item_bbox = item->visualBounds(); - SPItemView *itemview = item->display; do { if (itemview->arenaitem) diff --git a/src/sp-object.cpp b/src/sp-object.cpp index b622d14e9..895b36e1c 100644 --- a/src/sp-object.cpp +++ b/src/sp-object.cpp @@ -140,15 +140,15 @@ SPObject::~SPObject() { // CPPIFY: make pure virtual void SPObject::read_content() { - //throw; + //throw; } void SPObject::update(SPCtx* ctx, unsigned int flags) { - //throw; + //throw; } void SPObject::modified(unsigned int flags) { - //throw; + //throw; } namespace { @@ -221,7 +221,7 @@ SPObject *sp_object_unref(SPObject *object, SPObject *owner) object->refCount--; if (object->refCount < 0) { - delete object; + delete object; } return NULL; @@ -575,25 +575,27 @@ SPObject *SPObject::get_child_by_repr(Inkscape::XML::Node *repr) } void SPObject::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { - SPObject* object = this; + SPObject* object = this; - try { - const std::string typeString = NodeTraits::get_type_string(*child); + try { + const std::string typeString = NodeTraits::get_type_string(*child); - SPObject* ochild = SPFactory::instance().createObject(typeString); + SPObject* ochild = SPFactory::instance().createObject(typeString); - SPObject *prev = ref ? object->get_child_by_repr(ref) : NULL; - object->attach(ochild, prev); - sp_object_unref(ochild, NULL); + SPObject *prev = ref ? object->get_child_by_repr(ref) : NULL; + object->attach(ochild, prev); + sp_object_unref(ochild, NULL); - ochild->invoke_build(object->document, child, object->cloned); - } catch (const FactoryExceptions::TypeNotRegistered& e) { - g_warning("TypeNotRegistered exception: %s", e.what()); - } + ochild->invoke_build(object->document, child, object->cloned); + } catch (const FactoryExceptions::TypeNotRegistered& e) { + if (std::string(e.what()) != "rdf:RDF") { // temporary special case + g_warning("TypeNotRegistered exception: %s", e.what()); + } + } } void SPObject::release() { - SPObject* object = this; + SPObject* object = this; debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); while (object->children) { @@ -602,7 +604,7 @@ void SPObject::release() { } void SPObject::remove_child(Inkscape::XML::Node* child) { - SPObject* object = this; + SPObject* object = this; debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); SPObject *ochild = object->get_child_by_repr(child); @@ -613,7 +615,7 @@ void SPObject::remove_child(Inkscape::XML::Node* child) { } void SPObject::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node * old_ref, Inkscape::XML::Node *new_ref) { - SPObject* object = this; + SPObject* object = this; SPObject *ochild = object->get_child_by_repr(child); g_return_if_fail(ochild != NULL); @@ -623,7 +625,7 @@ void SPObject::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node * o } void SPObject::build(SPDocument *document, Inkscape::XML::Node *repr) { - SPObject* object = this; + SPObject* object = this; /* Nothing specific here */ debug("id=%x, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); @@ -639,22 +641,22 @@ void SPObject::build(SPDocument *document, Inkscape::XML::Node *repr) { // } // SPObject *child = SP_OBJECT(g_object_new(type, 0)); -// SPObject* child = SPFactory::instance().createObject(*rchild); -// if (!child) { -// continue; -// } +// SPObject* child = SPFactory::instance().createObject(*rchild); +// if (!child) { +// continue; +// } - try { - const std::string typeString = NodeTraits::get_type_string(*rchild); + try { + const std::string typeString = NodeTraits::get_type_string(*rchild); - SPObject* child = SPFactory::instance().createObject(typeString); + SPObject* child = SPFactory::instance().createObject(typeString); - object->attach(child, object->lastChild()); - sp_object_unref(child, NULL); - child->invoke_build(document, rchild, object->cloned); - } catch (const FactoryExceptions::TypeNotRegistered& e) { - //g_warning("TypeNotRegistered exception: %s", e.what()); - } + object->attach(child, object->lastChild()); + sp_object_unref(child, NULL); + child->invoke_build(document, rchild, object->cloned); + } catch (const FactoryExceptions::TypeNotRegistered& e) { + //g_warning("TypeNotRegistered exception: %s", e.what()); + } } } @@ -723,7 +725,7 @@ void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, uns int SPObject::getIntAttribute(char const *key, int def) { sp_repr_get_int(getRepr(),key,&def); - return def; + return def; } unsigned SPObject::getPosition(){ @@ -907,7 +909,7 @@ void SPObject::setKeyValue(unsigned int key, gchar const *value) //g_assert(object != NULL); //g_assert(SP_IS_OBJECT(object)); - this->set(key, value); + this->set(key, value); } void SPObject::readAttr(gchar const *key) @@ -964,7 +966,7 @@ static gchar const *sp_xml_get_space_string(unsigned int space) } Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { - SPObject* object = this; + SPObject* object = this; if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) { repr = object->getRepr()->duplicate(doc); @@ -1066,10 +1068,10 @@ Inkscape::XML::Node * SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscap return NULL; } - if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) { - repr = getRepr(); - } - return this->write(doc, repr, flags); + if (!(flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = getRepr(); + } + return this->write(doc, repr, flags); } @@ -1136,7 +1138,7 @@ void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) try { - this->update(ctx, flags); + this->update(ctx, flags); } catch(...) { diff --git a/src/sp-skeleton.cpp b/src/sp-skeleton.cpp deleted file mode 100644 index 83f2bc20d..000000000 --- a/src/sp-skeleton.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/** \file - * SVG <skeleton> implementation, used as an example for a base starting class - * when implementing new sp-objects. - * - * In vi, three global search-and-replaces will let you rename everything - * in this and the .h file: - * - * :%s/SKELETON/YOURNAME/g - * :%s/Skeleton/Yourname/g - * :%s/skeleton/yourname/g - */ -/* - * Authors: - * Kees Cook <kees@outflux.net> - * Abhishek Sharma - * - * Copyright (C) 2004 Kees Cook - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include "attributes.h" -#include "sp-skeleton.h" -#include "xml/repr.h" - -#define DEBUG_SKELETON -#ifdef DEBUG_SKELETON -# define debug(f, a...) { g_print("%s(%d) %s:", \ - __FILE__,__LINE__,__FUNCTION__); \ - g_print(f, ## a); \ - g_print("\n"); \ - } -#else -# define debug(f, a...) /**/ -#endif - -/* Skeleton base class */ -static void sp_skeleton_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr); -static void sp_skeleton_release(SPObject *object); -static void sp_skeleton_set(SPObject *object, unsigned int key, gchar const *value); -static void sp_skeleton_update(SPObject *object, SPCtx *ctx, guint flags); -static Inkscape::XML::Node *sp_skeleton_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags); - -G_DEFINE_TYPE(SPSkeleton, sp_skeleton, SP_TYPE_OBJECT); - -static void -sp_skeleton_class_init(SPSkeletonClass *klass) -{ - SPObjectClass *sp_object_class = (SPObjectClass *)klass; - -<<<<<<< TREE - sp_object_class->build = sp_skeleton_build; -======= - skeleton_parent_class = (SPObjectClass*)g_type_class_peek_parent(klass); - - //sp_object_class->build = sp_skeleton_build; ->>>>>>> MERGE-SOURCE - sp_object_class->release = sp_skeleton_release; - sp_object_class->write = sp_skeleton_write; - sp_object_class->set = sp_skeleton_set; - sp_object_class->update = sp_skeleton_update; -} - -static void -sp_skeleton_init(SPSkeleton *skeleton) -{ - debug("0x%p",skeleton); -} - -/** - * Reads the Inkscape::XML::Node, and initializes SPSkeleton variables. For this to get called, - * our name must be associated with a repr via "sp_object_type_register". Best done through - * sp-object-repr.cpp's repr_name_entries array. - */ -static void -sp_skeleton_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) -{ - debug("0x%p",object); -<<<<<<< TREE - if (((SPObjectClass *) sp_skeleton_parent_class)->build) { - ((SPObjectClass *) sp_skeleton_parent_class)->build(object, document, repr); - } -======= -// if (((SPObjectClass *) skeleton_parent_class)->build) { -// ((SPObjectClass *) skeleton_parent_class)->build(object, document, repr); -// } ->>>>>>> MERGE-SOURCE - - /* - Pay attention to certain settings here - - object->readAttr( "xlink:href" ); - object->readAttr( "attributeName" ); - object->readAttr( "attributeType" ); - object->readAttr( "begin" ); - object->readAttr( "dur" ); - object->readAttr( "end" ); - object->readAttr( "min" ); - object->readAttr( "max" ); - object->readAttr( "restart" ); - object->readAttr( "repeatCount" ); - object->readAttr( "repeatDur" ); - object->readAttr( "fill" ); - */ -} - -/** - * Drops any allocated memory. - */ -static void -sp_skeleton_release(SPObject *object) -{ - debug("0x%p",object); - - /* deal with our children and our selves here */ - - if (((SPObjectClass *) sp_skeleton_parent_class)->release) - ((SPObjectClass *) sp_skeleton_parent_class)->release(object); -} - -/** - * Sets a specific value in the SPSkeleton. - */ -static void -sp_skeleton_set(SPObject *object, unsigned int key, gchar const *value) -{ - debug("0x%p %s(%u): '%s'",object, - sp_attribute_name(key),key,value ? value : "<no value>"); - //SPSkeleton *skeleton = SP_SKELETON(object); - - /* See if any parents need this value. */ - if (((SPObjectClass *) sp_skeleton_parent_class)->set) { - ((SPObjectClass *) sp_skeleton_parent_class)->set(object, key, value); - } -} - -/** - * Receives update notifications. - */ -static void -sp_skeleton_update(SPObject *object, SPCtx *ctx, guint flags) -{ - debug("0x%p",object); - //SPSkeleton *skeleton = SP_SKELETON(object); - - if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | - SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { - - /* do something to trigger redisplay, updates? */ - - } - - if (((SPObjectClass *) sp_skeleton_parent_class)->update) { - ((SPObjectClass *) sp_skeleton_parent_class)->update(object, ctx, flags); - } -} - -/** - * Writes its settings to an incoming repr object, if any. - */ -static Inkscape::XML::Node * -sp_skeleton_write(SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) -{ - debug("0x%p",object); - //SPSkeleton *skeleton = SP_SKELETON(object); - - // Inkscape-only object, not copied during an "plain SVG" dump: - if (flags & SP_OBJECT_WRITE_EXT) { - if (repr) { - // is this sane? - repr->mergeFrom(object->getRepr(), "id"); - } else { - repr = object->getRepr()->duplicate(doc); - } - } - - if (((SPObjectClass *) sp_skeleton_parent_class)->write) { - ((SPObjectClass *) sp_skeleton_parent_class)->write(object, doc, repr, flags); - } - - return repr; -} - - -/* - 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 : diff --git a/src/sp-skeleton.h b/src/sp-skeleton.h deleted file mode 100644 index d01cbcada..000000000 --- a/src/sp-skeleton.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef SP_SKELETON_H_SEEN -#define SP_SKELETON_H_SEEN - -/** \file - * SVG <skeleton> implementation, see sp-skeleton.cpp. - */ -/* - * Authors: - * Kees Cook <kees@outflux.net> - * - * Copyright (C) 2004 Kees Cook - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "sp-object.h" - -/* Skeleton base class */ - -#define SP_TYPE_SKELETON (sp_skeleton_get_type()) -#define SP_SKELETON(o) (G_TYPE_CHECK_INSTANCE_CAST((o), SP_TYPE_SKELETON, SPSkeleton)) -#define SP_IS_SKELETON(o) (G_TYPE_CHECK_INSTANCE_TYPE((o), SP_TYPE_SKELETON)) - -class SPSkeleton; -class SPSkeletonClass; - -struct SPSkeleton : public SPObject { -}; - -struct SPSkeletonClass { - SPObjectClass parent_class; -}; - -GType sp_skeleton_get_type(); - - -#endif /* !SP_SKELETON_H_SEEN */ - -/* - 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 : diff --git a/src/sp-string.cpp b/src/sp-string.cpp index a826d182d..be450b248 100644 --- a/src/sp-string.cpp +++ b/src/sp-string.cpp @@ -35,11 +35,11 @@ #include "sp-factory.h" namespace { - SPObject* createString() { - return new SPString(); - } + SPObject* createString() { + return new SPString(); + } - bool stringRegistered = SPFactory::instance().registerObject("string", createString); + bool stringRegistered = SPFactory::instance().registerObject("string", createString); } /*##################################################### @@ -54,24 +54,19 @@ SPString::~SPString() { } void SPString::build(SPDocument *doc, Inkscape::XML::Node *repr) { - SPString* object = this; + SPString* object = this; object->read_content(); SPObject::build(doc, repr); } void SPString::release() { - SPString* object = this; - SPString *string = SP_STRING(object); - - //string->string.~ustring(); - SPObject::release(); } void SPString::read_content() { - SPString* object = this; + SPString* object = this; SPString *string = SP_STRING(object); diff --git a/src/sp-use.cpp b/src/sp-use.cpp index 0887ab50e..44935e61d 100644 --- a/src/sp-use.cpp +++ b/src/sp-use.cpp @@ -44,15 +44,15 @@ static void sp_use_delete_self(SPObject *deleted, SPUse *self); #include "sp-factory.h" namespace { - SPObject* createUse() { - return new SPUse(); - } + SPObject* createUse() { + return new SPUse(); + } - bool useRegistered = SPFactory::instance().registerObject("svg:use", createUse); + bool useRegistered = SPFactory::instance().registerObject("svg:use", createUse); } SPUse::SPUse() : SPItem() { - this->child = NULL; + this->child = NULL; this->x.unset(); this->y.unset(); @@ -102,7 +102,7 @@ void SPUse::build(SPDocument *document, Inkscape::XML::Node *repr) { void SPUse::release() { if (this->child) { - this->detach(this->child); + this->detach(this->child); this->child = NULL; } @@ -227,8 +227,6 @@ void SPUse::print(SPPrintContext* ctx) { } gchar* SPUse::description() { - char *ret; - if (this->child) { if( SP_IS_SYMBOL( this->child ) ) { char *symbol_desc = SP_ITEM(this->child)->title(); @@ -455,9 +453,9 @@ sp_use_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPUse *use) SPObject* obj = SPFactory::instance().createObject(NodeTraits::get_type_string(*childrepr)); if (SP_IS_ITEM(obj)) { - use->child = obj; + use->child = obj; - use->attach(use->child, use->lastChild()); + use->attach(use->child, use->lastChild()); sp_object_unref(use->child, use); (use->child)->invoke_build(use->document, childrepr, TRUE); @@ -469,7 +467,7 @@ sp_use_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPUse *use) } } } else { - delete obj; + delete obj; } use->_delete_connection = refobj->connectDelete(sigc::bind(sigc::ptr_fun(&sp_use_delete_self), use)); @@ -523,7 +521,7 @@ void SPUse::update(SPCtx *ctx, unsigned flags) { } if (this->y.unit == SVGLength::PERCENT) { - this->y.computed = this->y.value * ictx->viewport.height(); + this->y.computed = this->y.value * ictx->viewport.height(); } if (this->width.unit == SVGLength::PERCENT) { diff --git a/src/style.cpp b/src/style.cpp index db05a748f..e9cf22891 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -3170,7 +3170,10 @@ sp_style_clear(SPStyle *style) style->color_interpolation.value = style->color_interpolation.computed = SP_CSS_COLOR_INTERPOLATION_SRGB; style->color_interpolation_filters.set = FALSE; style->color_interpolation_filters.inherit = FALSE; - style->color_interpolation_filters.value = style->color_interpolation_filters.computed = SP_CSS_COLOR_INTERPOLATION_LINEARRGB; + style->color_interpolation_filters.value = style->color_interpolation_filters.computed = SP_CSS_COLOR_INTERPOLATION_SRGB; + //this line changed because rendering issues: Bug lp:1127103 + //style->color_interpolation_filters.value = style->color_interpolation_filters.computed = SP_CSS_COLOR_INTERPOLATION_LINEARRGB; + style->fill.clear(); style->fill.setColor(0.0, 0.0, 0.0); diff --git a/src/trace/imagemap-gdk.cpp b/src/trace/imagemap-gdk.cpp index 7c7139002..298414074 100644 --- a/src/trace/imagemap-gdk.cpp +++ b/src/trace/imagemap-gdk.cpp @@ -152,9 +152,9 @@ RgbMap *gdkPixbufToRgbMap(GdkPixbuf *buf) { int alpha = (int)p[3]; int white = 255 - alpha; - int r = (int)p[2]; r = r * alpha / 256 + white; + int r = (int)p[0]; r = r * alpha / 256 + white; int g = (int)p[1]; g = g * alpha / 256 + white; - int b = (int)p[0]; b = b * alpha / 256 + white; + int b = (int)p[2]; b = b * alpha / 256 + white; rgbMap->setPixel(rgbMap, x, y, r, g, b); p += n_channels; diff --git a/src/trace/trace.cpp b/src/trace/trace.cpp index cad8ea9be..e2cda6247 100644 --- a/src/trace/trace.cpp +++ b/src/trace/trace.cpp @@ -31,6 +31,7 @@ #include <2geom/transforms.h> #include "verbs.h" +#include "display/cairo-utils.h" #include "display/drawing.h" #include "display/drawing-shape.h" @@ -336,8 +337,17 @@ Glib::RefPtr<Gdk::Pixbuf> Tracer::getSelectedImage() if (!img->pixbuf) return Glib::RefPtr<Gdk::Pixbuf>(NULL); - Glib::RefPtr<Gdk::Pixbuf> pixbuf = - Glib::wrap(img->pixbuf, true); + GdkPixbuf *raw_pb = img->pixbuf->getPixbufRaw(false); + GdkPixbuf *trace_pb = gdk_pixbuf_copy(raw_pb); + if (img->pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { + convert_pixels_argb32_to_pixbuf( + gdk_pixbuf_get_pixels(trace_pb), + gdk_pixbuf_get_width(trace_pb), + gdk_pixbuf_get_height(trace_pb), + gdk_pixbuf_get_rowstride(trace_pb)); + } + + Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(trace_pb, false); if (sioxEnabled) { @@ -410,7 +420,16 @@ void Tracer::traceThread() return; } - Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(img->pixbuf, true); + GdkPixbuf *trace_pb = gdk_pixbuf_copy(img->pixbuf->getPixbufRaw(false)); + if (img->pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { + convert_pixels_argb32_to_pixbuf( + gdk_pixbuf_get_pixels(trace_pb), + gdk_pixbuf_get_width(trace_pb), + gdk_pixbuf_get_height(trace_pb), + gdk_pixbuf_get_rowstride(trace_pb)); + } + + Glib::RefPtr<Gdk::Pixbuf> pixbuf = Glib::wrap(trace_pb, false); pixbuf = sioxProcessImage(img, pixbuf); diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index c95dd35cc..233e01862 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -71,6 +71,7 @@ set(ui_SRC dialog/text-edit.cpp dialog/tile.cpp dialog/tracedialog.cpp + dialog/pixelartdialog.cpp dialog/transformation.cpp dialog/undo-history.cpp # dialog/whiteboard-connect.cpp diff --git a/src/ui/dialog/Makefile_insert b/src/ui/dialog/Makefile_insert index 09a7ef573..c37767a08 100644 --- a/src/ui/dialog/Makefile_insert +++ b/src/ui/dialog/Makefile_insert @@ -101,6 +101,8 @@ ink_common_sources += \ ui/dialog/tile.h \ ui/dialog/tracedialog.cpp \ ui/dialog/tracedialog.h \ + ui/dialog/pixelartdialog.cpp \ + ui/dialog/pixelartdialog.h \ ui/dialog/transformation.cpp \ ui/dialog/transformation.h \ ui/dialog/undo-history.cpp \ diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp index 0ce74f54e..17f6ae74d 100644 --- a/src/ui/dialog/dialog-manager.cpp +++ b/src/ui/dialog/dialog-manager.cpp @@ -35,6 +35,7 @@ #include "ui/dialog/symbols.h" #include "ui/dialog/tile.h" #include "ui/dialog/tracedialog.h" +#include "ui/dialog/pixelartdialog.h" #include "ui/dialog/transformation.h" #include "ui/dialog/undo-history.h" #include "ui/dialog/panel-dialog.h" @@ -118,6 +119,7 @@ DialogManager::DialogManager() { registerFactory("Symbols", &create<SymbolsDialog, FloatingBehavior>); registerFactory("TileDialog", &create<TileDialog, FloatingBehavior>); registerFactory("Trace", &create<TraceDialog, FloatingBehavior>); + registerFactory("PixelArt", &create<PixelArtDialog, FloatingBehavior>); registerFactory("Transformation", &create<Transformation, FloatingBehavior>); registerFactory("UndoHistory", &create<UndoHistory, FloatingBehavior>); registerFactory("InputDevices", &create<InputDialog, FloatingBehavior>); @@ -151,6 +153,7 @@ DialogManager::DialogManager() { registerFactory("Symbols", &create<SymbolsDialog, DockBehavior>); registerFactory("TileDialog", &create<TileDialog, DockBehavior>); registerFactory("Trace", &create<TraceDialog, DockBehavior>); + registerFactory("PixelArt", &create<PixelArtDialog, DockBehavior>); registerFactory("Transformation", &create<Transformation, DockBehavior>); registerFactory("UndoHistory", &create<UndoHistory, DockBehavior>); registerFactory("InputDevices", &create<InputDialog, DockBehavior>); diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index b06c1fd1f..e9cf2e753 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -884,7 +884,7 @@ void InkscapePreferences::initPageIO() int pathstringFormatValues[numPathstringFormat] = {0, 1, 2}; _svgoutput_pathformat.init("/options/svgoutput/pathstring_format", pathstringFormatLabels, pathstringFormatValues, numPathstringFormat, 2); - _page_svgoutput.add_line( true, _("Path string format"), _svgoutput_pathformat, "", _("Path data should be written: only with absolute coordinates, only with relative coordinates, or optimized for string length (mixed absolute and relative coordinates)"), false); + _page_svgoutput.add_line( true, _("Path string format:"), _svgoutput_pathformat, "", _("Path data should be written: only with absolute coordinates, only with relative coordinates, or optimized for string length (mixed absolute and relative coordinates)"), false); _svgoutput_forcerepeatcommands.init( _("Force repeat commands"), "/options/svgoutput/forcerepeatcommands", false); _page_svgoutput.add_line( true, "", _svgoutput_forcerepeatcommands, "", _("Force repeating of the same path command (for example, 'L 1,2 L 3,4' instead of 'L 1,2 3,4')"), false); diff --git a/src/ui/dialog/pixelartdialog.cpp b/src/ui/dialog/pixelartdialog.cpp new file mode 100644 index 000000000..ef181b357 --- /dev/null +++ b/src/ui/dialog/pixelartdialog.cpp @@ -0,0 +1,492 @@ +/** + * @file + * Pixel art tracing settings dialog - implementation. + */ +/* Authors: + * Bob Jamison <rjamison@titan.com> + * Stéphane Gimenez <dev@gim.name> + * Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004-2013 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pixelartdialog.h" +#include <gtkmm/radiobutton.h> +#include <gtkmm/stock.h> + +#include <gtk/gtk.h> //for GTK_RESPONSE* types +#include <glibmm/i18n.h> + +#include "ui/widget/spinbutton.h" +#include "ui/widget/frame.h" + +#include "desktop.h" +#include "desktop-tracker.h" +#include "message-stack.h" +#include "selection.h" +#include "preferences.h" + +#include "sp-image.h" +#include "display/cairo-utils.h" +#include "libdepixelize/kopftracer2011.h" +#include <algorithm> +#include "document.h" +#include "xml/repr.h" +#include "xml/document.h" +#include "svg/svg.h" +#include "svg/svg-color.h" +#include "color.h" +#include "svg/css-ostringstream.h" +#include "document-undo.h" + +#ifdef HAVE_OPENMP +#include <omp.h> +#endif // HAVE_OPENMP + +namespace Inkscape { +namespace UI { +namespace Dialog { + +template<class T> +T move(T &obj) +{ +#ifdef LIBDEPIXELIZE_ENABLE_CPP11 + return std::move(obj); +#else + T ret; + std::swap(obj, ret); + return ret; +#endif // LIBDEPIXELIZE_ENABLE_CPP11 +} + +/** + * A dialog for adjusting pixel art -> vector tracing parameters + */ +class PixelArtDialogImpl : public PixelArtDialog +{ +public: + PixelArtDialogImpl(); + + ~PixelArtDialogImpl(); + +private: + void setDesktop(SPDesktop *desktop); + void setTargetDesktop(SPDesktop *desktop); + + //############ Events + + void responseCallback(int response_id); + + //############ UI Logic + + Tracer::Kopf2011::Options options(); + + void vectorize(); + void processLibdepixelize(SPImage *img); + void setDefaults(); + void updatePreview(); + + bool ignorePreview; + bool pendingPreview; + + //############ UI + + Gtk::HBox buttonsHBox; + + Gtk::Button *mainOkButton; + Gtk::Button *mainCancelButton; + Gtk::Button *mainResetButton; + + Gtk::VBox heuristicsVBox; + UI::Widget::Frame heuristicsFrame; + + Gtk::HBox curvesMultiplierHBox; + Gtk::Label curvesMultiplierLabel; + Inkscape::UI::Widget::SpinButton curvesMultiplierSpinner; + + Gtk::HBox islandsWeightHBox; + Gtk::Label islandsWeightLabel; + Inkscape::UI::Widget::SpinButton islandsWeightSpinner; + + Gtk::HBox sparsePixelsMultiplierHBox; + Gtk::Label sparsePixelsMultiplierLabel; + Inkscape::UI::Widget::SpinButton sparsePixelsMultiplierSpinner; + + Gtk::HBox sparsePixelsRadiusHBox; + Gtk::Label sparsePixelsRadiusLabel; + Inkscape::UI::Widget::SpinButton sparsePixelsRadiusSpinner; + + Gtk::VBox outputVBox; + UI::Widget::Frame outputFrame; + Gtk::RadioButtonGroup outputGroup; + + Gtk::RadioButton voronoiRadioButton; + Gtk::RadioButton noOptimizeRadioButton; + Gtk::RadioButton optimizeRadioButton; + + SPDesktop *desktop; + DesktopTracker deskTrack; + sigc::connection desktopChangeConn; +}; + +void PixelArtDialogImpl::setDesktop(SPDesktop *desktop) +{ + Panel::setDesktop(desktop); + deskTrack.setBase(desktop); +} + +void PixelArtDialogImpl::setTargetDesktop(SPDesktop *desktop) +{ + this->desktop = desktop; +} + +PixelArtDialogImpl::PixelArtDialogImpl() : + PixelArtDialog(), + ignorePreview(false), + pendingPreview(false) +{ + + Gtk::Box *contents = _getContents(); + + // Heuristics + { + curvesMultiplierLabel.set_label(_("_Curves (multiplier)")); + curvesMultiplierLabel.set_use_underline(true); + curvesMultiplierLabel.set_mnemonic_widget(curvesMultiplierSpinner); + curvesMultiplierLabel.set_tooltip_text(_("Favors connections that are part of a long curve")); + curvesMultiplierSpinner.set_increments(0.125, 0); + curvesMultiplierSpinner.set_digits(3); + curvesMultiplierSpinner.set_range(-10, 10); + curvesMultiplierSpinner.get_adjustment()->signal_value_changed() + .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview)); + + curvesMultiplierHBox.pack_start(curvesMultiplierLabel, false, false); + curvesMultiplierHBox.pack_end(curvesMultiplierSpinner, false, false); + heuristicsVBox.pack_start(curvesMultiplierHBox, false, false); + + islandsWeightLabel.set_label(_("_Islands (weight)")); + islandsWeightLabel.set_use_underline(true); + islandsWeightLabel.set_mnemonic_widget(islandsWeightSpinner); + islandsWeightLabel.set_tooltip_text(_("Avoid single disconnected pixels")); + + islandsWeightSpinner.set_tooltip_text(_("A constant vote value")); + islandsWeightSpinner.set_increments(1, 0); + islandsWeightSpinner.set_range(-20, 20); + islandsWeightSpinner.get_adjustment()->signal_value_changed() + .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview)); + + islandsWeightHBox.pack_start(islandsWeightLabel, false, false); + islandsWeightHBox.pack_end(islandsWeightSpinner, false, false); + heuristicsVBox.pack_start(islandsWeightHBox, false, false); + + sparsePixelsRadiusLabel.set_label(_("Sparse pixels (window _radius)")); + sparsePixelsRadiusLabel.set_use_underline(true); + sparsePixelsRadiusLabel.set_mnemonic_widget(sparsePixelsRadiusSpinner); + + sparsePixelsRadiusSpinner.set_increments(1, 0); + sparsePixelsRadiusSpinner.set_range(2, 8); + sparsePixelsRadiusSpinner.get_adjustment()->signal_value_changed() + .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview)); + + sparsePixelsRadiusSpinner.set_tooltip_text(_("The radius of the window analyzed")); + sparsePixelsMultiplierLabel.set_label(_("Sparse pixels (_multiplier)")); + sparsePixelsMultiplierLabel.set_use_underline(true); + sparsePixelsMultiplierLabel.set_mnemonic_widget(sparsePixelsMultiplierSpinner); + + sparsePixelsMultiplierSpinner.set_increments(0.125, 0); + sparsePixelsMultiplierSpinner.set_digits(3); + sparsePixelsMultiplierSpinner.set_range(-10, 10); + sparsePixelsMultiplierSpinner.get_adjustment()->signal_value_changed() + .connect(sigc::mem_fun(*this, &PixelArtDialogImpl::updatePreview)); + + { + char const *str = _("Favors connections that are part of foreground color"); + sparsePixelsRadiusLabel.set_tooltip_text(str); + sparsePixelsMultiplierLabel.set_tooltip_text(str); + } + + { + char const *str = _("The heuristic computed vote will be multiplied by this value"); + curvesMultiplierSpinner.set_tooltip_text(str); + sparsePixelsMultiplierSpinner.set_tooltip_text(str); + } + + sparsePixelsRadiusHBox.pack_start(sparsePixelsRadiusLabel, false, false); + sparsePixelsRadiusHBox.pack_end(sparsePixelsRadiusSpinner, false, false); + heuristicsVBox.pack_start(sparsePixelsRadiusHBox, false, false); + + sparsePixelsMultiplierHBox.pack_start(sparsePixelsMultiplierLabel, false, false); + sparsePixelsMultiplierHBox.pack_end(sparsePixelsMultiplierSpinner, false, false); + heuristicsVBox.pack_start(sparsePixelsMultiplierHBox, false, false); + + heuristicsFrame.set_label(_("Heuristics")); + heuristicsFrame.add(heuristicsVBox); + contents->pack_start(heuristicsFrame, false, false); + } + + // Output + { + voronoiRadioButton.set_label(_("_Voronoi diagram")); + voronoiRadioButton.set_tooltip_text(_("Output composed of straight lines")); + voronoiRadioButton.set_use_underline(true); + outputGroup = voronoiRadioButton.get_group(); + + outputVBox.pack_start(voronoiRadioButton, false, false); + + noOptimizeRadioButton.set_label(_("Convert to _B-spline curves")); + noOptimizeRadioButton.set_tooltip_text(_("Preserve staircasing artifacts")); + noOptimizeRadioButton.set_use_underline(true); + noOptimizeRadioButton.set_group(outputGroup); + + outputVBox.pack_start(noOptimizeRadioButton, false, false); + + optimizeRadioButton.set_label(_("_Smooth curves")); + optimizeRadioButton.set_tooltip_text(_("The Kopf-Lischinski algorithm")); + optimizeRadioButton.set_use_underline(true); + optimizeRadioButton.set_group(outputGroup); + + outputVBox.pack_start(optimizeRadioButton, false, false); + + outputFrame.set_label(_("Output")); + outputFrame.add(outputVBox); + contents->pack_start(outputFrame, true, false); + } + + // Buttons + { + mainResetButton = addResponseButton(_("Reset"), GTK_RESPONSE_HELP, true); + mainResetButton ->set_tooltip_text(_("Reset all settings to defaults")); + + //## The OK button + mainCancelButton = addResponseButton(Gtk::Stock::STOP, GTK_RESPONSE_CANCEL); + if (mainCancelButton) { + mainCancelButton->set_tooltip_text(_("Abort a trace in progress")); + mainCancelButton->set_sensitive(false); + } + mainOkButton = addResponseButton(Gtk::Stock::OK, GTK_RESPONSE_OK); + mainOkButton->set_tooltip_text(_("Execute the trace")); + + contents->pack_start(buttonsHBox); + } + + setDefaults(); + + show_all_children(); + + desktopChangeConn = deskTrack.connectDesktopChanged( sigc::mem_fun(*this, &PixelArtDialogImpl::setTargetDesktop) ); + deskTrack.connect(GTK_WIDGET(gobj())); + + signalResponse().connect(sigc::mem_fun(*this, &PixelArtDialogImpl::responseCallback)); +} + +void PixelArtDialogImpl::responseCallback(int response_id) +{ + if (response_id == GTK_RESPONSE_OK) { + vectorize(); + } else if (response_id == GTK_RESPONSE_CANCEL) { + // TODO + } else if (response_id == GTK_RESPONSE_HELP) { + setDefaults(); + } else { + hide(); + return; + } +} + +Tracer::Kopf2011::Options PixelArtDialogImpl::options() +{ + Tracer::Kopf2011::Options options; + + options.curvesMultiplier = curvesMultiplierSpinner.get_value(); + options.islandsWeight = islandsWeightSpinner.get_value_as_int(); + options.sparsePixelsMultiplier = sparsePixelsMultiplierSpinner.get_value(); + options.sparsePixelsRadius = sparsePixelsRadiusSpinner.get_value_as_int(); + options.optimize = optimizeRadioButton.get_active(); + + options.nthreads = Inkscape::Preferences::get() + ->getIntLimited("/options/threading/numthreads", +#ifdef HAVE_OPENMP + omp_get_num_procs(), +#else + 1, +#endif // HAVE_OPENMP + 1, 256); + + return options; +} + +void PixelArtDialogImpl::vectorize() +{ + Inkscape::MessageStack *msgStack = desktop->messageStack(); + + if ( !desktop->selection ) { + char *msg = _("Select an <b>image</b> to trace"); + msgStack->flash(Inkscape::ERROR_MESSAGE, msg); + return; + } + + bool found = false; + + for ( GSList const *list = desktop->selection->itemList() ; list + ; list = list->next ) { + if ( !SP_IS_IMAGE(list->data) ) + continue; + + found = true; + + processLibdepixelize(SP_IMAGE(list->data)); + } + + if ( !found ) { + char *msg = _("Select an <b>image</b> to trace"); + msgStack->flash(Inkscape::ERROR_MESSAGE, msg); + return; + } + + DocumentUndo::done(desktop->doc(), SP_VERB_SELECTION_PIXEL_ART, + _("Trace pixel art")); + + // Flush pending updates + desktop->doc()->ensureUpToDate(); +} + +void PixelArtDialogImpl::processLibdepixelize(SPImage *img) +{ + Tracer::Splines out; + + Glib::RefPtr<Gdk::Pixbuf> pixbuf + = Glib::wrap(img->pixbuf->getPixbufRaw(), true); + + if ( voronoiRadioButton.get_active() ) { + out = Tracer::Kopf2011::to_voronoi(pixbuf, options()); + } else { + out = Tracer::Kopf2011::to_splines(pixbuf, options()); + } + + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node *group = xml_doc->createElement("svg:g"); + + for ( Tracer::Splines::iterator it = out.begin(), end = out.end() + ; it != end ; ++it ) { + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + + { + SPCSSAttr *css = sp_repr_css_attr_new(); + + { + gchar b[64]; + sp_svg_write_color(b, sizeof(b), + SP_RGBA32_U_COMPOSE(unsigned(it->rgba[0]), + unsigned(it->rgba[1]), + unsigned(it->rgba[2]), + unsigned(it->rgba[3]))); + + sp_repr_css_set_property(css, "fill", b); + } + + { + Inkscape::CSSOStringStream osalpha; + osalpha << float(it->rgba[3]) / 255.; + sp_repr_css_set_property(css, "fill-opacity", + osalpha.str().c_str()); + } + + sp_repr_css_set(repr, css, "style"); + sp_repr_css_attr_unref(css); + } + + gchar *str = sp_svg_write_path(move(it->pathVector)); + repr->setAttribute("d", str); + g_free(str); + + group->appendChild(repr); + + Inkscape::GC::release(repr); + } + + { + group->setAttribute("transform", + (std::string("translate(") + + sp_svg_length_write_with_units(img->x) + + ' ' + sp_svg_length_write_with_units(img->y) + + ')').c_str()); + } + + desktop->currentLayer()->appendChildRepr(group); + + Inkscape::GC::release(group); +} + +void PixelArtDialogImpl::setDefaults() +{ + ignorePreview = true; + + curvesMultiplierSpinner.set_value(Tracer::Kopf2011::Options + ::CURVES_MULTIPLIER); + + islandsWeightSpinner.set_value(Tracer::Kopf2011::Options::ISLANDS_WEIGHT); + + sparsePixelsRadiusSpinner.set_value(Tracer::Kopf2011::Options + ::SPARSE_PIXELS_RADIUS); + + sparsePixelsMultiplierSpinner.set_value(Tracer::Kopf2011::Options + ::SPARSE_PIXELS_MULTIPLIER); + + optimizeRadioButton.set_active(); + + ignorePreview = false; + + if ( pendingPreview ) + updatePreview(); +} + +void PixelArtDialogImpl::updatePreview() +{ + if ( ignorePreview ) { + pendingPreview = true; + return; + } + + // TODO: update preview + pendingPreview = false; +} + +/** + * Factory method. Use this to create a new PixelArtDialog + */ +PixelArtDialog &PixelArtDialog::getInstance() +{ + PixelArtDialog *dialog = new PixelArtDialogImpl(); + return *dialog; +} + +PixelArtDialogImpl::~PixelArtDialogImpl() +{ + desktopChangeConn.disconnect(); +} + + +} //namespace Dialog +} //namespace UI +} //namespace Inkscape + +/* + 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 : diff --git a/src/ui/dialog/pixelartdialog.h b/src/ui/dialog/pixelartdialog.h new file mode 100644 index 000000000..165cb8699 --- /dev/null +++ b/src/ui/dialog/pixelartdialog.h @@ -0,0 +1,58 @@ +/** @file + * @brief Bitmap tracing settings dialog + */ +/* Authors: + * Bob Jamison + * Vinícius dos Santos Oliveira <vini.ipsmaker@gmail.com> + * Other dudes from The Inkscape Organization + * + * Copyright (C) 2004, 2005 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef __PIXELARTDIALOG_H__ +#define __PIXELARTDIALOG_H__ + +#include "ui/widget/panel.h" +#include "verbs.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + + +/** + * A dialog that displays log messages + */ +class PixelArtDialog : public UI::Widget::Panel +{ + +public: + + PixelArtDialog() : + UI::Widget::Panel("", "/dialogs/pixelart", SP_VERB_SELECTION_PIXEL_ART) + {} + + + static PixelArtDialog &getInstance(); + + virtual ~PixelArtDialog() {}; +}; + + +} //namespace Dialog +} //namespace UI +} //namespace Inkscape + +#endif /* __PIXELARTDIALOGDIALOG_H__ */ + +/* + 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 : diff --git a/src/verbs.cpp b/src/verbs.cpp index 737d9e150..23a560423 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -1173,6 +1173,10 @@ void SelectionVerb::perform(SPAction *action, void *data) inkscape_dialogs_unhide(); dt->_dlg_mgr->showDialog("Trace"); break; + case SP_VERB_SELECTION_PIXEL_ART: + inkscape_dialogs_unhide(); + dt->_dlg_mgr->showDialog("PixelArt"); + break; case SP_VERB_SELECTION_CREATE_BITMAP: sp_selection_create_bitmap_copy(dt); break; @@ -2545,6 +2549,8 @@ Verb *Verb::_base_verbs[] = { // TRANSLATORS: "to trace" means "to convert a bitmap to vector graphics" (to vectorize) new SelectionVerb(SP_VERB_SELECTION_TRACE, "SelectionTrace", N_("_Trace Bitmap..."), N_("Create one or more paths from a bitmap by tracing it"), INKSCAPE_ICON("bitmap-trace")), + new SelectionVerb(SP_VERB_SELECTION_PIXEL_ART, "SelectionPixelArt", N_("Trace Pixel Art..."), + N_("Create paths using Kopf-Lischinski algorithm to vectorize pixel art"), INKSCAPE_ICON("pixelart-trace")), new SelectionVerb(SP_VERB_SELECTION_CREATE_BITMAP, "SelectionCreateBitmap", N_("Make a _Bitmap Copy"), N_("Export selection to a bitmap and insert it into document"), INKSCAPE_ICON("selection-make-bitmap-copy") ), new SelectionVerb(SP_VERB_SELECTION_COMBINE, "SelectionCombine", N_("_Combine"), diff --git a/src/verbs.h b/src/verbs.h index c4b01dfbf..40292745a 100644 --- a/src/verbs.h +++ b/src/verbs.h @@ -134,6 +134,7 @@ enum { SP_VERB_SELECTION_SIMPLIFY, SP_VERB_SELECTION_REVERSE, SP_VERB_SELECTION_TRACE, + SP_VERB_SELECTION_PIXEL_ART, SP_VERB_SELECTION_CREATE_BITMAP, SP_VERB_SELECTION_COMBINE, SP_VERB_SELECTION_BREAK_APART, |
