【问题标题】:Render pixel buffer with Cocoa使用 Cocoa 渲染像素缓冲区
【发布时间】:2018-01-18 05:53:49
【问题描述】:

我已经搜索了各种 Apple 的文档和 StackOverflow 的答案,但没有任何帮助,仍然有一个空白的应用程序窗口。我正在尝试在 NSWindow 中显示像素缓冲区的内容,为此我分配了一个缓冲区:

UInt8* row = (UInt8 *) malloc(WINDOW_WIDTH * WINDOW_HEIGHT * bytes_per_pixel);

UInt32 pitch = (WINDOW_WIDTH * bytes_per_pixel);

// For each row
for (UInt32 y = 0; y < WINDOW_HEIGHT; ++y) {
  Pixel* pixel = (Pixel *) row;
  // For each pixel in a row
  for (UInt32 x = 0; x < WINDOW_WIDTH; ++x) {
    *pixel++ = 0xFF000000;
  }
  row += pitch;
}

这应该准备一个带有红色像素的缓冲区。然后我正在创建一个NSBitmapImageRep

NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:(u8 *) row
                                                                             pixelsWide:WINDOW_WIDTH
                                                                             pixelsHigh:WINDOW_HEIGHT
                                                                          bitsPerSample:8
                                                                        samplesPerPixel:4
                                                                               hasAlpha:YES
                                                                               isPlanar:NO
                                                                         colorSpaceName:NSDeviceRGBColorSpace
                                                                            bytesPerRow:WINDOW_WIDTH * 4
                                                                           bitsPerPixel:32];

然后转换成NSImage:

NSSize imageSize = NSMakeSize(CGImageGetWidth([imageRep CGImage]), CGImageGetHeight([imageRep CGImage]));
NSImage *image = [[NSImage alloc] initWithSize:imageSize];
[image addRepresentation:imageRep];

然后我正在配置视图:

NSView *view = [window contentView];
[view setWantsLayer: YES];
[[view layer] setContents: image];

遗憾的是,这并没有给我预期的结果。

【问题讨论】:

  • 您的最终目标是使用 CALayers(用于动画),还是只是想显示图像?我建议简化问题。首先,将内容视图从NSView 更改为NSImageView,然后只需设置image 属性。
  • 其他问题:WIDTH == WINDOW_WIDTHHEIGHT == WINDOW_HEIGHT?它们必须是,否则-initWithBitmapDataPlanes:... 会产生垃圾。为什么pitch 的初始化表达式会转换为指针?
  • @JamesBucanek 谢谢,我试图简化 sn-p,因此写了这个垃圾,修复了。 CALayers 看起来是正确的方向,我相信我需要研究 Quartz 并使用 CGBitmapContextCreate,虽然不知道如何使这项工作
  • 您将使用CGBitmapContextCreate 创建一个由字节数组缓冲区支持的图形上下文,以便您在该上下文中绘制的任何内容都将呈现为像素,然后您可以在数组中检查它们。这基本上与您要完成的目标相反。
  • @JamesBucanek 我不确定我是否理解,听起来这正是我需要的,我有一个字节/像素数组(即row),我在每个周期都在填充/frame 然后在屏幕上渲染。我错过了什么吗?为什么是相反的方向?

标签: objective-c macos cocoa


【解决方案1】:

以下是您的代码存在的一些问题:

  1. 在每个 y 循环结束时,您将 row 递增 pitch。您从未将指针保存到缓冲区的开头。当您创建 NSBitmapImageRep 时,您会传递一个超出缓冲区末尾的指针。

  2. 您将 row 作为 initWithBitmapDataPlanes:... 的第一个 (planes) 参数传递,但您需要传递 &amp;rowThe documentation says

    一个字符指针数组,每个指针指向一个包含原始图像数据的缓冲区。[…]

    “字符指针数组”意味着(在 C 中)您将指针传递给指针。

  3. 你说“这应该准备一个带有红色像素的缓冲区。”但是你用0xFF000000 填充了缓冲区,你说的是hasAlpha:YES。根据初始化程序使用的字节顺序,您要么将 alpha 通道设置为 0,要么将 alpha 通道设置为 0xFF,但将所有颜色通道设置为 0。

    碰巧的是,您已将每个像素设置为不透明的黑色(alpha = 0xFF,颜色全为零)。尝试将每个像素设置为 0xFF00007F,你会得到一个暗红色(alpha = 0xFF,red = 0x7F)。

因此:

