You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Ruby Gtk实现图片缩放与拖拽选框功能?

Hey there! Great news—all the features you're asking for (zoom, drag scrolling, and drag selection) are absolutely achievable with Ruby-GTK. The trick is to use a custom Gtk::DrawingArea where you can handle low-level drawing and mouse events yourself.

Below is a complete, working example that implements everything you described, plus some extra quality-of-life features. I'll break down how each part works so you can adapt it to your existing code.

Ruby-GTK Image Viewer with Zoom, Drag Scrolling, and Selection

Full Working Example

require 'gtk3'

class ImageViewer < Gtk::DrawingArea
  def initialize(image_path)
    super()
    @image = Gdk::Pixbuf.new(file: image_path)
    @scale = 1.0
    @offset_x = 0.0
    @offset_y = 0.0
    @dragging = false
    @drag_start_x = 0
    @drag_start_y = 0
    @selection_start = nil
    @selection_end = nil
    @selecting = false

    # Enable required mouse events
    set_events(Gdk::EventMask::BUTTON_PRESS_MASK |
               Gdk::EventMask::BUTTON_RELEASE_MASK |
               Gdk::EventMask::POINTER_MOTION_MASK |
               Gdk::EventMask::SCROLL_MASK)

    # Connect event handlers
    signal_connect('button-press-event') { |_, event| on_button_press(event) }
    signal_connect('button-release-event') { |_, event| on_button_release(event) }
    signal_connect('motion-notify-event') { |_, event| on_motion(event) }
    signal_connect('scroll-event') { |_, event| on_scroll(event) }
    signal_connect('draw') { |_, cr| on_draw(cr) }
  end

  # Handle mouse button presses
  def on_button_press(event)
    if event.button == 1 # Left click: start selection
      @selecting = true
      @selection_start = [event.x, event.y]
      @selection_end = nil
    elsif event.button == 2 # Middle click: start panning
      @dragging = true
      @drag_start_x = event.x
      @drag_start_y = event.y
    end
    true # Indicate we handled the event
  end

  # Handle mouse button releases
  def on_button_release(event)
    if event.button == 1 && @selecting
      @selecting = false
      # Convert selection coordinates to original image space
      if @selection_end
        x1, y1 = @selection_start
        x2, y2 = @selection_end
        img_x1 = (x1 - @offset_x)/@scale
        img_y1 = (y1 - @offset_y)/@scale
        img_x2 = (x2 - @offset_x)/@scale
        img_y2 = (y2 - @offset_y)/@scale
        puts "Selected area: (#{img_x1.round}, #{img_y1.round}) to (#{img_x2.round}, #{img_y2.round})"
      end
    elsif event.button == 2 && @dragging
      @dragging = false
    end
    queue_draw # Refresh the view
    true
  end

  # Handle mouse motion
  def on_motion(event)
    if @dragging
      # Update pan offset based on mouse movement
      delta_x = event.x - @drag_start_x
      delta_y = event.y - @drag_start_y
      @offset_x += delta_x
      @offset_y += delta_y
      @drag_start_x = event.x
      @drag_start_y = event.y
      queue_draw
    elsif @selecting
      @selection_end = [event.x, event.y]
      queue_draw
    end
    true
  end

  # Handle scroll wheel for zoom
  def on_scroll(event)
    # Zoom in/out around the mouse cursor
    zoom_factor = event.direction == Gdk::ScrollDirection::UP ? 1.1 : 0.9
    mouse_x = event.x
    mouse_y = event.y

    # Calculate image position under mouse before zoom
    img_x = (mouse_x - @offset_x) / @scale
    img_y = (mouse_y - @offset_y) / @scale

    # Update scale
    @scale *= zoom_factor

    # Adjust offset to keep mouse over the same image point
    @offset_x = mouse_x - (img_x * @scale)
    @offset_y = mouse_y - (img_y * @scale)

    queue_draw
    true
  end

  # Custom drawing logic
  def on_draw(cr)
    # Clear background
    cr.set_source_rgb(1, 1, 1)
    cr.paint

    # Draw scaled image at current offset
    cr.save
    cr.translate(@offset_x, @offset_y)
    cr.scale(@scale, @scale)
    Gdk::Cairo.set_source_pixbuf(cr, @image, 0, 0)
    cr.paint
    cr.restore

    # Draw selection rectangle if active
    if @selecting && @selection_end
      x1, y1 = @selection_start
      x2, y2 = @selection_end
      # Semi-transparent blue fill
      cr.set_source_rgba(0, 0, 1, 0.3)
      cr.rectangle([x1, x2].min, [y1, y2].min, (x2 - x1).abs, (y2 - y1).abs)
      cr.fill
      # Solid blue border
      cr.set_source_rgb(0, 0, 1)
      cr.rectangle([x1, x2].min, [y1, y2].min, (x2 - x1).abs, (y2 - y1).abs)
      cr.stroke
    end

    true
  end

  # Reset view to centered, 100% scale
  def reset_view
    @scale = 1.0
    @offset_x = (allocated_width - @image.width)/2.0
    @offset_y = (allocated_height - @image.height)/2.0
    queue_draw
  end
