we (web engine): Experimental web browser project to understand the limits of Claude
1//! CoreGraphics FFI bindings for bitmap rendering.
2//!
3//! Provides wrappers around `CGColorSpace`, `CGContext` (bitmap contexts),
4//! and `CGImage` for software rendering into a Rust-owned pixel buffer.
5//!
6//! # Safety
7//!
8//! This module contains `unsafe` code for FFI with CoreGraphics.
9//! The `platform` crate is one of the few crates where `unsafe` is permitted.
10
11use std::os::raw::c_void;
12use std::ptr::NonNull;
13
14// ---------------------------------------------------------------------------
15// CoreGraphics framework link
16// ---------------------------------------------------------------------------
17
18#[link(name = "CoreGraphics", kind = "framework")]
19extern "C" {
20 fn CGColorSpaceCreateDeviceRGB() -> *mut c_void;
21 fn CGColorSpaceRelease(space: *mut c_void);
22
23 fn CGBitmapContextCreate(
24 data: *mut c_void,
25 width: usize,
26 height: usize,
27 bits_per_component: usize,
28 bytes_per_row: usize,
29 colorspace: *mut c_void,
30 bitmap_info: u32,
31 ) -> *mut c_void;
32
33 fn CGBitmapContextCreateImage(context: *mut c_void) -> *mut c_void;
34
35 fn CGContextRelease(context: *mut c_void);
36 fn CGImageRelease(image: *mut c_void);
37
38 fn CGContextDrawImage(context: *mut c_void, rect: CGRect, image: *mut c_void);
39 fn CGContextSetRGBFillColor(context: *mut c_void, red: f64, green: f64, blue: f64, alpha: f64);
40 fn CGContextFillRect(context: *mut c_void, rect: CGRect);
41}
42
43// ---------------------------------------------------------------------------
44// Geometry (re-export compatible with appkit types)
45// ---------------------------------------------------------------------------
46
47/// `CGRect` — a rectangle defined by origin and size.
48///
49/// This is layout-compatible with `NSRect` / the appkit `NSRect` type.
50#[repr(C)]
51#[derive(Debug, Clone, Copy)]
52pub struct CGRect {
53 pub origin: CGPoint,
54 pub size: CGSize,
55}
56
57/// `CGPoint` — a point in 2D space.
58#[repr(C)]
59#[derive(Debug, Clone, Copy)]
60pub struct CGPoint {
61 pub x: f64,
62 pub y: f64,
63}
64
65/// `CGSize` — a 2D size.
66#[repr(C)]
67#[derive(Debug, Clone, Copy)]
68pub struct CGSize {
69 pub width: f64,
70 pub height: f64,
71}
72
73impl CGRect {
74 /// Create a new rectangle.
75 pub fn new(x: f64, y: f64, width: f64, height: f64) -> CGRect {
76 CGRect {
77 origin: CGPoint { x, y },
78 size: CGSize { width, height },
79 }
80 }
81}
82
83// ---------------------------------------------------------------------------
84// CGBitmapInfo constants
85// ---------------------------------------------------------------------------
86
87/// Alpha info mask (lower 5 bits of CGBitmapInfo).
88pub const K_CG_IMAGE_ALPHA_PREMULTIPLIED_FIRST: u32 = 2;
89
90/// Byte order mask: 32-bit host byte order.
91pub const K_CG_BITMAP_BYTE_ORDER_32_HOST: u32 = 0;
92
93/// Standard BGRA premultiplied alpha format used by macOS windowing.
94///
95/// Pixel layout in memory on little-endian (ARM64): B, G, R, A
96/// This matches what CoreGraphics and AppKit expect for display.
97pub const BITMAP_INFO_PREMULTIPLIED_FIRST: u32 =
98 K_CG_IMAGE_ALPHA_PREMULTIPLIED_FIRST | K_CG_BITMAP_BYTE_ORDER_32_HOST;
99
100// ---------------------------------------------------------------------------
101// ColorSpace
102// ---------------------------------------------------------------------------
103
104/// An owned CoreGraphics device RGB color space.
105pub struct ColorSpace(NonNull<c_void>);
106
107impl ColorSpace {
108 /// Create a device RGB color space.
109 pub fn device_rgb() -> Option<ColorSpace> {
110 let ptr = unsafe { CGColorSpaceCreateDeviceRGB() };
111 NonNull::new(ptr).map(ColorSpace)
112 }
113
114 /// Return the raw pointer.
115 #[inline]
116 pub fn as_ptr(&self) -> *mut c_void {
117 self.0.as_ptr()
118 }
119}
120
121impl Drop for ColorSpace {
122 fn drop(&mut self) {
123 unsafe { CGColorSpaceRelease(self.as_ptr()) }
124 }
125}
126
127// ---------------------------------------------------------------------------
128// BitmapContext — a CGBitmapContext backed by a Rust-owned pixel buffer
129// ---------------------------------------------------------------------------
130
131/// A CoreGraphics bitmap context backed by a Rust-owned pixel buffer.
132///
133/// The pixel buffer is BGRA, 8 bits per component, premultiplied alpha.
134/// The buffer is owned by this struct and the `CGBitmapContext` renders into it.
135pub struct BitmapContext {
136 context: NonNull<c_void>,
137 buffer: Vec<u8>,
138 width: usize,
139 height: usize,
140}
141
142impl BitmapContext {
143 /// Create a new bitmap context with the given dimensions.
144 ///
145 /// Allocates a Rust-owned pixel buffer and wraps it in a CGBitmapContext.
146 /// Pixel format: 4 bytes per pixel (BGRA, premultiplied alpha).
147 pub fn new(width: usize, height: usize) -> Option<BitmapContext> {
148 if width == 0 || height == 0 {
149 return None;
150 }
151
152 let bytes_per_row = width * 4;
153 let mut buffer = vec![0u8; bytes_per_row * height];
154 let color_space = ColorSpace::device_rgb()?;
155
156 let ctx = unsafe {
157 CGBitmapContextCreate(
158 buffer.as_mut_ptr() as *mut c_void,
159 width,
160 height,
161 8, // bits per component
162 bytes_per_row,
163 color_space.as_ptr(),
164 BITMAP_INFO_PREMULTIPLIED_FIRST,
165 )
166 };
167
168 let context = NonNull::new(ctx)?;
169 Some(BitmapContext {
170 context,
171 buffer,
172 width,
173 height,
174 })
175 }
176
177 /// Get the width in pixels.
178 #[inline]
179 pub fn width(&self) -> usize {
180 self.width
181 }
182
183 /// Get the height in pixels.
184 #[inline]
185 pub fn height(&self) -> usize {
186 self.height
187 }
188
189 /// Get the bytes per row (stride).
190 #[inline]
191 pub fn bytes_per_row(&self) -> usize {
192 self.width * 4
193 }
194
195 /// Get a shared reference to the raw pixel buffer.
196 ///
197 /// Pixel format: BGRA, 8 bits per component, premultiplied alpha.
198 /// Layout: row-major, bottom-to-top (CG default coordinate system).
199 #[inline]
200 pub fn pixels(&self) -> &[u8] {
201 &self.buffer
202 }
203
204 /// Get a mutable reference to the raw pixel buffer.
205 ///
206 /// After modifying pixels directly, changes are immediately visible to
207 /// CoreGraphics since the bitmap context wraps this buffer.
208 #[inline]
209 pub fn pixels_mut(&mut self) -> &mut [u8] {
210 &mut self.buffer
211 }
212
213 /// Create a `CGImage` from the current bitmap context contents.
214 ///
215 /// Returns `None` if the image cannot be created.
216 pub fn create_image(&self) -> Option<Image> {
217 let img = unsafe { CGBitmapContextCreateImage(self.context.as_ptr()) };
218 NonNull::new(img).map(Image)
219 }
220
221 /// Fill a rectangle with an RGBA color using CoreGraphics.
222 ///
223 /// Color components are in 0.0..=1.0 range.
224 pub fn fill_rect(&self, rect: CGRect, r: f64, g: f64, b: f64, a: f64) {
225 unsafe {
226 CGContextSetRGBFillColor(self.context.as_ptr(), r, g, b, a);
227 CGContextFillRect(self.context.as_ptr(), rect);
228 }
229 }
230
231 /// Clear the entire buffer to a given RGBA color.
232 ///
233 /// Color components are in 0.0..=1.0 range. This uses CoreGraphics
234 /// `CGContextFillRect` to fill the entire bitmap.
235 pub fn clear(&self, r: f64, g: f64, b: f64, a: f64) {
236 let rect = CGRect::new(0.0, 0.0, self.width as f64, self.height as f64);
237 self.fill_rect(rect, r, g, b, a);
238 }
239
240 /// Return the raw CGContextRef pointer.
241 ///
242 /// Useful for passing to other CG drawing functions.
243 #[inline]
244 pub fn as_ptr(&self) -> *mut c_void {
245 self.context.as_ptr()
246 }
247}
248
249impl Drop for BitmapContext {
250 fn drop(&mut self) {
251 unsafe { CGContextRelease(self.context.as_ptr()) }
252 }
253}
254
255// ---------------------------------------------------------------------------
256// Image — an owned CGImage
257// ---------------------------------------------------------------------------
258
259/// An owned CoreGraphics image (CGImageRef).
260///
261/// Created from a [`BitmapContext`] via [`BitmapContext::create_image`].
262pub struct Image(NonNull<c_void>);
263
264impl Image {
265 /// Return the raw CGImageRef pointer.
266 #[inline]
267 pub fn as_ptr(&self) -> *mut c_void {
268 self.0.as_ptr()
269 }
270}
271
272impl Drop for Image {
273 fn drop(&mut self) {
274 unsafe { CGImageRelease(self.as_ptr()) }
275 }
276}
277
278// ---------------------------------------------------------------------------
279// Drawing a CGImage into a CGContext
280// ---------------------------------------------------------------------------
281
282/// Draw a `CGImage` into a CoreGraphics context at the given rectangle.
283///
284/// This is used in `drawRect:` to blit the bitmap into the view's context.
285///
286/// # Safety
287///
288/// `target_context` must be a valid CGContextRef (e.g., from
289/// `NSGraphicsContext.currentContext.CGContext`).
290pub unsafe fn draw_image_in_context(target_context: *mut c_void, rect: CGRect, image: &Image) {
291 CGContextDrawImage(target_context, rect, image.as_ptr());
292}
293
294// ---------------------------------------------------------------------------
295// Tests
296// ---------------------------------------------------------------------------
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn cgrect_new() {
304 let rect = CGRect::new(10.0, 20.0, 300.0, 400.0);
305 assert_eq!(rect.origin.x, 10.0);
306 assert_eq!(rect.origin.y, 20.0);
307 assert_eq!(rect.size.width, 300.0);
308 assert_eq!(rect.size.height, 400.0);
309 }
310
311 #[test]
312 fn bitmap_info_constant() {
313 assert_eq!(BITMAP_INFO_PREMULTIPLIED_FIRST, 2);
314 }
315
316 #[test]
317 fn create_color_space() {
318 let cs = ColorSpace::device_rgb().expect("should create device RGB color space");
319 assert!(!cs.as_ptr().is_null());
320 }
321
322 #[test]
323 fn create_bitmap_context() {
324 let ctx = BitmapContext::new(100, 100).expect("should create bitmap context");
325 assert_eq!(ctx.width(), 100);
326 assert_eq!(ctx.height(), 100);
327 assert_eq!(ctx.bytes_per_row(), 400);
328 assert_eq!(ctx.pixels().len(), 100 * 400);
329 }
330
331 #[test]
332 fn bitmap_context_zero_size_returns_none() {
333 assert!(BitmapContext::new(0, 100).is_none());
334 assert!(BitmapContext::new(100, 0).is_none());
335 assert!(BitmapContext::new(0, 0).is_none());
336 }
337
338 #[test]
339 fn bitmap_context_pixels_mut() {
340 let mut ctx = BitmapContext::new(10, 10).expect("should create");
341 let pixels = ctx.pixels_mut();
342 // Write a red pixel at (0, 0): BGRA format
343 pixels[0] = 0; // B
344 pixels[1] = 0; // G
345 pixels[2] = 255; // R
346 pixels[3] = 255; // A
347 assert_eq!(ctx.pixels()[0], 0);
348 assert_eq!(ctx.pixels()[2], 255);
349 }
350
351 #[test]
352 fn bitmap_context_clear_and_create_image() {
353 let ctx = BitmapContext::new(50, 50).expect("should create");
354 ctx.clear(0.0, 0.0, 1.0, 1.0); // blue
355 let img = ctx.create_image().expect("should create image");
356 assert!(!img.as_ptr().is_null());
357 }
358
359 #[test]
360 fn bitmap_context_fill_rect() {
361 let ctx = BitmapContext::new(100, 100).expect("should create");
362 let rect = CGRect::new(10.0, 10.0, 50.0, 50.0);
363 ctx.fill_rect(rect, 1.0, 0.0, 0.0, 1.0); // red rectangle
364 let img = ctx
365 .create_image()
366 .expect("should create image from filled context");
367 assert!(!img.as_ptr().is_null());
368 }
369}