typedef struct {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;
} Pixel;

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    size_t width = self.window.contentView.bounds.size.width;
    size_t height = self.window.contentView.bounds.size.height;

    Pixel color = { .red=127, .green=0, .blue=0, .alpha=255 };

    size_t pitch = width * sizeof(Pixel);
    uint8_t *buffer = malloc(pitch * height);

    for (size_t y = 0; y < height; ++y) {
        Pixel *row = (Pixel *)(buffer + y * pitch);
        for (size_t x = 0; x < width; ++x) {
            row[x] = color;
        }
    }

    NSBitmapImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&buffer
        pixelsWide:width pixelsHigh:height
        bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
        colorSpaceName:NSDeviceRGBColorSpace
        bytesPerRow:pitch bitsPerPixel:sizeof(Pixel) * 8];
    NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
    [image addRepresentation:rep];
    self.window.contentView.wantsLayer = YES;
    self.window.contentView.layer.contents = image;
}

@end

结果:

请注意,我没有释放 buffer。如果在rep 被销毁之前释放buffer,事情就会出错。例如,如果您只是将free(buffer) 添加到applicationDidFinishLaunching: 的末尾,则窗口显示为灰色。

这是一个需要解决的棘手问题。如果您改用 Core Graphics,则内存管理都会得到妥善处理。您可以要求 Core Graphics 为您分配缓冲区(通过传递 NULL 而不是有效指针),它会在适当的时候释放缓冲区。

您必须释放您创建的 Core Graphics 对象以避免内存泄漏,但您可以在完成它们后立即执行此操作。 Product > Analyze 命令也可以帮助您找到 Core Graphics 对象的泄漏,但不会帮助您找到未释放的 malloc 块的泄漏。

Core Graphics 解决方案如下所示:

typedef struct {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;
} Pixel;

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    size_t width = self.window.contentView.bounds.size.width;
    size_t height = self.window.contentView.bounds.size.height;

    CGColorSpaceRef rgb = CGColorSpaceCreateWithName(kCGColorSpaceLinearSRGB);
    CGContextRef gc = CGBitmapContextCreate(NULL, width, height, 8, 0, rgb, kCGImageByteOrder32Big | kCGImageAlphaPremultipliedLast);
    CGColorSpaceRelease(rgb);

    size_t pitch = CGBitmapContextGetBytesPerRow(gc);
    uint8_t *buffer = CGBitmapContextGetData(gc);

    Pixel color = { .red=127, .green=0, .blue=0, .alpha=255 };

    for (size_t y = 0; y < height; ++y) {
        Pixel *row = (Pixel *)(buffer + y * pitch);
        for (size_t x = 0; x < width; ++x) {
            row[x] = color;
        }
    }

    CGImageRef image = CGBitmapContextCreateImage(gc);
    CGContextRelease(gc);
    self.window.contentView.wantsLayer = YES;
    self.window.contentView.layer.contents = (__bridge id)image;
    CGImageRelease(image);
}

@end

【讨论】:

  • 感谢您抽出宝贵时间撰写出色的答案并解释代码问题。我的意图是将此缓冲区用于游戏原型的基本软件渲染器(用于教育目的)。所以我要在每个周期(在无限循环中)“重新填充”这个缓冲区。我想要让它工作,这需要一些改变,比如不使用委托等......我想知道在这种情况下生成CGImage 的方法是否正确?我不会出于科学目的研究 OpenGL 或 Metal
  • 我可能会从 Core Graphics 方法开始。您可以在启动时调用CGBitmapContextCreate 一次,每帧调用一次CGBitmapContextCreateImage
【解决方案2】:

注意确定发生了什么,但这里的代码已经运行了多年:

static NSImage* NewImageFromRGBA( const UInt8* rawRGBA, NSInteger width, NSInteger height )
{
    size_t rawRGBASize = height*width*4/* sizeof(RGBA) = 4 */;
    // Create a bitmap representation, allowing NSBitmapImageRep to allocate its own data buffer
    NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
                                                                         pixelsWide:width
                                                                         pixelsHigh:height
                                                                      bitsPerSample:8
                                                                    samplesPerPixel:4
                                                                           hasAlpha:YES
                                                                           isPlanar:NO
                                                                     colorSpaceName:NSCalibratedRGBColorSpace
                                                                        bytesPerRow:0
                                                                       bitsPerPixel:0];
    NSCAssert(imageRep!=nil,@"failed to create NSBitmapImageRep");
    NSCAssert((size_t)[imageRep bytesPerPlane]==rawRGBASize,@"alignment or size of CGContext buffer and NSImageRep do not agree");
    // Copy the raw bitmap image into the new image representation
    memcpy([imageRep bitmapData],rawRGBA,rawRGBASize);
    // Create an empty NSImage then add the bitmap representation to it
    NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(width,height)];
    [image addRepresentation:imageRep];

    return image;
}

【讨论】:

    猜你喜欢
    • 2011-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多