1use crate::ffi;
2use crate::image::{Image, ImageRegion};
3use apple_metal::{CommandBuffer, MetalBuffer, MetalDevice, MetalTexture};
4use core::ffi::c_void;
5use core::ptr;
6
7macro_rules! opaque_handle {
8 ($name:ident) => {
9 pub struct $name {
10 ptr: *mut c_void,
11 }
12
13 unsafe impl Send for $name {}
15 unsafe impl Sync for $name {}
17
18 impl Drop for $name {
19 fn drop(&mut self) {
20 if !self.ptr.is_null() {
21 unsafe { ffi::mps_object_release(self.ptr) };
23 self.ptr = ptr::null_mut();
24 }
25 }
26 }
27
28 impl $name {
29 #[must_use]
30 pub const fn as_ptr(&self) -> *mut c_void {
31 self.ptr
32 }
33 }
34 };
35}
36
37macro_rules! impl_unary_methods {
38 ($name:ident) => {
39 impl $name {
40 pub fn encode_image(
42 &self,
43 command_buffer: &CommandBuffer,
44 source: &Image,
45 destination: &Image,
46 ) {
47 unsafe {
49 ffi::mps_unary_encode_image(
50 self.ptr,
51 command_buffer.as_ptr(),
52 source.as_ptr(),
53 destination.as_ptr(),
54 )
55 };
56 }
57
58 pub fn encode_texture(
60 &self,
61 command_buffer: &CommandBuffer,
62 source: &MetalTexture,
63 destination: &MetalTexture,
64 ) {
65 unsafe {
67 ffi::mps_unary_encode_texture(
68 self.ptr,
69 command_buffer.as_ptr(),
70 source.as_ptr(),
71 destination.as_ptr(),
72 )
73 };
74 }
75
76 pub fn set_edge_mode(&self, edge_mode: usize) {
78 unsafe { ffi::mps_unary_set_edge_mode(self.ptr, edge_mode) };
80 }
81
82 pub fn set_clip_rect(&self, region: ImageRegion) {
84 unsafe {
86 ffi::mps_unary_set_clip_rect(
87 self.ptr,
88 region.x,
89 region.y,
90 region.z,
91 region.width,
92 region.height,
93 region.depth,
94 )
95 };
96 }
97 }
98 };
99}
100
101macro_rules! impl_binary_methods {
102 ($name:ident) => {
103 impl $name {
104 pub fn encode_image(
106 &self,
107 command_buffer: &CommandBuffer,
108 primary: &Image,
109 secondary: &Image,
110 destination: &Image,
111 ) {
112 unsafe {
114 ffi::mps_binary_encode_image(
115 self.ptr,
116 command_buffer.as_ptr(),
117 primary.as_ptr(),
118 secondary.as_ptr(),
119 destination.as_ptr(),
120 )
121 };
122 }
123
124 pub fn encode_texture(
126 &self,
127 command_buffer: &CommandBuffer,
128 primary: &MetalTexture,
129 secondary: &MetalTexture,
130 destination: &MetalTexture,
131 ) {
132 unsafe {
134 ffi::mps_binary_encode_texture(
135 self.ptr,
136 command_buffer.as_ptr(),
137 primary.as_ptr(),
138 secondary.as_ptr(),
139 destination.as_ptr(),
140 )
141 };
142 }
143
144 pub fn set_primary_edge_mode(&self, edge_mode: usize) {
146 unsafe { ffi::mps_binary_set_primary_edge_mode(self.ptr, edge_mode) };
148 }
149
150 pub fn set_secondary_edge_mode(&self, edge_mode: usize) {
152 unsafe { ffi::mps_binary_set_secondary_edge_mode(self.ptr, edge_mode) };
154 }
155
156 pub fn set_clip_rect(&self, region: ImageRegion) {
158 unsafe {
160 ffi::mps_binary_set_clip_rect(
161 self.ptr,
162 region.x,
163 region.y,
164 region.z,
165 region.width,
166 region.height,
167 region.depth,
168 )
169 };
170 }
171 }
172 };
173}
174
175#[derive(Debug, Clone, Copy)]
177pub struct ScaleTransform {
178 pub scale_x: f64,
179 pub scale_y: f64,
180 pub translate_x: f64,
181 pub translate_y: f64,
182}
183
184#[derive(Debug, Clone, Copy)]
186pub struct HistogramInfo {
187 pub number_of_entries: usize,
188 pub histogram_for_alpha: bool,
189 pub min_pixel_value: [f32; 4],
190 pub max_pixel_value: [f32; 4],
191}
192
193opaque_handle!(ImageGaussianBlur);
194impl ImageGaussianBlur {
195 #[must_use]
196 pub fn new(device: &MetalDevice, sigma: f32) -> Option<Self> {
197 let ptr = unsafe { ffi::mps_image_gaussian_blur_new(device.as_ptr(), sigma) };
199 if ptr.is_null() {
200 None
201 } else {
202 Some(Self { ptr })
203 }
204 }
205}
206impl_unary_methods!(ImageGaussianBlur);
207
208opaque_handle!(ImageBox);
209impl ImageBox {
210 #[must_use]
211 pub fn new(device: &MetalDevice, kernel_width: usize, kernel_height: usize) -> Option<Self> {
212 let ptr = unsafe { ffi::mps_image_box_new(device.as_ptr(), kernel_width, kernel_height) };
214 if ptr.is_null() {
215 None
216 } else {
217 Some(Self { ptr })
218 }
219 }
220}
221impl_unary_methods!(ImageBox);
222
223opaque_handle!(ImageSobel);
224impl ImageSobel {
225 #[must_use]
226 pub fn new(device: &MetalDevice) -> Option<Self> {
227 let ptr = unsafe { ffi::mps_image_sobel_new(device.as_ptr(), core::ptr::null()) };
229 if ptr.is_null() {
230 None
231 } else {
232 Some(Self { ptr })
233 }
234 }
235
236 #[must_use]
237 pub fn with_transform(device: &MetalDevice, transform: [f32; 3]) -> Option<Self> {
238 let ptr = unsafe { ffi::mps_image_sobel_new(device.as_ptr(), transform.as_ptr()) };
240 if ptr.is_null() {
241 None
242 } else {
243 Some(Self { ptr })
244 }
245 }
246}
247impl_unary_methods!(ImageSobel);
248
249opaque_handle!(ImageMedian);
250impl ImageMedian {
251 #[must_use]
252 pub fn new(device: &MetalDevice, kernel_diameter: usize) -> Option<Self> {
253 let ptr = unsafe { ffi::mps_image_median_new(device.as_ptr(), kernel_diameter) };
255 if ptr.is_null() {
256 None
257 } else {
258 Some(Self { ptr })
259 }
260 }
261}
262impl_unary_methods!(ImageMedian);
263
264opaque_handle!(ImageConvolution);
265impl ImageConvolution {
266 #[must_use]
267 pub fn new(
268 device: &MetalDevice,
269 kernel_width: usize,
270 kernel_height: usize,
271 weights: &[f32],
272 ) -> Option<Self> {
273 if weights.len() != kernel_width.saturating_mul(kernel_height) {
274 return None;
275 }
276
277 let ptr = unsafe {
279 ffi::mps_image_convolution_new(
280 device.as_ptr(),
281 kernel_width,
282 kernel_height,
283 weights.as_ptr(),
284 )
285 };
286 if ptr.is_null() {
287 None
288 } else {
289 Some(Self { ptr })
290 }
291 }
292}
293impl_unary_methods!(ImageConvolution);
294
295opaque_handle!(ImageBilinearScale);
296impl ImageBilinearScale {
297 #[must_use]
298 pub fn new(device: &MetalDevice) -> Option<Self> {
299 let ptr = unsafe { ffi::mps_image_bilinear_scale_new(device.as_ptr()) };
301 if ptr.is_null() {
302 None
303 } else {
304 Some(Self { ptr })
305 }
306 }
307
308 pub fn set_scale_transform(&self, transform: ScaleTransform) {
310 unsafe {
312 ffi::mps_image_scale_set_transform(
313 self.ptr,
314 transform.scale_x,
315 transform.scale_y,
316 transform.translate_x,
317 transform.translate_y,
318 );
319 };
320 }
321}
322impl_unary_methods!(ImageBilinearScale);
323
324opaque_handle!(ImageLanczosScale);
325impl ImageLanczosScale {
326 #[must_use]
327 pub fn new(device: &MetalDevice) -> Option<Self> {
328 let ptr = unsafe { ffi::mps_image_lanczos_scale_new(device.as_ptr()) };
330 if ptr.is_null() {
331 None
332 } else {
333 Some(Self { ptr })
334 }
335 }
336
337 pub fn set_scale_transform(&self, transform: ScaleTransform) {
339 unsafe {
341 ffi::mps_image_scale_set_transform(
342 self.ptr,
343 transform.scale_x,
344 transform.scale_y,
345 transform.translate_x,
346 transform.translate_y,
347 );
348 };
349 }
350}
351impl_unary_methods!(ImageLanczosScale);
352
353opaque_handle!(ImageThresholdBinary);
354impl ImageThresholdBinary {
355 #[must_use]
356 pub fn new(device: &MetalDevice, threshold_value: f32, maximum_value: f32) -> Option<Self> {
357 let ptr = unsafe {
359 ffi::mps_image_threshold_binary_new(
360 device.as_ptr(),
361 threshold_value,
362 maximum_value,
363 core::ptr::null(),
364 )
365 };
366 if ptr.is_null() {
367 None
368 } else {
369 Some(Self { ptr })
370 }
371 }
372
373 #[must_use]
374 pub fn with_transform(
375 device: &MetalDevice,
376 threshold_value: f32,
377 maximum_value: f32,
378 transform: [f32; 3],
379 ) -> Option<Self> {
380 let ptr = unsafe {
382 ffi::mps_image_threshold_binary_new(
383 device.as_ptr(),
384 threshold_value,
385 maximum_value,
386 transform.as_ptr(),
387 )
388 };
389 if ptr.is_null() {
390 None
391 } else {
392 Some(Self { ptr })
393 }
394 }
395}
396impl_unary_methods!(ImageThresholdBinary);
397
398opaque_handle!(ImageHistogram);
399impl ImageHistogram {
400 #[must_use]
401 pub fn new(device: &MetalDevice, info: HistogramInfo) -> Option<Self> {
402 let ptr = unsafe {
404 ffi::mps_image_histogram_new(
405 device.as_ptr(),
406 info.number_of_entries,
407 info.histogram_for_alpha,
408 info.min_pixel_value.as_ptr(),
409 info.max_pixel_value.as_ptr(),
410 )
411 };
412 if ptr.is_null() {
413 None
414 } else {
415 Some(Self { ptr })
416 }
417 }
418
419 pub fn encode_image(
421 &self,
422 command_buffer: &CommandBuffer,
423 source: &Image,
424 histogram_buffer: &MetalBuffer,
425 histogram_offset: usize,
426 ) {
427 unsafe {
429 ffi::mps_image_histogram_encode_image(
430 self.ptr,
431 command_buffer.as_ptr(),
432 source.as_ptr(),
433 histogram_buffer.as_ptr(),
434 histogram_offset,
435 );
436 };
437 }
438
439 pub fn encode_texture(
441 &self,
442 command_buffer: &CommandBuffer,
443 source: &MetalTexture,
444 histogram_buffer: &MetalBuffer,
445 histogram_offset: usize,
446 ) {
447 unsafe {
449 ffi::mps_image_histogram_encode_texture(
450 self.ptr,
451 command_buffer.as_ptr(),
452 source.as_ptr(),
453 histogram_buffer.as_ptr(),
454 histogram_offset,
455 );
456 };
457 }
458
459 #[must_use]
461 pub fn histogram_size_for_source_format(&self, source_format: usize) -> usize {
462 unsafe { ffi::mps_image_histogram_size_for_source_format(self.ptr, source_format) }
464 }
465}
466
467opaque_handle!(ImageStatisticsMinAndMax);
468impl ImageStatisticsMinAndMax {
469 #[must_use]
470 pub fn new(device: &MetalDevice) -> Option<Self> {
471 let ptr = unsafe { ffi::mps_image_statistics_min_max_new(device.as_ptr()) };
473 if ptr.is_null() {
474 None
475 } else {
476 Some(Self { ptr })
477 }
478 }
479}
480impl_unary_methods!(ImageStatisticsMinAndMax);
481
482opaque_handle!(ImageStatisticsMean);
483impl ImageStatisticsMean {
484 #[must_use]
485 pub fn new(device: &MetalDevice) -> Option<Self> {
486 let ptr = unsafe { ffi::mps_image_statistics_mean_new(device.as_ptr()) };
488 if ptr.is_null() {
489 None
490 } else {
491 Some(Self { ptr })
492 }
493 }
494}
495impl_unary_methods!(ImageStatisticsMean);
496
497opaque_handle!(ImageReduceRowMin);
498impl ImageReduceRowMin {
499 #[must_use]
500 pub fn new(device: &MetalDevice) -> Option<Self> {
501 let ptr = unsafe { ffi::mps_image_reduce_row_min_new(device.as_ptr()) };
503 if ptr.is_null() {
504 None
505 } else {
506 Some(Self { ptr })
507 }
508 }
509}
510impl_unary_methods!(ImageReduceRowMin);
511
512opaque_handle!(ImageReduceRowMax);
513impl ImageReduceRowMax {
514 #[must_use]
515 pub fn new(device: &MetalDevice) -> Option<Self> {
516 let ptr = unsafe { ffi::mps_image_reduce_row_max_new(device.as_ptr()) };
518 if ptr.is_null() {
519 None
520 } else {
521 Some(Self { ptr })
522 }
523 }
524}
525impl_unary_methods!(ImageReduceRowMax);
526
527opaque_handle!(ImageReduceRowMean);
528impl ImageReduceRowMean {
529 #[must_use]
530 pub fn new(device: &MetalDevice) -> Option<Self> {
531 let ptr = unsafe { ffi::mps_image_reduce_row_mean_new(device.as_ptr()) };
533 if ptr.is_null() {
534 None
535 } else {
536 Some(Self { ptr })
537 }
538 }
539}
540impl_unary_methods!(ImageReduceRowMean);
541
542opaque_handle!(ImageReduceRowSum);
543impl ImageReduceRowSum {
544 #[must_use]
545 pub fn new(device: &MetalDevice) -> Option<Self> {
546 let ptr = unsafe { ffi::mps_image_reduce_row_sum_new(device.as_ptr()) };
548 if ptr.is_null() {
549 None
550 } else {
551 Some(Self { ptr })
552 }
553 }
554}
555impl_unary_methods!(ImageReduceRowSum);
556
557opaque_handle!(ImageAdd);
558impl ImageAdd {
559 #[must_use]
560 pub fn new(device: &MetalDevice) -> Option<Self> {
561 let ptr = unsafe { ffi::mps_image_add_new(device.as_ptr()) };
563 if ptr.is_null() {
564 None
565 } else {
566 Some(Self { ptr })
567 }
568 }
569
570 pub fn set_scales(&self, primary_scale: f32, secondary_scale: f32, bias: f32) {
572 unsafe {
574 ffi::mps_image_arithmetic_set_scales_bias(
575 self.ptr,
576 primary_scale,
577 secondary_scale,
578 bias,
579 );
580 };
581 }
582
583 pub fn set_clamp(&self, minimum_value: f32, maximum_value: f32) {
585 unsafe { ffi::mps_image_arithmetic_set_clamp(self.ptr, minimum_value, maximum_value) };
587 }
588}
589impl_binary_methods!(ImageAdd);
590
591pub struct ImageScaleAndAdd {
593 inner: ImageAdd,
594}
595
596impl ImageScaleAndAdd {
597 #[must_use]
599 pub fn new(
600 device: &MetalDevice,
601 primary_scale: f32,
602 secondary_scale: f32,
603 bias: f32,
604 ) -> Option<Self> {
605 let inner = ImageAdd::new(device)?;
606 inner.set_scales(primary_scale, secondary_scale, bias);
607 Some(Self { inner })
608 }
609
610 #[must_use]
611 pub const fn as_ptr(&self) -> *mut c_void {
612 self.inner.as_ptr()
613 }
614
615 pub fn encode_image(
616 &self,
617 command_buffer: &CommandBuffer,
618 primary: &Image,
619 secondary: &Image,
620 destination: &Image,
621 ) {
622 self.inner
623 .encode_image(command_buffer, primary, secondary, destination);
624 }
625
626 pub fn encode_texture(
627 &self,
628 command_buffer: &CommandBuffer,
629 primary: &MetalTexture,
630 secondary: &MetalTexture,
631 destination: &MetalTexture,
632 ) {
633 self.inner
634 .encode_texture(command_buffer, primary, secondary, destination);
635 }
636
637 pub fn set_primary_edge_mode(&self, edge_mode: usize) {
638 self.inner.set_primary_edge_mode(edge_mode);
639 }
640
641 pub fn set_secondary_edge_mode(&self, edge_mode: usize) {
642 self.inner.set_secondary_edge_mode(edge_mode);
643 }
644
645 pub fn set_clip_rect(&self, region: ImageRegion) {
646 self.inner.set_clip_rect(region);
647 }
648
649 pub fn set_clamp(&self, minimum_value: f32, maximum_value: f32) {
650 self.inner.set_clamp(minimum_value, maximum_value);
651 }
652}