screencapturekit 8.0.0

Safe Rust bindings for Apple's ScreenCaptureKit framework - screen and audio capture on macOS
Documentation
// Strided CGImage render helpers.
//
// The tightly-packed renderers (cgimage_render_rgba_into /
// cgimage_render_bgra_into) live in apple-cf's CoreGraphicsBridge and hard-code
// bytesPerRow = width * 4. Consumers with row-aligned destination buffers
// (GPU upload, wgpu, Metal) need to specify their own row stride. These
// variants accept an explicit destBytesPerRow and build the CGContext with it.

import CoreGraphics
import Foundation

// MARK: - Strided CGImage Bridge

@_cdecl("cgimage_render_rgba_into_strided")
public func renderCGImageRGBAIntoStrided(
    _ image: OpaquePointer,
    _ destBuffer: UnsafeMutableRawPointer,
    _ destCapacity: Int,
    _ destBytesPerRow: Int
) -> Int {
    let cgImage = Unmanaged<CGImage>.fromOpaque(UnsafeRawPointer(image)).takeUnretainedValue()

    let width = cgImage.width
    let height = cgImage.height
    let bytesPerPixel = 4 // RGBA
    let minBytesPerRow = width * bytesPerPixel

    // The stride must hold at least one full row of pixels, and the caller's
    // buffer must span every (possibly padded) row. Mirror the bounds checks
    // performed Rust-side so a mismatch aborts rather than overflows.
    guard destBytesPerRow >= minBytesPerRow else {
        return 0
    }
    let (rowSpan, overflow) = height.multipliedReportingOverflow(by: destBytesPerRow)
    guard !overflow, rowSpan <= destCapacity else {
        return 0
    }
    let totalBytes = rowSpan

    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue

    guard let context = CGContext(
        data: destBuffer,
        width: width,
        height: height,
        bitsPerComponent: 8,
        bytesPerRow: destBytesPerRow,
        space: colorSpace,
        bitmapInfo: bitmapInfo
    ) else {
        return 0
    }

    // See cgimage_render_rgba_into in apple-cf — .copy keeps output
    // deterministic regardless of the destination buffer's prior contents.
    context.setBlendMode(.copy)
    context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

    return totalBytes
}

@_cdecl("cgimage_render_bgra_into_strided")
public func renderCGImageBGRAIntoStrided(
    _ image: OpaquePointer,
    _ destBuffer: UnsafeMutableRawPointer,
    _ destCapacity: Int,
    _ destBytesPerRow: Int
) -> Int {
    let cgImage = Unmanaged<CGImage>.fromOpaque(UnsafeRawPointer(image)).takeUnretainedValue()

    let width = cgImage.width
    let height = cgImage.height
    let bytesPerPixel = 4 // BGRA
    let minBytesPerRow = width * bytesPerPixel

    guard destBytesPerRow >= minBytesPerRow else {
        return 0
    }
    let (rowSpan, overflow) = height.multipliedReportingOverflow(by: destBytesPerRow)
    guard !overflow, rowSpan <= destCapacity else {
        return 0
    }
    let totalBytes = rowSpan

    // Native ScreenCaptureKit layout: premultipliedFirst + byteOrder32Little =
    // BGRA, matching kCVPixelFormatType_32BGRA and skipping the channel swap.
    let colorSpace = CGColorSpaceCreateDeviceRGB()
    let bitmapInfo = CGImageAlphaInfo.premultipliedFirst.rawValue
        | CGBitmapInfo.byteOrder32Little.rawValue

    guard let context = CGContext(
        data: destBuffer,
        width: width,
        height: height,
        bitsPerComponent: 8,
        bytesPerRow: destBytesPerRow,
        space: colorSpace,
        bitmapInfo: bitmapInfo
    ) else {
        return 0
    }

    context.setBlendMode(.copy)
    context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))

    return totalBytes
}