【问题标题】:How to capture the screen and save to BMP image file on iOS?如何在 iOS 上捕获屏幕并保存为 BMP 图像文件?
【发布时间】:2015-12-18 22:04:59
【问题描述】:

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

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

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

screenshot0:方法图片:

screenshot1:方法图片:

- (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;
}

【问题讨论】:

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


【解决方案1】:

CGDisplayCreateImage(CGMainDisplayID()) ?我不知道它是否可以通过 macOS 上的作品在 iOS 上运行。你为什么使用 CGDsipalyStream

【讨论】:

    猜你喜欢
    • 2012-03-20
    • 2013-12-12
    • 1970-01-01
    • 1970-01-01
    • 2021-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-16
    相关资源
    最近更新 更多