From 8e34c10dea7bf29ec44a3913d28eec84337027e0 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Krzysztof=20Kosi=C5=84ski?= Date: Fri, 5 Mar 2010 00:34:30 +0100 Subject: [PATCH] Improve behavior when pasting, DnDing and importing bitmap images --- src/extension/extension.cpp | 6 +- src/extension/extension.h | 2 + src/extension/input.h | 11 +- src/extension/internal/gdkpixbuf-input.cpp | 113 ++++++++++++++------- src/interface.cpp | 62 ++++------- src/ui/clipboard.cpp | 39 ++++--- 6 files changed, 133 insertions(+), 100 deletions(-) diff --git a/src/extension/extension.cpp b/src/extension/extension.cpp index 52d5f5148..b36d2e3a1 100644 --- a/src/extension/extension.cpp +++ b/src/extension/extension.cpp @@ -57,7 +57,9 @@ Parameter * param_shared (const gchar * name, GSList * list); not related to the module directly. If the Repr does not include a name and an ID the module will be left in an errored state. */ -Extension::Extension (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp) : _help(NULL) +Extension::Extension (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp) + : _help(NULL) + , _gui(true) { repr = in_repr; Inkscape::GC::anchor(in_repr); @@ -673,7 +675,7 @@ public: Gtk::Widget * Extension::autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal * changeSignal) { - if (param_visible_count() == 0) return NULL; + if (!_gui || param_visible_count() == 0) return NULL; AutoGUI * agui = Gtk::manage(new AutoGUI()); diff --git a/src/extension/extension.h b/src/extension/extension.h index 48ca86cf7..6210e2eba 100644 --- a/src/extension/extension.h +++ b/src/extension/extension.h @@ -99,6 +99,7 @@ private: state_t _state; /**< Which state the Extension is currently in */ std::vector _deps; /**< Dependencies for this extension */ static std::ofstream error_file; /**< This is the place where errors get reported */ + bool _gui; protected: Inkscape::XML::Node *repr; /**< The XML description of the Extension */ @@ -219,6 +220,7 @@ public: public: Gtk::Widget * autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal * changeSignal = NULL); void paramListString (std::list & retlist); + void set_gui(bool s) { _gui = s; } /* Extension editor dialog stuff */ public: diff --git a/src/extension/input.h b/src/extension/input.h index 55d807ce2..24cbc4896 100644 --- a/src/extension/input.h +++ b/src/extension/input.h @@ -11,6 +11,7 @@ #ifndef INKSCAPE_EXTENSION_INPUT_H__ #define INKSCAPE_EXTENSION_INPUT_H__ +#include #include #include "extension.h" #include "xml/repr.h" @@ -30,8 +31,14 @@ public: /* this is a hack for this release, this will be private shortly */ gchar *output_extension; /**< Setting of what output extension should be used */ public: - class open_failed {}; /**< Generic failure for an undescribed reason */ - class no_extension_found {}; /**< Failed because we couldn't find an extension to match the filename */ + struct open_failed : public std::exception { + virtual ~open_failed() throw() {} + const char *what() const throw() { return "Open failed"; } + }; + struct no_extension_found : public std::exception { + virtual ~no_extension_found() throw() {} + const char *what() const throw() { return "No suitable input extension found"; } + }; Input (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp); diff --git a/src/extension/internal/gdkpixbuf-input.cpp b/src/extension/internal/gdkpixbuf-input.cpp index 64a099c8a..47cde4a5a 100644 --- a/src/extension/internal/gdkpixbuf-input.cpp +++ b/src/extension/internal/gdkpixbuf-input.cpp @@ -1,8 +1,10 @@ #ifdef HAVE_CONFIG_H # include #endif +#include #include "document-private.h" #include +#include "extension/input.h" #include "extension/system.h" #include "gdkpixbuf-input.h" #include "selection-chemistry.h" @@ -16,69 +18,107 @@ GdkPixbuf* pixbuf_new_from_file( char const *utf8name, GError **error ); namespace Extension { namespace Internal { +static std::set create_lossy_set() +{ + std::set lossy; + lossy.insert(".jpg"); + lossy.insert(".jpeg"); + return lossy; +} + SPDocument * -GdkpixbufInput::open(Inkscape::Extension::Input */*mod*/, char const *uri) +GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) { + bool embed = !mod->get_param_bool("link"); SPDocument *doc = NULL; GdkPixbuf *pb = Inkscape::IO::pixbuf_new_from_file( uri, NULL ); + static std::set lossy = create_lossy_set(); if (pb) { /* We are readable */ + bool is_lossy; + Glib::ustring mime_type, ext; + Glib::ustring u = uri; + std::size_t dotpos = u.rfind('.'); + if (dotpos != Glib::ustring::npos) { + ext = u.substr(dotpos, Glib::ustring::npos); + } + + // HACK: replace with something better based on GIO + if (!ext.empty() && lossy.find(ext) != lossy.end()) { + is_lossy = true; + mime_type = "image/jpeg"; + } else { + is_lossy = false; + mime_type = "image/png"; + } + doc = sp_document_new(NULL, TRUE, TRUE); bool saved = sp_document_get_undo_sensitive(doc); sp_document_set_undo_sensitive(doc, false); // no need to undo in this temporary document - Inkscape::XML::Node *repr = NULL; - double width = gdk_pixbuf_get_width(pb); double height = gdk_pixbuf_get_height(pb); gchar const *str = gdk_pixbuf_get_option( pb, "Inkscape::DpiX" ); - if ( str ) - { + if ( str ) { gint dpi = atoi(str); - if ( dpi > 0 && dpi != 72 ) - { + if ( dpi > 0 && dpi != 72 ) { double scale = 72.0 / (double)dpi; width *= scale; } } str = gdk_pixbuf_get_option( pb, "Inkscape::DpiY" ); - if ( str ) - { + if ( str ) { gint dpi = atoi(str); - if ( dpi > 0 && dpi != 72 ) - { + if ( dpi > 0 && dpi != 72 ) { double scale = 72.0 / (double)dpi; height *= scale; } } + // Create image node Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc); - // import as - repr = xml_doc->createElement("svg:image"); - - // convert filename to uri - gchar* _uri = g_filename_to_uri(uri, NULL, NULL); - if(_uri) { - repr->setAttribute("xlink:href", _uri); - g_free(_uri); + Inkscape::XML::Node *image_node = xml_doc->createElement("svg:image"); + sp_repr_set_svg_double(image_node, "width", width); + sp_repr_set_svg_double(image_node, "height", height); + + if (embed) { + // Save pixbuf as JPEG or PNG for embedding + gchar *data; + gsize length; + gdk_pixbuf_save_to_buffer(pb, &data, &length, is_lossy ? "jpeg" : "png", NULL, NULL); + + // Save base64 encoded data in image node + // this formula taken from Glib docs + guint needed_size = length * 4 / 3 + length * 4 / (3 * 72) + 7; + needed_size += 5 + 8 + mime_type.size(); // 5 bytes for data:, 8 for ;base64, + + gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer; + buf_work += g_sprintf(buffer, "data:%s;base64,", mime_type.data()); + + gint state = 0, save = 0; + gsize written = 0; + written += g_base64_encode_step((guchar*) data, length, TRUE, buf_work, &state, &save); + written += g_base64_encode_close(TRUE, buf_work + written, &state, &save); + buf_work[written] = 0; // null terminate + + image_node->setAttribute("xlink:href", buffer); + g_free(buffer); } else { - repr->setAttribute("xlink:href", uri); + // convert filename to uri + gchar* _uri = g_filename_to_uri(uri, NULL, NULL); + if(_uri) { + image_node->setAttribute("xlink:href", _uri); + g_free(_uri); + } else { + image_node->setAttribute("xlink:href", uri); + } } - /* impl: doc->base is currently NULL, so we can use uri for href whether it's absolute - * or relative. The href will get rewritten by rebase_hrefs if by chance uri is relative - * and doc gets saved to a different directory. - * - * We don't bother setting sodipodi:absref, as we assume it's never useful to have - * sodipodi:absref with the same value as xlink:href, and rebase_hrefs will provide - * sodipodi:absref values where necessary. */ - - sp_repr_set_svg_double(repr, "width", width); - sp_repr_set_svg_double(repr, "height", height); - - SP_DOCUMENT_ROOT(doc)->appendChildRepr(repr); - Inkscape::GC::release(repr); - gdk_pixbuf_unref(pb); - //alter the canvas size to fit the image size + + g_object_unref(pb); + + // Add it to the current layer + SP_DOCUMENT_ROOT(doc)->appendChildRepr(image_node); + Inkscape::GC::release(image_node); fit_canvas_to_drawing(doc); // restore undo, as now this document may be shown to the user if a bitmap was opened sp_document_set_undo_sensitive(doc, saved); @@ -126,6 +166,9 @@ GdkpixbufInput::init(void) "\n" "" N_("%s GDK pixbuf Input") "\n" "org.inkscape.input.gdkpixbuf.%s\n" + "false" "\n" ".%s\n" "%s\n" diff --git a/src/interface.cpp b/src/interface.cpp index 9c1870019..cb021db18 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -23,7 +23,9 @@ #include #include "inkscape-private.h" +#include "extension/db.h" #include "extension/effect.h" +#include "extension/input.h" #include "widgets/icon.h" #include "preferences.h" #include "path-prefix.h" @@ -1454,48 +1456,26 @@ sp_ui_drag_data_received(GtkWidget *widget, case PNG_DATA: case JPEG_DATA: case IMAGE_DATA: { - Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc); - Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image"); - gchar *atom_name = gdk_atom_name(data->type); - - // this formula taken from Glib docs - guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7; - needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64, - - gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer; - buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name); - - gint state = 0, save = 0; - g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save); - g_base64_encode_close(TRUE, buf_work, &state, &save); - - newImage->setAttribute("xlink:href", buffer); - g_free(buffer); - - GError *error = NULL; - GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error ); - if ( loader ) { - error = NULL; - if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) { - GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader); - if ( pbuf ) { - char tmp[1024]; - int width = gdk_pixbuf_get_width(pbuf); - int height = gdk_pixbuf_get_height(pbuf); - snprintf( tmp, sizeof(tmp), "%d", width ); - newImage->setAttribute("width", tmp); - - snprintf( tmp, sizeof(tmp), "%d", height ); - newImage->setAttribute("height", tmp); - } - } - } - g_free(atom_name); + const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png"); - // Add it to the current layer - desktop->currentLayer()->appendChildRepr(newImage); - - Inkscape::GC::release(newImage); + Inkscape::Extension::DB::InputList o; + Inkscape::Extension::db.get_input_list(o); + Inkscape::Extension::DB::InputList::const_iterator i = o.begin(); + while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) { + ++i; + } + Inkscape::Extension::Extension *ext = *i; + bool save = ext->get_param_bool("link"); + ext->set_param_bool("link", false); + ext->set_gui(false); + + gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL ); + g_file_set_contents(filename, reinterpret_cast(data->data), data->length, NULL); + file_import(doc, filename, ext); + g_free(filename); + + ext->set_param_bool("link", save); + ext->set_gui(true); sp_document_done( doc , SP_VERB_NONE, _("Drop bitmap image")); break; diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index f213160f4..99d835730 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -873,26 +873,25 @@ bool ClipboardManagerImpl::_pasteImage() Glib::RefPtr img = _clipboard->wait_for_image(); if (!img) return false; - // Very stupid hack: Write into a file, then import the file into the document. - // To avoid using tmpfile and POSIX file handles, make the filename based on current time. - // This wasn't my idea, I just copied this from selection-chemistry.cpp - // and just can't think of something saner at the moment. Pasting more than - // one image per second will overwrite the image. - // However, I don't think anyone is able to copy a _different_ image into inkscape - // in 1 second. - time_t rawtime; - char image_filename[128]; - - time(&rawtime); - strftime(image_filename, 128, "inkscape_pasted_image_%Y%m%d_%H%M%S.png", localtime( &rawtime )); - /// @todo Check whether the encoding is correct here - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - std::string save_folder = Glib::filename_from_utf8(prefs->getString("/dialogs/save_as/path")); - - gchar *image_path = g_build_filename(save_folder.data(), image_filename, NULL); - img->save(image_path, "png"); - file_import(doc, image_path, NULL); - g_free(image_path); + // AARGH stupid + Inkscape::Extension::DB::InputList o; + Inkscape::Extension::db.get_input_list(o); + Inkscape::Extension::DB::InputList::const_iterator i = o.begin(); + while (i != o.end() && strcmp( (*i)->get_mimetype(), "image/png" ) != 0) { + ++i; + } + Inkscape::Extension::Extension *png = *i; + bool save = png->get_param_bool("link"); + png->set_param_bool("link", false); + png->set_gui(false); + + gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL ); + img->save(filename, "png"); + file_import(doc, filename, png); + g_free(filename); + + png->set_param_bool("link", save); + png->set_gui(true); return true; } -- 2.30.2