use core::ffi::{c_char, c_void};
unsafe extern "C" {
fn CGImageGetWidth(image: *mut c_void) -> usize;
fn CGImageGetHeight(image: *mut c_void) -> usize;
fn CGColorSpaceCreateDeviceRGB() -> *mut c_void;
fn CGColorSpaceRelease(space: *mut c_void);
fn CGBitmapContextCreate(
data: *mut c_void,
width: usize,
height: usize,
bits_per_component: usize,
bytes_per_row: usize,
space: *mut c_void,
bitmap_info: u32,
) -> *mut c_void;
fn CGBitmapContextCreateImage(ctx: *mut c_void) -> *mut c_void;
fn CGContextDrawImage(ctx: *mut c_void, rect: CGRect, image: *mut c_void);
fn CGContextRelease(ctx: *mut c_void);
fn CGContextTranslateCTM(ctx: *mut c_void, tx: f64, ty: f64);
fn CGContextScaleCTM(ctx: *mut c_void, sx: f64, sy: f64);
fn CGImageRelease(image: *mut c_void);
fn CFStringCreateWithCString(alloc: *const c_void, s: *const c_char, enc: u32) -> *const c_void;
fn CFURLCreateFromFileSystemRepresentation(
alloc: *const c_void,
path: *const u8,
len: isize,
is_dir: bool,
) -> *const c_void;
fn CFRelease(o: *const c_void);
fn CGImageDestinationCreateWithURL(
url: *const c_void,
ty: *const c_void,
count: usize,
options: *const c_void,
) -> *mut c_void;
fn CGImageDestinationAddImage(dest: *mut c_void, image: *mut c_void, props: *const c_void);
fn CGImageDestinationFinalize(dest: *mut c_void) -> bool;
}
#[repr(C)]
struct CGPoint {
x: f64,
y: f64,
}
#[repr(C)]
struct CGSize {
width: f64,
height: f64,
}
#[repr(C)]
struct CGRect {
origin: CGPoint,
size: CGSize,
}
const RGBA8: u32 = 1 | (4 << 12);
const UTF8: u32 = 0x0800_0100;
pub unsafe fn cgimage_to_rgba(image: *mut c_void) -> (u32, u32, Vec<u8>) {
unsafe {
let w = CGImageGetWidth(image);
let h = CGImageGetHeight(image);
let mut pixels = vec![0u8; w * h * 4];
let space = CGColorSpaceCreateDeviceRGB();
let ctx = CGBitmapContextCreate(
pixels.as_mut_ptr() as *mut c_void,
w,
h,
8,
w * 4,
space,
RGBA8,
);
assert!(!ctx.is_null(), "could not create a {w}x{h} bitmap context");
CGContextDrawImage(
ctx,
CGRect {
origin: CGPoint { x: 0.0, y: 0.0 },
size: CGSize { width: w as f64, height: h as f64 },
},
image,
);
CGContextRelease(ctx);
CGColorSpaceRelease(space);
(w as u32, h as u32, pixels)
}
}
pub unsafe fn rgba_to_cgimage(width: u32, height: u32, pixels: &[u8]) -> *mut c_void {
unsafe {
let (w, h) = (width as usize, height as usize);
let mut copy = pixels[..w * h * 4].to_vec();
let space = CGColorSpaceCreateDeviceRGB();
let ctx = CGBitmapContextCreate(
copy.as_mut_ptr() as *mut c_void,
w,
h,
8,
w * 4,
space,
RGBA8,
);
let image = if ctx.is_null() { core::ptr::null_mut() } else { CGBitmapContextCreateImage(ctx) };
if !ctx.is_null() {
CGContextRelease(ctx);
}
CGColorSpaceRelease(space);
image
}
}
pub fn write_png(width: u32, height: u32, pixels: &[u8], path: &str) -> bool {
unsafe {
let image = rgba_to_cgimage(width, height, pixels);
if image.is_null() {
return false;
}
let url = CFURLCreateFromFileSystemRepresentation(
core::ptr::null(),
path.as_ptr(),
path.len() as isize,
false,
);
let png = CFStringCreateWithCString(core::ptr::null(), c"public.png".as_ptr(), UTF8);
let dest = CGImageDestinationCreateWithURL(url, png, 1, core::ptr::null());
let ok = if dest.is_null() {
false
} else {
CGImageDestinationAddImage(dest, image, core::ptr::null());
let ok = CGImageDestinationFinalize(dest);
CFRelease(dest as *const c_void);
ok
};
CFRelease(png);
CFRelease(url);
CGImageRelease(image);
ok
}
}
pub unsafe fn capture_layer(layer: *mut c_void, width: u32, height: u32) -> Vec<u8> {
unsafe {
let (w, h) = (width as usize, height as usize);
let mut pixels = vec![0u8; w * h * 4];
let space = CGColorSpaceCreateDeviceRGB();
let ctx = CGBitmapContextCreate(
pixels.as_mut_ptr() as *mut c_void,
w,
h,
8,
w * 4,
space,
RGBA8,
);
assert!(!ctx.is_null(), "could not create a {w}x{h} bitmap context");
CGContextTranslateCTM(ctx, 0.0, h as f64);
CGContextScaleCTM(ctx, 1.0, -1.0);
crate::objc::send_id(layer, crate::objc::sel(c"renderInContext:"), ctx);
CGContextRelease(ctx);
CGColorSpaceRelease(space);
pixels
}
}