1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! A simple pixmap type.
use alloc::vec;
use alloc::vec::Vec;
#[cfg(feature = "png")]
use std::io::{BufRead, Seek};
use crate::peniko::color::{PremulRgba8, Rgba8};
#[cfg(feature = "png")]
extern crate std;
/// A pixmap of premultiplied RGBA8 values backed by [`u8`][core::u8].
#[derive(Debug, Clone)]
pub struct Pixmap {
/// Width of the pixmap in pixels.
width: u16,
/// Height of the pixmap in pixels.
height: u16,
/// Buffer of the pixmap in RGBA8 format.
buf: Vec<PremulRgba8>,
/// Whether the pixmap may have non-opaque pixels.
///
/// Note: This may become stale if pixels are modified via [`data_mut()`](Self::data_mut),
/// [`data_as_u8_slice_mut()`](Self::data_as_u8_slice_mut), or [`set_pixel()`](Self::set_pixel).
may_have_transparency: bool,
}
impl Pixmap {
/// Create a new pixmap with the given width and height in pixels.
///
/// All pixels are initialized to transparent black.
pub fn new(width: u16, height: u16) -> Self {
let buf = vec![PremulRgba8::from_u32(0); width as usize * height as usize];
Self {
width,
height,
buf,
may_have_transparency: true,
}
}
/// Create a new pixmap with the given premultiplied RGBA8 data.
///
/// The `data` vector must be of length `width * height` exactly.
///
/// The pixels are in row-major order.
///
/// This assumes the image may have transparent pixels. Use
/// [`from_parts_with_opacity`](Self::from_parts_with_opacity) if you already
/// know the opacity status to enable optimizations.
///
/// # Panics
///
/// Panics if the `data` vector is not of length `width * height`.
pub fn from_parts(data: Vec<PremulRgba8>, width: u16, height: u16) -> Self {
Self::from_parts_with_opacity(data, width, height, true)
}
/// Create a new pixmap with the given premultiplied RGBA8 data and precomputed opacity flag.
///
/// The `data` vector must be of length `width * height` exactly.
///
/// The pixels are in row-major order.
///
/// Use this when you've already determined whether the data contains
/// non-opaque pixels to avoid redundant scanning.
///
/// # Panics
///
/// Panics if the `data` vector is not of length `width * height`.
pub fn from_parts_with_opacity(
data: Vec<PremulRgba8>,
width: u16,
height: u16,
may_have_transparency: bool,
) -> Self {
assert_eq!(
data.len(),
usize::from(width) * usize::from(height),
"Expected `data` to have length of exactly `width * height`"
);
Self {
width,
height,
buf: data,
may_have_transparency,
}
}
/// Resizes the pixmap container to the given width and height; this does not resize the
/// contained image.
///
/// If the pixmap buffer has to grow to fit the new size, those pixels are set to transparent
/// black. If the pixmap buffer is larger than required, the buffer is truncated and its
/// reserved capacity is unchanged.
pub fn resize(&mut self, width: u16, height: u16) {
let new_len = usize::from(width) * usize::from(height);
// If we're growing, new pixels are transparent black
if new_len > self.buf.len() {
self.may_have_transparency = true;
}
self.width = width;
self.height = height;
self.buf.resize(new_len, PremulRgba8::from_u32(0));
}
/// Shrink the capacity of the pixmap buffer to fit the pixmap's current size.
pub fn shrink_to_fit(&mut self) {
self.buf.shrink_to_fit();
}
/// The reserved capacity (in pixels) of this pixmap.
///
/// When calling [`Pixmap::resize`] with a `width * height` smaller than this value, the pixmap
/// does not need to reallocate.
pub fn capacity(&self) -> usize {
self.buf.capacity()
}
/// Return the width of the pixmap.
pub fn width(&self) -> u16 {
self.width
}
/// Return the height of the pixmap.
pub fn height(&self) -> u16 {
self.height
}
/// Returns whether the pixmap may have non-opaque pixels.
///
/// This value is computed at construction time. It may become stale if pixels are
/// modified directly via [`data_mut()`](Self::data_mut),
/// [`data_as_u8_slice_mut()`](Self::data_as_u8_slice_mut), or [`set_pixel()`](Self::set_pixel).
///
/// Use [`set_may_have_transparency()`](Self::set_may_have_transparency) to manually update the flag,
/// or [`recompute_may_have_transparency()`](Self::recompute_may_have_transparency) to recalculate it
/// by scanning all pixels.
pub fn may_have_transparency(&self) -> bool {
self.may_have_transparency
}
/// Manually set the `may_have_transparency` flag.
///
/// Use this after modifying pixels via [`data_mut()`](Self::data_mut) or
/// [`set_pixel()`](Self::set_pixel) when you know whether the image has
/// non-opaque pixels.
pub fn set_may_have_transparency(&mut self, may_have_transparency: bool) {
self.may_have_transparency = may_have_transparency;
}
/// Recalculate `may_have_transparency` by scanning all pixels.
///
/// Use this after modifying pixels via [`data_mut()`](Self::data_mut) or
/// [`set_pixel()`](Self::set_pixel) when you need accurate opacity information.
pub fn recompute_may_have_transparency(&mut self) {
self.may_have_transparency = self.buf.iter().any(|pixel| pixel.a != 255);
}
/// Apply an alpha value to the whole pixmap.
pub fn multiply_alpha(&mut self, alpha: u8) {
#[expect(
clippy::cast_possible_truncation,
reason = "cannot overflow in this case"
)]
let multiply = |component| ((u16::from(alpha) * u16::from(component)) / 255) as u8;
for pixel in self.data_mut() {
*pixel = PremulRgba8 {
r: multiply(pixel.r),
g: multiply(pixel.g),
b: multiply(pixel.b),
a: multiply(pixel.a),
};
}
// If we applied a non-opaque alpha, the image now has transparency
if alpha != 255 {
self.may_have_transparency = true;
}
}
/// Create a pixmap from a PNG file.
#[cfg(feature = "png")]
pub fn from_png(data: impl BufRead + Seek) -> Result<Self, png::DecodingError> {
let mut decoder = png::Decoder::new(data);
decoder.set_transformations(
png::Transformations::normalize_to_color8() | png::Transformations::ALPHA,
);
let mut reader = decoder.read_info()?;
let mut pixmap = {
let info = reader.info();
let width: u16 = info
.width
.try_into()
.map_err(|_| png::DecodingError::LimitsExceeded)?;
let height: u16 = info
.height
.try_into()
.map_err(|_| png::DecodingError::LimitsExceeded)?;
Self::new(width, height)
};
// Note `reader.info()` returns the pre-transformation color type output, whereas
// `reader.output_color_type()` takes the transformation into account.
let (color_type, bit_depth) = reader.output_color_type();
debug_assert_eq!(
bit_depth,
png::BitDepth::Eight,
"normalize_to_color8 means the bit depth is always 8."
);
match color_type {
png::ColorType::Rgb | png::ColorType::Grayscale => {
unreachable!("We set a transformation to always convert to alpha")
}
png::ColorType::Indexed => {
unreachable!("Transformation should have expanded indexed images")
}
png::ColorType::Rgba => {
debug_assert_eq!(
Some(pixmap.data_as_u8_slice().len()),
reader.output_buffer_size(),
"The pixmap buffer should have the same number of bytes as the image."
);
reader.next_frame(pixmap.data_as_u8_slice_mut())?;
}
png::ColorType::GrayscaleAlpha => {
debug_assert_eq!(
Some(pixmap.data().len() * 2),
reader.output_buffer_size(),
"The pixmap buffer should have twice the number of bytes of the grayscale image."
);
let mut grayscale_data = vec![0; reader.output_buffer_size().unwrap_or_default()];
reader.next_frame(&mut grayscale_data)?;
for (grayscale_pixel, pixmap_pixel) in
grayscale_data.chunks_exact(2).zip(pixmap.data_mut())
{
let [gray, alpha] = grayscale_pixel.try_into().unwrap();
*pixmap_pixel = PremulRgba8 {
r: gray,
g: gray,
b: gray,
a: alpha,
};
}
}
};
let mut may_have_transparency = false;
for pixel in pixmap.data_mut() {
let alpha = pixel.a;
if alpha != 255 {
may_have_transparency = true;
}
let alpha_u16 = u16::from(alpha);
#[expect(
clippy::cast_possible_truncation,
reason = "Overflow should be impossible."
)]
let premultiply = |e: u8| ((u16::from(e) * alpha_u16) / 255) as u8;
pixel.r = premultiply(pixel.r);
pixel.g = premultiply(pixel.g);
pixel.b = premultiply(pixel.b);
}
pixmap.may_have_transparency = may_have_transparency;
Ok(pixmap)
}
/// Return the current content of the pixmap as a PNG.
#[cfg(feature = "png")]
pub fn into_png(self) -> Result<Vec<u8>, png::EncodingError> {
let mut data = Vec::new();
let mut encoder = png::Encoder::new(&mut data, self.width as u32, self.height as u32);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
writer.write_image_data(bytemuck::cast_slice(&self.take_unpremultiplied()))?;
writer.finish().map(|_| data)
}
/// Returns a reference to the underlying data as premultiplied RGBA8.
///
/// The pixels are in row-major order.
pub fn data(&self) -> &[PremulRgba8] {
&self.buf
}
/// Returns a mutable reference to the underlying data as premultiplied RGBA8.
///
/// The pixels are in row-major order.
pub fn data_mut(&mut self) -> &mut [PremulRgba8] {
&mut self.buf
}
/// Returns a reference to the underlying data as premultiplied RGBA8.
///
/// The pixels are in row-major order. Each pixel consists of four bytes in the order
/// `[r, g, b, a]`.
pub fn data_as_u8_slice(&self) -> &[u8] {
bytemuck::cast_slice(&self.buf)
}
/// Returns a mutable reference to the underlying data as premultiplied RGBA8.
///
/// The pixels are in row-major order. Each pixel consists of four bytes in the order
/// `[r, g, b, a]`.
pub fn data_as_u8_slice_mut(&mut self) -> &mut [u8] {
bytemuck::cast_slice_mut(&mut self.buf)
}
/// Sample a pixel from the pixmap.
///
/// The pixel data is [premultiplied RGBA8][PremulRgba8].
#[inline(always)]
pub fn sample(&self, x: u16, y: u16) -> PremulRgba8 {
let idx = self.width as usize * y as usize + x as usize;
self.buf[idx]
}
/// Sample a pixel from a custom-calculated index. This index should be calculated assuming that
/// the data is stored in row-major order.
#[inline(always)]
pub fn sample_idx(&self, idx: u32) -> PremulRgba8 {
self.buf[idx as usize]
}
/// Set a pixel in the pixmap at the given coordinates.
///
/// The pixel data should be [premultiplied RGBA8][PremulRgba8]. The coordinate system has
/// its origin at the top-left corner, with `x` increasing to the right and `y` increasing
/// downward.
#[inline(always)]
pub fn set_pixel(&mut self, x: u16, y: u16, pixel: PremulRgba8) {
let idx = self.width as usize * y as usize + x as usize;
self.buf[idx] = pixel;
}
/// Consume the pixmap, returning the data as the underlying [`Vec`] of premultiplied RGBA8.
///
/// The pixels are in row-major order.
pub fn take(self) -> Vec<PremulRgba8> {
self.buf
}
/// Consume the pixmap, returning the data as (unpremultiplied) RGBA8.
///
/// Not fast, but useful for saving to PNG etc.
///
/// The pixels are in row-major order.
pub fn take_unpremultiplied(self) -> Vec<Rgba8> {
self.buf
.into_iter()
.map(|PremulRgba8 { r, g, b, a }| {
let alpha = 255.0 / f32::from(a);
if a != 0 {
#[expect(clippy::cast_possible_truncation, reason = "deliberate quantization")]
let unpremultiply = |component| (f32::from(component) * alpha + 0.5) as u8;
Rgba8 {
r: unpremultiply(r),
g: unpremultiply(g),
b: unpremultiply(b),
a,
}
} else {
Rgba8 { r, g, b, a }
}
})
.collect()
}
}