高效方法绘制数百万点的直线

20

我正在使用Cocoa编写一款音频波形编辑器,具有广泛的缩放选项。在最宽的范围内,它显示了整首歌曲的波形(在视图中大约有10百万个样本)。在最窄的范围内,它显示了声波的像素精确表示(在视图中有大约1千个样本)。我想能够在这些缩放级别之间平滑地转换。一些商业编辑器(如Ableton Live)似乎以非常低廉的价格做到了这一点。

我的当前实现满足了我所需的缩放范围,但效率低下且不流畅。这个设计很大程度上受到了这篇关于使用Quartz绘制波形的优秀文章的启发:

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

我为各种缩减级别的音频文件创建了多个CGMutablePathRef。当我缩放到最小时,我使用已经减少到每x千个样本一个点的路径。当我缩放到最大时,我使用包含每个样本的点的路径。当我处于缩减级别之间时,我水平缩放路径。这使其功能正常,但仍然相当昂贵且在缩减级别之间转换时会出现伪影。

有一个想法是如何使这个过程更加便宜,那就是去掉反锯齿。我的编辑器中的波形是反锯齿的,而Ableton的不是(请参见下面的比较)。 enter image description here enter image description here

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

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

2个回答

6

我在我的应用程序中使用CGContextMoveToPoint+CGContextAddLineToPoint+CGContextStrokePath。每个屏幕点使用预先计算的背景缓冲区进行绘制,缓冲区包含要绘制的精确点,并使用信号的插值表示(基于缩放/比例)。尽管如果渲染到图像缓冲区可能会更快且外观更好,但我从未收到过任何投诉。如果正确设置,您可以从第二个线程计算和渲染所有这些内容。

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

CGFloat(CGPaths的本机输入)对于概述来说过于复杂,作为中间表示和计算波形概述。16位应该足够。当传递给CG调用时,您需要将其转换为CGFloat。

您需要进行分析以找出时间花费在哪里-重点关注花费最多的部分。此外,在必须时仅绘制必需的内容,避免覆盖/动画。如果需要覆盖,最好将其渲染到图像/缓冲区并根据需要更新。当表面较大时,有时将显示分成多个绘图表面会有所帮助。

半OT:Ableton使用s+h值,这可能会稍微快一些,但我更喜欢将其作为选项。如果您的实现使用线性插值(根据其外观可能是这样),请考虑更直观的方法。线性插值有点欺骗,并且如果您正在开发专业应用程序,则确实不符合用户的期望。


感谢您详细的回复!听起来您也有经验。确实,我比需要更频繁地绘制波形。我将尝试使用覆盖方式替换完全重新绘制。关于Ableton使用s+h值,那是什么?您正确地假设我的实现使用线性插值。看起来Ableton的平方线条可能更有效率,但不确定如何实现。 - tassock
@tassock 不用谢。我所说的“s+h”是指信号的“采样和保持”值。这只是按照时间域中所表示的波形步骤精确地绘制波形。在我看来,最好是插值并构建信号的表示。S+H可以,但应该作为一个选项,以插值表示为默认选项。当我说插值时,我指的是一种更具代表性的模拟信号方法,而不是S+H或线性插值。为此,可能需要在性能和准确性之间取得平衡。一个sinc会很好,(续) - justin
但是你可以尝试使用更高阶的样条曲线(以此作为一个例子)。正确地插值/重构波形会给你当前的实现增加大量CPU负担。 - justin
使用绘制线条的方法,s+h绘图实现非常简单 - 实际上您需要绘制更多的点,但数学计算更简单,并且可以在int(或short)中执行,节省大量内存(在某些情况下)以及CPU。 - justin

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);

啊,感谢指出上下文抗锯齿设置。不幸的是,它似乎并没有导致显著的性能提升。显然,在我当前的实现中,我有太多其他低效的进程。 - tassock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接