1use core::ffi::c_void;
4use core::ptr;
5
6use crate::NullPointerError;
7
8use super::{CGAffineTransform, CGRect};
9use super::drawing::{CGColorSpace, CGImage};
10use super::ffi;
11
12const BITS_PER_COMPONENT_8: usize = 8;
13const CG_IMAGE_ALPHA_NONE: u32 = 0;
14const CG_IMAGE_ALPHA_ONLY: u32 = 7;
15const CG_IMAGE_ALPHA_PREMULTIPLIED_LAST: u32 = 1;
16const CG_BITMAP_BYTE_ORDER_32_BIG: u32 = 16_384;
17const RGBA8_BITMAP_INFO: u32 = CG_IMAGE_ALPHA_PREMULTIPLIED_LAST | CG_BITMAP_BYTE_ORDER_32_BIG;
18const GRAYSCALE8_BITMAP_INFO: u32 = CG_IMAGE_ALPHA_NONE;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21#[repr(u32)]
22pub enum LineCap {
23 Butt = 0,
24 Round = 1,
25 Square = 2,
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29#[repr(u32)]
30pub enum LineJoin {
31 Miter = 0,
32 Round = 1,
33 Bevel = 2,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37#[repr(u32)]
38pub enum PathDrawingMode {
39 Fill = 0,
40 EvenOddFill = 1,
41 Stroke = 2,
42 FillStroke = 3,
43 EvenOddFillStroke = 4,
44}
45
46#[derive(Debug)]
51pub struct CGContext {
52 ptr: *mut c_void,
53}
54
55crate::utils::retained::cf_retained!(
56 CGContext,
57 field = ptr,
58 retain = ffi::CGContextRetain,
59 release = ffi::CGContextRelease,
60 drop = null_out,
61);
62
63impl CGContext {
64 #[must_use]
71 pub const unsafe fn from_raw(ptr: *mut c_void) -> Self {
72 Self { ptr }
73 }
74
75 fn new_bitmap(
76 width: usize,
77 height: usize,
78 bytes_per_row: usize,
79 color_space: &CGColorSpace,
80 bitmap_info: u32,
81 ) -> Result<Self, NullPointerError> {
82 let context = unsafe {
83 if bytes_per_row == 0 {
84 ffi::CGBitmapContextCreate(
85 ptr::null_mut(),
86 width,
87 height,
88 BITS_PER_COMPONENT_8,
89 0,
90 color_space.as_ptr(),
91 bitmap_info,
92 )
93 } else {
94 crate::ffi::acf_cg_context_create_bitmap(
95 width,
96 height,
97 bytes_per_row,
98 color_space.as_ptr(),
99 bitmap_info,
100 )
101 }
102 };
103
104 if context.is_null() {
105 Err(NullPointerError::new("CGBitmapContextCreate"))
106 } else {
107 let context = Self { ptr: context };
108 let data = context.data();
109 let len = context.buffer_len();
110 if !data.is_null() && len != 0 {
111 unsafe { data.write_bytes(0, len) };
112 }
113 Ok(context)
114 }
115 }
116
117 pub fn new_rgba8(width: usize, height: usize) -> Result<Self, NullPointerError> {
123 let color_space = CGColorSpace::device_rgb();
124 Self::new_bitmap(width, height, 0, &color_space, RGBA8_BITMAP_INFO)
125 }
126
127 #[allow(clippy::missing_errors_doc)]
128 pub fn new_srgb_rgba8(width: usize, height: usize) -> Result<Self, NullPointerError> {
129 let bytes_per_row = width
130 .checked_mul(4)
131 .ok_or_else(|| NullPointerError::new("CGBitmapContextCreate"))?;
132 let color_space = CGColorSpace::srgb();
133 Self::new_bitmap(width, height, bytes_per_row, &color_space, RGBA8_BITMAP_INFO)
134 }
135
136 #[must_use]
137 #[allow(clippy::missing_safety_doc)]
138 pub unsafe fn copy_bytes(&self) -> Vec<u8> {
139 unsafe { self.as_bytes() }.to_vec()
140 }
141
142 pub fn new_grayscale(width: usize, height: usize) -> Result<Self, NullPointerError> {
148 let color_space = CGColorSpace::device_gray();
149 Self::new_bitmap(width, height, 0, &color_space, GRAYSCALE8_BITMAP_INFO)
150 }
151
152 #[allow(clippy::missing_errors_doc)]
153 pub fn new_alpha_only(width: usize, height: usize) -> Result<Self, NullPointerError> {
154 let color_space = CGColorSpace::device_gray();
155 Self::new_bitmap(width, height, width, &color_space, CG_IMAGE_ALPHA_ONLY)
156 }
157
158 #[allow(clippy::missing_errors_doc)]
159 pub fn new_premultiplied_last(width: usize, height: usize) -> Result<Self, NullPointerError> {
160 let bytes_per_row = width
161 .checked_mul(4)
162 .ok_or_else(|| NullPointerError::new("CGBitmapContextCreate"))?;
163 let color_space = CGColorSpace::device_rgb();
164 Self::new_bitmap(
165 width,
166 height,
167 bytes_per_row,
168 &color_space,
169 CG_IMAGE_ALPHA_PREMULTIPLIED_LAST,
170 )
171 }
172
173 fn buffer_len(&self) -> usize {
174 self.height().checked_mul(self.bytes_per_row()).unwrap_or(0)
175 }
176
177 #[must_use]
179 pub fn width(&self) -> usize {
180 unsafe { ffi::CGBitmapContextGetWidth(self.ptr) }
181 }
182
183 #[must_use]
185 pub fn height(&self) -> usize {
186 unsafe { ffi::CGBitmapContextGetHeight(self.ptr) }
187 }
188
189 #[must_use]
191 pub fn bytes_per_row(&self) -> usize {
192 unsafe { ffi::CGBitmapContextGetBytesPerRow(self.ptr) }
193 }
194
195 #[must_use]
197 pub fn bits_per_component(&self) -> usize {
198 unsafe { ffi::CGBitmapContextGetBitsPerComponent(self.ptr) }
199 }
200
201 #[must_use]
203 pub fn bits_per_pixel(&self) -> usize {
204 unsafe { ffi::CGBitmapContextGetBitsPerPixel(self.ptr) }
205 }
206
207 #[must_use]
212 pub fn data(&self) -> *mut u8 {
213 unsafe { ffi::CGBitmapContextGetData(self.ptr).cast::<u8>() }
214 }
215
216 #[must_use]
218 pub fn color_space(&self) -> Option<CGColorSpace> {
219 unsafe {
220 let color_space = ffi::CGBitmapContextGetColorSpace(self.ptr);
221 if color_space.is_null() {
222 None
223 } else {
224 Some(CGColorSpace::from_raw(ffi::CGColorSpaceRetain(color_space)))
225 }
226 }
227 }
228
229 #[must_use]
231 pub fn alpha_info(&self) -> u32 {
232 unsafe { ffi::CGBitmapContextGetAlphaInfo(self.ptr) }
233 }
234
235 #[must_use]
244 pub unsafe fn as_bytes(&self) -> &[u8] {
245 let data = self.data();
246 let len = self.buffer_len();
247 if data.is_null() || len == 0 || isize::try_from(len).is_err() {
248 &[]
249 } else {
250 unsafe { std::slice::from_raw_parts(data.cast_const(), len) }
251 }
252 }
253
254 #[must_use]
263 pub unsafe fn as_bytes_mut(&mut self) -> &mut [u8] {
264 let data = self.data();
265 let len = self.buffer_len();
266 if data.is_null() || len == 0 || isize::try_from(len).is_err() {
267 &mut []
268 } else {
269 unsafe { std::slice::from_raw_parts_mut(data, len) }
270 }
271 }
272
273 pub fn set_rgb_fill_color(&self, r: f64, g: f64, b: f64, a: f64) {
275 unsafe { ffi::CGContextSetRGBFillColor(self.ptr, r, g, b, a) };
276 }
277
278 pub fn set_gray_fill_color(&self, gray: f64, alpha: f64) {
279 unsafe { crate::ffi::acf_cg_context_set_gray_fill_color(self.ptr, gray, alpha) };
280 }
281
282 pub fn set_text_fill_mode(&self) {
283 unsafe { crate::ffi::acf_cg_context_set_text_fill_mode(self.ptr) };
284 }
285
286 pub fn set_allows_antialiasing(&self, allowed: bool) {
287 unsafe { crate::ffi::acf_cg_context_set_allows_antialiasing(self.ptr, allowed) };
288 }
289
290 pub fn set_should_antialias(&self, enabled: bool) {
291 unsafe { crate::ffi::acf_cg_context_set_should_antialias(self.ptr, enabled) };
292 }
293
294 pub fn set_allows_font_subpixel_positioning(&self, allowed: bool) {
295 unsafe {
296 crate::ffi::acf_cg_context_set_allows_font_subpixel_positioning(self.ptr, allowed);
297 };
298 }
299
300 pub fn set_should_subpixel_position_fonts(&self, enabled: bool) {
301 unsafe { crate::ffi::acf_cg_context_set_should_subpixel_position_fonts(self.ptr, enabled) };
302 }
303
304 pub fn set_allows_font_subpixel_quantization(&self, allowed: bool) {
305 unsafe {
306 crate::ffi::acf_cg_context_set_allows_font_subpixel_quantization(self.ptr, allowed);
307 };
308 }
309
310 pub fn set_should_subpixel_quantize_fonts(&self, enabled: bool) {
311 unsafe { crate::ffi::acf_cg_context_set_should_subpixel_quantize_fonts(self.ptr, enabled) };
312 }
313
314 pub fn set_should_smooth_fonts(&self, enabled: bool) {
315 unsafe { crate::ffi::acf_cg_context_set_should_smooth_fonts(self.ptr, enabled) };
316 }
317
318 pub fn set_rgb_stroke_color(&self, r: f64, g: f64, b: f64, a: f64) {
320 unsafe { ffi::CGContextSetRGBStrokeColor(self.ptr, r, g, b, a) };
321 }
322
323 pub fn set_line_width(&self, w: f64) {
325 unsafe { ffi::CGContextSetLineWidth(self.ptr, w) };
326 }
327
328 pub fn clear_rect(&self, x: f64, y: f64, w: f64, h: f64) {
330 unsafe { ffi::CGContextClearRect(self.ptr, CGRect::new(x, y, w, h)) };
331 }
332
333 pub fn fill_rect(&self, x: f64, y: f64, w: f64, h: f64) {
335 unsafe { ffi::CGContextFillRect(self.ptr, CGRect::new(x, y, w, h)) };
336 }
337
338 pub fn stroke_rect(&self, x: f64, y: f64, w: f64, h: f64) {
340 unsafe { ffi::CGContextStrokeRect(self.ptr, CGRect::new(x, y, w, h)) };
341 }
342
343 pub fn begin_path(&self) {
345 unsafe { ffi::CGContextBeginPath(self.ptr) };
346 }
347
348 pub fn close_path(&self) {
350 unsafe { ffi::CGContextClosePath(self.ptr) };
351 }
352
353 pub fn move_to(&self, x: f64, y: f64) {
355 unsafe { ffi::CGContextMoveToPoint(self.ptr, x, y) };
356 }
357
358 pub fn add_line_to(&self, x: f64, y: f64) {
360 unsafe { ffi::CGContextAddLineToPoint(self.ptr, x, y) };
361 }
362
363 pub fn add_quad_curve_to(&self, cx: f64, cy: f64, x: f64, y: f64) {
364 unsafe { ffi::CGContextAddQuadCurveToPoint(self.ptr, cx, cy, x, y) };
365 }
366
367 pub fn add_curve_to(&self, c1x: f64, c1y: f64, c2x: f64, c2y: f64, x: f64, y: f64) {
368 unsafe { ffi::CGContextAddCurveToPoint(self.ptr, c1x, c1y, c2x, c2y, x, y) };
369 }
370
371 pub fn set_line_cap(&self, cap: LineCap) {
372 unsafe { ffi::CGContextSetLineCap(self.ptr, cap as u32) };
373 }
374
375 pub fn set_line_join(&self, join: LineJoin) {
376 unsafe { ffi::CGContextSetLineJoin(self.ptr, join as u32) };
377 }
378
379 pub fn set_miter_limit(&self, limit: f64) {
380 unsafe { ffi::CGContextSetMiterLimit(self.ptr, limit) };
381 }
382
383 pub fn draw_path(&self, mode: PathDrawingMode) {
384 unsafe { ffi::CGContextDrawPath(self.ptr, mode as u32) };
385 }
386
387 pub fn clip(&self, even_odd: bool) {
388 unsafe {
389 if even_odd {
390 ffi::CGContextEOClip(self.ptr);
391 } else {
392 ffi::CGContextClip(self.ptr);
393 }
394 }
395 }
396
397 pub fn concat_transform(&self, transform: CGAffineTransform) {
398 unsafe { ffi::CGContextConcatCTM(self.ptr, transform) };
399 }
400
401 pub fn add_rect(&self, x: f64, y: f64, w: f64, h: f64) {
403 unsafe { ffi::CGContextAddRect(self.ptr, CGRect::new(x, y, w, h)) };
404 }
405
406 pub fn add_ellipse_in_rect(&self, x: f64, y: f64, w: f64, h: f64) {
408 unsafe { ffi::CGContextAddEllipseInRect(self.ptr, CGRect::new(x, y, w, h)) };
409 }
410
411 pub fn fill_path(&self) {
413 unsafe { ffi::CGContextFillPath(self.ptr) };
414 }
415
416 pub fn stroke_path(&self) {
418 unsafe { ffi::CGContextStrokePath(self.ptr) };
419 }
420
421 pub fn draw_image(&self, x: f64, y: f64, w: f64, h: f64, image: &CGImage) {
423 unsafe { ffi::CGContextDrawImage(self.ptr, CGRect::new(x, y, w, h), image.as_ptr()) };
424 }
425
426 pub fn translate(&self, tx: f64, ty: f64) {
428 unsafe { ffi::CGContextTranslateCTM(self.ptr, tx, ty) };
429 }
430
431 pub fn scale(&self, sx: f64, sy: f64) {
433 unsafe { ffi::CGContextScaleCTM(self.ptr, sx, sy) };
434 }
435
436 pub fn rotate(&self, radians: f64) {
438 unsafe { ffi::CGContextRotateCTM(self.ptr, radians) };
439 }
440
441 pub fn save_g_state(&self) {
443 unsafe { ffi::CGContextSaveGState(self.ptr) };
444 }
445
446 pub fn restore_g_state(&self) {
448 unsafe { ffi::CGContextRestoreGState(self.ptr) };
449 }
450
451 #[must_use]
453 pub fn snapshot_to_image(&self) -> Option<CGImage> {
454 let image = unsafe { ffi::CGBitmapContextCreateImage(self.ptr) };
455 if image.is_null() {
456 None
457 } else {
458 Some(unsafe { CGImage::from_raw(image) })
459 }
460 }
461
462 #[must_use]
464 pub const fn as_ptr(&self) -> *mut c_void {
465 self.ptr
466 }
467}