【问题标题】: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 年,来自开罗的一位用户在文档中建议“做”同样的事情,但显然还没有做到。

有什么建议吗?

【问题讨论】:

    标签: c++ cairo gtkmm gtkmm3


    【解决方案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?非常感谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-16
      • 1970-01-01
      • 1970-01-01
      • 2018-03-09
      • 2016-05-11
      • 2011-10-31
      • 1970-01-01
      相关资源
      最近更新 更多