From 72c9401a5f252374f54655930b97882bcb6be141 Mon Sep 17 00:00:00 2001 From: Steve Cook Date: Sun, 17 Jul 2022 00:35:59 -0400 Subject: [PATCH] Initial CAM service and example --- ctru-rs/examples/camera-image.rs | 204 ++++++++++++++++ ctru-rs/src/services/cam.rs | 284 +++++++++++++++++++++++ ctru-rs/src/services/mod.rs | 1 + ctru-sys/src/bin/docstring-to-rustdoc.rs | 11 +- 4 files changed, 493 insertions(+), 7 deletions(-) create mode 100644 ctru-rs/examples/camera-image.rs create mode 100644 ctru-rs/src/services/cam.rs diff --git a/ctru-rs/examples/camera-image.rs b/ctru-rs/examples/camera-image.rs new file mode 100644 index 0000000..be93f86 --- /dev/null +++ b/ctru-rs/examples/camera-image.rs @@ -0,0 +1,204 @@ +use ctru::console::Console; +use ctru::gfx::{RawFrameBuffer, Screen, Side}; +use ctru::services::cam::{ + Cam, CamContext, CamOutputFormat, CamPort, CamSelect, CamShutterSoundType, CamSize, +}; +use ctru::services::hid::KeyPad; +use ctru::services::{Apt, Hid}; +use ctru::Gfx; +use ctru_sys::Handle; + +const WIDTH: usize = 400; +const HEIGHT: usize = 240; +const SCREEN_SIZE: usize = WIDTH * HEIGHT * 2; +const BUF_SIZE: usize = SCREEN_SIZE * 2; + +const WAIT_TIMEOUT: i64 = 300000000; + +fn main() { + ctru::init(); + + let apt = Apt::init().expect("Failed to initialize Apt service."); + let hid = Hid::init().expect("Failed to initialize Hid service."); + let gfx = Gfx::init().expect("Failed to initialize GFX service."); + + gfx.top_screen.borrow_mut().set_double_buffering(true); + gfx.bottom_screen.borrow_mut().set_double_buffering(false); + + let _console = Console::init(gfx.bottom_screen.borrow_mut()); + + let mut key_down; + let mut key_held; + + println!("Initializing camera"); + + let mut cam = Cam::init().expect("Failed to initialize CAM service."); + + cam.set_size( + CamSelect::SELECT_OUT1_OUT2, + CamSize::SIZE_CTR_TOP_LCD, + CamContext::CONTEXT_A, + ) + .expect("Failed to set camera size"); + cam.set_output_format( + CamSelect::SELECT_OUT1_OUT2, + CamOutputFormat::OUTPUT_RGB_565, + CamContext::CONTEXT_A, + ) + .expect("Failed to set camera output format"); + + cam.set_noise_filter(CamSelect::SELECT_OUT1_OUT2, true) + .expect("Failed to enable noise filter"); + cam.set_auto_exposure(CamSelect::SELECT_OUT1_OUT2, true) + .expect("Failed to enable auto exposure"); + cam.set_auto_white_balance(CamSelect::SELECT_OUT1_OUT2, true) + .expect("Failed to enable auto white balance"); + + cam.set_trimming(CamPort::PORT_CAM1, false) + .expect("Failed to disable trimming for Cam Port 1"); + cam.set_trimming(CamPort::PORT_CAM2, false) + .expect("Failed to disable trimming for Cam Port 2"); + + let mut buf = vec![0u8; BUF_SIZE]; + + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + + let mut held_r = false; + + println!("\nPress R to take a new picture"); + println!("Press Start to exit to Homebrew Launcher"); + + gfx.top_screen.borrow_mut().set_3d_enabled(false); + + while apt.main_loop() { + hid.scan_input(); + key_down = hid.keys_down(); + key_held = hid.keys_held(); + + if key_down.contains(KeyPad::KEY_START) { + break; + } + + if key_held.contains(KeyPad::KEY_R) && !held_r { + println!("Capturing new image"); + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + held_r = true; + take_picture(&mut cam, buf.as_mut_ptr()); + } else if !key_held.contains(KeyPad::KEY_R) { + held_r = false; + } + + write_picture_to_frame_buffer_rgb_565( + gfx.top_screen.borrow_mut().get_raw_framebuffer(Side::Left), + buf.as_mut_ptr(), + 0, + 0, + WIDTH as u16, + HEIGHT as u16, + ); + + gfx.flush_buffers(); + gfx.swap_buffers(); + gfx.wait_for_vblank(); + } +} + +fn take_picture(cam: &mut Cam, buf: *mut u8) { + let mut buf_size = 0; + cam.get_max_bytes(&mut buf_size, WIDTH as i16, HEIGHT as i16) + .expect("Failed to get max bytes"); + cam.set_transfer_bytes(CamPort::PORT_BOTH, buf_size, WIDTH as i16, HEIGHT as i16) + .expect("Failed to set transfer bytes"); + + cam.activate(CamSelect::SELECT_OUT1_OUT2) + .expect("Failed to activate camera"); + + let mut receive_event: Handle = 0; + let mut receive_event2: Handle = 0; + + cam.clear_buffer(CamPort::PORT_BOTH) + .expect("Failed to clear buffer"); + cam.synchronize_vsync_timing(CamSelect::SELECT_OUT1, CamSelect::SELECT_OUT2) + .expect("Failed to sync vsync timings"); + + cam.start_capture(CamPort::PORT_BOTH) + .expect("Failed to start capture"); + + cam.set_receiving( + &mut receive_event, + buf, + CamPort::PORT_CAM1, + SCREEN_SIZE as u32, + buf_size as i16, + ) + .expect("Failed to set receiving"); + cam.set_receiving( + &mut receive_event2, + unsafe { buf.add(SCREEN_SIZE) }, + CamPort::PORT_CAM2, + SCREEN_SIZE as u32, + buf_size as i16, + ) + .expect("Failed to set receiving"); + unsafe { + let mut r = ctru_sys::svcWaitSynchronization(receive_event, WAIT_TIMEOUT); + if r < 0 { + panic!("Failed to wait for handle synchronization"); + } + r = ctru_sys::svcWaitSynchronization(receive_event2, WAIT_TIMEOUT); + if r < 0 { + panic!("Failed to wait for handle 2 synchronization"); + } + }; + + cam.play_shutter_sound(CamShutterSoundType::SHUTTER_SOUND_TYPE_NORMAL) + .expect("Failed to play shutter sound"); + + unsafe { + let mut r = ctru_sys::svcCloseHandle(receive_event); + if r < 0 { + panic!("Failed to close handle"); + } + r = ctru_sys::svcCloseHandle(receive_event2); + if r < 0 { + panic!("Failed to close handle 2"); + } + }; + + cam.activate(CamSelect::SELECT_NONE) + .expect("Failed to deactivate camera"); +} + +fn write_picture_to_frame_buffer_rgb_565( + fb: RawFrameBuffer, + img: *mut u8, + x: u16, + y: u16, + width: u16, + height: u16, +) { + let fb_8 = fb.ptr; + let img_16 = img as *mut u16; + let mut draw_x; + let mut draw_y; + for j in 0..height { + for i in 0..width { + draw_y = y + height - j; + draw_x = x + i; + let v = (draw_y as usize + draw_x as usize * height as usize) * 3; + let data = unsafe { *img_16.add(j as usize * width as usize + i as usize) }; + let b = (((data >> 11) & 0x1F) << 3) as u8; + let g = (((data >> 5) & 0x3F) << 2) as u8; + let r = ((data & 0x1F) << 3) as u8; + unsafe { + *fb_8.add(v) = r; + *fb_8.add(v + 1) = g; + *fb_8.add(v + 2) = b; + }; + } + } +} diff --git a/ctru-rs/src/services/cam.rs b/ctru-rs/src/services/cam.rs new file mode 100644 index 0000000..0c24093 --- /dev/null +++ b/ctru-rs/src/services/cam.rs @@ -0,0 +1,284 @@ +use bitflags::bitflags; + +pub struct Cam(()); + +bitflags! { + #[derive(Default)] + pub struct CamPort: u32 { + const PORT_NONE = 0; + const PORT_CAM1 = 1; + const PORT_CAM2 = 2; + const PORT_BOTH = Self::PORT_CAM1.bits | Self::PORT_CAM2.bits; + } +} + +bitflags! { + #[derive(Default)] + pub struct CamSelect: u32 { + const SELECT_NONE = 0; + const SELECT_OUT1 = 1; + const SELECT_IN1 = 2; + const SELECT_OUT2 = 4; + const SELECT_IN1_OUT1 = Self::SELECT_OUT1.bits | Self::SELECT_IN1.bits; + const SELECT_OUT1_OUT2 = Self::SELECT_OUT1.bits | Self::SELECT_OUT2.bits; + const SELECT_IN1_OUT2 = Self::SELECT_IN1.bits | Self::SELECT_OUT2.bits; + const SELECT_ALL = Self::SELECT_IN1.bits | Self::SELECT_OUT1.bits | Self::SELECT_OUT2.bits; + } +} + +bitflags! { + #[derive(Default)] + pub struct CamSize: u32 { + const SIZE_VGA = 0; + const SIZE_QVGA = 1; + const SIZE_QQVGA = 2; + const SIZE_CIF = 3; + const SIZE_QCIF = 4; + const SIZE_DS_LCD = 5; + const SIZE_DS_LCD_X4 = 6; + const SIZE_CTR_TOP_LCD = 7; + const SIZE_CTR_BOTTOM_LCD = Self::SIZE_QVGA.bits; + } +} + +bitflags! { + #[derive(Default)] + pub struct CamContext: u32 { + const CONTEXT_NONE = 0; + const CONTEXT_A = 1; + const CONTEXT_B = 2; + const CONTEXT_BOTH = Self::CONTEXT_A.bits | Self::CONTEXT_B.bits; + } +} + +bitflags! { + #[derive(Default)] + pub struct CamOutputFormat: u32 { + const OUTPUT_YUV_422 = 0; + const OUTPUT_RGB_565 = 1; + } +} + +bitflags! { + #[derive(Default)] + pub struct CamShutterSoundType: u32 { + const SHUTTER_SOUND_TYPE_NORMAL = 0; + const SHUTTER_SOUND_TYPE_MOVIE = 1; + const SHUTTER_SOUND_TYPE_MOVIE_END = 2; + } +} + +impl Cam { + pub fn init() -> crate::Result { + unsafe { + let r = ctru_sys::camInit(); + if r < 0 { + Err(r.into()) + } else { + Ok(Cam(())) + } + } + } + + pub fn set_size( + &mut self, + camera: CamSelect, + size: CamSize, + context: CamContext, + ) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SetSize(camera.bits(), size.bits(), context.bits()); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn set_output_format( + &mut self, + camera: CamSelect, + format: CamOutputFormat, + context: CamContext, + ) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SetOutputFormat(camera.bits(), format.bits(), context.bits()); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn set_noise_filter(&mut self, camera: CamSelect, enabled: bool) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SetNoiseFilter(camera.bits(), enabled); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn set_auto_exposure(&mut self, camera: CamSelect, enabled: bool) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SetAutoExposure(camera.bits(), enabled); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn set_auto_white_balance( + &mut self, + camera: CamSelect, + enabled: bool, + ) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SetAutoWhiteBalance(camera.bits(), enabled); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn set_trimming(&mut self, port: CamPort, enabled: bool) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SetTrimming(port.bits(), enabled); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn get_max_bytes(&self, buf_size: &mut u32, width: i16, height: i16) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_GetMaxBytes(buf_size, width, height); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn set_transfer_bytes( + &mut self, + port: CamPort, + buf_size: u32, + width: i16, + height: i16, + ) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SetTransferBytes(port.bits(), buf_size, width, height); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn clear_buffer(&mut self, port: CamPort) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_ClearBuffer(port.bits()); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn synchronize_vsync_timing( + &mut self, + camera1: CamSelect, + camera2: CamSelect, + ) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SynchronizeVsyncTiming(camera1.bits(), camera2.bits()); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn set_receiving( + &mut self, + handle: &mut u32, + buf: *mut u8, + port: CamPort, + size: u32, + buf_size: i16, + ) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_SetReceiving(handle, buf as *mut ::libc::c_void, port.bits(), size, buf_size); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn play_shutter_sound(&self, sound: CamShutterSoundType) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_PlayShutterSound(sound.bits()); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn start_capture(&self, port: CamPort) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_StartCapture(port.bits()); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn stop_capture(&self, port: CamPort) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_StopCapture(port.bits()); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } + + pub fn activate(&mut self, camera: CamSelect) -> crate::Result<()> { + unsafe { + let r = ctru_sys::CAMU_Activate(camera.bits()); + if r < 0 { + Err(r.into()) + } else { + Ok(()) + } + } + } +} + +impl Drop for Cam { + fn drop(&mut self) { + unsafe { ctru_sys::camExit() }; + } +} diff --git a/ctru-rs/src/services/mod.rs b/ctru-rs/src/services/mod.rs index 0dc750c..25a800d 100644 --- a/ctru-rs/src/services/mod.rs +++ b/ctru-rs/src/services/mod.rs @@ -1,4 +1,5 @@ pub mod apt; +pub mod cam; pub mod fs; pub mod gspgpu; pub mod hid; diff --git a/ctru-sys/src/bin/docstring-to-rustdoc.rs b/ctru-sys/src/bin/docstring-to-rustdoc.rs index b7c7cb9..a843a1b 100644 --- a/ctru-sys/src/bin/docstring-to-rustdoc.rs +++ b/ctru-sys/src/bin/docstring-to-rustdoc.rs @@ -19,8 +19,8 @@ //! The followings are _partially_ transformed to Rustdoc format: //! * `@param` -use std::{env, fs, io}; use std::path::Path; +use std::{env, fs, io}; fn main() -> io::Result<()> { let args: Vec = env::args().collect(); @@ -33,8 +33,7 @@ fn main() -> io::Result<()> { .map(|v| { // Only modify lines with the following structure: `` #[doc ... ] `` if v.trim_start().starts_with("#[doc") && v.trim_end().ends_with("]") { - v - .replace("@brief", "") + v.replace("@brief", "") // Example: ``@param offset Offset of the RomFS...`` -> ``- offset Offset of the RomFS...`` // Will improve in the future .replace("@param", "* ") @@ -54,9 +53,7 @@ fn main() -> io::Result<()> { String::from(v) } }) - .map(|v| { - v + "\n" - }) + .map(|v| v + "\n") .collect::(); let old_bindings_path = bindings_path.to_str().unwrap().to_owned() + ".old"; @@ -67,4 +64,4 @@ fn main() -> io::Result<()> { fs::remove_file(&old_bindings_path)?; Ok(()) -} \ No newline at end of file +}