【问题标题】:Efficient method to draw a line with millions of points用数百万点绘制一条线的有效方法
【发布时间】:2011-06-18 10:58:15
【问题描述】:

我正在 Cocoa 中编写一个具有多种缩放选项的音频波形编辑器。在最宽处,它显示了整首歌曲的波形(大约 1000 万个样本)。在最窄处,它显示了声波的像素精确表示(视图中约 1000 个样本)。我希望能够在这些缩放级别之间平滑过渡。一些像 Ableton Live 这样的商业编辑器似乎以一种非常便宜的方式来做到这一点。

我当前的实现满足我想要的缩放范围,但效率低下且不稳定。该设计很大程度上受到这篇关于使用石英绘制波形的优秀文章的启发:

http://supermegaultragroovy.com/blog/2009/10/06/drawing-waveforms/

我为音频文件创建了多个 CGMutablePathRef's 在不同的减少级别。当我一直放大时,我使用已减少到每 x 千个样本一个点的路径。当我一直放大时,我使用包含每个样本的点的路径。当我处于减少级别之间时,我会水平缩放路径。这使它具有功能性,但仍然非常昂贵,并且在减少级别之间转换时会出现伪影。

关于如何降低成本的一个想法是取消抗锯齿。我的编辑器中的波形是抗锯齿的,而 Ableton 中的波形不是(参见下面的比较)。

我没有看到关闭 CGMutablePathRef 的抗锯齿的方法。在 Cocoa 的世界中是否有 CGMutablePathRef 的非抗锯齿替代品?如果没有,有没有人知道一些 OpenGL 类或示例代码可能会让我更有效地绘制我的大线?

2014 年 1 月 21 日更新: 现在有一个很棒的库,可以完全满足我的需求:https://github.com/syedhali/EZAudio

【问题讨论】:

    标签: cocoa audio quartz-2d waveform cgpath


    【解决方案1】:

    我在我的应用程序中使用 CGContextMoveToPoint+CGContextAddLineToPoint+CGContextStrokePath。使用预先计算的后备缓冲区为概览绘制每个屏幕点一个点。缓冲区包含要绘制的确切点,并使用信号的插值表示(基于缩放/比例)。虽然如果我渲染到图像缓冲区可能会更快并且看起来更好,但我从来没有抱怨过。如果设置正确,您可以从辅助线程计算和渲染所有这些。

    抗锯齿与图形上下文有关。

    CGFloat(CGPaths 的本机输入)对于概览、作为中间表示和计算波形概览来说太过分了。 16 位应该足够了。当然,您必须在传递给 CG 调用时转换为 CGFloat。

    您需要进行概要分析以了解您的时间都花在了哪些地方——专注于花费最多时间的部分。另外,请确保只在必须时绘制必须绘制的内容,并尽可能避免叠加/动画。如果您需要叠加,最好渲染到图像/缓冲区并根据需要进行更新。有时,当表面较大时,将显示分解为多个绘图表面会有所帮助。

    semi-OT:ableton 使用 s+h 值可能会稍微快一些,但是……我更喜欢它作为一个选项。如果您的实现使用线性插值(根据其外观可能),请考虑更直观的方法。线性插值有点作弊,如果您正在开发专业应用程序,这确实不是用户所期望的。

    【讨论】:

    • 感谢您的详细回复!听起来你也来过这里。事实上,我绘制波形的频率比我需要的要多。我将考虑用覆盖替换完整的重新绘制。关于使用 s+h 值的 Ableton,那些是什么?您认为我的实现使用线性插值是正确的。看起来 Ableton 的平方线可以更有效地绘制,但不知道我会如何去做。
    • @tassock 不客气。 “s+h”是指信号的“采样和保持”值。这只是完全按照它们在时域中的表示来绘制波形步骤。最好(imo)内插和构造信号的表示。 s+h 没问题,但它应该是一个选项,默认使用插值表示。当我说插值时,我指的是一种比 s+h 或线性插值更能代表模拟信号的方法。为此,可能需要您在性能和准确性之间取得平衡。 sinc 会很好,(续)
    • 但您可能会使用更高阶的样条曲线(例如)。正确插值/重建波形可以为您当前的实现增加大量 CPU。
    • 使用画线方法,s+h 绘图实现非常简单 - 你实际上有更多的点要绘制,但数学更简单,可以在 int(或短)中执行,节省大量内存(在某些情况下)以及 cpu。
    【解决方案2】:

    关于抗锯齿的特定问题。在 Quartz 中,抗锯齿在绘制时应用于上下文。 CGPathRef 与绘图上下文无关。因此,可以将相同的 CGPathRef 渲染到抗锯齿上下文或非抗锯齿上下文中。例如,要在动画期间禁用抗锯齿:

    CGContextRef context = UIGraphicsGetCurrentContext();
    GMutablePathRef fill_path = CGPathCreateMutable();
    // Fill the path with the wave
    ...
    
    CGContextAddPath(context, fill_path);
    if ([self animating])
        CGContextSetAllowsAntialiasing(context, NO);
    else
        CGContextSetAllowsAntialiasing(context, YES);
    // Do the drawing
    CGContextDrawPath(context, kCGPathStroke);
    

    【讨论】:

    • 啊,感谢您指出上下文抗锯齿设置。不幸的是,它似乎并没有带来显着的性能提升。显然,在我当前的实现中,我还有太多其他低效的流程。
    猜你喜欢
    • 2016-04-26
    • 2015-09-30
    • 1970-01-01
    • 2023-04-03
    • 2015-03-03
    • 2016-09-05
    • 2016-06-29
    • 1970-01-01
    • 2020-09-28
    相关资源
    最近更新 更多