【问题标题】:Using NSImageView to display multiple images in quick sucession使用 NSImageView 快速连续显示多张图片
【发布时间】:2026-02-06 03:30:01
【问题描述】:

我有一个应用程序,其中一个窗口中有一个 NSImageView。用户应该能够将任何文件/文件夹(不仅是图像)拖放到图像视图中,因此我继承了 NSImageView 类以添加对这些类型的支持。

我选择 NSImageView 而不是普通视图的原因是因为我还想在用户将鼠标悬停在准备删除的文件上时显示动画(比如向下和上下移动的箭头)。我的问题是:执行此操作的最佳方法是什么(最有效、最快、最少 CPU 使用率等)?

事实上,我已经做到了,但让我提出这个问题的原因是,当我将图像设置为以低于 0.02 秒的速度变化时,它开始出现滞后。这是我的做法:

在 NSImageView 子类中:

  • 有一个ivar:NSTimer* animTimer;
  • 覆盖 awakeFromNib,调用 [super awakeFromNib] 并使用 NSImage 将图像加载到一个数组中(大约 45 个图像)
  • 每当用户输入文件时,启动频率 = 0.025 的 animTimer(更少且滞后),以及设置数组中下一个图像的选择器(称为 drawNextImage)
  • 每当用户退出或结束拖放时,调用 [animTimer invalidate] 停止更新图像

这是我在子类中设置图像的方式:

- (void)drawNextImage
{
    currentImageIndex++; // ivar / kNumberDNDImages is a constant defined as 46
    if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
    [super setImage: [imagesArray objectAtIndex: currentImageIndex]]; // imagesArray is ivar
}

那么,我怎样才能足够快地做到这一点?我希望频率约为 0.01 秒,但滞后时间小于 0.025,所以这就是我目前设定的。哦,我的图像尺寸正确(+ 或 - 一个像素或其他东西),它们是 .png 格式(我需要透明度 - 例如,jpeg 不会这样做)。

编辑:

我已尝试遵循 NSResponder 的建议,并将我的方法更新为:

- (void)drawNextImage
{
    currentImageIndex++;
    if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
    NSRect smallImgRect;
    smallImgRect.origin = NSMakePoint(kSmallImageWidth * currentImageIndex, [self.bigDNDImage size].height); // Up left corner - ??
    smallImgRect.size = NSMakeSize(kSmallImageWidth, [self.bigDNDImage size].height);

    // Bottom left corner - ??
    NSPoint imgPoint = NSMakePoint(([self bounds].size.width - kSmallImageWidth) / 2, 0);

    [bigDNDImage drawAtPoint: imgPoint fromRect: smallImgRect operation: NSCompositeCopy fraction: 1];
}

我还将这个方法和其他拖放方法从 NSImageView 子类移到了我已经拥有的 NSView 子类中。一切都完全一样,除了超类和这个方法。我还修改了一些常量。

在我对此的早期测试中,我收到了一些错误/警告消息,这些消息并没有停止执行谈论 NSGraphicsContext 或其他内容。这些现在已经消失了,但你知道的。我完全不知道他们为什么出现以及他们的意思。如果它们再次出现,我会担心它们,而不是现在 :)

编辑 2:

这就是我现在正在做的事情:

- (void)drawNextImage
{
    currentImageIndex++;
    if (currentImageIndex >= kNumberDNDImages) { currentImageIndex = 0;}
    [self drawCurrentImage];
}
- (void)drawCurrentImage
{
    NSRect smallImgRect;
    smallImgRect.origin = NSMakePoint(kSmallImageWidth * currentImageIndex, 0); // Bottom left, for sure
    smallImgRect.size = NSMakeSize(kSmallImageWidth, [self.bigDNDImage size].height);

    // Bottom left as well
    NSPoint imgPoint = NSMakePoint(([self bounds].size.width - kSmallImageWidth) / 2, 0);

    [bigDNDImage drawAtPoint: imgPoint fromRect: smallImgRect operation: NSCompositeCopy fraction: 1];
}

这里的问题是在调用 drawRect 时调用 drawCurrentImage(看,它实际上比我想象的更容易解决)。

现在,我必须说我还没有对合成图像进行过这种尝试,因为我找不到一种好的快速方法来按照我想要的方式(一个并排)合并 40 多个图像。但是对于那些不感兴趣的人,我修改了它以与我的 NSImageView 子类做同样的事情(从数组中读取 40 多张图像并显示它们),我发现没有减速带:NSView 与 NSImageView 一样滞后于 0.025 以下。另外我在使用核心动画时发现了一些问题(图像是在奇怪的地方而不是我告诉她的地方绘制的)和一些关于 NSGraphicsContext 的警告,我根本不知道如何解决(我是一个完整的使用Objective-C工具进行绘图等方面的菜鸟)。所以目前我正在使用 NSImageView,除非我找到一种方法来合并所有这些图像并尝试使用 NSView。

【问题讨论】:

    标签: objective-c cocoa performance nstimer nsimageview


    【解决方案1】:

    Core Animation 可能是最快的,因为它会在 GPU 上完成所有工作。为每个图像创建一个图层,将每个图层的 contents 设置为您可以从每个图像制作的 CGImage,将它们全部添加为单个顶层图层 host the top-level layer in a plain NSView 的子图层,然后只需切换每个图像图层的 hidden属性。

    【讨论】:

    • 您能举个例子吗?我在“将它们全部添加为单个顶层层的子层”部分迷失了你。
    • @Charlie:为每个图像创建一个图层并将该图层的contents 设置为该图像作为CGImage,并将所有这些图层添加为一个父图层的子图层。将该层设置为 NSView 的层。
    • 实际上,我最终让它工作了......除了autoresizingMask 没有。但是,我将其发布到了它自己的问题中。
    【解决方案2】:

    我可能会将所有组件图像绘制成一张长图像,并使用-drawAtPoint:fromRect:operation:fraction: 将片段绘制到视图中。不过,我相信你可以通过使用 OpenGL 来加快速度。

    【讨论】:

    • 我在这里不明白的一件事是 (0,0) 点在哪里。 NSImage 的默认 (0,0) 点是什么:左上、右上、左下或右下?视图的 (0,0) 点也是如此。它在哪里?我需要这个来计算 drawAtPoint: 和 fromRect: 的值我假设 NSImage 使用左上角,视图使用左下角,但它不工作。我已经更新了我的问题以显示我现在在做什么。
    • @Alex Truppel:它在左下角,除非它不是(isFlipped 是真的)。您自己没有设置的一个例外是 NSScreens 的框架,其原点位于左上角。
    • 另一种加快速度的方法是将合成图像制作成 CGLayer。这些是专门用来反复绘制的。
    • @peter 我发现我的图像根本没有显示的解决方案(当我询问坐标时这是我的问题):我忘记在drawrect上绘制它,所以我只画了它一次,然后图像消失了。关于 CGLayer,我不知道,因为我确实多次绘制图像,但问题是图像每次都在变化。如果我正确理解了文档,那对速度没有好处。不过,我可能错了。
    • @Alex Truppel:是的,您需要在drawRect: 中进行所有视图绘图,或者响应直接从那里发送的消息。至于 CGLayer,我特意说的是“合成图”,指的是 NSResponder 提出的将所有图像合成为一个图像的建议。