如何在macOS中编程实现指定区域屏幕截图(适配Unity插件)
macOS 指定区域桌面截图的实用实现方案(适配Unity原生插件)
针对你需要根据displayId、x、y、width、height返回截图字节数据的需求,以下是几种可行的简便实现方式:
1. 继续使用已弃用的CGWindowListCreateImage(快速兼容)
虽然官方标记为弃用,但该API目前在macOS 14及以下版本仍能正常运行,不会影响功能,适合快速实现需求。你可以封装一个函数,直接通过该API截取指定区域,再转换为Unity可用的字节数据:
#import <CoreGraphics/CoreGraphics.h> #import <ImageIO/ImageIO.h> NSData* captureDisplayArea(CGDirectDisplayID displayId, CGFloat x, CGFloat y, CGFloat width, CGFloat height) { // 获取显示器的像素缩放因子(适配Retina屏幕) CGFloat scale = CGDisplayScreenSize(displayId).width / CGDisplayBounds(displayId).size.width; CGRect captureRect = CGRectMake(x * scale, y * scale, width * scale, height * scale); // 创建截图 CGImageRef image = CGWindowListCreateImage( captureRect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault ); if (!image) return nil; // 将CGImage转换为NSData(这里用PNG格式,也可换成RGBA raw数据适配Unity) NSMutableData* data = [NSMutableData data]; CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)data, kUTTypePNG, 1, NULL); CGImageDestinationAddImage(dest, image, NULL); CGImageDestinationFinalize(dest); CFRelease(dest); CGImageRelease(image); return data; }
如果需要直接返回Unity可用的RGBA字节流,可以跳过ImageIO转换,直接从CGImage中提取像素数据。
2. 基于CGDisplayCreateImage的裁剪方案
用CGDisplayCreateImage先捕获整个显示器画面,再裁剪指定区域,这种方式同样属于旧API,但逻辑更直接:
NSData* captureCroppedDisplayArea(CGDirectDisplayID displayId, CGFloat x, CGFloat y, CGFloat width, CGFloat height) { CGFloat scale = CGDisplayScreenSize(displayId).width / CGDisplayBounds(displayId).size.width; CGRect captureRect = CGRectMake(x * scale, y * scale, width * scale, height * scale); CGImageRef fullDisplayImage = CGDisplayCreateImage(displayId); if (!fullDisplayImage) return nil; // 裁剪指定区域 CGImageRef croppedImage = CGImageCreateWithImageInRect(fullDisplayImage, captureRect); CGImageRelease(fullDisplayImage); if (!croppedImage) return nil; // 转换为字节数据(示例为PNG,可替换为raw像素) NSMutableData* data = [NSMutableData data]; CGImageDestinationRef dest = CGImageDestinationCreateWithData((CFMutableDataRef)data, kUTTypePNG, 1, NULL); CGImageDestinationAddImage(dest, croppedImage, NULL); CGImageDestinationFinalize(dest); CFRelease(dest); CGImageRelease(croppedImage); return data; }
3. 适配ScreenCaptureKit的单次截图方案
如果不想依赖弃用API,可以利用ScreenCaptureKit实现单次截图:启动流并捕获第一帧后立即停止,模拟单次截图的效果:
#import <ScreenCaptureKit/ScreenCaptureKit.h> typedef void(^CaptureCompletion)(NSData*); void captureAreaWithScreenCaptureKit(CGDirectDisplayID displayId, CGFloat x, CGFloat y, CGFloat width, CGFloat height, CaptureCompletion completion) { // 获取目标显示器 SCShareableContent* content = [SCShareableContent shareableContentWithError:nil]; SCDisplay* targetDisplay = nil; for (SCDisplay* display in content.displays) { if (display.displayID == displayId) { targetDisplay = display; break; } } if (!targetDisplay) { completion(nil); return; } // 配置流参数,指定裁剪区域 SCStreamConfiguration* config = [[SCStreamConfiguration alloc] init]; CGRect captureRect = CGRectMake(x, y, width, height); config.cropRect = captureRect; config.pixelFormat = kCVPixelFormatType_32BGRA; // 适配Unity的RGBA格式 // 创建流 SCStream* stream = [[SCStream alloc] initWithDisplay:targetDisplay configuration:config delegate:nil]; __block BOOL hasCaptured = NO; [stream addStreamOutput:^(SCStreamFrame * _Nonnull frame, CMSampleBufferRef _Nonnull sampleBuffer, NSError * _Nullable error) { if (hasCaptured) return; hasCaptured = YES; // 从SampleBuffer中提取像素数据 CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferLockBaseAddress(imageBuffer, 0); void* baseAddress = CVPixelBufferGetBaseAddress(imageBuffer); size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer); NSData* pixelData = [NSData dataWithBytes:baseAddress length:bufferSize]; CVPixelBufferUnlockBaseAddress(imageBuffer, 0); // 停止流 [stream stopCaptureWithCompletionHandler:^(NSError * _Nullable error) { completion(pixelData); }]; }]; // 启动捕获 [stream startCaptureWithCompletionHandler:^(NSError * _Nullable error) { if (error) completion(nil); }]; }
这种方式需要申请屏幕录制权限,适合长期维护的插件。
内容的提问来源于stack exchange,提问作者Rahel Gunaratne
相关产品推荐
相关产品推荐

