如何捕获屏幕并保存到iOS上的BMP图像文件?

时间:2015-09-22 04:10:29

标签: ios objective-c bmp iphone-privateapi iosurface

我想捕获iOS的整个屏幕并将其保存到BMP(使用私有API),我首先得到IOSurfaceRef IOMobileFramebufferConnection,然后找到一种方法将表面字节保存到一个BMP文件。

我尝试了两种方法,方法screenshot0:直接从screenSurface获取字节并将其保存到BMP,但得到了模糊的位错图像;方法screenshot1:使用IOSurfaceAcceleratorTransferSurface将表面字节传输到新的IOSurfaceRef并将其保存到BMP文件中,获得了清晰但镜像且360度旋转的图像。

我想知道,为什么我不能直接使用原始IOSurfaceRef中的字节? IOSurfaceRef中的字节是否已镜像?如何获得正确的BMP截图? 谢谢!

screenshot0:方法图片:

enter image description here

screenshot1:方法图片:

enter image description here

- (NSString *)getBmpSavePath:(NSString *)savePath
{
    NSString *path = nil;
    if (![[[savePath pathExtension] lowercaseString] isEqualToString:@"bmp"]) {
        path = [savePath stringByDeletingPathExtension];
        path = [path stringByAppendingPathExtension:@"bmp"];
    }
    return path;
}

- (IOSurfaceRef)getScreenSurface
{
    IOSurfaceRef screenSurface = NULL;
    io_service_t framebufferService = NULL;
    IOMobileFramebufferConnection framebufferConnection = NULL;

    framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleH1CLCD"));
    if(!framebufferService)
        framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleM2CLCD"));
    if(!framebufferService)
        framebufferService = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleCLCD"));
    if (framebufferService) {
        kern_return_t result;
        result = IOMobileFramebufferOpen(framebufferService, mach_task_self(), 0, &framebufferConnection);
        if (result == KERN_SUCCESS) {
            IOMobileFramebufferGetLayerDefaultSurface(framebufferConnection, 0, &screenSurface);
        }
    }
    return screenSurface;
}

- (void)screenshot0:(NSString *)savePath
{
    IOSurfaceRef screenSurface = [self getScreenSurface];
    if (screenSurface) {
        IOSurfaceLock(screenSurface, kIOSurfaceLockReadOnly, NULL);

        size_t width = IOSurfaceGetWidth(screenSurface);
        size_t height = IOSurfaceGetHeight(screenSurface);

        void *bytes = IOSurfaceGetBaseAddress(screenSurface);

        NSString *path = [self getBmpSavePath:savePath];

        bmp_write(bytes, width, height, [path UTF8String]);

        IOSurfaceUnlock(screenSurface, kIOSurfaceLockReadOnly, NULL);
    }
}

- (void)screenshot1:(NSString *)savePath
{
    IOSurfaceRef screenSurface = [self getScreenSurface];
    if (screenSurface) {
        IOSurfaceLock(screenSurface, kIOSurfaceLockReadOnly, NULL);

        size_t width = IOSurfaceGetWidth(screenSurface);
        size_t height = IOSurfaceGetHeight(screenSurface);
        size_t bytesPerElement = IOSurfaceGetBytesPerElement(screenSurface);
        OSType pixelFormat = IOSurfaceGetPixelFormat(screenSurface);
        size_t bytesPerRow = self.bytesPerElement * self.width;
        size_t allocSize = bytesPerRow * self.height;


        //============== Why shoud I do this step? Why can't I IOSurfaceGetBaseAddress directly from screenSurface like method screenshot0:???
        NSDictionary *properties = [NSDictionary dictionaryWithObjectsAndKeys:
        [NSNumber numberWithBool:YES], kIOSurfaceIsGlobal,
        [NSNumber numberWithUnsignedLong:bytesPerElement], kIOSurfaceBytesPerElement,
        [NSNumber numberWithUnsignedLong:bytesPerRow], kIOSurfaceBytesPerRow,
        [NSNumber numberWithUnsignedLong:width], kIOSurfaceWidth,
        [NSNumber numberWithUnsignedLong:height], kIOSurfaceHeight,
        [NSNumber numberWithUnsignedInt:pixelFormat], kIOSurfacePixelFormat,
        [NSNumber numberWithUnsignedLong:allocSize], kIOSurfaceAllocSize,
        nil];

        IOSurfaceRef destSurf = IOSurfaceCreate((__bridge CFDictionaryRef)(properties));

        IOSurfaceAcceleratorRef outAcc;
        IOSurfaceAcceleratorCreate(NULL, 0, &outAcc);
        IOSurfaceLock(screenSurface, kIOSurfaceLockReadOnly, NULL);
        IOSurfaceAcceleratorTransferSurface(outAcc, screenSurface, destSurf, (__bridge CFDictionaryRef)(properties), NULL);
        IOSurfaceUnlock(screenSurface, kIOSurfaceLockReadOnly, NULL);
        CFRelease(outAcc);
        //==============

        void *bytes = IOSurfaceGetBaseAddress(destSurf);

        NSString *path = [self getBmpSavePath:savePath];

        bmp_write(bytes, width, height, [path UTF8String]);

        IOSurfaceUnlock(screenSurface, kIOSurfaceLockReadOnly, NULL);
    }
}

int bmp_write(const void *image, size_t xsize, size_t ysize, const char *filename)
{
    unsigned char header[54] = {
        0x42, 0x4d, 0, 0, 0, 0, 0, 0, 0, 0,
        54, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 32, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0
    };

    long file_size = (long)xsize * (long)ysize * 4 + 54;
    header[2] = (unsigned char)(file_size &0x000000ff);
    header[3] = (file_size >> 8) & 0x000000ff;
    header[4] = (file_size >> 16) & 0x000000ff;
    header[5] = (file_size >> 24) & 0x000000ff;

    long width = xsize;
    header[18] = width & 0x000000ff;
    header[19] = (width >> 8) &0x000000ff;
    header[20] = (width >> 16) &0x000000ff;
    header[21] = (width >> 24) &0x000000ff;

    long height = ysize;
    header[22] = height &0x000000ff;
    header[23] = (height >> 8) &0x000000ff;
    header[24] = (height >> 16) &0x000000ff;
    header[25] = (height >> 24) &0x000000ff;

    char fname_bmp[128];
    sprintf(fname_bmp, "%s", filename);

    FILE *fp;
    if (!(fp = fopen(fname_bmp, "wb")))
        return -1;

    fwrite(header, sizeof(unsigned char), 54, fp);
    fwrite(image, sizeof(unsigned char), (size_t)(long)xsize * ysize * 4, fp);

    fclose(fp);
    return 0;
}

1 个答案:

答案 0 :(得分:0)

CGDisplayCreateImage(CGMainDisplayID())?我不知道它是否适用于iOS上的macOS工作。你为什么使用 CGDsipalyStream