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#[derive(Debug)]
21pub struct CGContext {
22    ptr: *mut c_void,
23}
24
25impl Drop for CGContext {
26    fn drop(&mut self) {
27        if !self.ptr.is_null() {
28            unsafe { ffi::CGContextRelease(self.ptr) };
29            self.ptr = ptr::null_mut();
30        }
31    }
32}
33
34impl Clone for CGContext {
35    fn clone(&self) -> Self {
36        Self {
37            ptr: unsafe { ffi::CGContextRetain(self.ptr) },
38        }
39    }
40}
41
42impl CGContext {
43    /// Wrap a raw `CGContextRef` pointer — takes ownership without retaining.
44    ///
45    /// # Safety
46    ///
47    /// `ptr` must be a non-null `CGContextRef` whose ownership the caller is
48    /// transferring to the returned [`CGContext`].
49    #[must_use]
50    pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
51        Self { ptr }
52    }
53
54    fn new_bitmap(
55        width: usize,
56        height: usize,
57        color_space: &CGColorSpace,
58        bitmap_info: u32,
59    ) -> Result<Self, CFError> {
60        let context = unsafe {
61            ffi::CGBitmapContextCreate(
62                ptr::null_mut(),
63                width,
64                height,
65                BITS_PER_COMPONENT_8,
66                0,
67                color_space.as_ptr(),
68                bitmap_info,
69            )
70        };
71
72        if context.is_null() {
73            Err(CFError::new("CGBitmapContextCreate"))
74        } else {
75            Ok(Self { ptr: 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    #[must_use]
135    pub fn data(&self) -> *mut u8 {
136        unsafe { ffi::CGBitmapContextGetData(self.ptr).cast::<u8>() }
137    }
138
139    /// The context color space, if one is set.
140    #[must_use]
141    pub fn color_space(&self) -> Option<CGColorSpace> {
142        unsafe {
143            let color_space = ffi::CGBitmapContextGetColorSpace(self.ptr);
144            if color_space.is_null() {
145                None
146            } else {
147                Some(CGColorSpace::from_raw(ffi::CGColorSpaceRetain(color_space)))
148            }
149        }
150    }
151
152    /// The raw `CGImageAlphaInfo` value for the bitmap context.
153    #[must_use]
154    pub fn alpha_info(&self) -> u32 {
155        unsafe { ffi::CGBitmapContextGetAlphaInfo(self.ptr) }
156    }
157
158    /// The bitmap storage as immutable bytes.
159    #[must_use]
160    pub fn as_bytes(&self) -> &[u8] {
161        let data = self.data();
162        let len = self.buffer_len();
163        if data.is_null() || len == 0 {
164            &[]
165        } else {
166            unsafe { std::slice::from_raw_parts(data.cast_const(), len) }
167        }
168    }
169
170    /// The bitmap storage as mutable bytes.
171    #[must_use]
172    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
173        let data = self.data();
174        let len = self.buffer_len();
175        if data.is_null() || len == 0 {
176            &mut []
177        } else {
178            unsafe { std::slice::from_raw_parts_mut(data, len) }
179        }
180    }
181
182    /// Set the current fill color in `DeviceRGB`.
183    pub fn set_rgb_fill_color(&self, r: f64, g: f64, b: f64, a: f64) {
184        unsafe { ffi::CGContextSetRGBFillColor(self.ptr, r, g, b, a) };
185    }
186
187    /// Set the current stroke color in `DeviceRGB`.
188    pub fn set_rgb_stroke_color(&self, r: f64, g: f64, b: f64, a: f64) {
189        unsafe { ffi::CGContextSetRGBStrokeColor(self.ptr, r, g, b, a) };
190    }
191
192    /// Set the stroke line width.
193    pub fn set_line_width(&self, w: f64) {
194        unsafe { ffi::CGContextSetLineWidth(self.ptr, w) };
195    }
196
197    /// Clear a rectangle to transparent black.
198    pub fn clear_rect(&self, x: f64, y: f64, w: f64, h: f64) {
199        unsafe { ffi::CGContextClearRect(self.ptr, CGRect::new(x, y, w, h)) };
200    }
201
202    /// Fill a rectangle.
203    pub fn fill_rect(&self, x: f64, y: f64, w: f64, h: f64) {
204        unsafe { ffi::CGContextFillRect(self.ptr, CGRect::new(x, y, w, h)) };
205    }
206
207    /// Stroke a rectangle.
208    pub fn stroke_rect(&self, x: f64, y: f64, w: f64, h: f64) {
209        unsafe { ffi::CGContextStrokeRect(self.ptr, CGRect::new(x, y, w, h)) };
210    }
211
212    /// Begin a new path.
213    pub fn begin_path(&self) {
214        unsafe { ffi::CGContextBeginPath(self.ptr) };
215    }
216
217    /// Close the current path.
218    pub fn close_path(&self) {
219        unsafe { ffi::CGContextClosePath(self.ptr) };
220    }
221
222    /// Move the current point.
223    pub fn move_to(&self, x: f64, y: f64) {
224        unsafe { ffi::CGContextMoveToPoint(self.ptr, x, y) };
225    }
226
227    /// Add a line segment to the current path.
228    pub fn add_line_to(&self, x: f64, y: f64) {
229        unsafe { ffi::CGContextAddLineToPoint(self.ptr, x, y) };
230    }
231
232    /// Add a rectangle to the current path.
233    pub fn add_rect(&self, x: f64, y: f64, w: f64, h: f64) {
234        unsafe { ffi::CGContextAddRect(self.ptr, CGRect::new(x, y, w, h)) };
235    }
236
237    /// Add an ellipse inscribed in the rectangle.
238    pub fn add_ellipse_in_rect(&self, x: f64, y: f64, w: f64, h: f64) {
239        unsafe { ffi::CGContextAddEllipseInRect(self.ptr, CGRect::new(x, y, w, h)) };
240    }
241
242    /// Fill the current path.
243    pub fn fill_path(&self) {
244        unsafe { ffi::CGContextFillPath(self.ptr) };
245    }
246
247    /// Stroke the current path.
248    pub fn stroke_path(&self) {
249        unsafe { ffi::CGContextStrokePath(self.ptr) };
250    }
251
252    /// Draw an image into the target rectangle.
253    pub fn draw_image(&self, x: f64, y: f64, w: f64, h: f64, image: &CGImage) {
254        unsafe { ffi::CGContextDrawImage(self.ptr, CGRect::new(x, y, w, h), image.as_ptr()) };
255    }
256
257    /// Translate the current transformation matrix.
258    pub fn translate(&self, tx: f64, ty: f64) {
259        unsafe { ffi::CGContextTranslateCTM(self.ptr, tx, ty) };
260    }
261
262    /// Scale the current transformation matrix.
263    pub fn scale(&self, sx: f64, sy: f64) {
264        unsafe { ffi::CGContextScaleCTM(self.ptr, sx, sy) };
265    }
266
267    /// Rotate the current transformation matrix.
268    pub fn rotate(&self, radians: f64) {
269        unsafe { ffi::CGContextRotateCTM(self.ptr, radians) };
270    }
271
272    /// Save the current graphics state.
273    pub fn save_g_state(&self) {
274        unsafe { ffi::CGContextSaveGState(self.ptr) };
275    }
276
277    /// Restore the most recently saved graphics state.
278    pub fn restore_g_state(&self) {
279        unsafe { ffi::CGContextRestoreGState(self.ptr) };
280    }
281
282    /// Snapshot the current bitmap contents to a `CGImage`.
283    #[must_use]
284    pub fn snapshot_to_image(&self) -> Option<CGImage> {
285        let image = unsafe { ffi::CGBitmapContextCreateImage(self.ptr) };
286        if image.is_null() {
287            None
288        } else {
289            Some(unsafe { CGImage::from_raw(image) })
290        }
291    }
292
293    /// Raw `CGContextRef` pointer.
294    #[must_use]
295    pub const fn as_ptr(&self) -> *mut c_void {
296        self.ptr
297    }
298}