From 0908f6166628069e73d44514707ac12af413e485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Fri, 31 Jul 2026 15:47:03 -0700 Subject: [PATCH 1/2] Add graphics readback and clear. --- crates/processing_ffi/src/lib.rs | 58 ++++++++++++++++++++++++++ crates/processing_pyo3/src/graphics.rs | 8 ++++ crates/processing_pyo3/src/lib.rs | 6 +++ 3 files changed, 72 insertions(+) diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index 0b424cf..51b4f71 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -226,6 +226,23 @@ pub extern "C" fn processing_background_image(graphics_id: u64, image_id: u64) { }); } +/// Clear the graphics surface to transparent. +/// +/// SAFETY: +/// - graphics_id is a valid ID returned from graphics_create. +/// - This is called from the same thread as init. +#[unsafe(no_mangle)] +pub extern "C" fn processing_clear(graphics_id: u64) { + error::clear_error(); + let graphics_entity = Entity::from_bits(graphics_id); + error::check(|| { + graphics_record_command( + graphics_entity, + DrawCommand::BackgroundColor(bevy::prelude::Color::NONE), + ) + }); +} + /// Begins the draw for the given graphics context. /// /// SAFETY: @@ -1601,6 +1618,47 @@ pub unsafe extern "C" fn processing_image_readback( }); } +/// Load pixels from the graphics surface into a caller-provided buffer. +/// +/// # Safety +/// - Init and graphics_create have been called. +/// - graphics_id is a valid ID returned from graphics_create. +/// - buffer is a valid pointer to at least buffer_len Color elements. +/// - buffer_len must equal width * height of the graphics surface. +/// - This is called from the same thread as init. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn processing_graphics_readback( + graphics_id: u64, + buffer: *mut Color, + buffer_len: usize, +) { + error::clear_error(); + let graphics_entity = Entity::from_bits(graphics_id); + error::check(|| { + let colors = graphics_readback(graphics_entity)?; + + if colors.len() != buffer_len { + let error_msg = format!( + "Buffer size mismatch: expected {}, got {}", + colors.len(), + buffer_len + ); + error::set_error(&error_msg); + return Err(error::ProcessingError::InvalidArgument(error_msg)); + } + + // SAFETY: Caller guarantees buffer is valid for buffer_len elements + unsafe { + let buffer_slice = std::slice::from_raw_parts_mut(buffer, buffer_len); + for (i, color) in colors.iter().enumerate() { + buffer_slice[i] = Color::from_linear(*color); + } + } + + Ok(()) + }); +} + /// Set the tint color applied to images. /// /// SAFETY: diff --git a/crates/processing_pyo3/src/graphics.rs b/crates/processing_pyo3/src/graphics.rs index 62bf730..0cad553 100644 --- a/crates/processing_pyo3/src/graphics.rs +++ b/crates/processing_pyo3/src/graphics.rs @@ -664,6 +664,14 @@ impl Graphics { .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) } + pub fn clear(&self) -> PyResult<()> { + graphics_record_command( + self.entity, + DrawCommand::BackgroundColor(bevy::prelude::Color::NONE), + ) + .map_err(|e| PyRuntimeError::new_err(format!("{e}"))) + } + #[pyo3(signature = (*args))] pub fn fill(&self, args: &Bound<'_, PyTuple>) -> PyResult<()> { if args.len() == 1 diff --git a/crates/processing_pyo3/src/lib.rs b/crates/processing_pyo3/src/lib.rs index a3ab56e..1a7ac92 100644 --- a/crates/processing_pyo3/src/lib.rs +++ b/crates/processing_pyo3/src/lib.rs @@ -1133,6 +1133,12 @@ mod mewnala { } } + #[pyfunction] + #[pyo3(pass_module)] + fn clear(module: &Bound<'_, PyModule>) -> PyResult<()> { + graphics!(module).clear() + } + #[pyfunction] #[pyo3(pass_module, signature = (mode, max1=None, max2=None, max3=None, max_alpha=None))] fn color_mode<'py>( From 3a77f31080e3e44bde59c614276a673f3da53e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Fri, 31 Jul 2026 15:51:46 -0700 Subject: [PATCH 2/2] Add get/set. --- crates/processing_ffi/src/color.rs | 7 +++ crates/processing_ffi/src/lib.rs | 68 ++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/crates/processing_ffi/src/color.rs b/crates/processing_ffi/src/color.rs index cde66b5..ae0ea82 100644 --- a/crates/processing_ffi/src/color.rs +++ b/crates/processing_ffi/src/color.rs @@ -30,4 +30,11 @@ impl Color { space: ColorSpace::Linear as u8, } } + + pub fn to_linear(self) -> LinearRgba { + ColorSpace::from_u8(self.space) + .unwrap_or(ColorSpace::Linear) + .color(self.c1, self.c2, self.c3, self.a) + .to_linear() + } } diff --git a/crates/processing_ffi/src/lib.rs b/crates/processing_ffi/src/lib.rs index 51b4f71..f7eac79 100644 --- a/crates/processing_ffi/src/lib.rs +++ b/crates/processing_ffi/src/lib.rs @@ -1659,6 +1659,74 @@ pub unsafe extern "C" fn processing_graphics_readback( }); } +/// Write a caller-provided pixel buffer back onto the graphics surface. +/// +/// # Safety +/// - Init and graphics_create have been called. +/// - graphics_id is a valid ID returned from graphics_create. +/// - buffer is a valid pointer to at least buffer_len Color elements. +/// - buffer_len must equal width * height of the graphics surface. +/// - This is called from the same thread as init. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn processing_graphics_update( + graphics_id: u64, + buffer: *const Color, + buffer_len: usize, +) { + error::clear_error(); + let graphics_entity = Entity::from_bits(graphics_id); + error::check(|| { + // SAFETY: Caller guarantees buffer is valid for buffer_len elements + let pixels: Vec<_> = unsafe { std::slice::from_raw_parts(buffer, buffer_len) } + .iter() + .map(|color| color.to_linear()) + .collect(); + graphics_update(graphics_entity, &pixels) + }); +} + +/// Write a caller-provided pixel buffer onto a rectangular region of the surface. +/// +/// # Safety +/// - Init and graphics_create have been called. +/// - graphics_id is a valid ID returned from graphics_create. +/// - buffer is a valid pointer to at least buffer_len Color elements. +/// - buffer_len must equal width * height. +/// - This is called from the same thread as init. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn processing_graphics_update_region( + graphics_id: u64, + x: u32, + y: u32, + width: u32, + height: u32, + buffer: *const Color, + buffer_len: usize, +) { + error::clear_error(); + let graphics_entity = Entity::from_bits(graphics_id); + error::check(|| { + // SAFETY: Caller guarantees buffer is valid for buffer_len elements + let pixels: Vec<_> = unsafe { std::slice::from_raw_parts(buffer, buffer_len) } + .iter() + .map(|color| color.to_linear()) + .collect(); + graphics_update_region(graphics_entity, x, y, width, height, &pixels) + }); +} + +/// Set a single pixel on the graphics surface. +/// +/// SAFETY: +/// - graphics_id is a valid ID returned from graphics_create. +/// - This is called from the same thread as init. +#[unsafe(no_mangle)] +pub extern "C" fn processing_graphics_set(graphics_id: u64, x: u32, y: u32, color: Color) { + error::clear_error(); + let graphics_entity = Entity::from_bits(graphics_id); + error::check(|| graphics_update_region(graphics_entity, x, y, 1, 1, &[color.to_linear()])); +} + /// Set the tint color applied to images. /// /// SAFETY: