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
29impl Drop for CGColorSpace {
30    fn drop(&mut self) {
31        if !self.ptr.is_null() {
32            unsafe { cg_ffi::CGColorSpaceRelease(self.ptr) };
33            self.ptr = ptr::null_mut();
34        }
35    }
36}
37
38impl Clone for CGColorSpace {
39    fn clone(&self) -> Self {
40        let p = unsafe { cg_ffi::CGColorSpaceRetain(self.ptr) };
41        Self { ptr: p }
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
119impl Drop for CGImage {
120    fn drop(&mut self) {
121        if !self.ptr.is_null() {
122            unsafe { cg_ffi::CGImageRelease(self.ptr) };
123            self.ptr = ptr::null_mut();
124        }
125    }
126}
127
128impl Clone for CGImage {
129    fn clone(&self) -> Self {
130        let p = unsafe { cg_ffi::CGImageRetain(self.ptr) };
131        Self { ptr: p }
132    }
133}
134
135impl CGImage {
136    /// Wrap a raw `CGImageRef` pointer — takes ownership without retaining.
137    ///
138    /// # Safety
139    ///
140    /// `ptr` must be a non-null `CGImageRef` whose ownership the caller is
141    /// transferring to the returned [`CGImage`].
142    #[must_use]
143    pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
144        Self { ptr }
145    }
146
147    /// Width in pixels.
148    #[must_use]
149    pub fn width(&self) -> usize {
150        unsafe { cg_ffi::CGImageGetWidth(self.ptr) }
151    }
152
153    /// Height in pixels.
154    #[must_use]
155    pub fn height(&self) -> usize {
156        unsafe { cg_ffi::CGImageGetHeight(self.ptr) }
157    }
158
159    /// Bits per component (`8`, `16`, `32`).
160    #[must_use]
161    pub fn bits_per_component(&self) -> usize {
162        unsafe { cg_ffi::CGImageGetBitsPerComponent(self.ptr) }
163    }
164
165    /// Bits per pixel.
166    #[must_use]
167    pub fn bits_per_pixel(&self) -> usize {
168        unsafe { cg_ffi::CGImageGetBitsPerPixel(self.ptr) }
169    }
170
171    /// Bytes per row.
172    #[must_use]
173    pub fn bytes_per_row(&self) -> usize {
174        unsafe { cg_ffi::CGImageGetBytesPerRow(self.ptr) }
175    }
176
177    /// Save the image as a PNG file.
178    ///
179    /// # Errors
180    ///
181    /// Returns an I/O error if the path contains an interior NUL byte or if
182    /// the underlying `ImageIO` export fails.
183    pub fn save_png<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
184        let c_path = CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|_| {
185            io::Error::new(
186                io::ErrorKind::InvalidInput,
187                "path contains an interior NUL byte",
188            )
189        })?;
190
191        if unsafe { bridge_ffi::cgimage_save_png(self.ptr, c_path.as_ptr()) } {
192            Ok(())
193        } else {
194            Err(io::Error::new(
195                io::ErrorKind::Other,
196                "cgimage_save_png returned false",
197            ))
198        }
199    }
200
201    /// Raw `CGImageRef` pointer.
202    #[must_use]
203    pub const fn as_ptr(&self) -> *mut c_void {
204        self.ptr
205    }
206}
207
208extern "C" {
209    fn CFStringCreateWithCString(
210        allocator: *const c_void,
211        bytes: *const u8,
212        encoding: u32,
213    ) -> *mut c_void;
214    fn CFRelease(cf: *const c_void);
215}
216
217#[allow(non_snake_case)]
218unsafe fn CFStringCreateWithCStringLite(bytes: *const u8) -> *const c_void {
219    CFStringCreateWithCString(ptr::null(), bytes, 0x0800_0100).cast_const()
220}
221
222#[allow(non_snake_case)]
223unsafe fn CFReleaseLite(p: *const c_void) {
224    CFRelease(p);
225}