1use core::ffi::{c_char, c_void};
14
15unsafe extern "C" {
16 fn CGImageGetWidth(image: *mut c_void) -> usize;
17 fn CGImageGetHeight(image: *mut c_void) -> usize;
18 fn CGColorSpaceCreateDeviceRGB() -> *mut c_void;
19 fn CGColorSpaceRelease(space: *mut c_void);
20 fn CGBitmapContextCreate(
21 data: *mut c_void,
22 width: usize,
23 height: usize,
24 bits_per_component: usize,
25 bytes_per_row: usize,
26 space: *mut c_void,
27 bitmap_info: u32,
28 ) -> *mut c_void;
29 fn CGBitmapContextCreateImage(ctx: *mut c_void) -> *mut c_void;
30 fn CGContextDrawImage(ctx: *mut c_void, rect: CGRect, image: *mut c_void);
31 fn CGContextRelease(ctx: *mut c_void);
32 fn CGContextTranslateCTM(ctx: *mut c_void, tx: f64, ty: f64);
33 fn CGContextScaleCTM(ctx: *mut c_void, sx: f64, sy: f64);
34 fn CGImageRelease(image: *mut c_void);
35
36 fn CFStringCreateWithCString(alloc: *const c_void, s: *const c_char, enc: u32) -> *const c_void;
37 fn CFURLCreateFromFileSystemRepresentation(
38 alloc: *const c_void,
39 path: *const u8,
40 len: isize,
41 is_dir: bool,
42 ) -> *const c_void;
43 fn CFRelease(o: *const c_void);
44 fn CGImageDestinationCreateWithURL(
45 url: *const c_void,
46 ty: *const c_void,
47 count: usize,
48 options: *const c_void,
49 ) -> *mut c_void;
50 fn CGImageDestinationAddImage(dest: *mut c_void, image: *mut c_void, props: *const c_void);
51 fn CGImageDestinationFinalize(dest: *mut c_void) -> bool;
52}
53
54#[repr(C)]
55struct CGPoint {
56 x: f64,
57 y: f64,
58}
59#[repr(C)]
60struct CGSize {
61 width: f64,
62 height: f64,
63}
64#[repr(C)]
65struct CGRect {
66 origin: CGPoint,
67 size: CGSize,
68}
69
70const RGBA8: u32 = 1 | (4 << 12);
76const UTF8: u32 = 0x0800_0100;
77
78pub unsafe fn cgimage_to_rgba(image: *mut c_void) -> (u32, u32, Vec<u8>) {
83 unsafe {
84 let w = CGImageGetWidth(image);
85 let h = CGImageGetHeight(image);
86 let mut pixels = vec![0u8; w * h * 4];
87 let space = CGColorSpaceCreateDeviceRGB();
88 let ctx = CGBitmapContextCreate(
89 pixels.as_mut_ptr() as *mut c_void,
90 w,
91 h,
92 8,
93 w * 4,
94 space,
95 RGBA8,
96 );
97 assert!(!ctx.is_null(), "could not create a {w}x{h} bitmap context");
98 CGContextDrawImage(
99 ctx,
100 CGRect {
101 origin: CGPoint { x: 0.0, y: 0.0 },
102 size: CGSize { width: w as f64, height: h as f64 },
103 },
104 image,
105 );
106 CGContextRelease(ctx);
107 CGColorSpaceRelease(space);
108 (w as u32, h as u32, pixels)
109 }
110}
111
112pub unsafe fn rgba_to_cgimage(width: u32, height: u32, pixels: &[u8]) -> *mut c_void {
117 unsafe {
118 let (w, h) = (width as usize, height as usize);
119 let mut copy = pixels[..w * h * 4].to_vec();
120 let space = CGColorSpaceCreateDeviceRGB();
121 let ctx = CGBitmapContextCreate(
122 copy.as_mut_ptr() as *mut c_void,
123 w,
124 h,
125 8,
126 w * 4,
127 space,
128 RGBA8,
129 );
130 let image = if ctx.is_null() { core::ptr::null_mut() } else { CGBitmapContextCreateImage(ctx) };
131 if !ctx.is_null() {
132 CGContextRelease(ctx);
133 }
134 CGColorSpaceRelease(space);
135 image
136 }
137}
138
139pub fn write_png(width: u32, height: u32, pixels: &[u8], path: &str) -> bool {
141 unsafe {
142 let image = rgba_to_cgimage(width, height, pixels);
143 if image.is_null() {
144 return false;
145 }
146 let url = CFURLCreateFromFileSystemRepresentation(
147 core::ptr::null(),
148 path.as_ptr(),
149 path.len() as isize,
150 false,
151 );
152 let png = CFStringCreateWithCString(core::ptr::null(), c"public.png".as_ptr(), UTF8);
153 let dest = CGImageDestinationCreateWithURL(url, png, 1, core::ptr::null());
154 let ok = if dest.is_null() {
155 false
156 } else {
157 CGImageDestinationAddImage(dest, image, core::ptr::null());
158 let ok = CGImageDestinationFinalize(dest);
159 CFRelease(dest as *const c_void);
160 ok
161 };
162 CFRelease(png);
163 CFRelease(url);
164 CGImageRelease(image);
165 ok
166 }
167}
168
169pub unsafe fn capture_layer(layer: *mut c_void, width: u32, height: u32) -> Vec<u8> {
178 unsafe {
179 let (w, h) = (width as usize, height as usize);
180 let mut pixels = vec![0u8; w * h * 4];
181 let space = CGColorSpaceCreateDeviceRGB();
182 let ctx = CGBitmapContextCreate(
183 pixels.as_mut_ptr() as *mut c_void,
184 w,
185 h,
186 8,
187 w * 4,
188 space,
189 RGBA8,
190 );
191 assert!(!ctx.is_null(), "could not create a {w}x{h} bitmap context");
192 CGContextTranslateCTM(ctx, 0.0, h as f64);
193 CGContextScaleCTM(ctx, 1.0, -1.0);
194 crate::objc::send_id(layer, crate::objc::sel(c"renderInContext:"), ctx);
195 CGContextRelease(ctx);
196 CGColorSpaceRelease(space);
197 pixels
198 }
199}