Skip to main content

apple_cf/cg/
context.rs

1//! `CGContext` — offscreen bitmap drawing with Core Graphics.
2
3use core::ffi::c_void;
4use core::ptr;
5
6use crate::CFError;
7
8use super::drawing::{CGColorSpace, CGImage};
9use super::ffi;
10use super::CGRect;
11
12const BITS_PER_COMPONENT_8: usize = 8;
13const CG_IMAGE_ALPHA_NONE: u32 = 0;
14const CG_IMAGE_ALPHA_PREMULTIPLIED_LAST: u32 = 1;
15const CG_BITMAP_BYTE_ORDER_32_BIG: u32 = 16_384;
16const RGBA8_BITMAP_INFO: u32 = CG_IMAGE_ALPHA_PREMULTIPLIED_LAST | CG_BITMAP_BYTE_ORDER_32_BIG;
17const GRAYSCALE8_BITMAP_INFO: u32 = CG_IMAGE_ALPHA_NONE;
18
19/// Reference-counted `CGContextRef` backed by a bitmap.
20///
21/// Clones retain the same mutable native context; they are not independent
22/// drawing surfaces.
23#[derive(Debug)]
24pub struct CGContext {
25    ptr: *mut c_void,
26}
27
28crate::utils::retained::cf_retained!(
29    CGContext,
30    field = ptr,
31    retain = ffi::CGContextRetain,
32    release = ffi::CGContextRelease,
33    drop = null_out,
34);
35
36impl CGContext {
37    /// Wrap a raw `CGContextRef` pointer — takes ownership without retaining.
38    ///
39    /// # Safety
40    ///
41    /// `ptr` must be a non-null `CGContextRef` whose ownership the caller is
42    /// transferring to the returned [`CGContext`].
43    #[must_use]
44    pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
45        Self { ptr }
46    }
47
48    fn new_bitmap(
49        width: usize,
50        height: usize,
51        color_space: &CGColorSpace,
52        bitmap_info: u32,
53    ) -> Result<Self, CFError> {
54        let context = unsafe {
55            ffi::CGBitmapContextCreate(
56                ptr::null_mut(),
57                width,
58                height,
59                BITS_PER_COMPONENT_8,
60                0,
61                color_space.as_ptr(),
62                bitmap_info,
63            )
64        };
65
66        if context.is_null() {
67            Err(CFError::new("CGBitmapContextCreate"))
68        } else {
69            let context = Self { ptr: context };
70            let data = context.data();
71            let len = context.buffer_len();
72            if !data.is_null() && len != 0 {
73                unsafe { data.write_bytes(0, len) };
74            }
75            Ok(context)
76        }
77    }
78
79    /// Create a premultiplied-last RGBA8 bitmap context.
80    ///
81    /// # Errors
82    ///
83    /// Returns [`CFError`] if Core Graphics fails to create the bitmap context.
84    pub fn new_rgba8(width: usize, height: usize) -> Result<Self, CFError> {
85        let color_space = CGColorSpace::device_rgb();
86        Self::new_bitmap(width, height, &color_space, RGBA8_BITMAP_INFO)
87    }
88
89    /// Create an 8-bit grayscale bitmap context.
90    ///
91    /// # Errors
92    ///
93    /// Returns [`CFError`] if Core Graphics fails to create the bitmap context.
94    pub fn new_grayscale(width: usize, height: usize) -> Result<Self, CFError> {
95        let color_space = CGColorSpace::device_gray();
96        Self::new_bitmap(width, height, &color_space, GRAYSCALE8_BITMAP_INFO)
97    }
98
99    fn buffer_len(&self) -> usize {
100        self.height().checked_mul(self.bytes_per_row()).unwrap_or(0)
101    }
102
103    /// Width in pixels.
104    #[must_use]
105    pub fn width(&self) -> usize {
106        unsafe { ffi::CGBitmapContextGetWidth(self.ptr) }
107    }
108
109    /// Height in pixels.
110    #[must_use]
111    pub fn height(&self) -> usize {
112        unsafe { ffi::CGBitmapContextGetHeight(self.ptr) }
113    }
114
115    /// Bytes per row.
116    #[must_use]
117    pub fn bytes_per_row(&self) -> usize {
118        unsafe { ffi::CGBitmapContextGetBytesPerRow(self.ptr) }
119    }
120
121    /// Bits per component.
122    #[must_use]
123    pub fn bits_per_component(&self) -> usize {
124        unsafe { ffi::CGBitmapContextGetBitsPerComponent(self.ptr) }
125    }
126
127    /// Bits per pixel.
128    #[must_use]
129    pub fn bits_per_pixel(&self) -> usize {
130        unsafe { ffi::CGBitmapContextGetBitsPerPixel(self.ptr) }
131    }
132
133    /// Raw bitmap data pointer.
134    ///
135    /// Dereferencing the pointer is unsafe because retained aliases and native
136    /// drawing operations can mutate the same storage.
137    #[must_use]
138    pub fn data(&self) -> *mut u8 {
139        unsafe { ffi::CGBitmapContextGetData(self.ptr).cast::<u8>() }
140    }
141
142    /// The context color space, if one is set.
143    #[must_use]
144    pub fn color_space(&self) -> Option<CGColorSpace> {
145        unsafe {
146            let color_space = ffi::CGBitmapContextGetColorSpace(self.ptr);
147            if color_space.is_null() {
148                None
149            } else {
150                Some(CGColorSpace::from_raw(ffi::CGColorSpaceRetain(color_space)))
151            }
152        }
153    }
154
155    /// The raw `CGImageAlphaInfo` value for the bitmap context.
156    #[must_use]
157    pub fn alpha_info(&self) -> u32 {
158        unsafe { ffi::CGBitmapContextGetAlphaInfo(self.ptr) }
159    }
160
161    /// The bitmap storage as immutable bytes.
162    ///
163    /// # Safety
164    ///
165    /// Every byte in the range must already be initialized. For the returned
166    /// reference's lifetime, the bitmap allocation must remain live and
167    /// immovable, and no retained alias or native call may draw into, clear,
168    /// replace, or otherwise mutate the context storage.
169    #[must_use]
170    pub unsafe fn as_bytes(&self) -> &[u8] {
171        let data = self.data();
172        let len = self.buffer_len();
173        if data.is_null() || len == 0 || isize::try_from(len).is_err() {
174            &[]
175        } else {
176            unsafe { std::slice::from_raw_parts(data.cast_const(), len) }
177        }
178    }
179
180    /// The bitmap storage as mutable bytes.
181    ///
182    /// # Safety
183    ///
184    /// Every byte in the range must already be initialized. For the returned
185    /// reference's lifetime, the caller must have unique access to the bitmap
186    /// storage across every retained wrapper and native alias, and no drawing
187    /// or snapshot operation may access the context.
188    #[must_use]
189    pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
190        let data = self.data();
191        let len = self.buffer_len();
192        if data.is_null() || len == 0 || isize::try_from(len).is_err() {
193            &mut []
194        } else {
195            unsafe { std::slice::from_raw_parts_mut(data, len) }
196        }
197    }
198
199    /// Set the current fill color in `DeviceRGB`.
200    pub fn set_rgb_fill_color(&self, r: f64, g: f64, b: f64, a: f64) {
201        unsafe { ffi::CGContextSetRGBFillColor(self.ptr, r, g, b, a) };
202    }
203
204    /// Set the current stroke color in `DeviceRGB`.
205    pub fn set_rgb_stroke_color(&self, r: f64, g: f64, b: f64, a: f64) {
206        unsafe { ffi::CGContextSetRGBStrokeColor(self.ptr, r, g, b, a) };
207    }
208
209    /// Set the stroke line width.
210    pub fn set_line_width(&self, w: f64) {
211        unsafe { ffi::CGContextSetLineWidth(self.ptr, w) };
212    }
213
214    /// Clear a rectangle to transparent black.
215    pub fn clear_rect(&self, x: f64, y: f64, w: f64, h: f64) {
216        unsafe { ffi::CGContextClearRect(self.ptr, CGRect::new(x, y, w, h)) };
217    }
218
219    /// Fill a rectangle.
220    pub fn fill_rect(&self, x: f64, y: f64, w: f64, h: f64) {
221        unsafe { ffi::CGContextFillRect(self.ptr, CGRect::new(x, y, w, h)) };
222    }
223
224    /// Stroke a rectangle.
225    pub fn stroke_rect(&self, x: f64, y: f64, w: f64, h: f64) {
226        unsafe { ffi::CGContextStrokeRect(self.ptr, CGRect::new(x, y, w, h)) };
227    }
228
229    /// Begin a new path.
230    pub fn begin_path(&self) {
231        unsafe { ffi::CGContextBeginPath(self.ptr) };
232    }
233
234    /// Close the current path.
235    pub fn close_path(&self) {
236        unsafe { ffi::CGContextClosePath(self.ptr) };
237    }
238
239    /// Move the current point.
240    pub fn move_to(&self, x: f64, y: f64) {
241        unsafe { ffi::CGContextMoveToPoint(self.ptr, x, y) };
242    }
243
244    /// Add a line segment to the current path.
245    pub fn add_line_to(&self, x: f64, y: f64) {
246        unsafe { ffi::CGContextAddLineToPoint(self.ptr, x, y) };
247    }
248
249    /// Add a rectangle to the current path.
250    pub fn add_rect(&self, x: f64, y: f64, w: f64, h: f64) {
251        unsafe { ffi::CGContextAddRect(self.ptr, CGRect::new(x, y, w, h)) };
252    }
253
254    /// Add an ellipse inscribed in the rectangle.
255    pub fn add_ellipse_in_rect(&self, x: f64, y: f64, w: f64, h: f64) {
256        unsafe { ffi::CGContextAddEllipseInRect(self.ptr, CGRect::new(x, y, w, h)) };
257    }
258
259    /// Fill the current path.
260    pub fn fill_path(&self) {
261        unsafe { ffi::CGContextFillPath(self.ptr) };
262    }
263
264    /// Stroke the current path.
265    pub fn stroke_path(&self) {
266        unsafe { ffi::CGContextStrokePath(self.ptr) };
267    }
268
269    /// Draw an image into the target rectangle.
270    pub fn draw_image(&self, x: f64, y: f64, w: f64, h: f64, image: &CGImage) {
271        unsafe { ffi::CGContextDrawImage(self.ptr, CGRect::new(x, y, w, h), image.as_ptr()) };
272    }
273
274    /// Translate the current transformation matrix.
275    pub fn translate(&self, tx: f64, ty: f64) {
276        unsafe { ffi::CGContextTranslateCTM(self.ptr, tx, ty) };
277    }
278
279    /// Scale the current transformation matrix.
280    pub fn scale(&self, sx: f64, sy: f64) {
281        unsafe { ffi::CGContextScaleCTM(self.ptr, sx, sy) };
282    }
283
284    /// Rotate the current transformation matrix.
285    pub fn rotate(&self, radians: f64) {
286        unsafe { ffi::CGContextRotateCTM(self.ptr, radians) };
287    }
288
289    /// Save the current graphics state.
290    pub fn save_g_state(&self) {
291        unsafe { ffi::CGContextSaveGState(self.ptr) };
292    }
293
294    /// Restore the most recently saved graphics state.
295    pub fn restore_g_state(&self) {
296        unsafe { ffi::CGContextRestoreGState(self.ptr) };
297    }
298
299    /// Snapshot the current bitmap contents to a `CGImage`.
300    #[must_use]
301    pub fn snapshot_to_image(&self) -> Option<CGImage> {
302        let image = unsafe { ffi::CGBitmapContextCreateImage(self.ptr) };
303        if image.is_null() {
304            None
305        } else {
306            Some(unsafe { CGImage::from_raw(image) })
307        }
308    }
309
310    /// Raw `CGContextRef` pointer.
311    #[must_use]
312    pub const fn as_ptr(&self) -> *mut c_void {
313        self.ptr
314    }
315}