we (web engine): Experimental web browser project to understand the limits of Claude
at css-loading 369 lines 12 kB view raw
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}