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

Gtkmm结合Cairo在图片上绘图时如何保留已绘制的各类形状

问题核心原因

你的实现存在两个核心逻辑错误导致绘制内容丢失:

  • 反复对signal_draw()绑定回调:每次点击、拖拽操作都会新增一个绘制回调,回调仅能绘制当前操作的单个形状,且没有存储历史绘制的形状参数,旧回调失效后对应形状自然消失
  • 没有做绘制内容缓存:每次窗口触发重绘时会清空画布,你没有把已完成的绘制内容持久化存储,仅靠临时绑定的回调无法保留历史内容

修复方案

我们通过「存储历史形状参数+统一单绘制回调」的逻辑实现绘制内容永久保留,修改代码如下:

第一步:修改 Drawing.h 新增定义

#ifndef DRAWING_H
#define DRAWING_H

#include <gtkmm.h>
#include <cairo/cairo.h>
#include <vector>

// 新增形状类型枚举
enum ShapeType {
    SHAPE_RECTANGLE,
    SHAPE_CIRCLE
};

// 新增形状参数结构体,存储每个已绘制形状的所有属性
struct Shape {
    ShapeType type;
    double x, y, w, h;
    double line_width = 10;
    double r = 0, g = 0, b = 1, a = 1;
};

class MyDrawing : public Gtk::Layout {
public:
    MyDrawing();
    ~MyDrawing();

    void switch_to_circle();
    void switch_to_rectangle();

protected:
    // 只保留一个统一的绘制回调
    virtual bool on_draw(const Cairo::RefPtr<::Cairo::Context>& cr) override;

private:
    Glib::RefPtr<Gdk::Pixbuf> pix;
    double beginPoint_x, beginPoint_y, endPoint_x, endPoint_y, width, height;
    bool isDrawRectangle;

    // 新增成员
    std::vector<Shape> m_completed_shapes; // 存储所有已完成绘制的形状
    bool m_is_dragging = false; // 标记是否正在拖拽绘制新形状
};

#endif // DRAWING_H

第二步:修改 Drawing.cpp 逻辑

#include <iostream>
#include "MyDrawing.h"
#include <cairomm/context.h>
#include <cairomm/surface.h>
#include <cmath>

MyDrawing::MyDrawing()
        : isDrawRectangle(true),
          width(20),
          height(20) {

    pix = Gdk::Pixbuf::create_from_file("file.svg", 500, 500);
    add_events(Gdk::BUTTON1_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);

    // 只绑定一次统一的绘制回调,不要在其他地方重复绑定
    signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::on_draw));

    // 鼠标按下:记录起点,标记开始拖拽
    signal_button_press_event().connect([&](GdkEventButton *e) {
        beginPoint_x = e->x;
        beginPoint_y = e->y;
        endPoint_x = e->x;
        endPoint_y = e->y;
        m_is_dragging = true;
        queue_draw();
        return true;
    });

    // 鼠标拖拽:更新当前形状参数,触发重绘预览
    signal_motion_notify_event().connect([&](GdkEventMotion *e) {
        if (!m_is_dragging) return true;
        endPoint_x = e->x;
        endPoint_y = e->y;
        width = endPoint_x - beginPoint_x;
        height = endPoint_y - beginPoint_y;
        queue_draw();
        return true;
    });

    // 鼠标释放:保存当前形状到历史列表,结束拖拽
    signal_button_release_event().connect([&](GdkEventButton* e) {
        if (!m_is_dragging) return true;
        m_is_dragging = false;
        // 保存当前完成的形状
        Shape new_shape;
        new_shape.type = isDrawRectangle ? SHAPE_RECTANGLE : SHAPE_CIRCLE;
        new_shape.x = beginPoint_x;
        new_shape.y = beginPoint_y;
        new_shape.w = width;
        new_shape.h = height;
        m_completed_shapes.push_back(new_shape);
        queue_draw();
        return true;
    });
}

MyDrawing::~MyDrawing() = default;

// 统一绘制回调,所有绘制逻辑都在这里处理
bool MyDrawing::on_draw(const Cairo::RefPtr<::Cairo::Context>& cr) {
    // 1. 先绘制背景图片
    if (pix) {
        cr->save();
        Gdk::Cairo::set_source_pixbuf(cr, pix, 100, 100);
        cr->rectangle(0, 0, get_width(), get_height());
        cr->fill();
        cr->restore();
    }

    // 2. 绘制所有已完成的历史形状
    for (const auto& shape : m_completed_shapes) {
        cr->save();
        cr->set_line_width(shape.line_width);
        cr->set_source_rgba(shape.r, shape.g, shape.b, shape.a);
        if (shape.type == SHAPE_RECTANGLE) {
            cr->rectangle(shape.x, shape.y, shape.w, shape.h);
        } else {
            double radius = std::max(std::abs(shape.w), std::abs(shape.h));
            cr->arc(shape.x, shape.y, radius, 0, 2 * M_PI);
        }
        cr->stroke();
        cr->restore();
    }

    // 3. 绘制当前正在拖拽的预览形状
    if (m_is_dragging) {
        cr->save();
        cr->set_line_width(10);
        cr->set_source_rgba(0, 0, 1, 0.8); // 预览状态可以设置半透明效果
        if (isDrawRectangle) {
            cr->rectangle(beginPoint_x, beginPoint_y, width, height);
        } else {
            double radius = std::max(std::abs(width), std::abs(height));
            cr->arc(beginPoint_x, beginPoint_y, radius, 0, 2 * M_PI);
        }
        cr->stroke();
        cr->restore();
    }

    return false;
}

void MyDrawing::switch_to_circle() {
    isDrawRectangle = false;
}

void MyDrawing::switch_to_rectangle() {
    isDrawRectangle = true;
}

性能优化说明

如果后续绘制的形状数量非常多,每次重绘遍历所有形状会有性能损耗,可以新增离屏Cairo表面,把已完成的形状预先绘制到离屏表面缓存,每次重绘直接贴离屏表面的内容即可,不需要再遍历绘制所有历史形状。

内容的提问来源于stack exchange,提问作者mohamadp91

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.10 06:51:00