ctru/services/
cam.rs

1//! Camera service.
2//!
3//! The CAM service provides access to the built-in cameras. [`Camera`]s can return images
4//! in the form of byte vectors which can be displayed to the screen or used in other ways.
5#![doc(alias = "camera")]
6
7use crate::error::{Error, ResultCode};
8use crate::services::ServiceReference;
9use crate::services::gspgpu::FramebufferFormat;
10use ctru_sys::Handle;
11use private::Configuration;
12
13use std::sync::Mutex;
14use std::time::Duration;
15
16static CAM_ACTIVE: Mutex<()> = Mutex::new(());
17
18/// Handle to the Camera service.
19pub struct Cam {
20    _service_handler: ServiceReference,
21    /// Inside-facing camera.
22    pub inner_cam: InwardCam,
23    /// Outside-facing right camera.
24    pub outer_right_cam: OutwardRightCam,
25    /// Outside-facing left camera.
26    pub outer_left_cam: OutwardLeftCam,
27    /// Both outside-facing cameras (mainly used for 3D photos).
28    pub both_outer_cams: BothOutwardCam,
29}
30
31/// Different kinds of flip modes.
32///
33/// See [`Camera::flip_image()`] to learn how to use this.
34#[doc(alias = "CAMU_Flip")]
35#[derive(Copy, Clone, Debug, PartialEq, Eq)]
36#[repr(u8)]
37pub enum FlipMode {
38    /// No flip.
39    None = ctru_sys::FLIP_NONE,
40    /// Horizontal flip.
41    Horizontal = ctru_sys::FLIP_HORIZONTAL,
42    /// Vertical flip.
43    Vertical = ctru_sys::FLIP_VERTICAL,
44    /// Both vertical and horizontal flip.
45    Reverse = ctru_sys::FLIP_REVERSE,
46}
47
48/// Size of the camera view.
49///
50/// See [`Camera::set_view_size()`] to learn how to use this.
51#[doc(alias = "CAMU_Size")]
52#[derive(Copy, Clone, Debug, PartialEq, Eq)]
53#[repr(u8)]
54pub enum ViewSize {
55    /// Size of the 3DS' top screen. (400 × 240)
56    ///
57    /// Useful if the image is meant to be displayed immediately.
58    TopLCD = ctru_sys::SIZE_CTR_TOP_LCD,
59    /// Size of the 3DS' bottom screen. (320 × 240)
60    ///
61    /// Equivalent to QVga.
62    BottomLCD = ctru_sys::SIZE_CTR_BOTTOM_LCD,
63    /// VGA display size. (640 × 480)
64    Vga = ctru_sys::SIZE_VGA,
65    /// QQVGA display size. (160 × 120)
66    QQVga = ctru_sys::SIZE_QQVGA,
67    /// CIF display size. (352 × 288)
68    Cif = ctru_sys::SIZE_CIF,
69    /// QCIF display size. (176 × 144)
70    QCif = ctru_sys::SIZE_QCIF,
71    /// Nintendo DS Screen size. (256 × 192)
72    DS = ctru_sys::SIZE_DS_LCD,
73    /// Nintendo DS Screen size x4. (512 × 384)
74    DSX4 = ctru_sys::SIZE_DS_LCDx4,
75}
76
77/// Framerate settings.
78///
79/// See [`Camera::set_frame_rate()`] to learn how to use this.
80#[doc(alias = "CAMU_FramRate")]
81#[derive(Copy, Clone, Debug, PartialEq, Eq)]
82#[repr(u8)]
83pub enum FrameRate {
84    /// 15 FPS.
85    Fps15 = ctru_sys::FRAME_RATE_15,
86    /// 15 to 5 FPS.
87    Fps15To5 = ctru_sys::FRAME_RATE_15_TO_5,
88    /// 15 to 2 FPS.
89    Fps15To2 = ctru_sys::FRAME_RATE_15_TO_2,
90    /// 10 FPS.
91    Fps10 = ctru_sys::FRAME_RATE_10,
92    /// 8.5 FPS.
93    Fps8_5 = ctru_sys::FRAME_RATE_8_5,
94    /// 5 FPS.
95    Fps5 = ctru_sys::FRAME_RATE_5,
96    /// 20 FPS.
97    Fps20 = ctru_sys::FRAME_RATE_20,
98    /// 20 to 5 FPS.
99    Fps20To5 = ctru_sys::FRAME_RATE_20_TO_5,
100    /// 30 FPS.
101    Fps30 = ctru_sys::FRAME_RATE_30,
102    /// 30 to 5 FPS.
103    Fps30To5 = ctru_sys::FRAME_RATE_30_TO_5,
104    /// 15 to 10 FPS.
105    Fps15To10 = ctru_sys::FRAME_RATE_15_TO_10,
106    /// 20 to 10 FPS.
107    Fps20To10 = ctru_sys::FRAME_RATE_20_TO_10,
108    /// 30 to 10 FPS.
109    Fps30To10 = ctru_sys::FRAME_RATE_30_TO_10,
110}
111
112/// White balance settings.
113///
114/// See [`Camera::set_white_balance()`] to learn how to use this.
115#[doc(alias = "CAMU_WhiteBalance")]
116#[derive(Copy, Clone, Debug, PartialEq, Eq)]
117#[repr(u8)]
118pub enum WhiteBalance {
119    /// Automatic white balance.
120    Auto = ctru_sys::WHITE_BALANCE_AUTO,
121    /// Tungsten.
122    Temp3200K = ctru_sys::WHITE_BALANCE_3200K,
123    /// Fluorescent Light.
124    Temp4150K = ctru_sys::WHITE_BALANCE_4150K,
125    /// Daylight.
126    Temp5200K = ctru_sys::WHITE_BALANCE_5200K,
127    /// Cloudy/Horizon.
128    Temp6000K = ctru_sys::WHITE_BALANCE_6000K,
129    /// Shade.
130    Temp7000K = ctru_sys::WHITE_BALANCE_7000K,
131}
132
133/// Photo mode settings.
134///
135/// See [`Camera::set_photo_mode()`] to learn how to use this.
136#[doc(alias = "CAMU_PhotoMode")]
137#[derive(Copy, Clone, Debug, PartialEq, Eq)]
138#[repr(u8)]
139pub enum PhotoMode {
140    /// Normal mode.
141    Normal = ctru_sys::PHOTO_MODE_NORMAL,
142    /// Portrait mode.
143    Portrait = ctru_sys::PHOTO_MODE_PORTRAIT,
144    /// Landscape mode.
145    Landscape = ctru_sys::PHOTO_MODE_LANDSCAPE,
146    /// NightView mode.
147    NightView = ctru_sys::PHOTO_MODE_NIGHTVIEW,
148    /// Letter mode.
149    Letter = ctru_sys::PHOTO_MODE_LETTER,
150}
151
152/// Special camera effects.
153///
154/// See [`Camera::set_effect()`] to learn how to use this.
155#[doc(alias = "CAMU_Effect")]
156#[derive(Copy, Clone, Debug, PartialEq, Eq)]
157#[repr(u8)]
158pub enum Effect {
159    /// No effects.
160    None = ctru_sys::EFFECT_NONE,
161    /// Mono effect.
162    Mono = ctru_sys::EFFECT_MONO,
163    /// Sepia effect.
164    Sepia = ctru_sys::EFFECT_SEPIA,
165    /// Negative effect.
166    Negative = ctru_sys::EFFECT_NEGATIVE,
167    /// Negative film effect.
168    Negafilm = ctru_sys::EFFECT_NEGAFILM,
169    /// Sepia effect.
170    ///
171    /// The difference between this and [`Sepia`](Effect::Sepia) is unknown.
172    Sepia01 = ctru_sys::EFFECT_SEPIA01,
173}
174
175/// Contrast settings.
176///
177/// See [`Camera::set_contrast()`] to learn how to use this.
178#[doc(alias = "CAMU_Contrast")]
179#[derive(Copy, Clone, Debug, PartialEq, Eq)]
180#[repr(u8)]
181pub enum Contrast {
182    /// Low contrast.
183    Low = ctru_sys::CONTRAST_LOW,
184    /// Brightness ratio: 70.
185    Normal = ctru_sys::CONTRAST_NORMAL,
186    /// Brightness ratio: 90.
187    High = ctru_sys::CONTRAST_HIGH,
188}
189
190/// Lens correction settings.
191///
192/// See [`Camera::set_lens_correction()`] to learn how to use this.
193#[doc(alias = "CAMU_LensCorrection")]
194#[derive(Copy, Clone, Debug, PartialEq, Eq)]
195#[repr(u8)]
196pub enum LensCorrection {
197    /// No lens correction.
198    Off = ctru_sys::LENS_CORRECTION_DARK,
199    /// Normal lens correction.
200    Normal = ctru_sys::LENS_CORRECTION_NORMAL,
201    /// Bright lens correction.
202    Bright = ctru_sys::LENS_CORRECTION_BRIGHT,
203}
204
205/// Image output format.
206///
207/// See [`Camera::set_output_format()`] to learn how to use this.
208#[doc(alias = "CAMU_OutputFormat")]
209#[derive(Copy, Clone, Debug, PartialEq, Eq)]
210#[repr(u8)]
211pub enum OutputFormat {
212    /// YUV422 output format. 16 bits per pixel.
213    Yuv422 = ctru_sys::OUTPUT_YUV_422,
214    /// RGB565 output format. 16 bits per pixel.
215    Rgb565 = ctru_sys::OUTPUT_RGB_565,
216}
217
218/// Playable shutter sounds.
219///
220/// See [`Cam::play_shutter_sound()`] to learn how to use this.
221#[doc(alias = "CAMU_ShutterSoundType")]
222#[derive(Copy, Clone, Debug, PartialEq, Eq)]
223#[repr(u8)]
224pub enum ShutterSound {
225    /// Photo shutter sound.
226    Normal = ctru_sys::SHUTTER_SOUND_TYPE_NORMAL,
227    /// Shutter sound to begin a movie recording.
228    Movie = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE,
229    /// Shutter sound to finish a movie recording.
230    MovieEnd = ctru_sys::SHUTTER_SOUND_TYPE_MOVIE_END,
231}
232
233/// Configuration to handle image trimming.
234///
235/// See [`Trimming::new_centered()`] and the other associated methods for controlled
236/// ways of configuring trimming.
237#[non_exhaustive]
238#[derive(Clone, Copy, Debug, PartialEq, Eq)]
239pub enum Trimming {
240    /// Trimming configuration relatively to the center of the image.
241    #[allow(missing_docs)]
242    Centered { width: i16, height: i16 },
243    /// Trimming disabled.
244    Off,
245}
246
247/// Data used by the camera to calibrate image quality for a single camera.
248// TODO: Implement Image quality calibration.
249#[doc(alias = "CAMU_ImageQualityCalibrationData")]
250#[derive(Default, Clone, Copy, Debug)]
251pub struct ImageQualityCalibration(pub ctru_sys::CAMU_ImageQualityCalibrationData);
252
253/// Data used by the camera to calibrate image quality when using both outward cameras.
254// TODO: Implement Stereo camera calibration.
255#[doc(alias = "CAMU_StereoCameraCalibrationData")]
256#[derive(Default, Clone, Copy, Debug)]
257pub struct StereoCameraCalibration(ctru_sys::CAMU_StereoCameraCalibrationData);
258
259/// Inward camera representation (facing the user of the 3DS).
260///
261/// Usually used for selfies.
262pub struct InwardCam {
263    configuration: Configuration,
264}
265
266/// Right-side outward camera representation.
267pub struct OutwardRightCam {
268    configuration: Configuration,
269}
270
271/// Left-side outward camera representation.
272pub struct OutwardLeftCam {
273    configuration: Configuration,
274}
275
276/// Both outer cameras combined.
277///
278/// Usually used for 3D photos.
279pub struct BothOutwardCam {
280    configuration: Configuration,
281}
282
283mod private {
284    use super::{BothOutwardCam, InwardCam, OutwardLeftCam, OutwardRightCam, Trimming, ViewSize};
285
286    /// Basic configuration needed to properly use the built-in cameras.
287    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
288    pub struct Configuration {
289        pub view_size: ViewSize,
290        pub trimming: Trimming,
291    }
292
293    impl Configuration {
294        pub fn new() -> Self {
295            Self::default()
296        }
297    }
298
299    impl Default for Configuration {
300        fn default() -> Self {
301            Self {
302                view_size: ViewSize::TopLCD,
303                trimming: Trimming::Off,
304            }
305        }
306    }
307
308    pub trait ConfigurableCamera {
309        fn configuration(&self) -> &Configuration;
310
311        fn configuration_mut(&mut self) -> &mut Configuration;
312    }
313
314    impl ConfigurableCamera for InwardCam {
315        fn configuration(&self) -> &Configuration {
316            &self.configuration
317        }
318
319        fn configuration_mut(&mut self) -> &mut Configuration {
320            &mut self.configuration
321        }
322    }
323
324    impl ConfigurableCamera for OutwardRightCam {
325        fn configuration(&self) -> &Configuration {
326            &self.configuration
327        }
328
329        fn configuration_mut(&mut self) -> &mut Configuration {
330            &mut self.configuration
331        }
332    }
333
334    impl ConfigurableCamera for OutwardLeftCam {
335        fn configuration(&self) -> &Configuration {
336            &self.configuration
337        }
338
339        fn configuration_mut(&mut self) -> &mut Configuration {
340            &mut self.configuration
341        }
342    }
343
344    impl ConfigurableCamera for BothOutwardCam {
345        fn configuration(&self) -> &Configuration {
346            &self.configuration
347        }
348
349        fn configuration_mut(&mut self) -> &mut Configuration {
350            &mut self.configuration
351        }
352    }
353}
354
355impl BothOutwardCam {
356    /// Set whether to enable or disable brightness synchronization between the two cameras.
357    #[doc(alias = "CAMU_SetBrightnessSynchronization")]
358    pub fn set_brightness_synchronization(
359        &mut self,
360        brightness_synchronization: bool,
361    ) -> crate::Result<()> {
362        unsafe {
363            ResultCode(ctru_sys::CAMU_SetBrightnessSynchronization(
364                brightness_synchronization,
365            ))?;
366        }
367
368        Ok(())
369    }
370
371    #[doc(alias = "CAMU_GetStereoCameraCalibrationData")]
372    /// Returns the currently set [`StereoCameraCalibration`].
373    pub fn stereo_calibration(&self) -> crate::Result<StereoCameraCalibration> {
374        let mut calibration = StereoCameraCalibration::default();
375
376        unsafe {
377            ResultCode(ctru_sys::CAMU_GetStereoCameraCalibrationData(
378                &mut calibration.0,
379            ))?;
380        }
381
382        Ok(calibration)
383    }
384
385    #[doc(alias = "CAMU_SetStereoCameraCalibrationData")]
386    /// Set the [`StereoCameraCalibration`].
387    // TODO: This seems to have no effect.
388    pub fn set_stereo_calibration(
389        &mut self,
390        mut stereo_calibration: StereoCameraCalibration,
391    ) -> crate::Result<()> {
392        let view_size = self.final_view_size();
393
394        stereo_calibration.0.imageWidth = view_size.0;
395        stereo_calibration.0.imageHeight = view_size.1;
396
397        unsafe {
398            ResultCode(ctru_sys::CAMU_SetStereoCameraCalibrationData(
399                stereo_calibration.0,
400            ))?;
401        }
402
403        Ok(())
404    }
405}
406
407impl Camera for InwardCam {
408    fn camera_as_raw(&self) -> ctru_sys::u32_ {
409        ctru_sys::SELECT_IN1.into()
410    }
411}
412
413impl Camera for OutwardRightCam {
414    fn camera_as_raw(&self) -> ctru_sys::u32_ {
415        ctru_sys::SELECT_OUT1.into()
416    }
417}
418
419impl Camera for OutwardLeftCam {
420    fn camera_as_raw(&self) -> ctru_sys::u32_ {
421        ctru_sys::SELECT_OUT2.into()
422    }
423}
424
425impl Camera for BothOutwardCam {
426    fn camera_as_raw(&self) -> ctru_sys::u32_ {
427        ctru_sys::SELECT_OUT1_OUT2.into()
428    }
429
430    fn port_as_raw(&self) -> ctru_sys::u32_ {
431        ctru_sys::PORT_BOTH.into()
432    }
433
434    fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> {
435        // Check whether the provided buffer is big enough to store the image.
436        let max_size = self.final_byte_length();
437        if buffer.len() < max_size {
438            return Err(Error::BufferTooShort {
439                provided: buffer.len(),
440                wanted: max_size,
441            });
442        }
443
444        let final_view = self.final_view_size();
445
446        // The transfer unit is NOT the "max number of bytes" or whatever the docs make you think it is...
447        let transfer_unit = unsafe {
448            let mut transfer_unit = 0;
449
450            ResultCode(ctru_sys::CAMU_GetMaxBytes(
451                &mut transfer_unit,
452                final_view.0,
453                final_view.1,
454            ))?;
455
456            transfer_unit
457        };
458
459        unsafe {
460            ResultCode(ctru_sys::CAMU_SetTransferBytes(
461                self.port_as_raw(),
462                transfer_unit,
463                final_view.0,
464                final_view.1,
465            ))?;
466        };
467
468        unsafe {
469            ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?;
470            ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?;
471        };
472
473        // Synchronize the two cameras.
474        unsafe {
475            ResultCode(ctru_sys::CAMU_SynchronizeVsyncTiming(
476                ctru_sys::SELECT_OUT1.into(),
477                ctru_sys::SELECT_OUT2.into(),
478            ))?;
479        }
480
481        // Start capturing with the camera.
482        unsafe {
483            ResultCode(ctru_sys::CAMU_StartCapture(self.port_as_raw()))?;
484        };
485
486        let receive_event_1 = unsafe {
487            let mut completion_handle: Handle = 0;
488
489            ResultCode(ctru_sys::CAMU_SetReceiving(
490                &mut completion_handle,
491                buffer.as_mut_ptr().cast(),
492                ctru_sys::PORT_CAM1.into(),
493                (max_size / 2) as u32,
494                transfer_unit.try_into().unwrap(),
495            ))?;
496
497            completion_handle
498        };
499
500        let receive_event_2 = unsafe {
501            let mut completion_handle: Handle = 0;
502
503            ResultCode(ctru_sys::CAMU_SetReceiving(
504                &mut completion_handle,
505                buffer[max_size / 2..].as_mut_ptr().cast(),
506                ctru_sys::PORT_CAM2.into(),
507                (max_size / 2) as u32,
508                transfer_unit.try_into().unwrap(),
509            ))?;
510
511            completion_handle
512        };
513
514        unsafe {
515            // Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully.
516            let wait_result_1 = ResultCode(ctru_sys::svcWaitSynchronization(
517                receive_event_1,
518                timeout.as_nanos().try_into().unwrap(),
519            ));
520
521            let wait_result_2 = ResultCode(ctru_sys::svcWaitSynchronization(
522                receive_event_2,
523                timeout.as_nanos().try_into().unwrap(),
524            ));
525
526            // We close everything first, then we check for possible errors
527            let _ = ctru_sys::svcCloseHandle(receive_event_1); // We wouldn't return the error even if there was one, so no use of ResultCode is needed.
528            let _ = ctru_sys::svcCloseHandle(receive_event_2);
529
530            // Camera state cleanup
531            ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?;
532            ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?;
533            ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE.into()))?;
534
535            wait_result_1?;
536            wait_result_2?;
537        };
538
539        Ok(())
540    }
541}
542
543/// Generic functionality common to all cameras.
544pub trait Camera: private::ConfigurableCamera {
545    /// Returns the raw value of the selected camera.
546    fn camera_as_raw(&self) -> ctru_sys::u32_;
547
548    /// Returns view size of the selected camera.
549    ///
550    /// # Notes
551    ///
552    /// This view is the full resolution at which the camera will take the photo.
553    /// If you are interested in the final image's size, calculated while taking into account all processing and modifications,
554    /// have a look at [`Camera::final_view_size()`].
555    fn view_size(&self) -> ViewSize {
556        self.configuration().view_size
557    }
558
559    /// Returns the raw port of the selected camera.
560    fn port_as_raw(&self) -> ctru_sys::u32_ {
561        ctru_sys::PORT_CAM1.into()
562    }
563
564    /// Returns `true` if the camera is busy (receiving data).
565    ///
566    /// # Example
567    ///
568    /// ```
569    /// # let _runner = test_runner::GdbRunner::default();
570    /// # use std::error::Error;
571    /// # fn main() -> Result<(), Box<dyn Error>> {
572    /// #
573    /// use ctru::services::cam::{Cam, Camera};
574    /// let cam = Cam::new()?;
575    ///
576    /// let inward = &cam.inner_cam;
577    ///
578    /// // Inward cam is not busy since it is not being used.
579    /// assert!(!inward.is_busy()?);
580    /// #
581    /// # Ok(())
582    /// # }
583    /// ```
584    #[doc(alias = "CAMU_IsBusy")]
585    fn is_busy(&self) -> crate::Result<bool> {
586        unsafe {
587            let mut is_busy = false;
588            ResultCode(ctru_sys::CAMU_IsBusy(&mut is_busy, self.port_as_raw()))?;
589            Ok(is_busy)
590        }
591    }
592
593    /// Returns the maximum amount of bytes the final image will occupy in memory based on the view size, trimming, pixel depth and other
594    /// modifications set to the camera.
595    ///
596    /// # Notes
597    ///
598    /// The value returned will be double the image size if requested by [`BothOutwardCam`].
599    /// Remember to query this information again if *any* changes are applied to the [`Camera`] configuration!
600    ///
601    /// # Example
602    ///
603    /// ```
604    /// # let _runner = test_runner::GdbRunner::default();
605    /// # use std::error::Error;
606    /// # fn main() -> Result<(), Box<dyn Error>> {
607    /// #
608    /// use ctru::services::cam::{Cam, Camera};
609    /// let cam = Cam::new()?;
610    ///
611    /// let inward = &cam.inner_cam;
612    ///
613    /// let transfer_count = inward.final_byte_length();
614    /// #
615    /// # Ok(())
616    /// # }
617    /// ```
618    fn final_byte_length(&self) -> usize {
619        let size = self.final_view_size();
620
621        let mut res: usize = (size.0 as usize * size.1 as usize) * std::mem::size_of::<i16>();
622
623        // If we are taking a picture using both outwards cameras, we need to expect 2 images, rather than just 1
624        if self.port_as_raw() == ctru_sys::PORT_BOTH.into() {
625            res *= 2;
626        }
627
628        res
629    }
630
631    /// Returns the dimensions of the final image based on the view size, trimming and other
632    /// modifications set to the camera.
633    ///
634    /// # Notes
635    ///
636    /// Remember to query this information again if *any* changes are applied to the [`Camera`] configuration!
637    ///
638    /// # Example
639    ///
640    /// ```
641    /// # use std::error::Error;
642    /// # fn main() -> Result<(), Box<dyn Error>> {
643    /// #
644    /// use ctru::services::cam::{Cam, Camera, Trimming, ViewSize};
645    /// let mut cam = Cam::new()?;
646    ///
647    /// let mut inward = &mut cam.inner_cam;
648    ///
649    /// // We trim the image down so that it fits on a DS screen!
650    /// inward.set_trimming(Trimming::new_centered_with_view(ViewSize::DS));
651    ///
652    /// // This result will take into account the trimming.
653    /// let final_resolution = inward.final_view_size();
654    /// #
655    /// # Ok(())
656    /// # }
657    /// ```
658    fn final_view_size(&self) -> (i16, i16) {
659        match self.trimming() {
660            Trimming::Centered { width, height } => (width, height),
661            Trimming::Off => self.view_size().into(),
662        }
663    }
664
665    /// Returns the [`Trimming`] configuration currently set.
666    fn trimming(&self) -> Trimming {
667        self.configuration().trimming
668    }
669
670    /// Set trimming bounds to trim the camera photo.
671    ///
672    /// # Notes
673    ///
674    /// The trimmed image must have a pixel area of (`width * height`) multiple of 128.
675    /// If not, a raw `libctru` error may be returned.
676    ///
677    /// # Panics
678    ///
679    /// Setting up a [`Trimming`] configurations that exceeds the bounds of the original
680    /// image's size will result in a panic.
681    #[doc(alias = "CAMU_SetTrimming")]
682    fn set_trimming(&mut self, trimming: Trimming) -> crate::Result<()> {
683        match trimming {
684            Trimming::Centered { width, height } => unsafe {
685                let view_size: (i16, i16) = self.view_size().into();
686                let trim_size: (i16, i16) = (width, height);
687
688                // Check whether the trim size is within the view.
689                assert!(
690                    trim_size.0 <= view_size.0 && trim_size.1 <= view_size.1,
691                    "trimmed view is bigger than the camera view",
692                );
693
694                ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?;
695
696                ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter(
697                    self.port_as_raw(),
698                    trim_size.0,
699                    trim_size.1,
700                    view_size.0,
701                    view_size.1,
702                ))?;
703            },
704            Trimming::Off => unsafe {
705                ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?;
706            },
707        }
708
709        self.configuration_mut().trimming = trimming;
710
711        Ok(())
712    }
713
714    /// Returns whether or not trimming is currently enabled for the camera.
715    #[doc(alias = "CAMU_IsTrimming")]
716    fn is_trimming(&self) -> bool {
717        matches!(self.trimming(), Trimming::Off)
718    }
719
720    /// Set the exposure level of the camera.
721    #[doc(alias = "CAMU_SetExposure")]
722    fn set_exposure(&mut self, exposure: i8) -> crate::Result<()> {
723        unsafe {
724            ResultCode(ctru_sys::CAMU_SetExposure(self.camera_as_raw(), exposure))?;
725            Ok(())
726        }
727    }
728
729    /// Set the white balance of the camera.
730    #[doc(alias = "CAMU_SetWhiteBalance")]
731    fn set_white_balance(&mut self, white_balance: WhiteBalance) -> crate::Result<()> {
732        unsafe {
733            ResultCode(ctru_sys::CAMU_SetWhiteBalance(
734                self.camera_as_raw(),
735                white_balance.into(),
736            ))?;
737            Ok(())
738        }
739    }
740
741    /// Set the sharpness of the camera.
742    #[doc(alias = "CAMU_SetSharpness")]
743    fn set_sharpness(&mut self, sharpness: i8) -> crate::Result<()> {
744        unsafe {
745            ResultCode(ctru_sys::CAMU_SetSharpness(self.camera_as_raw(), sharpness))?;
746            Ok(())
747        }
748    }
749
750    /// Set whether auto exposure is enabled or disabled for the camera.
751    #[doc(alias = "CAMU_SetAutoExposure")]
752    fn set_auto_exposure(&mut self, enabled: bool) -> crate::Result<()> {
753        unsafe {
754            ResultCode(ctru_sys::CAMU_SetAutoExposure(
755                self.camera_as_raw(),
756                enabled,
757            ))?;
758            Ok(())
759        }
760    }
761
762    /// Returns `true` if auto exposure is enabled for the camera.
763    #[doc(alias = "CAMU_IsAutoExposure")]
764    fn is_auto_exposure_enabled(&self) -> crate::Result<bool> {
765        unsafe {
766            let mut enabled = false;
767            ResultCode(ctru_sys::CAMU_IsAutoExposure(
768                &mut enabled,
769                self.camera_as_raw(),
770            ))?;
771            Ok(enabled)
772        }
773    }
774
775    /// Set the flip mode of the camera's image.
776    #[doc(alias = "CAMU_FlipImage")]
777    fn flip_image(&mut self, flip: FlipMode) -> crate::Result<()> {
778        unsafe {
779            ResultCode(ctru_sys::CAMU_FlipImage(
780                self.camera_as_raw(),
781                flip.into(),
782                ctru_sys::CONTEXT_A,
783            ))?;
784            Ok(())
785        }
786    }
787
788    /// Set the view size of the camera.
789    ///
790    /// # Notes
791    ///
792    /// Calling this function will reset the trimming configuration.
793    #[doc(alias = "CAMU_SetSize")]
794    fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> {
795        unsafe {
796            ResultCode(ctru_sys::CAMU_SetSize(
797                self.camera_as_raw(),
798                size.into(),
799                ctru_sys::CONTEXT_A,
800            ))?;
801        }
802
803        self.configuration_mut().view_size = size;
804
805        self.set_trimming(Trimming::Off)?;
806
807        Ok(())
808    }
809
810    /// Set the frame rate of the camera.
811    #[doc(alias = "CAMU_SetFrameRate")]
812    fn set_frame_rate(&mut self, frame_rate: FrameRate) -> crate::Result<()> {
813        unsafe {
814            ResultCode(ctru_sys::CAMU_SetFrameRate(
815                self.camera_as_raw(),
816                frame_rate.into(),
817            ))?;
818            Ok(())
819        }
820    }
821
822    /// Set the photo mode of the camera.
823    #[doc(alias = "CAMU_SetPhotoMode")]
824    fn set_photo_mode(&mut self, photo_mode: PhotoMode) -> crate::Result<()> {
825        unsafe {
826            ResultCode(ctru_sys::CAMU_SetPhotoMode(
827                self.camera_as_raw(),
828                photo_mode.into(),
829            ))?;
830            Ok(())
831        }
832    }
833
834    /// Set the effect of the camera.
835    ///
836    /// # Notes
837    ///
838    /// This operation will override any previously set [`Effect`].
839    #[doc(alias = "CAMU_SetEffect")]
840    fn set_effect(&mut self, effect: Effect) -> crate::Result<()> {
841        unsafe {
842            ResultCode(ctru_sys::CAMU_SetEffect(
843                self.camera_as_raw(),
844                effect.into(),
845                ctru_sys::CONTEXT_A,
846            ))?;
847            Ok(())
848        }
849    }
850
851    /// Set the contrast of the camera.
852    #[doc(alias = "CAMU_SetContrast")]
853    fn set_contrast(&mut self, contrast: Contrast) -> crate::Result<()> {
854        unsafe {
855            ResultCode(ctru_sys::CAMU_SetContrast(
856                self.camera_as_raw(),
857                contrast.into(),
858            ))?;
859            Ok(())
860        }
861    }
862
863    /// Set the lens correction of the camera.
864    #[doc(alias = "CAMU_SetLensCorrection")]
865    fn set_lens_correction(&mut self, lens_correction: LensCorrection) -> crate::Result<()> {
866        unsafe {
867            ResultCode(ctru_sys::CAMU_SetLensCorrection(
868                self.camera_as_raw(),
869                lens_correction.into(),
870            ))?;
871            Ok(())
872        }
873    }
874
875    /// Set the output format of the camera.
876    #[doc(alias = "CAMU_SetOutputFormat")]
877    fn set_output_format(&mut self, format: OutputFormat) -> crate::Result<()> {
878        unsafe {
879            ResultCode(ctru_sys::CAMU_SetOutputFormat(
880                self.camera_as_raw(),
881                format.into(),
882                ctru_sys::CONTEXT_A,
883            ))?;
884            Ok(())
885        }
886    }
887
888    /// Set the region in which auto exposure should be based on.
889    ///
890    /// # Arguments
891    ///
892    /// * `x` - Starting x coordinate of the window
893    /// * `y` - Starting y coordinate of the window
894    /// * `width` - Width of the window
895    /// * `height` - Height of the window
896    #[doc(alias = "CAMU_SetAutoExposureWindow")]
897    fn set_auto_exposure_window(
898        &mut self,
899        x: i16,
900        y: i16,
901        width: i16,
902        height: i16,
903    ) -> crate::Result<()> {
904        unsafe {
905            ResultCode(ctru_sys::CAMU_SetAutoExposureWindow(
906                self.camera_as_raw(),
907                x,
908                y,
909                width,
910                height,
911            ))?;
912            Ok(())
913        }
914    }
915
916    /// Set the region in which auto white balance should be based on.
917    ///
918    /// # Arguments
919    ///
920    /// * `x` - Starting x coordinate of the window
921    /// * `y` - Starting y coordinate of the window
922    /// * `width` - Width of the window
923    /// * `height` - Height of the window
924    ///
925    /// # Notes
926    ///
927    /// To activate automatic white balance, you must pass [`WhiteBalance::Auto`] into [`Camera::set_white_balance()`].
928    #[doc(alias = "CAMU_SetAutoWhiteBalanceWindow")]
929    fn set_auto_white_balance_window(
930        &mut self,
931        x: i16,
932        y: i16,
933        width: i16,
934        height: i16,
935    ) -> crate::Result<()> {
936        unsafe {
937            ResultCode(ctru_sys::CAMU_SetAutoWhiteBalanceWindow(
938                self.camera_as_raw(),
939                x,
940                y,
941                width,
942                height,
943            ))?;
944            Ok(())
945        }
946    }
947
948    /// Set whether the noise filter should be enabled or disabled for the camera.
949    #[doc(alias = "CAMU_SetNoiseFilter")]
950    fn set_noise_filter(&mut self, enabled: bool) -> crate::Result<()> {
951        unsafe {
952            ResultCode(ctru_sys::CAMU_SetNoiseFilter(self.camera_as_raw(), enabled))?;
953            Ok(())
954        }
955    }
956
957    /// Set the [`ImageQualityCalibration`] for the camera.
958    #[doc(alias = "CAMU_SetImageQualityCalibrationData")]
959    fn set_image_quality_calibration(
960        &mut self,
961        data: ImageQualityCalibration,
962    ) -> crate::Result<()> {
963        unsafe {
964            ResultCode(ctru_sys::CAMU_SetImageQualityCalibrationData(data.0))?;
965            Ok(())
966        }
967    }
968
969    /// Returns the current [`ImageQualityCalibration`] for the camera.
970    #[doc(alias = "CAMU_GetImageQualityCalibrationData")]
971    fn image_quality_calibration(&self) -> crate::Result<ImageQualityCalibration> {
972        unsafe {
973            let mut data = ImageQualityCalibration::default();
974            ResultCode(ctru_sys::CAMU_GetImageQualityCalibrationData(&mut data.0))?;
975            Ok(data)
976        }
977    }
978
979    /// Request the camera to take a picture and write it in a buffer.
980    ///
981    /// # Errors
982    ///
983    /// This function will return an error if the camera is already busy or if the timeout duration is reached.
984    ///
985    /// # Notes
986    ///
987    /// If the picture is taken using [`BothOutwardCam`], the buffer will have to be able to hold both images
988    /// (from each camera), which will be written into it sequentially.
989    /// Use [`Camera::final_byte_length()`] to know how big the buffer needs to be to hold your next image.
990    ///
991    /// # Example
992    ///
993    /// ```
994    /// # let _runner = test_runner::GdbRunner::default();
995    /// # use std::error::Error;
996    /// # use std::time::Duration;
997    /// # fn main() -> Result<(), Box<dyn Error>> {
998    /// #
999    /// use ctru::services::cam::{Cam, Camera, ViewSize, OutputFormat, WhiteBalance};
1000    /// let mut cam = Cam::new()?;
1001    ///
1002    /// // We borrow the inward facing `Camera`.
1003    /// let camera = &mut cam.inner_cam;
1004    ///
1005    /// camera.set_view_size(ViewSize::TopLCD)?;
1006    /// camera.set_output_format(OutputFormat::Rgb565)?;
1007    /// camera.set_noise_filter(true)?;
1008    /// camera.set_auto_exposure(true)?;
1009    /// camera.set_white_balance(WhiteBalance::Auto)?;
1010    ///
1011    /// // Size of the top screen buffer at 2 bytes per pixel (RGB565).
1012    /// let mut buffer = vec![0; camera.final_byte_length()];
1013    ///
1014    /// // Take picture with 3 seconds of timeout.
1015    /// camera.take_picture(&mut buffer, Duration::from_secs(3));
1016    /// #
1017    /// # Ok(())
1018    /// # }
1019    /// ```
1020    fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> {
1021        // Check whether the provided buffer is big enough to store the image.
1022        let max_size = self.final_byte_length();
1023        if buffer.len() < max_size {
1024            return Err(Error::BufferTooShort {
1025                provided: buffer.len(),
1026                wanted: max_size,
1027            });
1028        }
1029
1030        let final_view = self.final_view_size();
1031
1032        // The transfer unit is NOT the "max number of bytes" or whatever the docs make you think it is...
1033        let transfer_unit = unsafe {
1034            let mut transfer_unit = 0;
1035
1036            ResultCode(ctru_sys::CAMU_GetMaxBytes(
1037                &mut transfer_unit,
1038                final_view.0,
1039                final_view.1,
1040            ))?;
1041
1042            transfer_unit
1043        };
1044
1045        unsafe {
1046            ResultCode(ctru_sys::CAMU_SetTransferBytes(
1047                self.port_as_raw(),
1048                transfer_unit,
1049                final_view.0,
1050                final_view.1,
1051            ))?;
1052        };
1053
1054        unsafe {
1055            ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?;
1056            ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?;
1057        };
1058
1059        let receive_event = unsafe {
1060            let mut completion_handle: Handle = 0;
1061
1062            ResultCode(ctru_sys::CAMU_SetReceiving(
1063                &mut completion_handle,
1064                buffer.as_mut_ptr().cast(),
1065                self.port_as_raw(),
1066                max_size as u32,
1067                transfer_unit.try_into().unwrap(),
1068            ))?;
1069
1070            completion_handle
1071        };
1072
1073        // Start capturing with the camera.
1074        unsafe {
1075            ResultCode(ctru_sys::CAMU_StartCapture(self.port_as_raw()))?;
1076        };
1077
1078        unsafe {
1079            // Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully.
1080            let wait_result = ResultCode(ctru_sys::svcWaitSynchronization(
1081                receive_event,
1082                timeout.as_nanos().try_into().unwrap(),
1083            ));
1084
1085            // We close everything first, then we check for possible errors
1086            let _ = ctru_sys::svcCloseHandle(receive_event); // We wouldn't return the error even if there was one, so no use of ResultCode is needed.
1087
1088            // Camera state cleanup
1089            ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?;
1090            ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?;
1091            ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE.into()))?;
1092
1093            wait_result?;
1094        };
1095
1096        Ok(())
1097    }
1098}
1099
1100impl Trimming {
1101    /// Create a new [`Trimming`] configuration using width and height centered to the original image.
1102    ///
1103    /// # Panics
1104    ///
1105    /// This function will panic if the pixel area of the new configuration (`width * height`)
1106    /// is not a multiple of 128.
1107    pub fn new_centered(width: i16, height: i16) -> Self {
1108        // Pixel area must be a multiple of 128.
1109        assert!((width * height) % 128 == 0);
1110
1111        Self::Centered { width, height }
1112    }
1113
1114    /// Create a new [`Trimming`] configuration using a standard view size centered to the original image.
1115    pub fn new_centered_with_view(size: ViewSize) -> Self {
1116        let size: (i16, i16) = size.into();
1117
1118        Self::Centered {
1119            width: size.0,
1120            height: size.1,
1121        }
1122    }
1123}
1124
1125impl Cam {
1126    /// Initialize a new service handle.
1127    ///
1128    /// # Notes
1129    ///
1130    /// All cameras default to taking photos with [`ViewSize::TopLCD`] and [`OutputFormat::Yuv422`].
1131    /// Have a look at [`Camera::set_view_size()`] and [`Camera::set_output_format()`] to change these settings.
1132    ///
1133    /// # Errors
1134    ///
1135    /// This function will return an error if the service was unable to be initialized.
1136    /// Since this service requires no special or elevated permissions, errors are
1137    /// rare in practice.
1138    ///
1139    /// # Example
1140    ///
1141    /// ```
1142    /// # let _runner = test_runner::GdbRunner::default();
1143    /// # use std::error::Error;
1144    /// # fn main() -> Result<(), Box<dyn Error>> {
1145    /// #
1146    /// use ctru::services::cam::Cam;
1147    ///
1148    /// let cam = Cam::new()?;
1149    /// #
1150    /// # Ok(())
1151    /// # }
1152    /// ```
1153    #[doc(alias = "camInit")]
1154    pub fn new() -> crate::Result<Cam> {
1155        let _service_handler = ServiceReference::new(
1156            &CAM_ACTIVE,
1157            || {
1158                ResultCode(unsafe { ctru_sys::camInit() })?;
1159
1160                Ok(())
1161            },
1162            || unsafe {
1163                ctru_sys::camExit();
1164            },
1165        )?;
1166
1167        let configuration = Configuration::new();
1168
1169        let mut inner_cam = InwardCam { configuration };
1170        let mut outer_right_cam = OutwardRightCam { configuration };
1171        let mut outer_left_cam = OutwardLeftCam { configuration };
1172        let mut both_outer_cams = BothOutwardCam { configuration };
1173
1174        inner_cam.set_view_size(ViewSize::TopLCD)?;
1175        outer_right_cam.set_view_size(ViewSize::TopLCD)?;
1176        outer_left_cam.set_view_size(ViewSize::TopLCD)?;
1177        both_outer_cams.set_view_size(ViewSize::TopLCD)?;
1178
1179        Ok(Cam {
1180            _service_handler,
1181            inner_cam,
1182            outer_right_cam,
1183            outer_left_cam,
1184            both_outer_cams,
1185        })
1186    }
1187
1188    /// Play the specified sound based on the [`ShutterSound`] argument
1189    ///
1190    /// # Notes
1191    ///
1192    /// Playing the shutter sound does not require a living handle to the [`Ndsp`](crate::services::ndsp::Ndsp) service.
1193    /// Volume will always be maxed out to ensure everyone within photo range can hear the picture being taken (as by Japanese law).
1194    ///
1195    /// # Example
1196    ///
1197    /// ```
1198    /// # let _runner = test_runner::GdbRunner::default();
1199    /// # use std::error::Error;
1200    /// # fn main() -> Result<(), Box<dyn Error>> {
1201    /// #
1202    /// use ctru::services::cam::{Cam, ShutterSound};
1203    /// let cam = Cam::new()?;
1204    ///
1205    /// // We play the shutter sound on the console's speakers!
1206    /// // (even though we aren't taking a photo :P)
1207    /// cam.play_shutter_sound(ShutterSound::Normal);
1208    /// #
1209    /// # Ok(())
1210    /// # }
1211    /// ```
1212    #[doc(alias = "CAMU_PlayShutterSound")]
1213    pub fn play_shutter_sound(&self, sound: ShutterSound) -> crate::Result<()> {
1214        unsafe {
1215            ResultCode(ctru_sys::CAMU_PlayShutterSound(sound.into()))?;
1216            Ok(())
1217        }
1218    }
1219}
1220
1221impl TryFrom<FramebufferFormat> for OutputFormat {
1222    type Error = ();
1223
1224    fn try_from(value: FramebufferFormat) -> Result<Self, Self::Error> {
1225        match value {
1226            FramebufferFormat::Rgb565 => Ok(OutputFormat::Rgb565),
1227            _ => Err(()),
1228        }
1229    }
1230}
1231
1232impl TryFrom<OutputFormat> for FramebufferFormat {
1233    type Error = ();
1234
1235    fn try_from(value: OutputFormat) -> Result<Self, Self::Error> {
1236        match value {
1237            OutputFormat::Rgb565 => Ok(FramebufferFormat::Rgb565),
1238            _ => Err(()),
1239        }
1240    }
1241}
1242
1243impl From<ViewSize> for (i16, i16) {
1244    fn from(value: ViewSize) -> Self {
1245        match value {
1246            ViewSize::TopLCD => (400, 240),
1247            ViewSize::BottomLCD => (320, 240),
1248            ViewSize::Vga => (640, 480),
1249            ViewSize::QQVga => (160, 120),
1250            ViewSize::Cif => (352, 288),
1251            ViewSize::QCif => (176, 144),
1252            ViewSize::DS => (256, 192),
1253            ViewSize::DSX4 => (512, 384),
1254        }
1255    }
1256}
1257
1258from_impl!(FlipMode, ctru_sys::CAMU_Flip);
1259from_impl!(ViewSize, ctru_sys::CAMU_Size);
1260from_impl!(FrameRate, ctru_sys::CAMU_FrameRate);
1261from_impl!(WhiteBalance, ctru_sys::CAMU_WhiteBalance);
1262from_impl!(PhotoMode, ctru_sys::CAMU_PhotoMode);
1263from_impl!(Effect, ctru_sys::CAMU_Effect);
1264from_impl!(Contrast, ctru_sys::CAMU_Contrast);
1265from_impl!(LensCorrection, ctru_sys::CAMU_LensCorrection);
1266from_impl!(OutputFormat, ctru_sys::CAMU_OutputFormat);
1267from_impl!(ShutterSound, ctru_sys::CAMU_ShutterSoundType);