diff --git a/rtgui/inspector.cc b/rtgui/inspector.cc index 4624bab50..471f08e95 100644 --- a/rtgui/inspector.cc +++ b/rtgui/inspector.cc @@ -82,7 +82,7 @@ InspectorBuffer::~InspectorBuffer() { // return deg; //} -Inspector::Inspector () : currImage(nullptr), scaled(false), active(false), pinned(false) +Inspector::Inspector () : currImage(nullptr), scaled(false), scale(1.0), zoomScale(1.0), zoomScaleBegin(1.0), active(false), pinned(false), dirty(false) { set_name("Inspector"); window.set_visible(false); @@ -93,6 +93,9 @@ Inspector::Inspector () : currImage(nullptr), scaled(false), active(false), pinn window.signal_key_press_event().connect(sigc::mem_fun(*this, &Inspector::on_key_press)); add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK); + gestureZoom = Gtk::GestureZoom::create(*this); + gestureZoom->signal_begin().connect(sigc::mem_fun(*this, &Inspector::on_zoom_begin)); + gestureZoom->signal_scale_changed().connect(sigc::mem_fun(*this, &Inspector::on_zoom_scale_changed)); window.add(*this); window.show_all(); @@ -119,6 +122,7 @@ bool Inspector::on_key_release(GdkEventKey *event) switch (event->keyval) { case GDK_KEY_f: case GDK_KEY_F: + zoomScale = 1.0; window.set_visible(false); return true; } @@ -131,14 +135,19 @@ bool Inspector::on_key_press(GdkEventKey *event) switch (event->keyval) { case GDK_KEY_z: case GDK_KEY_F: + if (pinned || scaled) + zoomScale = 1.0; // reset if not key hold scaled = false; queue_draw(); return true; case GDK_KEY_f: + if (pinned || !scaled) + zoomScale = 1.0; // reset if not key hold scaled = true; queue_draw(); return true; case GDK_KEY_Escape: + zoomScale = 1.0; window.set_visible(false); return true; } @@ -162,12 +171,10 @@ bool Inspector::on_scroll_event(GdkEventScroll *event) if (!currImage) return false; - rtengine::Coord margin; // limit for scroll area + bool alt = event->state & GDK_MOD1_MASK; int deviceScale = get_scale_factor(); int imW = currImage->imgBuffer.getWidth(); int imH = currImage->imgBuffer.getHeight(); - margin.x = (window.get_width() * deviceScale) / 2; - margin.y = (window.get_height() * deviceScale) / 2; #ifdef GDK_WINDOWING_QUARTZ // event reports speed of scroll wheel @@ -206,15 +213,66 @@ bool Inspector::on_scroll_event(GdkEventScroll *event) break; } + if (alt) { + // zoom + beginZoom(event->x, event->y); + if (std::fabs(delta_y) > std::fabs(delta_x)) + on_zoom_scale_changed(1.0 + (double)delta_y / imH / deviceScale); + else + on_zoom_scale_changed(1.0 + (double)delta_x / imW / deviceScale); + return true; + } + + // scroll + rtengine::Coord margin; // limit for scroll area + margin.x = rtengine::min(window.get_width() * deviceScale / scale, imW) / 2; + margin.y = rtengine::min(window.get_height() * deviceScale / scale, imH) / 2; center.set(rtengine::LIM(center.x + delta_x, margin.x, imW - margin.x), rtengine::LIM(center.y + delta_y, margin.y, imH - margin.y)); - queue_draw(); + + if (!dirty) { + dirty = true; + queue_draw(); + } return true; } +void Inspector::beginZoom(double x, double y) +{ + int deviceScale = get_scale_factor(); + dcenterBegin.x = (x - window.get_width()/2) / scale * deviceScale; + dcenterBegin.y = (y - window.get_height()/2) / scale * deviceScale; + centerBegin = center; + zoomScaleBegin = zoomScale; +} + +void Inspector::on_zoom_begin(GdkEventSequence *s) +{ + double x, y; + if (gestureZoom->get_point(s, x, y)) + beginZoom(x, y); +} + +void Inspector::on_zoom_scale_changed(double zscale) +{ + if (!currImage) + return; + + zoomScale = rtengine::LIM(zoomScaleBegin * zscale, 0.01, 16.0); + double dcenterRatio = 1.0 - zoomScaleBegin / zoomScale; + center.x = centerBegin.x + dcenterBegin.x * dcenterRatio; + center.y = centerBegin.y + dcenterBegin.y * dcenterRatio; + + if (!dirty) { + dirty = true; + queue_draw(); + } +} + bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) { + dirty = false; Glib::RefPtr win = get_window(); @@ -240,15 +298,21 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) int deviceScale = get_scale_factor(); availableSize.x = win->get_width() * deviceScale; availableSize.y = win->get_height() * deviceScale; - int imW = currImage->imgBuffer.getWidth(); - int imH = currImage->imgBuffer.getHeight(); - double scale = 1.0; + int imW = rtengine::max(currImage->imgBuffer.getWidth(), 1); + int imH = rtengine::max(currImage->imgBuffer.getHeight(), 1); + scale = rtengine::min((double)availableSize.x / imW, (double)availableSize.y / imH); if (scaled) { - // reduce size of image to fit into window - scale = rtengine::min(1.0, rtengine::min((double)availableSize.x/imW, (double)availableSize.y/imH)); - availableSize.x /= scale; - availableSize.y /= scale; + // reduce size of image to fit into window, no further zoom down + zoomScale = rtengine::max(zoomScale, 1.0); + scale *= zoomScale; } + else { + // limit zoom to fill at least complete window or 1:1 + zoomScale = rtengine::max(zoomScale, rtengine::min(1.0, scale)); + scale = zoomScale; + } + availableSize.x /= scale; + availableSize.y /= scale; if (imW < availableSize.x) { // center the image in the available space along X @@ -275,7 +339,6 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) topLeft.y -= availableSize.y; topLeft.y = rtengine::max(topLeft.y, 0); } - //printf("center: %d, %d (img: %d, %d) (availableSize: %d, %d) (topLeft: %d, %d)\n", center.x, center.y, imW, imH, availableSize.x, availableSize.y, topLeft.x, topLeft.y); // define the destination area @@ -302,7 +365,7 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) cr->fill (); //*/ - bool scaledImage = scaled && (imW > win->get_width() || imH > win->get_height()); + bool scaledImage = scale != 1.0; if (deviceScale == 1 && !scaledImage) { // standard drawing currImage->imgBuffer.copySurface(win); @@ -314,13 +377,15 @@ bool Inspector::on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) cairo_surface_set_device_scale(cr->get_target()->cobj(), scale, scale); scaledImage = false; } - Glib::RefPtr crop = Gdk::Pixbuf::create(currImage->imgBuffer.getSurface(), topLeft.x, topLeft.y, rtengine::min(imW, availableSize.x), rtengine::min(imH, availableSize.y)); + int viewW = rtengine::min(imW, availableSize.x); + int viewH = rtengine::min(imH, availableSize.y); + Glib::RefPtr crop = Gdk::Pixbuf::create(currImage->imgBuffer.getSurface(), topLeft.x, topLeft.y, viewW, viewH); if (!scaledImage) { Gdk::Cairo::set_source_pixbuf(cr, crop, dest.x, dest.y); } else { - // assume that the device does not support scaling (Linux) - crop = crop->scale_simple(imW*scale, imH*scale, Gdk::INTERP_BILINEAR); + // scale crop as the device does not seem to support it (Linux) + crop = crop->scale_simple(viewW*scale, viewH*scale, Gdk::INTERP_BILINEAR); Gdk::Cairo::set_source_pixbuf(cr, crop, dest.x*scale, dest.y*scale); } cr->paint(); diff --git a/rtgui/inspector.h b/rtgui/inspector.h index 5262ccc8f..18e285b39 100644 --- a/rtgui/inspector.h +++ b/rtgui/inspector.h @@ -47,9 +47,13 @@ private: rtengine::Coord center; std::vector images; InspectorBuffer* currImage; - bool scaled; + bool scaled; // fit image into window + double scale; // current scale + double zoomScale, zoomScaleBegin; // scale during zoom + rtengine::Coord centerBegin, dcenterBegin; // center during zoom bool active; bool pinned; + bool dirty; sigc::connection delayconn; Glib::ustring next_image_path; @@ -61,6 +65,11 @@ private: bool on_button_press_event(GdkEventButton *event) override; bool on_scroll_event(GdkEventScroll *event) override; + Glib::RefPtr gestureZoom; + void beginZoom(double x, double y); + void on_zoom_begin(GdkEventSequence *); + void on_zoom_scale_changed(double zscale); + bool on_draw(const ::Cairo::RefPtr< Cairo::Context> &cr) override; void deleteBuffers();