summaryrefslogtreecommitdiffstats
path: root/src/ui/widget/image-properties.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/widget/image-properties.cpp')
-rw-r--r--src/ui/widget/image-properties.cpp295
1 files changed, 295 insertions, 0 deletions
diff --git a/src/ui/widget/image-properties.cpp b/src/ui/widget/image-properties.cpp
new file mode 100644
index 0000000..e661dde
--- /dev/null
+++ b/src/ui/widget/image-properties.cpp
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * Image properties widget for "Fill and Stroke" dialog
+ *
+ * Copyright (C) 2023 Michael Kowalski
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "image-properties.h"
+#include <array>
+#include <glib/gi18n.h>
+#include <glibmm/convert.h>
+#include <glibmm/markup.h>
+#include <glibmm/ustring.h>
+#include <gtkmm/button.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/drawingarea.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/enums.h>
+#include <gtkmm/grid.h>
+#include <gtkmm/label.h>
+#include <gtkmm/radiobutton.h>
+#include <gtkmm/window.h>
+#include <sstream>
+#include <string>
+#include "display/cairo-utils.h"
+#include "document-undo.h"
+#include "enums.h"
+#include "helper/choose-file.h"
+#include "helper/save-image.h"
+#include "object/sp-image.h"
+#include "ui/builder-utils.h"
+#include "ui/icon-names.h"
+#include "ui/util.h"
+#include "util/format_size.h"
+#include "util/object-renderer.h"
+#include "xml/href-attribute-helper.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Widget {
+
+Cairo::RefPtr<Cairo::Surface> draw_preview(SPImage* image, double width, double height, int device_scale, uint32_t frame_color, uint32_t background) {
+ if (!image || !image->pixbuf) return Cairo::RefPtr<Cairo::Surface>();
+
+ object_renderer r;
+ object_renderer::options opt;
+ opt.frame(frame_color);
+ auto s = image->style;
+ // here for preview purposes using image's own opacity only
+ double alpha = s && s->opacity.set && !s->opacity.inherit ? SP_SCALE24_TO_FLOAT(s->opacity.value) : 1.0;
+ opt.image_opacity(alpha);
+ opt.checkerboard(background);
+ return r.render(*image, width, height, device_scale, opt);
+}
+
+void link_image(Gtk::Window* window, SPImage* image) {
+ if (!window || !image) return;
+
+ static std::string current_folder;
+ std::vector<Glib::ustring> mime_types = {
+ "image/png", "image/jpeg", "image/gif", "image/bmp", "image/tiff"
+ };
+ auto file = choose_file_open(_("Change Image"), window, mime_types, current_folder);
+ if (file.empty()) return;
+
+ // link image now
+ // todo: set/calc dpi?
+ // todo: set color profile?
+ try {
+ // convert filename to uri
+ auto uri = Glib::filename_to_uri(file);
+ setHrefAttribute(*image->getRepr(), uri);
+ }
+ catch (Glib::ConvertError const &e) {
+ g_warning("Error converting path to URI: %s", e.what().c_str());
+ setHrefAttribute(*image->getRepr(), file);
+ }
+ // SPImage modifies size when href changes; trigger it now before undo concludes
+ // TODO: this needs to be fixed in SPImage
+ image->document->_updateDocument(0);
+ DocumentUndo::done(image->document, _("Change image"), INKSCAPE_ICON("shape-image"));
+}
+
+void set_rendering_mode(SPImage* image, int index) {
+ static const std::array<const char*, 5> render = {
+ "auto", "optimizeSpeed", "optimizeQuality", "crisp-edges", "pixelated"
+ }; // SPImageRendering values
+
+ if (!image || index < 0 || index >= render.size()) return;
+
+ auto css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, "image-rendering", render[index]);
+ if (auto image_node = image->getRepr()) {
+ sp_repr_css_change(image_node, css, "style");
+ DocumentUndo::done(image->document, _("Set image rendering option"), INKSCAPE_ICON("shape-image"));
+ }
+ sp_repr_css_attr_unref(css);
+}
+
+void set_aspect_ratio(SPImage* image, bool preserve_aspect_ratio) {
+ if (!image) return;
+ image->setAttribute("preserveAspectRatio", preserve_aspect_ratio ? "xMidYMid" : "none");
+ DocumentUndo::done(image->document, _("Preserve image aspect ratio"), INKSCAPE_ICON("shape-image"));
+}
+
+ImageProperties::ImageProperties() :
+ Gtk::Box(Gtk::ORIENTATION_HORIZONTAL),
+ _builder(create_builder("image-properties.glade")),
+ _preview(get_widget<Gtk::DrawingArea>(_builder, "preview")),
+ _aspect(get_widget<Gtk::RadioButton>(_builder, "preserve")),
+ _stretch(get_widget<Gtk::RadioButton>(_builder, "stretch")),
+ _rendering(get_widget<Gtk::ComboBoxText>(_builder, "rendering")),
+ _embed(get_widget<Gtk::Button>(_builder, "embed"))
+{
+
+ auto& main = get_widget<Gtk::Grid>(_builder, "main");
+ pack_start(main, true, true);
+
+ // arbitrarily selected max preview size for image content:
+ _preview_max_width = 120;
+ _preview_max_height = 90;
+
+ _preview.signal_draw().connect([=](const Cairo::RefPtr<Cairo::Context>& ctx){
+ if (_preview_image) {
+ ctx->set_source(_preview_image, 0, 0);
+ ctx->paint();
+ }
+ return true;
+ });
+
+ auto& change = get_widget<Gtk::Button>(_builder, "change-img");
+ change.signal_clicked().connect([=](){
+ if (_update.pending()) return;
+ auto window = dynamic_cast<Gtk::Window*>(get_toplevel());
+ link_image(window, _image);
+ });
+
+ auto& extract = get_widget<Gtk::Button>(_builder, "export");
+ extract.signal_clicked().connect([=](){
+ if (_update.pending()) return;
+ auto window = dynamic_cast<Gtk::Window*>(get_toplevel());
+ extract_image(window, _image);
+ });
+
+ _embed.signal_clicked().connect([=](){
+ if (_update.pending() || !_image) return;
+ // embed image in the current document
+ Inkscape::Pixbuf copy(*_image->pixbuf);
+ sp_embed_image(_image->getRepr(), &copy);
+ DocumentUndo::done(_image->document, _("Embed image"), INKSCAPE_ICON("selection-make-bitmap-copy"));
+ });
+
+ _rendering.signal_changed().connect([=](){
+ if (_update.pending()) return;
+ auto index = _rendering.get_active_row_number();
+ set_rendering_mode(_image, index);
+ });
+
+ _aspect.signal_toggled().connect([=](){
+ if (_update.pending()) return;
+ set_aspect_ratio(_image, _aspect.get_active());
+ });
+ _stretch.signal_toggled().connect([=](){
+ if (_update.pending()) return;
+ set_aspect_ratio(_image, !_stretch.get_active());
+ });
+}
+
+void ImageProperties::update(SPImage* image) {
+ if (!image && !_image) return; // nothing to do
+
+ _image = image;
+
+ auto scoped(_update.block());
+
+ auto small = [](const char* str) { return "<small>" + Glib::Markup::escape_text(str ? str : "") + "</small>"; };
+ auto& name = get_widget<Gtk::Label>(_builder, "name");
+ auto& info = get_widget<Gtk::Label>(_builder, "info");
+ auto& url = get_widget<Gtk::Entry>(_builder, "href");
+
+ if (!image) {
+ name.set_markup(small("-"));
+ info.set_markup(small("-"));
+ }
+ else {
+ Glib::ustring id(image->getId() ? image->getId() : "");
+ name.set_markup(small(id.empty() ? "-" : ("#" + id).c_str()));
+
+ bool embedded = false;
+ bool linked = false;
+ auto href = Inkscape::getHrefAttribute(*image->getRepr()).second;
+ if (href && std::strncmp(href, "data:", 5) == 0) {
+ embedded = true;
+ }
+ else if (href && *href) {
+ linked = true;
+ }
+
+ if (image->pixbuf) {
+ std::ostringstream ost;
+ if (!image->missing) {
+ auto times = "\u00d7"; // multiplication sign
+ // dimensions
+ ost << image->pixbuf->width() << times << image->pixbuf->height() << " px\n";
+
+ if (embedded) {
+ ost << _("Embedded");
+ ost << " (" << Util::format_file_size(std::strlen(href)) << ")\n";
+ }
+ if (linked) {
+ ost << _("Linked");
+ ost << '\n';
+ }
+ // color space
+ if (image->color_profile && *image->color_profile) {
+ ost << _("Color profile:") << ' ' << image->color_profile << '\n';
+ }
+ }
+ else {
+ ost << _("Missing image") << '\n';
+ }
+ info.set_markup(small(ost.str().c_str()));
+ }
+ else {
+ info.set_markup(small("-"));
+ }
+
+ url.set_text(linked ? href : "");
+ url.set_sensitive(linked);
+ _embed.set_sensitive(linked && image->pixbuf);
+
+ // aspect ratio
+ bool aspect_none = false;
+ if (image->aspect_set) {
+ aspect_none = image->aspect_align == SP_ASPECT_NONE;
+ }
+ if (aspect_none) {
+ _stretch.set_active();
+ }
+ else {
+ _aspect.set_active();
+ }
+
+ // rendering
+ _rendering.set_active(image->style ? image->style->image_rendering.value : -1);
+ }
+
+ int width = _preview_max_width;
+ int height = _preview_max_height;
+ if (image && image->pixbuf) {
+ double sw = image->pixbuf->width();
+ double sh = image->pixbuf->height();
+ double sx = sw / width;
+ double sy = sh / height;
+ auto scale = 1.0 / std::max(sx, sy);
+ width = std::max(1, int(sw * scale + 0.5));
+ height = std::max(1, int(sh * scale + 0.5));
+ }
+ // expand size to account for a frame around the image
+ int frame = 2;
+ width += frame;
+ height += frame;
+ _preview.set_size_request(width, height);
+ _preview.queue_draw();
+
+ // prepare preview
+ auto device_scale = get_scale_factor();
+ auto context = get_style_context();
+ Gdk::RGBA fg = context->get_color(Gtk::STATE_FLAG_NORMAL);
+ auto foreground = conv_gdk_color_to_rgba(fg, 0.30);
+ if (!_background_color) {
+ update_bg_color();
+ }
+ _preview_image = draw_preview(_image, width, height, device_scale, foreground, _background_color);
+}
+
+void ImageProperties::update_bg_color() {
+ if (auto wnd = dynamic_cast<Gtk::Window*>(get_toplevel())) {
+ auto sc = wnd->get_style_context();
+ auto color = get_background_color(sc);
+ _background_color = conv_gdk_color_to_rgba(color);
+ }
+ else {
+ _background_color = 0x808080ff;
+ }
+}
+
+void ImageProperties::on_style_updated() {
+ update_bg_color();
+ update(_image);
+}
+
+}}} // namespaces