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
相关产品推荐
相关产品推荐

