有没有办法在开罗保存路径并恢复它?
Posted
技术标签:
【中文标题】有没有办法在开罗保存路径并恢复它?【英文标题】:Is there any way to save the path and restore it in Cairo? 【发布时间】:2020-04-29 09:51:06 【问题描述】:我有两个在 gtkmm 应用程序上绘制信号的图表。
当我必须绘制一个包含许多点(大约 300-350k)和线到以下点的图形时,问题就出现了,因为每次迭代绘制所有点的速度都会大大降低。
bool DrawArea::on_draw(const Cairo::RefPtr<Cairo::Context>& c)
cairo_t* cr = c->cobj();
//xSignal.size() = ySignal.size() = 350000
for (int j = 0; j < xSignal.size() - 1; ++j)
cairo_move_to(cr, xSignal[j], ySignal[j]);
cairo_line_to(cr, xSignal[j + 1], ySignal[j + 1]);
cairo_stroke(cr);
return true;
我知道存在 cairo_stroke_preserve,但我认为这对我无效,因为当我在图表之间切换时,它会消失。
我一直在研究如何在 Cairo 文档中保存路径并恢复它,但我什么也没看到。 2007 年,来自开罗的一位用户在文档中建议“做”同样的事情,但显然还没有做到。
有什么建议吗?
【问题讨论】:
【参考方案1】:您不必在on_draw 中绘制所有内容。我从您的帖子中了解到,您有一个实时波形绘图应用程序,其中可以在固定周期(我假设每隔几毫秒)提供样本。您可以遵循三种方法。
方法 1
当您的内存有限并且不关心在调整窗口大小或未覆盖时保留绘图时,这非常有用。以下可能是接收样本的函数(一个接一个)。 注意:以 m_ 为前缀的变量是类成员。
void DrawingArea::PlotSample(int nSample)
Cairo::RefPtr <Cairo::Context> refCairoContext;
double dNewY;
//Get window's cairo context
refCairoContext = get_window()->create_cairo_context();
//TODO Scale and transform sample to new Y coordinate
dNewY = nSample;
//Clear area for new waveform segment
refCairoContext->rectangle(m_dPreviousX
+ 1,
m_dPreviousY,
ERASER_WIDTH,
get_allocated_height()); //See note below on m_dPreviousX + 1
refCairoContext->set_source_rgb(0,
0,
0);
refCairoContext->fill();
//Setup Cairo context for the trace
refCairoContext->set_source_rgb(1,
1,
1);
refCairoContext->set_antialias(Cairo::ANTIALIAS_SUBPIXEL); //This is up to you
refCairoContext->set_line_width(1); //It's 2 by default and better that way with anti-aliasing
//Add sub-path and stroke
refCairoContext->move_to(m_dPreviousX,
m_dPreviousY);
m_dPreviousX += m_dXStep;
refCairoContext->line_to(m_dPreviousX,
dNewY);
refCairoContext->stroke();
//Update coordinates
if (m_dPreviousX
>= get_allocated_width())
m_dPreviousX = 0;
m_dPreviousY = dNewY;
在清除区域时,X 坐标必须偏移 1,否则“橡皮擦”将清除最后一列的抗锯齿,并且您的跟踪将具有锯齿状边缘。它可能需要大于 1,具体取决于您的线条粗细。 就像我之前说的,使用这种方法,如果小部件被调整大小或“显示”,您的跟踪将被清除。
方法 2
即使在这里,样本的绘制方式也与以前相同。唯一的区别是接收到的每个样本都被直接推入缓冲区。当窗口被调整大小或“陶醉”时,小部件的 on_draw 被调用,您可以一次绘制所有样本。当然,您需要一些内存(如果队列中有 350K 样本,则需要很多内存),但无论如何跟踪都会保留在屏幕上。
方法 3
这也占用了一点内存(可能更多取决于您的小部件的大小),并使用屏幕外缓冲区。在这里,我们存储渲染结果而不是存储样本。覆盖小部件的 on_map 方法和 on_size_allocate 以创建一个离线缓冲区。
void DrawingArea::CreateOffscreenBuffer(void)
Glib::RefPtr <Gdk::Window> refWindow = get_window();
Gtk::Allocation oAllocation = get_allocation();
if (refWindow)
Cairo::RefPtr <Cairo::Context> refCairoContext;
m_refOffscreenSurface =
refWindow->create_similar_surface(Cairo::CONTENT_COLOR,
oAllocation.get_width(),
oAllocation.get_height());
refCairoContext = Cairo::Context::create(m_refOffscreenSurface);
//TODO paint the background (grids may be?)
现在当您收到样本时,而不是直接绘制到窗口中,而不是直接绘制到屏幕外表面。然后通过将此表面设置为窗口的 cairo 上下文的源来阻止复制屏幕外表面,然后绘制一个矩形以绘制新绘制的样本。同样在您的小部件的 on_draw 中,只需将此表面设置为小部件的 cairo 上下文的源并执行 Cairo::Context::paint()。如果您的小部件可能没有调整大小,这种方法特别有用,并且优点是 blitting(将一个表面的内容传输到另一个表面)比绘制单个线段要快得多。
【讨论】:
【参考方案2】:回答您的问题:
有cairo_copy_path()
和cairo_append_path()
(还有cairo_copy_path_flat()
和cairo_path_destroy()
)。
因此,您可以使用cairo_copy_path()
保存路径,然后使用cairo_append_path()
将其附加到当前路径。
回答你的非问题:
我怀疑这会加快您的绘图速度。将这些行附加到当前路径不太可能很慢。相反,我希望这些线的实际绘制速度很慢。
您写道“每次迭代绘制所有点都会减慢很多。”。我不确定“每次迭代”指的是什么,但你为什么一直画所有这些点?只绘制一次然后重复使用绘制的结果不是更有意义吗?
【讨论】:
感谢您的回答。当我说“它减慢......每次迭代”时,这是因为函数 on_draw() 提供了一个 Cairo 上下文,您将使用它在 Gtk::DrawingArea 小部件中进行绘图,并且正如 Gtk 文档所说“没有必要保存并在 on_draw()" 中恢复这个 Cairo 上下文,这是因为每次调用该函数时都会恢复上下文。我用 queue_draw() 调用这个函数,它还有一些每次调用它的处理程序。我想只用 queue_draw() 调用这个函数,但我不知道这是否可能...... 当您的小部件中没有任何更改时,您为什么要排队重绘?还是我误会了什么? 真正的问题是 on_draw 函数会自行刷新,当有大量数据要绘制时,应用程序会变慢。我认为复制路径并重新绘制它会更快,所以我问的第一个问题......有没有办法只用 queue_draw() 调用 on_draw?非常感谢。以上是关于有没有办法在开罗保存路径并恢复它?的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法为 ASP.NET Core 中的内容设置通用根路径?
有没有办法在 Java 中暂停和恢复 Files.walkFileTree?