image_texel/lib.rs
1// Distributed under The MIT License (MIT)
2//
3// Copyright (c) 2019-2025 The `image-rs` developers
4//! An in-memory buffer for image data.
5//!
6//! It acknowledges that in a typical image pipeline there exist multiple valid but competing
7//! representations:
8//!
9//! - A reader that decodes bytes into pixel representations loads a texel type from raw bytes,
10//! decodes it, and stores it into a matrix as another type. For this it may primarily have to
11//! work with unaligned data.
12//! - Library transformation typically consume data as typed slices: `&[Rgb<u16>]`.
13//! - SIMD usage as well as GPU buffers may require that data is passed as slice of highly aligned
14//! data: `&[Simd<u16, 16>]`.
15//! - The workload of transformations must be shared between workers that need to access portions
16//! of the image that do not overlap in a _logical_ sense, eg. squares of 8x8 pixels each, but
17//! that do overlap in the sense of the physical layout where references to contiguous bytes
18//! would alias.
19//! - Planar layouts split channels to different aligned portions in a larger allocation, each such
20//! plane having its own internal layout (e.g. with subsampling or heterogeneous channel types).
21//!
22//! This crate offers the vocabulary types for buffers, layouts, and texels to *safely* cover many
23//! of the above use cases.
24//!
25//! Table of contents:
26//!
27//! 1. [General Usage](#general-usage)
28//! 1. [Planar layouts](#planar-layouts)
29//! 2. [Concurrent editing](#concurrent-editing)
30//! 2. [Image data transfer](#image-data-transfer)
31//!
32//! # General Usage
33//!
34//! In a simple example we will allocate a matrix representing 16-bit rgb pixels, then transform
35//! those pixels into network endian byte order in-place, and encode that as a PNM image according
36//! to the `netpbm` implementation. Note how we can convert quite freely between different ways of
37//! viewing the data.
38//!
39#![cfg_attr(not(miri), doc = "```")]
40#![cfg_attr(miri, doc = "```no_run")] // too expensive and pointless
41//! use image_texel::{Matrix, Image, texels::U16};
42//! // Editing is simpler with the `Matrix` type.
43//! let mut matrix = Matrix::<[u16; 4]>::with_width_and_height(400, 400);
44//!
45//! // Draw a bright red line.
46//! for i in 0..400 {
47//! // Assign color as u8-RGBA
48//! matrix[(i, i)] = [0xFFFF, 0x00, 0x00, 0xFFFF];
49//! }
50//!
51//! // General operations are simpler with the `Image` type.
52//! let mut image = Image::from(matrix);
53//!
54//! // Encode components to network endian by iterating one long slice.
55//! image
56//! .as_mut_texels(U16)
57//! .iter_mut()
58//! .for_each(|p| *p = p.to_be());
59//!
60//! let pam_header = format!("P7\nWIDTH {}\nHEIGHT {}\nDEPTH 4\nMAXVAL 65535\nTUPLETYPE
61//! RGB_ALPHA\nENDHDR\n", 400, 400);
62//!
63//! let mut network_data = vec![];
64//! network_data.extend_from_slice(pam_header.as_bytes());
65//! network_data.extend_from_slice(image.as_bytes());
66//! ```
67//!
68//! It is your responsibility to ensure the different layouts are compatible, although the library
69//! tries to help by a few standardized layout traits and aiming to make correct methods simpler
70//! to use than methods with preconditions. You are allowed to mix it up, in fact this is
71//! encouraged for reusing buffers. However, of course when you do you may be a little surprised
72//! with the data you find—the data is not zeroed.
73//!
74#![cfg_attr(not(miri), doc = "```")]
75#![cfg_attr(miri, doc = "```no_run")] // too expensive and pointless
76//! use image_texel::{Image, layout, texels};
77//! let matrix_u8 = layout::Matrix::from_width_height(texels::U8, 32, 32).unwrap();
78//! let mut image = Image::new(matrix_u8);
79//! // Fill with some arbitrary data..
80//! image.shade(|x, y, pix| *pix = (x * y) as u8);
81//!
82//! let matrix_u32 = layout::Matrix::from_width_height(texels::U32, 32, 32).unwrap();
83//! let reuse = image.with_layout(matrix_u32);
84//! // Huh? The 9th element now aliases the start of the second row before.
85//! assert_eq!(reuse.as_slice()[8], u32::to_be(0x00010203));
86//! ```
87//!
88//! ## Planar layouts
89//!
90//! The contiguous image allocation of `image_texel`'s buffers can be split into _planes_ of data.
91//! A plane starts at any arbitrary byte boundary that is aligned to the maximum alignment of
92//! texels, which is to say that algorithms that can be applied to a buffer as a whole may can also
93//! interact with a plane. A more precise meaning of a plane depends entirely on the layout
94//! containing it.
95//!
96//! The `layout` module defines a some containers that represent planar layouts built from their
97//! individual components. This relationship is expressed as [`PlaneOf`][`layout::PlaneOf`].
98//!
99//! ```
100//! use image_texel::{Image, layout, texels};
101//!
102//! let matrix_rgb = layout::Matrix::from_width_height(texels::U16.array::<3>(), 32, 32).unwrap();
103//! let matrix_a = layout::Matrix::from_width_height(texels::U8, 32, 32).unwrap();
104//!
105//! // An RGB plane, followed by its alpha mask.
106//! let image = Image::new(layout::PlaneBytes::new([matrix_rgb.into(), matrix_a.into()]));
107//!
108//! // Split them into planes via `impl PlaneOf<PlaneBytes> for usize`
109//! let [rgb, a] = image.as_ref().into_planes([0, 1]).unwrap();
110//! ```
111//!
112//! ## Concurrent editing
113//!
114//! This becomes especially important for shared buffers. We have two kinds of buffers that can be
115//! edited by multiple owners. A [`CellImage`][`image::CellImage`] is shared between owners but can
116//! not be sent across threads. Crucially, however, each duplicate owner may use any layout type
117//! and layout value that is chooses. This buffer behaves very similar to an [`Image`] except that
118//! its operations do not reallocate the buffer as this would remove the sharing.
119//!
120#![cfg_attr(not(miri), doc = "```")]
121#![cfg_attr(miri, doc = "```no_run")] // too expensive and pointless
122//! use image_texel::{image::CellImage, layout, texels};
123//!
124//! let matrix = layout::Matrix::from_width_height(texels::U32, 32, 32).unwrap();
125//! // An RGB plane, followed by its alpha mask.
126//! let image_u32 = CellImage::new(matrix);
127//!
128//! // Another way to refer to that image may be interpreting each u32 as 4 channels.
129//! let matrix = layout::Matrix::from_width_height(texels::U8.array::<4>(), 32, 32).unwrap();
130//! let image_rgba = image_u32.clone().try_with_layout(matrix)?;
131//!
132//! // Let's pretend we have some async thread pool that reads into the image and works on it:
133//! # fn spawn_local<T>(_: T) {} // hey this is just for show. The types are not accurate
134//!
135//! // We do not care about the component type in this function.
136//! async fn fill_image(matrix: CellImage<layout::MatrixBytes>) {
137//! loop {
138//! // .. refill the buffer by reads whenever we are signalled.
139//! }
140//! }
141//!
142//! async fn consume_buffer(matrix: CellImage<layout::Matrix<[u8; 4]>>) {
143//! // do some work on each new image.
144//! }
145//!
146//! spawn_local(fill_image(image_u32.decay()));
147//! spawn_local(consume_buffer(image_rgba));
148//!
149//! # Ok::<_, image_texel::BufferReuseError>(())
150//! ```
151//!
152//! An [`AtomicImage`][`image::AtomicImage`] can be shared between threads but its buffer
153//! modifications are not straightforward. In simplistic terms, it allows modifying disjunct parts
154//! of images concurrently but you should synchronize all modifications on the same part, e.g. via
155//! a lock, when the result values of those modifications is important. It always maintains
156//! soundness guarantees with such modifications.
157//!
158#![cfg_attr(not(miri), doc = "```")]
159#![cfg_attr(miri, doc = "```no_run")] // too expensive and pointless
160//! use image_texel::{image::AtomicImage, layout, texels};
161//!
162//! let matrix = layout::Matrix::from_width_height(texels::U8.array::<4>(), 1920, 1080).unwrap();
163//! let image = AtomicImage::new(matrix);
164//!
165//! # struct PretendThisDefinesABlock;
166//! # fn matrix_tiles(_: &impl layout::MatrixLayout) -> Vec<PretendThisDefinesABlock> { vec![] }
167//! # fn fill_block(_: AtomicImage<layout::Matrix<[u8; 4]>>, _: PretendThisDefinesABlock) {}
168//!
169//! std::thread::scope(|s| {
170//! // Decouple our work into tiles of the original image.
171//! for tile in matrix_tiles(image.layout()) {
172//! let work_image = image.clone();
173//! s.spawn(move || {
174//! fill_block(work_image, tile);
175//! });
176//! }
177//! });
178//!
179//! # Ok::<_, image_texel::BufferReuseError>(())
180//! ```
181//!
182//! # Image data transfer
183//!
184//! By data transfer we mean writing and reading semantically meaningful parts of an image into
185//! *unaligned* external byte buffers. In particular, when that part is one plane out of a larger
186//! number of image planes. A common case might be the initialization of an alpha mask. If you do
187//! not care about layouts of your data at all then you may get a slice or mutable slice to your
188//! data from any [`Image`]—or a `Cell<u8>` and [`texels::AtomicSliceRef`] for the shared data
189//! equivalents—and interact through those.
190//!
191//! Otherwise, the primary module for interacting with data is [`image::data`][`image::data`] as
192//! well as the [`as_source`][`Image::as_source`] and [`as_target`][`Image::as_target`] methods
193//! that view any applicable image container as a byte data container. Let's see the example before
194//! where an image contains two planes and we write the alpha mask:
195//!
196//! ```
197//! # fn testing() -> Option<()> {
198//! use image_texel::{Image, image::data, layout, texels};
199//!
200//! let matrix_rgb = layout::Matrix::from_width_height(texels::U16.array::<3>(), 32, 32)?;
201//! let matrix_a = layout::Matrix::from_width_height(texels::U8, 32, 32)?;
202//! // An RGB plane, followed by its alpha mask.
203//! let mut image = Image::new(layout::PlaneBytes::new([matrix_rgb.into(), matrix_a.into()]));
204//!
205//! // Grab a mutable reference to its alpha plane.
206//! let [alpha] = image.as_mut().into_planes([1]).unwrap();
207//! # let ref your_32x32_byte_mask = [0u8; 32 * 32];
208//! let bytes = data::DataRef::new(your_32x32_byte_mask);
209//!
210//! // Ignore the alpha layout, just write our bits in there. This gives
211//! // us back an image with a `Bytes` layout which we do not need.
212//! let _ = bytes.as_source().write_to_mut(alpha.decay())?;
213//! # Some::<()>(())
214//! # }
215//! ```
216//!
217//! If you want to modify the layout of the image you're assigning to, then you would instead use
218//! [`assign`][`Image::assign`] which will modify the layout in-place instead of returning a
219//! container with a new type. For this you interpret the data source with the layout you want to
220//! apply:
221//!
222//! ```
223//! # fn testing() -> Option<()> {
224//! use image_texel::{Image, image::data, layout, texels};
225//! // An initially empty image.
226//! let mut image = Image::new(layout::Matrix::empty(texels::U8));
227//!
228//! let matrix_a = layout::Matrix::from_width_height(texels::U8, 16, 16)?;
229//! # let ref your_16x16_ico = [0u8; 16 * 16];
230//! let bytes = data::DataRef::with_layout_at(your_16x16_ico, matrix_a, 0)?;
231//!
232//! image.assign(bytes.as_source());
233//! assert_eq!(image.layout().byte_len(), 256);
234//! # Some::<()>(())
235//! # }
236//! ```
237// Be std for doctests, avoids a weird warning about missing allocator.
238#![cfg_attr(not(doctest), no_std)]
239// The only module allowed to be `unsafe` is `texel`. We need it however, as we have a custom
240// dynamically sized type with an unsafe alignment invariant.
241#![deny(unsafe_code)]
242extern crate alloc;
243
244mod buf;
245pub mod image;
246pub mod layout;
247mod matrix;
248mod rec;
249mod stride;
250mod texel;
251
252pub use self::image::Image;
253pub use self::matrix::{Matrix, MatrixReuseError};
254pub use self::rec::{BufferReuseError, TexelBuffer};
255pub use self::texel::{AsTexel, Texel};
256
257/// Constants for predefined texel types.
258///
259/// Holding an instance of `Texel<T>` certifies that the type `T` is compatible with the texel
260/// concept, that is: its alignment requirement is *small* enough, its size is non-zero, it does
261/// not contain any padding, and it is a plain old data type without any inner invariants
262/// (including sharing predicates). These assertions allow a number of operations such as
263/// reinterpreting aligned byte slices, writing to and read from byte buffers, fallible cast to
264/// other texels, etc.
265///
266/// For types that guarantee the property, see [`AsTexel`] and its impls.
267///
268/// # Extending
269///
270/// The recommended method of extending this with a custom type is by implementing `bytemuck::Pod`
271/// for this type. This applies a number of consistency checks.
272///
273/// ```rust
274/// use bytemuck::{Pod, Zeroable};
275/// use image_texel::{AsTexel, Texel};
276///
277/// #[derive(Clone, Copy, Pod, Zeroable)]
278/// #[repr(C)]
279/// struct Rgb(pub [u8; 3]);
280///
281/// impl AsTexel for Rgb {
282/// fn texel() -> Texel<Rgb> {
283/// Texel::for_type().expect("verified by bytemuck and image_texel")
284/// }
285/// }
286///
287/// impl Rgb {
288/// const TEXEL: Texel<Self> = match Texel::for_type() {
289/// Some(texel) => texel,
290/// None => panic!("compilation error"),
291/// };
292/// }
293/// ```
294pub mod texels {
295 pub use crate::texel::constants::*;
296 pub use crate::texel::IsTransparentWrapper;
297 pub use crate::texel::MaxAligned;
298 pub use crate::texel::MaxAtomic;
299 pub use crate::texel::MaxCell;
300
301 pub use crate::buf::{
302 atomic_buf, buf, cell_buf, AtomicBuffer, AtomicRef, AtomicSliceRef, Buffer, CellBuffer,
303 TexelRange,
304 };
305}