Browse Source

Squash down camera functions

pull/137/head
Andrea Ciliberti 1 year ago
parent
commit
ce8fc4ef83
  1. 44
      ctru-rs/examples/camera-image.rs
  2. 351
      ctru-rs/src/services/cam.rs

44
ctru-rs/examples/camera-image.rs

@ -32,28 +32,30 @@ fn main() {
// Camera setup. // Camera setup.
let camera = &mut cam.outer_right_cam; let camera = &mut cam.outer_right_cam;
{
camera camera
.set_view_size(ViewSize::TopLCD) .set_view_size(ViewSize::TopLCD)
.expect("Failed to set camera size"); .expect("Failed to set camera size");
camera camera
.set_output_format(OutputFormat::Rgb565) .set_output_format(OutputFormat::Rgb565)
.expect("Failed to set camera output format"); .expect("Failed to set camera output format");
camera camera
.set_noise_filter(true) .set_noise_filter(true)
.expect("Failed to enable noise filter"); .expect("Failed to enable noise filter");
camera camera
.set_auto_exposure(true) .set_auto_exposure(true)
.expect("Failed to enable auto exposure"); .expect("Failed to enable auto exposure");
camera camera
.set_white_balance(WhiteBalance::Auto) .set_white_balance(WhiteBalance::Auto)
.expect("Failed to enable auto white balance"); .expect("Failed to enable auto white balance");
// This line has no effect on the camera since the photos are already shot with `TopLCD` size.
// Un-comment this line and see how it changes! camera
// camera.set_trimming(Trimming::Centered(ViewSize::BottomLCD)); .set_trimming(Trimming::Centered(ViewSize::TopLCD))
.expect("Failed to enable trimming");
}
// We don't intend on making any other modifications to the camera, so this size should be enough. // We don't intend on making any other modifications to the camera, so this size should be enough.
let len = camera.max_byte_count(); let len = camera.final_byte_length();
let mut buf = vec![0u8; len]; let mut buf = vec![0u8; len];
println!("\nPress R to take a new picture"); println!("\nPress R to take a new picture");
@ -78,7 +80,7 @@ fn main() {
.take_picture(&mut buf, WAIT_TIMEOUT) .take_picture(&mut buf, WAIT_TIMEOUT)
.expect("Failed to take picture"); .expect("Failed to take picture");
let image_size = camera.final_image_size(); let image_size = camera.final_view_size();
// Play the normal shutter sound. // Play the normal shutter sound.
cam.play_shutter_sound(ShutterSound::Normal) cam.play_shutter_sound(ShutterSound::Normal)

351
ctru-rs/src/services/cam.rs

@ -8,6 +8,7 @@ use crate::error::{Error, ResultCode};
use crate::services::gspgpu::FramebufferFormat; use crate::services::gspgpu::FramebufferFormat;
use crate::services::ServiceReference; use crate::services::ServiceReference;
use ctru_sys::Handle; use ctru_sys::Handle;
use private::Configuration;
use std::sync::Mutex; use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;
@ -250,38 +251,19 @@ pub struct ImageQualityCalibrationData(pub ctru_sys::CAMU_ImageQualityCalibratio
#[derive(Default, Clone, Copy, Debug)] #[derive(Default, Clone, Copy, Debug)]
pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData); pub struct StereoCameraCalibrationData(pub ctru_sys::CAMU_StereoCameraCalibrationData);
/// Basic configuration needed to properly use the built-in cameras.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Configuration {
view_size: ViewSize,
trimming: Trimming,
}
impl Configuration {
fn new() -> Self {
Self {
view_size: ViewSize::TopLCD,
trimming: Trimming::Off,
}
}
}
/// Inward camera representation (facing the user of the 3DS). /// Inward camera representation (facing the user of the 3DS).
/// ///
/// Usually used for selfies. /// Usually used for selfies.
#[non_exhaustive]
pub struct InwardCam { pub struct InwardCam {
configuration: Configuration, configuration: Configuration,
} }
/// Right-side outward camera representation. /// Right-side outward camera representation.
#[non_exhaustive]
pub struct OutwardRightCam { pub struct OutwardRightCam {
configuration: Configuration, configuration: Configuration,
} }
/// Left-side outward camera representation. /// Left-side outward camera representation.
#[non_exhaustive]
pub struct OutwardLeftCam { pub struct OutwardLeftCam {
configuration: Configuration, configuration: Configuration,
} }
@ -289,113 +271,107 @@ pub struct OutwardLeftCam {
/// Both outer cameras combined. /// Both outer cameras combined.
/// ///
/// Usually used for 3D photos. /// Usually used for 3D photos.
#[non_exhaustive]
pub struct BothOutwardCam { pub struct BothOutwardCam {
configuration: Configuration, configuration: Configuration,
} }
impl BothOutwardCam { mod private {
/// Set whether to enable or disable brightness synchronization between the two cameras. use super::{BothOutwardCam, InwardCam, OutwardLeftCam, OutwardRightCam, Trimming, ViewSize};
#[doc(alias = "CAMU_SetBrightnessSynchronization")]
pub fn set_brightness_synchronization( /// Basic configuration needed to properly use the built-in cameras.
&mut self, #[derive(Clone, Copy, Debug, PartialEq, Eq)]
brightness_synchronization: bool, pub struct Configuration {
) -> crate::Result<()> { pub view_size: ViewSize,
unsafe { pub trimming: Trimming,
ResultCode(ctru_sys::CAMU_SetBrightnessSynchronization(
brightness_synchronization,
))?;
Ok(())
}
} }
}
macro_rules! trimming_checks { impl Configuration {
($trimming:ident, $view_size:expr) => { pub fn new() -> Self {
match $trimming { Self::default()
Trimming::Centered(trim_size) => { }
let view_size: (i16, i16) = $view_size; }
let trim_size: (i16, i16) = trim_size.into();
// Trim sizes are within the view. impl Default for Configuration {
assert!(trim_size.0 <= view_size.0 && trim_size.1 <= view_size.1); fn default() -> Self {
Self {
view_size: ViewSize::TopLCD,
trimming: Trimming::Off,
} }
Trimming::Off => (),
} }
};
}
impl Camera for InwardCam {
fn camera_as_raw(&self) -> ctru_sys::u32_ {
ctru_sys::SELECT_IN1
} }
fn view_size(&self) -> ViewSize { pub trait ConfigurableCamera {
self.configuration.view_size fn configuration(&self) -> &Configuration;
fn configuration_mut(&mut self) -> &mut Configuration;
} }
fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { impl ConfigurableCamera for InwardCam {
unsafe { fn configuration(&self) -> &Configuration {
ResultCode(ctru_sys::CAMU_SetSize( &self.configuration
self.camera_as_raw(),
size.into(),
ctru_sys::CONTEXT_A,
))?;
} }
self.configuration.view_size = size; fn configuration_mut(&mut self) -> &mut Configuration {
&mut self.configuration
self.set_trimming(Trimming::Off); }
Ok(())
} }
fn trimming(&self) -> Trimming { impl ConfigurableCamera for OutwardRightCam {
self.configuration.trimming fn configuration(&self) -> &Configuration {
&self.configuration
}
fn configuration_mut(&mut self) -> &mut Configuration {
&mut self.configuration
}
} }
fn set_trimming(&mut self, trimming: Trimming) { impl ConfigurableCamera for OutwardLeftCam {
// Run checks for all trimming possibilities. fn configuration(&self) -> &Configuration {
trimming_checks!(trimming, self.view_size().into()); &self.configuration
}
self.configuration.trimming = trimming; fn configuration_mut(&mut self) -> &mut Configuration {
&mut self.configuration
}
} }
}
impl Camera for OutwardRightCam { impl ConfigurableCamera for BothOutwardCam {
fn camera_as_raw(&self) -> ctru_sys::u32_ { fn configuration(&self) -> &Configuration {
ctru_sys::SELECT_OUT1 &self.configuration
} }
fn view_size(&self) -> ViewSize { fn configuration_mut(&mut self) -> &mut Configuration {
self.configuration.view_size &mut self.configuration
}
} }
}
fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> { impl BothOutwardCam {
/// Set whether to enable or disable brightness synchronization between the two cameras.
#[doc(alias = "CAMU_SetBrightnessSynchronization")]
pub fn set_brightness_synchronization(
&mut self,
brightness_synchronization: bool,
) -> crate::Result<()> {
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_SetSize( ResultCode(ctru_sys::CAMU_SetBrightnessSynchronization(
self.camera_as_raw(), brightness_synchronization,
size.into(),
ctru_sys::CONTEXT_A,
))?; ))?;
Ok(())
} }
self.configuration.view_size = size;
self.set_trimming(Trimming::Off);
Ok(())
} }
}
fn trimming(&self) -> Trimming { impl Camera for InwardCam {
self.configuration.trimming fn camera_as_raw(&self) -> ctru_sys::u32_ {
ctru_sys::SELECT_IN1
} }
}
fn set_trimming(&mut self, trimming: Trimming) { impl Camera for OutwardRightCam {
// Run checks for all trimming possibilities. fn camera_as_raw(&self) -> ctru_sys::u32_ {
trimming_checks!(trimming, self.view_size().into()); ctru_sys::SELECT_OUT1
self.configuration.trimming = trimming;
} }
} }
@ -403,37 +379,6 @@ impl Camera for OutwardLeftCam {
fn camera_as_raw(&self) -> ctru_sys::u32_ { fn camera_as_raw(&self) -> ctru_sys::u32_ {
ctru_sys::SELECT_OUT2 ctru_sys::SELECT_OUT2
} }
fn view_size(&self) -> ViewSize {
self.configuration.view_size
}
fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetSize(
self.camera_as_raw(),
size.into(),
ctru_sys::CONTEXT_A,
))?;
}
self.configuration.view_size = size;
self.set_trimming(Trimming::Off);
Ok(())
}
fn trimming(&self) -> Trimming {
self.configuration.trimming
}
fn set_trimming(&mut self, trimming: Trimming) {
// Run checks for all trimming possibilities.
trimming_checks!(trimming, self.view_size().into());
self.configuration.trimming = trimming;
}
} }
impl Camera for BothOutwardCam { impl Camera for BothOutwardCam {
@ -444,41 +389,10 @@ impl Camera for BothOutwardCam {
fn port_as_raw(&self) -> ctru_sys::u32_ { fn port_as_raw(&self) -> ctru_sys::u32_ {
ctru_sys::PORT_BOTH ctru_sys::PORT_BOTH
} }
fn view_size(&self) -> ViewSize {
self.configuration.view_size
}
fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetSize(
self.camera_as_raw(),
size.into(),
ctru_sys::CONTEXT_A,
))?;
}
self.configuration.view_size = size;
self.set_trimming(Trimming::Off);
Ok(())
}
fn trimming(&self) -> Trimming {
self.configuration.trimming
}
fn set_trimming(&mut self, trimming: Trimming) {
// Run checks for all trimming possibilities.
trimming_checks!(trimming, self.view_size().into());
self.configuration.trimming = trimming;
}
} }
/// Generic functionality common to all cameras. /// Generic functionality common to all cameras.
pub trait Camera { pub trait Camera: private::ConfigurableCamera {
/// Returns the raw value of the selected camera. /// Returns the raw value of the selected camera.
fn camera_as_raw(&self) -> ctru_sys::u32_; fn camera_as_raw(&self) -> ctru_sys::u32_;
@ -489,7 +403,9 @@ pub trait Camera {
/// This view is the full resolution at which the camera will take the photo. /// This view is the full resolution at which the camera will take the photo.
/// If you are interested in the final image's size, calculated while taking into account all processing and modifications, /// If you are interested in the final image's size, calculated while taking into account all processing and modifications,
/// have a look at [`Camera::final_view_size()`]. /// have a look at [`Camera::final_view_size()`].
fn view_size(&self) -> ViewSize; fn view_size(&self) -> ViewSize {
self.configuration().view_size
}
/// Returns the raw port of the selected camera. /// Returns the raw port of the selected camera.
fn port_as_raw(&self) -> ctru_sys::u32_ { fn port_as_raw(&self) -> ctru_sys::u32_ {
@ -595,11 +511,50 @@ pub trait Camera {
} }
/// Returns the [`Trimming`] configuration currently set. /// Returns the [`Trimming`] configuration currently set.
fn trimming(&self) -> Trimming; fn trimming(&self) -> Trimming {
self.configuration().trimming
}
/// Set trimming bounds to trim the camera photo. /// Set trimming bounds to trim the camera photo.
///
/// # Notes
///
/// Trimming the image view directly on the cameras can only work if the final image has the size of one of the supported image formats.
/// As such, even after trimming the image (which will result in a "zoomed in" view of the original image)
/// the resulting picture must still be of [`ViewSize`] dimensions.
#[doc(alias = "CAMU_SetTrimming")] #[doc(alias = "CAMU_SetTrimming")]
fn set_trimming(&mut self, trimming: Trimming); fn set_trimming(&mut self, trimming: Trimming) -> crate::Result<()> {
// Run checks for all trimming possibilities.
match trimming {
Trimming::Centered(trim_size) => unsafe {
let view_size: (i16, i16) = self.view_size().into();
let trim_size: (i16, i16) = trim_size.into();
// Trim sizes are within the view.
assert!(
trim_size.0 <= view_size.0 && trim_size.1 <= view_size.1,
"trimmed view is bigger than the camera view"
);
ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?;
ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter(
self.port_as_raw(),
trim_size.0,
trim_size.1,
view_size.0,
view_size.1,
))?;
},
Trimming::Off => unsafe {
ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?;
},
}
self.configuration_mut().trimming = trimming;
Ok(())
}
/// Returns whether or not trimming is currently enabled for the camera. /// Returns whether or not trimming is currently enabled for the camera.
#[doc(alias = "CAMU_IsTrimming")] #[doc(alias = "CAMU_IsTrimming")]
@ -681,7 +636,21 @@ pub trait Camera {
/// ///
/// Calling this function will reset the trimming configuration. /// Calling this function will reset the trimming configuration.
#[doc(alias = "CAMU_SetSize")] #[doc(alias = "CAMU_SetSize")]
fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()>; fn set_view_size(&mut self, size: ViewSize) -> crate::Result<()> {
unsafe {
ResultCode(ctru_sys::CAMU_SetSize(
self.camera_as_raw(),
size.into(),
ctru_sys::CONTEXT_A,
))?;
}
self.configuration_mut().view_size = size;
self.set_trimming(Trimming::Off)?;
Ok(())
}
/// Set the frame rate of the camera. /// Set the frame rate of the camera.
#[doc(alias = "CAMU_SetFrameRate")] #[doc(alias = "CAMU_SetFrameRate")]
@ -711,8 +680,7 @@ pub trait Camera {
/// ///
/// # Notes /// # Notes
/// ///
/// This operation will override any previously set [`Effect`]s. /// This operation will override any previously set [`Effect`].
/// Multiple effects can be set at once by combining the bitflags of [`Effect`].
#[doc(alias = "CAMU_SetEffect")] #[doc(alias = "CAMU_SetEffect")]
fn set_effect(&mut self, effect: Effect) -> crate::Result<()> { fn set_effect(&mut self, effect: Effect) -> crate::Result<()> {
unsafe { unsafe {
@ -857,14 +825,7 @@ pub trait Camera {
/// ///
/// # Errors /// # Errors
/// ///
/// This function will return an error if the camera is busy or if the timeout duration gets reached. /// This function will return an error if the camera is already busy or if the timeout duration is reached.
///
/// # Arguments
///
/// * `width` - Width of the desired image
/// * `height` - Height of the desired image
/// * `timeout` - Duration to wait for the image
///
/// # Example /// # Example
/// ///
/// ```no_run /// ```no_run
@ -894,27 +855,16 @@ pub trait Camera {
/// # } /// # }
/// ``` /// ```
fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> { fn take_picture(&mut self, buffer: &mut [u8], timeout: Duration) -> crate::Result<()> {
// Obtain the final view size and make the needed modifications to the camera. // Check whether the provided buffer is big enough to store the image.
match self.trimming() { let max_size = self.final_byte_length();
Trimming::Centered(trim_size) => unsafe { if buffer.len() < max_size {
ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), true))?;
let full_view: (i16, i16) = self.view_size().into();
let trim_size: (i16, i16) = trim_size.into();
ResultCode(ctru_sys::CAMU_SetTrimmingParamsCenter(
self.port_as_raw(),
trim_size.0,
trim_size.1,
full_view.0,
full_view.1,
))?;
},
Trimming::Off => unsafe {
ResultCode(ctru_sys::CAMU_SetTrimming(self.port_as_raw(), false))?;
},
};
return Err(Error::BufferTooShort {
provided: buffer.len(),
wanted: max_size,
});
}
let final_view = self.final_view_size(); let final_view = self.final_view_size();
// The transfer unit is NOT the "max number of bytes" or whatever the docs make you think it is... // The transfer unit is NOT the "max number of bytes" or whatever the docs make you think it is...
@ -939,21 +889,6 @@ pub trait Camera {
))?; ))?;
}; };
// Check whether the input buffer is big enough for the image.
let max_size = self.final_byte_length();
if buffer.len() < max_size {
// We deactivate the camera prematurely.
//
// Note that it shouldn't be too important whether the camera closes or not here,
// since it only starts capturing later.
unsafe { ResultCode(ctru_sys::CAMU_Activate(ctru_sys::SELECT_NONE))? };
return Err(Error::BufferTooShort {
provided: buffer.len(),
wanted: max_size,
});
}
unsafe { unsafe {
ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?; ResultCode(ctru_sys::CAMU_Activate(self.camera_as_raw()))?;
ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?; ResultCode(ctru_sys::CAMU_ClearBuffer(self.port_as_raw()))?;
@ -979,14 +914,14 @@ pub trait Camera {
}; };
unsafe { unsafe {
// Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully (TODO: SVC module) // Panicking without closing an SVC handle causes an ARM exception, we have to handle it carefully.
let wait_result = ResultCode(ctru_sys::svcWaitSynchronization( let wait_result = ResultCode(ctru_sys::svcWaitSynchronization(
receive_event, receive_event,
timeout.as_nanos().try_into().unwrap(), timeout.as_nanos().try_into().unwrap(),
)); ));
// We close everything first, then we check for possible errors // We close everything first, then we check for possible errors
let _ = ctru_sys::svcCloseHandle(receive_event); // We wouldn't return the error even if there was one, so no use of ResultCode is needed let _ = ctru_sys::svcCloseHandle(receive_event); // We wouldn't return the error even if there was one, so no use of ResultCode is needed.
// Camera state cleanup // Camera state cleanup
ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?; ResultCode(ctru_sys::CAMU_StopCapture(self.port_as_raw()))?;

Loading…
Cancel
Save