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 std::fmt::Debug for CGColorSpace {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        f.debug_struct("CGColorSpace")
48            .field("ptr", &self.ptr)
49            .finish()
50    }
51}
52
53impl CGColorSpace {
54    /// Wrap a raw `CGColorSpaceRef` pointer — takes ownership without retaining.
55    ///
56    /// # Safety
57    ///
58    /// `ptr` must be a non-null `CGColorSpaceRef` whose ownership the caller is
59    /// transferring to the returned [`CGColorSpace`].
60    #[must_use]
61    pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
62        Self { ptr }
63    }
64
65    /// Device RGB.
66    #[must_use]
67    pub fn device_rgb() -> Self {
68        Self {
69            ptr: unsafe { cg_ffi::CGColorSpaceCreateDeviceRGB() },
70        }
71    }
72
73    /// Device gray.
74    #[must_use]
75    pub fn device_gray() -> Self {
76        Self {
77            ptr: unsafe { cg_ffi::CGColorSpaceCreateDeviceGray() },
78        }
79    }
80
81    /// sRGB.
82    #[must_use]
83    pub fn srgb() -> Self {
84        unsafe {
85            let n = CFStringCreateWithCStringLite(b"kCGColorSpaceSRGB\0".as_ptr());
86            let p = cg_ffi::CGColorSpaceCreateWithName(n);
87            CFReleaseLite(n);
88            Self { ptr: p }
89        }
90    }
91
92    /// Display P3.
93    #[must_use]
94    pub fn display_p3() -> Self {
95        unsafe {
96            let n = CFStringCreateWithCStringLite(b"kCGColorSpaceDisplayP3\0".as_ptr());
97            let p = cg_ffi::CGColorSpaceCreateWithName(n);
98            CFReleaseLite(n);
99            Self { ptr: p }
100        }
101    }
102
103    /// Number of color components (`3` for RGB, `1` for gray, …).
104    #[must_use]
105    pub fn number_of_components(&self) -> usize {
106        unsafe { cg_ffi::CGColorSpaceGetNumberOfComponents(self.ptr) }
107    }
108
109    /// Raw `CGColorSpaceRef` pointer.
110    #[must_use]
111    pub const fn as_ptr(&self) -> *mut c_void {
112        self.ptr
113    }
114}
115
116/// Reference-counted `CGImageRef` — an immutable bitmap.
117pub struct CGImage {
118    ptr: *mut c_void,
119}
120
121// SAFETY: `CGImageRef` is an immutable, reference-counted Core Foundation
122// object.  Apple's retain/release primitives are thread-safe, and the image
123// data itself is read-only after creation.
124unsafe impl Send for CGImage {}
125unsafe impl Sync for CGImage {}
126
127impl Drop for CGImage {
128    fn drop(&mut self) {
129        if !self.ptr.is_null() {
130            unsafe { cg_ffi::CGImageRelease(self.ptr) };
131            self.ptr = ptr::null_mut();
132        }
133    }
134}
135
136impl Clone for CGImage {
137    fn clone(&self) -> Self {
138        let p = unsafe { cg_ffi::CGImageRetain(self.ptr) };
139        Self { ptr: p }
140    }
141}
142
143impl std::fmt::Debug for CGImage {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        f.debug_struct("CGImage").field("ptr", &self.ptr).finish()
146    }
147}
148
149impl CGImage {
150    /// Wrap a raw `CGImageRef` pointer — takes ownership without retaining.
151    ///
152    /// # Safety
153    ///
154    /// `ptr` must be a non-null `CGImageRef` whose ownership the caller is
155    /// transferring to the returned [`CGImage`].
156    #[must_use]
157    pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
158        Self { ptr }
159    }
160
161    /// Width in pixels.
162    #[must_use]
163    pub fn width(&self) -> usize {
164        unsafe { cg_ffi::CGImageGetWidth(self.ptr) }
165    }
166
167    /// Height in pixels.
168    #[must_use]
169    pub fn height(&self) -> usize {
170        unsafe { cg_ffi::CGImageGetHeight(self.ptr) }
171    }
172
173    /// Bits per component (`8`, `16`, `32`).
174    #[must_use]
175    pub fn bits_per_component(&self) -> usize {
176        unsafe { cg_ffi::CGImageGetBitsPerComponent(self.ptr) }
177    }
178
179    /// Bits per pixel.
180    #[must_use]
181    pub fn bits_per_pixel(&self) -> usize {
182        unsafe { cg_ffi::CGImageGetBitsPerPixel(self.ptr) }
183    }
184
185    /// Bytes per row.
186    #[must_use]
187    pub fn bytes_per_row(&self) -> usize {
188        unsafe { cg_ffi::CGImageGetBytesPerRow(self.ptr) }
189    }
190
191    /// Save the image as a PNG file.
192    ///
193    /// # Errors
194    ///
195    /// Returns an I/O error if the path contains an interior NUL byte or if
196    /// the underlying `ImageIO` export fails.
197    pub fn save_png<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
198        let c_path = CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|_| {
199            io::Error::new(
200                io::ErrorKind::InvalidInput,
201                "path contains an interior NUL byte",
202            )
203        })?;
204
205        if unsafe { bridge_ffi::cgimage_save_png(self.ptr, c_path.as_ptr()) } {
206            Ok(())
207        } else {
208            Err(io::Error::new(
209                io::ErrorKind::Other,
210                "cgimage_save_png returned false",
211            ))
212        }
213    }
214
215    /// Raw `CGImageRef` pointer.
216    #[must_use]
217    pub const fn as_ptr(&self) -> *mut c_void {
218        self.ptr
219    }
220}
221
222extern "C" {
223    fn CFStringCreateWithCString(
224        allocator: *const c_void,
225        bytes: *const u8,
226        encoding: u32,
227    ) -> *mut c_void;
228    fn CFRelease(cf: *const c_void);
229}
230
231#[allow(non_snake_case)]
232unsafe fn CFStringCreateWithCStringLite(bytes: *const u8) -> *const c_void {
233    CFStringCreateWithCString(ptr::null(), bytes, 0x0800_0100).cast_const()
234}
235
236#[allow(non_snake_case)]
237unsafe fn CFReleaseLite(p: *const c_void) {
238    CFRelease(p);
239}