end

# Main application setup
if __FILE__ == $0
  Gtk.init

  window = Gtk::Window.new(title: 'Custom Image Viewer')
  window.set_default_size(800, 600)
  window.signal_connect('destroy') { Gtk.main_quit }

  # Replace with your image path
  viewer = ImageViewer.new('path/to/your/image.png')
  
  # Add reset button
  reset_btn = Gtk::Button.new(label: 'Reset View')
  reset_btn.signal_connect('clicked') { viewer.reset_view }

  # Layout setup
  vbox = Gtk::Box.new(:vertical, 5)
  vbox.pack_start(viewer, expand: true, fill: true, padding: 0)
  vbox.pack_start(reset_btn, expand: false, fill: true, padding: 5)

  window.add(vbox)
  window.show_all

  # Reset view when window is resized
  window.signal_connect('configure-event') { viewer.reset_view unless viewer.instance_variable_get(:@dragging) }

  Gtk.main
end

Key Features Breakdown

1. Zoom Functionality

  • Uses the scroll wheel to zoom in/out around the mouse cursor (so the point under your mouse stays in place)
  • Adjusts the @scale variable and recalculates the offset to maintain the correct position
  • Handled in the on_scroll method

2. Drag Scrolling

  • Middle-click and drag to pan the image across the viewport
  • Tracks mouse movement delta during drag to update the image's offset
  • Handled in the mouse press, motion, and release events for the middle button

3. Drag Selection

  • Left-click and drag to draw a semi-transparent blue selection rectangle
  • Converts on-screen coordinates to the original image's coordinate system (accounting for scale and offset) when you release the mouse
  • You can extend this to perform actions like cropping on the selected area

4. Quality-of-Life Extras

  • Reset button to center the image and return to 100% scale
  • Automatic view reset when the window is resized (unless you're actively dragging)

Tips for Extending

  • Add Zoom Buttons: Add "+" and "-" buttons that call a method to adjust @scale and refresh the view
  • Image Refresh: Integrate your existing refresh logic by updating the @image variable and calling queue_draw
  • Crop Functionality: Use the selected coordinates to create a new Gdk::Pixbuf from the original image
  • Smooth Zoom: Add animation for zoom transitions using a timer to incrementally adjust the scale

Notes

  • Make sure to replace 'path/to/your/image.png' with the actual path to your image file
  • This example uses GTK3, so you'll need the ruby-gtk3 gem installed (gem install gtk3)
  • All event handlers return true to indicate we've handled the event (prevents default GTK behavior)

Content of the question来源于Stack Exchange, 提问作者ArafatK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:42:10