Skip to main content

apple_cf/cg/
drawing.rs

1//! `CGColorSpace` and `CGImage` — the most-used Core Graphics drawing types.
2//!
3//! These are RAII wrappers around `CFType`-style references with
4//! retain/release. Use them with [`crate::cg::CGContext`] for offscreen
5//! rasterisation, or for converting between formats via `ImageIO`.
6
7use core::ffi::c_void;
8use core::ptr;
9use std::ffi::CString;
10use std::io;
11use std::os::unix::ffi::OsStrExt;
12use std::path::Path;
13
14use crate::ffi as bridge_ffi;
15
16use super::ffi as cg_ffi;
17
18/// Reference-counted `CGColorSpaceRef`.
19pub struct CGColorSpace {
20    ptr: *mut c_void,
21}
22
23// SAFETY: `CGColorSpaceRef` is an immutable, reference-counted Core Foundation
24// object.  All mutations go through Apple's thread-safe retain/release
25// primitives; sending or sharing the opaque pointer across threads is safe.
26unsafe impl Send for CGColorSpace {}
27unsafe impl Sync for CGColorSpace {}
28
29crate::utils::retained::cf_retained!(
30    CGColorSpace,
31    field = ptr,
32    retain = cg_ffi::CGColorSpaceRetain,
33    release = cg_ffi::CGColorSpaceRelease,
34    drop = null_out,
35);
36
37impl std::fmt::Debug for CGColorSpace {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        f.debug_struct("CGColorSpace")
40            .field("ptr", &self.ptr)
41            .finish()
42    }
43}
44
45impl CGColorSpace {
46    /// Wrap a raw `CGColorSpaceRef` pointer — takes ownership without retaining.
47    ///
48    /// # Safety
49    ///
50    /// `ptr` must be a non-null `CGColorSpaceRef` whose ownership the caller is
51    /// transferring to the returned [`CGColorSpace`].
52    #[must_use]
53    pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
54        Self { ptr }
55    }
56
57    /// Device RGB.
58    #[must_use]
59    pub fn device_rgb() -> Self {
60        Self {
61            ptr: unsafe { cg_ffi::CGColorSpaceCreateDeviceRGB() },
62        }
63    }
64
65    /// Device gray.
66    #[must_use]
67    pub fn device_gray() -> Self {
68        Self {
69            ptr: unsafe { cg_ffi::CGColorSpaceCreateDeviceGray() },
70        }
71    }
72
73    /// sRGB.
74    #[must_use]
75    pub fn srgb() -> Self {
76        unsafe {
77            let n = CFStringCreateWithCStringLite(b"kCGColorSpaceSRGB\0".as_ptr());
78            let p = cg_ffi::CGColorSpaceCreateWithName(n);
79            CFReleaseLite(n);
80            Self { ptr: p }
81        }
82    }
83
84    /// Display P3.
85    #[must_use]
86    pub fn display_p3() -> Self {
87        unsafe {
88            let n = CFStringCreateWithCStringLite(b"kCGColorSpaceDisplayP3\0".as_ptr());
89            let p = cg_ffi::CGColorSpaceCreateWithName(n);
90            CFReleaseLite(n);
91            Self { ptr: p }
92        }
93    }
94
95    /// Number of color components (`3` for RGB, `1` for gray, …).
96    #[must_use]
97    pub fn number_of_components(&self) -> usize {
98        unsafe { cg_ffi::CGColorSpaceGetNumberOfComponents(self.ptr) }
99    }
100
101    /// Raw `CGColorSpaceRef` pointer.
102    #[must_use]
103    pub const fn as_ptr(&self) -> *mut c_void {
104        self.ptr
105    }
106}
107
108/// Reference-counted `CGImageRef` — an immutable bitmap.
109pub struct CGImage {
110    ptr: *mut c_void,
111}
112
113// SAFETY: `CGImageRef` is an immutable, reference-counted Core Foundation
114// object.  Apple's retain/release primitives are thread-safe, and the image
115// data itself is read-only after creation.
116unsafe impl Send for CGImage {}
117unsafe impl Sync for CGImage {}
118
119crate::utils::retained::cf_retained!(
120    CGImage,
121    field = ptr,
122    retain = cg_ffi::CGImageRetain,
123    release = cg_ffi::CGImageRelease,
124    drop = null_out,
125);
126
127impl std::fmt::Debug for CGImage {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        f.debug_struct("CGImage").field("ptr", &self.ptr).finish()
130    }
131}
132
133impl CGImage {
134    /// Wrap a raw `CGImageRef` pointer — takes ownership without retaining.
135    ///
136    /// # Safety
137    ///
138    /// `ptr` must be a non-null `CGImageRef` whose ownership the caller is
139    /// transferring to the returned [`CGImage`].
140    #[must_use]
141    pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
142        Self { ptr }
143    }
144
145    /// Width in pixels.
146    #[must_use]
147    pub fn width(&self) -> usize {
148        unsafe { cg_ffi::CGImageGetWidth(self.ptr) }
149    }
150
151    /// Height in pixels.
152    #[must_use]
153    pub fn height(&self) -> usize {
154        unsafe { cg_ffi::CGImageGetHeight(self.ptr) }
155    }
156
157    /// Bits per component (`8`, `16`, `32`).
158    #[must_use]
159    pub fn bits_per_component(&self) -> usize {
160        unsafe { cg_ffi::CGImageGetBitsPerComponent(self.ptr) }
161    }
162
163    /// Bits per pixel.
164    #[must_use]
165    pub fn bits_per_pixel(&self) -> usize {
166        unsafe { cg_ffi::CGImageGetBitsPerPixel(self.ptr) }
167    }
168
169    /// Bytes per row.
170    #[must_use]
171    pub fn bytes_per_row(&self) -> usize {
172        unsafe { cg_ffi::CGImageGetBytesPerRow(self.ptr) }
173    }
174
175    /// Save the image as a PNG file.
176    ///
177    /// # Errors
178    ///
179    /// Returns an I/O error if the path contains an interior NUL byte or if
180    /// the underlying `ImageIO` export fails.
181    pub fn save_png<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
182        let c_path = CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|_| {
183            io::Error::new(
184                io::ErrorKind::InvalidInput,
185                "path contains an interior NUL byte",
186            )
187        })?;
188
189        if unsafe { bridge_ffi::cgimage_save_png(self.ptr, c_path.as_ptr()) } {
190            Ok(())
191        } else {
192            Err(io::Error::other("cgimage_save_png returned false"))
193        }
194    }
195
196    /// Raw `CGImageRef` pointer.
197    #[must_use]
198    pub const fn as_ptr(&self) -> *mut c_void {
199        self.ptr
200    }
201}
202
203extern "C" {
204    fn CFStringCreateWithCString(
205        allocator: *const c_void,
206        bytes: *const u8,
207        encoding: u32,
208    ) -> *mut c_void;
209    fn CFRelease(cf: *const c_void);
210}
211
212#[allow(non_snake_case)]
213unsafe fn CFStringCreateWithCStringLite(bytes: *const u8) -> *const c_void {
214    CFStringCreateWithCString(ptr::null(), bytes, 0x0800_0100).cast_const()
215}
216
217#[allow(non_snake_case)]
218unsafe fn CFReleaseLite(p: *const c_void) {
219    CFRelease(p);
220}