Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
actions-element-image.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Gio::Actions for use with <image>.
4 *
5 * Copyright (C) 2022 Tavmjong Bah
6 *
7 * The contents of this file may be used under the GNU General Public License Version 2 or later.
8 *
9 */
10
12#include "actions-helper.h"
13
14#include <giomm.h> // Not <gtkmm.h>! To eventually allow a headless version!
15#include <glibmm/i18n.h>
16#include <gtkmm.h> // OK, we lied. We pop-up an message dialog if external editor not found and if we have a GUI.
17
18#include "desktop.h"
19#include "document.h"
20#include "document-undo.h"
22#include "inkscape-window.h"
23#include "object/sp-clippath.h"
24#include "object/sp-image.h"
25#include "object/sp-rect.h"
26#include "object/sp-use.h"
27#include "object/uri.h"
28#include "preferences.h"
29#include "selection.h" // Selection
30#include "ui/dialog-run.h"
32#include "util/format_size.h"
34
35namespace {
36Glib::ustring image_get_editor_name(bool is_svg)
37{
39 if (is_svg) {
40 return prefs->getString("/options/svgeditor/value", "inkscape");
41 }
42 return prefs->getString("/options/bitmapeditor/value", "gimp");
43}
44
45Inkscape::URI get_base_path_uri(SPDocument const &document)
46{
47 if (const char *document_base = document.getDocumentBase()) {
48 return Inkscape::URI::from_dirname(document_base);
49 }
50 return Inkscape::URI::from_dirname(Glib::get_current_dir().c_str());
51}
52
53bool has_svg_extension(std::string const &filename)
54{
55 return Glib::str_has_suffix(filename, ".svg") || Glib::str_has_suffix(filename, ".SVG");
56}
57} // namespace
58
59// Note that edits are external to Inkscape and thus we cannot undo them!
61{
62 auto selection = app->get_active_selection();
63 if (selection->isEmpty()) {
64 // Nothing to do.
65 return;
66 }
67
68 for (auto item : selection->items()) {
69 // In the case of a clone of an image, edit the original image.
70 if (auto const *clone = cast<SPUse>(item)) {
71 item = clone->trueOriginal();
72 }
73 if (!is<SPImage>(item)) {
74 continue;
75 }
76
77 const char *href = Inkscape::getHrefAttribute(*item->getRepr()).second;
78 if (!href) {
79 show_output("image_edit: no xlink:href");
80 continue;
81 }
82
83 auto const uri = Inkscape::URI(href, get_base_path_uri(*selection->document()));
84 if (uri.hasScheme("data")) {
85 // data URL scheme, see https://www.ietf.org/rfc/rfc2397.txt
86 g_warning("Edit Externally: Editing embedded images (data URL) is not supported");
87 continue;
88 }
89 if (const char *other_scheme = uri.getScheme(); other_scheme && !uri.hasScheme("file")) {
90 // any other scheme than 'file'
91 g_warning("Edit Externally: Cannot edit image (scheme '%s' not supported)", other_scheme);
92 continue;
93 }
94
95 std::string const filename = uri.toNativeFilename();
96 std::string const command = Glib::shell_quote(image_get_editor_name(has_svg_extension(filename))) + " " +
97 Glib::shell_quote(filename);
98
99 const char *const message = _("Failed to edit external image.\n<small>Note: Path to editor can be set in "
100 "Preferences dialog.</small>");
101 try {
102 Glib::spawn_command_line_async(command);
103 } catch (Glib::SpawnError &error) {
104 if (auto window = app->get_active_window()) {
105 auto dialog = std::make_unique<Gtk::MessageDialog>(*window, message, true, Gtk::MessageType::WARNING, Gtk::ButtonsType::OK);
106 dialog->property_destroy_with_parent() = true;
107 dialog->set_name("SetEditorDialog");
108 dialog->set_title(_("External Edit Image:"));
109 dialog->set_secondary_text(
110 Glib::ustring::compose(_("System error message: %1"), error.what()));
112 } else {
113 show_output(Glib::ustring("image_edit: ") + message);
114 }
115 } catch (Glib::ShellError &error) {
116 g_error("Edit Externally: %s\n%s %s", message, _("System error message:"), error.what());
117 }
118 }
119}
120
126{
127 auto win = app->get_active_window();
128 auto doc = app->get_active_document();
129 auto msg = win->get_desktop()->messageStack();
130 auto const tool = win->get_desktop()->getTool();
131 int done = 0;
132 int bytes = 0;
133
134 auto selection = app->get_active_selection();
135 if (selection->isEmpty()) {
136 msg->flash(Inkscape::ERROR_MESSAGE, _("Nothing selected."));
137 return;
138 }
139
140 // Find a target rectangle, if provided.
141 Geom::OptRect target;
142 SPRect *rect = nullptr;
143 for (auto item : selection->items()) {
144 rect = cast<SPRect>(item);
145 if (rect) {
146 target = rect->geometricBounds(rect->i2doc_affine());
147 break;
148 }
149 }
150
151 // For each selected item, we loop through and attempt to crop the
152 // raster image to the geometric bounds of the clipping object.
153 for (auto item : selection->items()) {
154 if (auto image = cast<SPImage>(item)) {
155 bytes -= std::strlen(image->href);
156 Geom::OptRect area;
157 if (target) {
158 // MODE A. Crop to selected rectangle.
159 area = target;
160 } else if (auto clip = image->getClipObject()) {
161 // MODE B. Crop to image's xisting clip region
162 area = clip->geometricBounds(image->i2doc_affine());
163 }
164 done += (int)(area && image->cropToArea(*area));
165 bytes += std::strlen(image->href);
166 }
167 }
168 if (rect) {
169 rect->deleteObject();
170 }
171
172 // Tell the user what happened, since so many things could have changed.
173 if (done) {
174 // The select tool has no idea the image description needs updating. Force it.
175 if (auto selector = dynamic_cast<Inkscape::UI::Tools::SelectTool*>(tool)) {
176 selector->updateDescriber(selection);
177 }
178 std::stringstream ss;
179 ss << ngettext("<b>%d</b> image cropped", "<b>%d</b> images cropped", done);
180 if (bytes < 0) {
181 ss << ", " << ngettext("%s byte removed", "%s bytes removed", abs(bytes));
182 } else if (bytes > 0) {
183 ss << ", <b>" << ngettext("%s byte added!", "%s bytes added!", bytes) << "</b>";
184 }
185 // Do flashing after select tool update.
186 msg->flashF(Inkscape::INFORMATION_MESSAGE, ss.str().c_str(), done, Inkscape::Util::format_size(abs(bytes)).c_str());
187 Inkscape::DocumentUndo::done(doc, "ActionImageCrop", "Crop Images");
188 } else {
189 msg->flash(Inkscape::WARNING_MESSAGE, _("No images cropped!"));
190 }
191}
192
193const Glib::ustring SECTION = NC_("Action Section", "Images");
194
195std::vector<std::vector<Glib::ustring>> raw_data_element_image =
196{
197 // clang-format off
198 {"app.element-image-crop", N_("Crop image to clip"), SECTION, N_("Remove parts of the image outside the applied clipping area.") },
199 {"app.element-image-edit", N_("Edit externally"), SECTION, N_("Edit image externally (image must be selected and not embedded).") },
200 // clang-format on
201};
202
203void
205{
206 auto *gapp = app->gio_app();
207
208 // clang-format off
209 gapp->add_action( "element-image-crop", sigc::bind(sigc::ptr_fun(&image_crop), app));
210 gapp->add_action( "element-image-edit", sigc::bind(sigc::ptr_fun(&image_edit), app));
211 // clang-format on
212
214}
215
216
217/*
218 Local Variables:
219 mode:c++
220 c-file-style:"stroustrup"
221 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
222 indent-tabs-mode:nil
223 fill-column:99
224 End:
225*/
226// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
void add_actions_element_image(InkscapeApplication *app)
const Glib::ustring SECTION
void image_edit(InkscapeApplication *app)
void image_crop(InkscapeApplication *app)
Attempt to crop an image's physical pixels by the rectangle give OR if not specified,...
std::vector< std::vector< Glib::ustring > > raw_data_element_image
void show_output(Glib::ustring const &data, bool const is_cerr)
Axis-aligned rectangle that can be empty.
Definition rect.h:203
void add_data(std::vector< std::vector< Glib::ustring > > const &raw_data)
InkActionExtraData & get_action_extra_data()
InkscapeWindow * get_active_window()
Gio::Application * gio_app()
The Gio application instance, never NULL.
SPDocument * get_active_document()
Inkscape::Selection * get_active_selection()
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
Preference storage class.
Definition preferences.h:61
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
Represents an URI as per RFC 2396.
Definition uri.h:36
static URI from_dirname(char const *path)
URI of a local directory.
Definition uri.cpp:197
Typed SVG document implementation.
Definition document.h:101
char const * getDocumentBase() const
Definition document.h:233
Geom::OptRect geometricBounds(Geom::Affine const &transform=Geom::identity()) const
Get item's geometric bounding box in this item's coordinate system.
Definition sp-item.cpp:927
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1832
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
Glib::ustring msg
Editable view implementation.
TODO: insert short description here.
std::unique_ptr< Magick::Image > image
SPItem * item
Inkscape - An SVG editor.
void dialog_show_modal_and_selfdestruct(std::unique_ptr< Gtk::Dialog > dialog, Gtk::Root *root)
Show a dialog modally, destroying it when the user dismisses it.
Glib::ustring format_size(std::size_t value)
std::pair< char const *, char const * > getHrefAttribute(XML::Node const &node)
Get the 'href' or 'xlink:href' (fallback) attribute from an XML node.
@ INFORMATION_MESSAGE
Definition message.h:30
@ ERROR_MESSAGE
Definition message.h:29
@ WARNING_MESSAGE
Definition message.h:28
static T clip(T const &v, T const &a, T const &b)
Singleton class to access the preferences file in a convenient way.
SVG <image> implementation.