有没有办法在开罗保存路径并恢复它?

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?

如何将文件保存在路径中并使用 ../../ 从不同的路径 PHP 访问它

如何保存android通知并在重启时恢复它们

.ASD文件怎么打开,并恢复word文档?

如何在excel VBA中获取保存文件的路径和名称?