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