From 0b9a5aa7bf3b6230b2f383063ae3ef63331e449e Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 5 Mar 2026 20:00:31 +0100 Subject: [PATCH 1/2] Add Buffer data_u8 and rows_u8 accessors --- CHANGELOG.md | 1 + src/lib.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0c9b006..e6cc5807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Added `Buffer::pixels_iter()` for iterating over each pixel with its associated `x`/`y` coordinate. - Added `Buffer::byte_stride()` for pixel buffers whose rows are aligned and may contain padding bytes at the end. Prefer to use the above helpers instead of accessing pixel data directly. - Renamed `Surface::buffer_mut()` to `Surface::next_buffer()`. +- Added `Buffer::data_u8()` and `Buffer::rows_u8()` for accessing the buffer's data as `u8` slices. - **Breaking:** Add `Pixel` struct, and use that for pixels instead of `u32`. - **Breaking:** The pixel format is now target-dependent. Access `PixelFormat::default()` to see which format is used on the current platform. - **Breaking:** Removed generic type parameters `D` and `W` from `Buffer<'_>` struct. diff --git a/src/lib.rs b/src/lib.rs index ed05a9e7..3d205a6c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -397,6 +397,69 @@ impl Buffer<'_> { } } +/// Helper methods for writing to the buffer's "raw" data. +impl Buffer<'_> { + /// Access the buffer's data as a `u8` slice. + /// + /// The size of the returned slice is `buffer.byte_stride() * buffer.height()`, and it is + /// guaranteed to have an alignment of at least 4. + /// + /// # Examples + /// + /// Zero the buffer. + /// + /// ```no_run + /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); + /// buffer.data_u8().fill(0x00); + /// ``` + #[inline] + pub fn data_u8(&mut self) -> &mut [u8] { + let data = self.buffer_impl.pixels_mut(); + // SAFETY: `u32` can be reinterpreted as 4 `u8`s. + unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().cast::(), data.len() * 4) } + } + + /// Iterate over each row of the buffer's data as `u8` subslices. + /// + /// Each slice returned from the iterator has a length of `buffer.byte_stride()` and alignment + /// of at least 4. + /// + /// # Examples + /// + /// Work with the data as individual `u8` components: + /// + /// ```no_run + /// use softbuffer::PixelFormat; + /// + /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); + /// for row in buffer.rows_u8() { + /// // Ignore remaining pixels, each row should have at least `4` elements. + /// let (row, _remainder) = row.as_chunks_mut::<4>(); + /// + /// for [b0, b1, b2, b3] in row { + /// let [r, g, b, a] = match PixelFormat::default() { + /// PixelFormat::Bgra8 => [b2, b1, b0, b3], + /// PixelFormat::Rgba8 => [b0, b1, b2, b3], + /// format => unimplemented!("unknown default pixel format: {format:?}"), + /// }; + /// // Write a red pixel. + /// *r = 0xff; + /// *b = 0x00; + /// *g = 0x00; + /// *a = 0x00; + /// } + /// } + /// ``` + #[inline] + pub fn rows_u8(&mut self) -> impl DoubleEndedIterator + ExactSizeIterator { + let byte_stride = self.byte_stride().get() as usize; + let data = self.data_u8(); + assert_eq!(data.len() % byte_stride, 0, "must be multiple of stride"); + // NOTE: This won't panic because `byte_stride` came from `NonZeroU32` + data.chunks_mut(byte_stride) + } +} + /// Helper methods for writing to the buffer as RGBA pixel data. impl Buffer<'_> { /// Get a mutable reference to the buffer's pixels. @@ -453,12 +516,12 @@ impl Buffer<'_> { /// { /// // Use zero-copy implementation when possible. /// - /// // SAFETY: `Pixel` can be reinterpreted as `[u8; 4]`. - /// let pixels = unsafe { std::mem::transmute::<&mut [Pixel], &mut [[u8; 4]]>(buffer.pixels()) }; + /// // Reinterpret data from `&mut [u8]` to `&mut [[u8; 4]]`. + /// let pixels = buffer.data_u8().as_chunks_mut::<4>().0; /// // CORRECTNESS: We just checked that: - /// // - The format is RGBA. - /// // - The `stride == width * 4`. - /// // - The alpha channel is ignored (A -> X). + /// // - Format is RGBA. + /// // - `stride == width * 4`. + /// // - Alpha channel is ignored (A -> X). /// // /// // So we can correctly render in the user's expected simplified RGBX format. /// render(pixels, width, height); From b399c7796fa013ffe974ba4e6fea7773b602abc4 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Wed, 22 Apr 2026 21:46:14 +0200 Subject: [PATCH 2/2] Move RGBX rendering example to `data_u8` --- src/lib.rs | 126 ++++++++++++++++++++++++++--------------------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3d205a6c..e308f22d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -412,69 +412,6 @@ impl Buffer<'_> { /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); /// buffer.data_u8().fill(0x00); /// ``` - #[inline] - pub fn data_u8(&mut self) -> &mut [u8] { - let data = self.buffer_impl.pixels_mut(); - // SAFETY: `u32` can be reinterpreted as 4 `u8`s. - unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().cast::(), data.len() * 4) } - } - - /// Iterate over each row of the buffer's data as `u8` subslices. - /// - /// Each slice returned from the iterator has a length of `buffer.byte_stride()` and alignment - /// of at least 4. - /// - /// # Examples - /// - /// Work with the data as individual `u8` components: - /// - /// ```no_run - /// use softbuffer::PixelFormat; - /// - /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); - /// for row in buffer.rows_u8() { - /// // Ignore remaining pixels, each row should have at least `4` elements. - /// let (row, _remainder) = row.as_chunks_mut::<4>(); - /// - /// for [b0, b1, b2, b3] in row { - /// let [r, g, b, a] = match PixelFormat::default() { - /// PixelFormat::Bgra8 => [b2, b1, b0, b3], - /// PixelFormat::Rgba8 => [b0, b1, b2, b3], - /// format => unimplemented!("unknown default pixel format: {format:?}"), - /// }; - /// // Write a red pixel. - /// *r = 0xff; - /// *b = 0x00; - /// *g = 0x00; - /// *a = 0x00; - /// } - /// } - /// ``` - #[inline] - pub fn rows_u8(&mut self) -> impl DoubleEndedIterator + ExactSizeIterator { - let byte_stride = self.byte_stride().get() as usize; - let data = self.data_u8(); - assert_eq!(data.len() % byte_stride, 0, "must be multiple of stride"); - // NOTE: This won't panic because `byte_stride` came from `NonZeroU32` - data.chunks_mut(byte_stride) - } -} - -/// Helper methods for writing to the buffer as RGBA pixel data. -impl Buffer<'_> { - /// Get a mutable reference to the buffer's pixels. - /// - /// The size of the returned slice is `buffer.byte_stride() * buffer.height() / 4`. - /// - /// # Examples - /// - /// Clear the buffer with red. - /// - /// ```no_run - /// # use softbuffer::{Buffer, Pixel}; - /// # let buffer: Buffer<'_> = unimplemented!(); - /// buffer.pixels().fill(Pixel::new_rgb(0xff, 0x00, 0x00)); - /// ``` /// /// Render to a slice of `[u8; 4]`s. This might be useful for library authors that want to /// provide a simple API that provides RGBX rendering. @@ -546,6 +483,69 @@ impl Buffer<'_> { /// /// buffer.present(); /// ``` + #[inline] + pub fn data_u8(&mut self) -> &mut [u8] { + let data = self.buffer_impl.pixels_mut(); + // SAFETY: `u32` can be reinterpreted as 4 `u8`s. + unsafe { std::slice::from_raw_parts_mut(data.as_mut_ptr().cast::(), data.len() * 4) } + } + + /// Iterate over each row of the buffer's data as `u8` subslices. + /// + /// Each slice returned from the iterator has a length of `buffer.byte_stride()` and alignment + /// of at least 4. + /// + /// # Examples + /// + /// Work with the data as individual `u8` components: + /// + /// ```no_run + /// use softbuffer::PixelFormat; + /// + /// # let buffer: softbuffer::Buffer<'_> = unimplemented!(); + /// for row in buffer.rows_u8() { + /// // Ignore remaining pixels, each row should have at least `4` elements. + /// let (row, _remainder) = row.as_chunks_mut::<4>(); + /// + /// for [b0, b1, b2, b3] in row { + /// let [r, g, b, a] = match PixelFormat::default() { + /// PixelFormat::Bgra8 => [b2, b1, b0, b3], + /// PixelFormat::Rgba8 => [b0, b1, b2, b3], + /// format => unimplemented!("unknown default pixel format: {format:?}"), + /// }; + /// // Write a red pixel. + /// *r = 0xff; + /// *b = 0x00; + /// *g = 0x00; + /// *a = 0x00; + /// } + /// } + /// ``` + #[inline] + pub fn rows_u8(&mut self) -> impl DoubleEndedIterator + ExactSizeIterator { + let byte_stride = self.byte_stride().get() as usize; + let data = self.data_u8(); + assert_eq!(data.len() % byte_stride, 0, "must be multiple of stride"); + // NOTE: This won't panic because `byte_stride` came from `NonZeroU32` + data.chunks_mut(byte_stride) + } +} + +/// Helper methods for writing to the buffer as RGBA pixel data. +impl Buffer<'_> { + /// Get a mutable reference to the buffer's pixels. + /// + /// The size of the returned slice is `buffer.byte_stride() * buffer.height() / 4`. + /// + /// # Examples + /// + /// Clear the buffer with red. + /// + /// ```no_run + /// # use softbuffer::{Buffer, Pixel}; + /// # let buffer: Buffer<'_> = unimplemented!(); + /// buffer.pixels().fill(Pixel::new_rgb(0xff, 0x00, 0x00)); + /// ``` pub fn pixels(&mut self) -> &mut [Pixel] { self.buffer_impl.pixels_mut() }