objective-c - 如何在 macOS Cocoa Objective-C 中快照和保存渲染的 Metal drawable?
问题描述
我有一个应用程序,它使用 Metal 对屏幕进行一些渲染(必要时使用 a CAMetalLayer
,而不是 a MTKView
),我想为用户提供将结果快照保存到磁盘的选项。在转换为 Objective-C 时尝试在https://stackoverflow.com/a/47632198/2752221上遵循答案,我首先像这样编写了一个 commandBuffer 完成回调(注意这是手动保留/释放代码,而不是 ARC;抱歉,遗留代码):
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
[self performSelectorOnMainThread:@selector(saveImageTakingDrawable:) withObject:[drawable retain] waitUntilDone:NO];
optionKeyPressedAndUnhandled_ = NO;
}];
我打电话后立即这样做[commandBuffer presentDrawable:drawable]
;我id <CAMetalDrawable> drawable
的仍在范围内。这是我的实现saveImageTakingDrawable:
:
- (void)saveImageTakingDrawable:(id <CAMetalDrawable>)drawable
{
// We need to have an image to save
if (!drawable) { NSBeep(); return; }
id<MTLTexture> displayTexture = drawable.texture;
if (!displayTexture) { NSBeep(); return; }
CIImage *ciImage = [CIImage imageWithMTLTexture:displayTexture options:nil];
// release the metal texture as soon as we can, to free up the system resources
[drawable release];
if (!ciImage) { NSBeep(); return; }
NSCIImageRep *rep = [NSCIImageRep imageRepWithCIImage:ciImage];
if (!rep) { NSBeep(); return; }
NSImage *nsImage = [[[NSImage alloc] initWithSize:rep.size] autorelease];
[nsImage addRepresentation:rep];
NSData *tiffData = [nsImage TIFFRepresentation];
if (!tiffData) { NSBeep(); return; }
... filesystem cruft culminating in ...
if ([tiffData writeToFile:filePath options:NSDataWritingWithoutOverwriting error:nil])
{
// play a sound to acknowledge saving
[[NSSound soundNamed:@"Tink"] play];
return;
}
NSBeep();
return;
}
结果是“叮当”的声音和合理尺寸 (1784x1090) 的 7.8 MB .tif 文件,但它是透明的,其中没有可用的图像数据;在 Hex Fiend 中查看文件显示整个文件都是零,除了相当简短的页眉和页脚部分。
我怀疑基本方法由于某种原因存在缺陷。当我尝试这个快照时,我得到了几个控制台日志:
2020-06-04 18:20:40.203669-0400 MetalTest[37773:1065740] [CAMetalLayerDrawable texture] should not be called after already presenting this drawable. Get a nextDrawable instead.
Input Metal texture was created with a device that does not match the current context device.
Input Metal texture was created with a device that does not match the current context device.
2020-06-04 18:20:40.247637-0400 MetalTest[37773:1065740] [plugin] AddInstanceForFactory: No factory registered for id <CFUUID 0x600000297260> F8BB1C28-BAE8-11D6-9C31-00039315CD46
2020-06-04 18:20:40.281161-0400 MetalTest[37773:1065740] HALC_ShellDriverPlugIn::Open: Can't get a pointer to the Open routine
第一个日志似乎表明,在首先呈现纹理之后,我什至不允许将纹理从可绘制对象中取出。那么......这样做的正确方法是什么?
更新:
请注意,我并不热衷于saveImageTakingDrawable:
's 代码的后面部分。我很乐意写出 PNG 而不是 TIFF,如果有办法在不使用 、 或 的情况下到达我要去的地方CIImage
,NSCIImageRep
那就NSImage
更好了。我只是想以某种方式将drawable的纹理图像保存为PNG或TIFF 。
解决方案
我只是想以某种方式将可绘制的纹理图像保存为 PNG 或 TIFF。
这是您可以测试的另一种方法;需要将路径设置为要保存图像文件的任何位置。
- (void) windowCapture: (id)sender {
NSTask *task = [[NSTask alloc]init];
[task setLaunchPath:@"/bin/sh"];
NSArray *args = [NSArray arrayWithObjects: @"-c", @"screencapture -i -c -Jwindow", nil];
[task setArguments:args];
NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task launch];
[task waitUntilExit];
int status = [task terminationStatus];
NSData *dataRead = [[pipe fileHandleForReading] readDataToEndOfFile];
NSString *pipeOutput = [[[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding]autorelease];
// Tell us if there was a problem
if (!(status == 0)){NSLog(@"Error: %@",pipeOutput);}
[task release];
// Get image data from pasteboard and write to file
NSPasteboard *pboard = [NSPasteboard generalPasteboard];
NSData *pngData = [pboard dataForType:NSPasteboardTypePNG];
NSError *err;
BOOL success = [pngData writeToFile:@"/Users/xxxx/Desktop/ABCD.png" options:NSDataWritingAtomic error:&err];
if(!success){NSLog(@"Unable to write to file: %@",err);} else {NSLog(@"File written to desktop.");}
}
推荐阅读
- pyqt - PyQt5 为每个 QTreeView 设置 toolTip
- bootstrap-4 - 移动时如何在引导程序中保持元素内联
- flutter - 如何记录 Firestore 用户?
- java - 有没有办法拦截 websocket 连接请求
- java - 井字游戏获胜逻辑 Java
- sql-server - 如何在 SQL Server Management Studio 的查询窗口中找到加号?
- mysql - 尝试在 mysql 中使用重复键
- flutter - 有任何方法可以使用谷歌 API 为地址创建自动完成字段
- javascript - 如何从获取结果中加载 node-sqlite3 数据库
- mysql - 如何检查每个日期-mysql中是否存在另一列的值?