1use crate::{ops::clamp_dimensions, prelude::*, renderer::Rendering};
4#[cfg(not(target_arch = "wasm32"))]
5use anyhow::Context;
6#[cfg(not(target_arch = "wasm32"))]
7use png::{BitDepth, ColorType, Decoder};
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10#[cfg(not(target_arch = "wasm32"))]
11use std::{
12 ffi::OsStr,
13 fs::File,
14 io::{self, BufReader, BufWriter},
15 path::{Path, PathBuf},
16};
17use std::{fmt, iter::Copied, slice};
18
19#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
21#[non_exhaustive]
22#[must_use]
23#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
24pub enum PixelFormat {
25 Rgb,
27 Rgba,
29}
30
31impl PixelFormat {
32 #[inline]
34 #[must_use]
35 pub const fn channels(&self) -> usize {
36 match self {
37 PixelFormat::Rgb => 3,
38 PixelFormat::Rgba => 4,
39 }
40 }
41}
42
43#[derive(Debug, Copy, Clone, PartialEq, Eq)]
45#[doc(hidden)]
46pub struct TryFromColorTypeError(pub(crate) ());
47
48#[doc(hidden)]
49impl TryFrom<png::ColorType> for PixelFormat {
50 type Error = TryFromColorTypeError;
51 fn try_from(color_type: png::ColorType) -> std::result::Result<Self, Self::Error> {
52 match color_type {
53 png::ColorType::Rgb => Ok(Self::Rgb),
54 png::ColorType::Rgba => Ok(Self::Rgba),
55 _ => Err(TryFromColorTypeError(())),
56 }
57 }
58}
59
60#[doc(hidden)]
61impl From<PixelFormat> for png::ColorType {
62 fn from(format: PixelFormat) -> Self {
63 match format {
64 PixelFormat::Rgb => Self::Rgb,
65 PixelFormat::Rgba => Self::Rgba,
66 }
67 }
68}
69
70impl Default for PixelFormat {
71 fn default() -> Self {
72 Self::Rgba
73 }
74}
75
76#[derive(Default, Clone)]
78#[must_use]
79pub struct Image {
80 width: u32,
82 height: u32,
84 data: Vec<u8>,
86 format: PixelFormat,
88}
89
90impl Image {
91 #[inline]
93 pub fn new(width: u32, height: u32) -> Self {
94 Self::rgba(width, height)
95 }
96
97 #[doc(alias = "new")]
101 #[inline]
102 pub fn rgba(width: u32, height: u32) -> Self {
103 let format = PixelFormat::Rgba;
104 let data = vec![0x00; format.channels() * (width * height) as usize];
105 Self::from_vec(width, height, data, format)
106 }
107
108 #[inline]
110 pub fn rgb(width: u32, height: u32) -> Self {
111 let format = PixelFormat::Rgb;
112 let data = vec![0x00; format.channels() * (width * height) as usize];
113 Self::from_vec(width, height, data, format)
114 }
115
116 #[inline]
123 pub fn from_bytes<B: AsRef<[u8]>>(
124 width: u32,
125 height: u32,
126 bytes: B,
127 format: PixelFormat,
128 ) -> PixResult<Self> {
129 let bytes = bytes.as_ref();
130 if bytes.len() != (format.channels() * width as usize * height as usize) {
131 return Err(PixError::InvalidImage {
132 width,
133 height,
134 size: bytes.len(),
135 format,
136 }
137 .into());
138 }
139 Ok(Self::from_vec(width, height, bytes.to_vec(), format))
140 }
141
142 #[inline]
149 pub fn from_pixels<P: AsRef<[Color]>>(
150 width: u32,
151 height: u32,
152 pixels: P,
153 format: PixelFormat,
154 ) -> PixResult<Self> {
155 let pixels = pixels.as_ref();
156 if pixels.len() != (width as usize * height as usize) {
157 return Err(PixError::InvalidImage {
158 width,
159 height,
160 size: pixels.len() * format.channels(),
161 format,
162 }
163 .into());
164 }
165 let bytes: Vec<u8> = match format {
166 PixelFormat::Rgb => pixels
167 .iter()
168 .flat_map(|p| [p.red(), p.green(), p.blue()])
169 .collect(),
170 PixelFormat::Rgba => pixels.iter().flat_map(Color::channels).collect(),
171 };
172 Ok(Self::from_vec(width, height, bytes, format))
173 }
174
175 #[inline]
177 pub fn from_vec(width: u32, height: u32, data: Vec<u8>, format: PixelFormat) -> Self {
178 Self {
179 width,
180 height,
181 data,
182 format,
183 }
184 }
185
186 #[cfg(not(target_arch = "wasm32"))]
192 pub fn from_file<P: AsRef<Path>>(path: P) -> PixResult<Self> {
193 let path = path.as_ref();
194 let ext = path.extension();
195 if ext != Some(OsStr::new("png")) {
196 return Err(PixError::UnsupportedFileType(ext.map(OsStr::to_os_string)).into());
197 }
198 Self::from_read(File::open(path)?)
199 }
200
201 #[cfg(not(target_arch = "wasm32"))]
208 pub fn from_read<R: io::Read>(read: R) -> PixResult<Self> {
209 let png_file = BufReader::new(read);
210 let png = Decoder::new(png_file);
211
212 let mut reader = png.read_info().context("failed to read png data")?;
218 let mut buf = vec![0x00; reader.output_buffer_size()];
219 let info = reader
220 .next_frame(&mut buf)
221 .context("failed to read png data frame")?;
222 let bit_depth = info.bit_depth;
223 let color_type = info.color_type;
224 if bit_depth != BitDepth::Eight || !matches!(color_type, ColorType::Rgb | ColorType::Rgba) {
225 return Err(PixError::UnsupportedImageFormat {
226 bit_depth,
227 color_type,
228 }
229 .into());
230 }
231
232 let data = &buf[..info.buffer_size()];
233 let format = info
234 .color_type
235 .try_into()
236 .map_err(|_| PixError::UnsupportedImageFormat {
237 bit_depth,
238 color_type,
239 })?;
240 Self::from_bytes(info.width, info.height, data, format)
241 }
242
243 #[inline]
245 #[must_use]
246 pub const fn width(&self) -> u32 {
247 self.width
248 }
249
250 #[inline]
252 #[must_use]
253 pub const fn height(&self) -> u32 {
254 self.height
255 }
256
257 #[inline]
259 #[must_use]
260 pub const fn dimensions(&self) -> (u32, u32) {
261 (self.width, self.height)
262 }
263
264 #[inline]
267 #[must_use]
268 pub const fn pitch(&self) -> usize {
269 self.width() as usize * self.format.channels()
270 }
271
272 #[inline]
278 pub fn bounding_rect(&self) -> Rect<i32> {
279 let (width, height) = clamp_dimensions(self.width, self.height);
280 rect![0, 0, width, height]
281 }
282
283 #[inline]
285 pub fn bounding_rect_offset<P>(&self, offset: P) -> Rect<i32>
286 where
287 P: Into<Point<i32>>,
288 {
289 let (width, height) = clamp_dimensions(self.width, self.height);
290 rect![offset.into(), width, height]
291 }
292
293 #[inline]
295 pub fn center(&self) -> Point<i32> {
296 let (width, height) = clamp_dimensions(self.width, self.height);
297 point!(width / 2, height / 2)
298 }
299
300 #[inline]
302 pub fn bytes(&self) -> Bytes<'_> {
303 Bytes(self.as_bytes().iter().copied())
304 }
305
306 #[inline]
308 #[must_use]
309 pub fn as_bytes(&self) -> &[u8] {
310 &self.data
311 }
312
313 #[inline]
315 #[must_use]
316 pub fn as_mut_bytes(&mut self) -> &mut [u8] {
317 &mut self.data
318 }
319
320 #[inline]
324 #[must_use]
325 #[allow(clippy::missing_const_for_fn)]
327 pub fn into_bytes(self) -> Vec<u8> {
328 self.data
329 }
330
331 #[inline]
333 pub fn pixels(&self) -> Pixels<'_> {
334 Pixels(self.format.channels(), self.as_bytes().iter().copied())
335 }
336
337 #[inline]
343 #[must_use]
344 pub fn into_pixels(self) -> Vec<Color> {
345 self.data
346 .chunks(self.format.channels())
347 .map(|slice| match *slice {
348 [red, green, blue] => Color::rgb(red, green, blue),
349 [red, green, blue, alpha] => Color::rgba(red, green, blue, alpha),
350 _ => Color::TRANSPARENT,
351 })
352 .collect()
353 }
354
355 #[inline]
362 pub fn get_pixel(&self, x: u32, y: u32) -> Color {
363 let idx = self.idx(x, y);
364 let channels = self.format.channels();
365 match self.data.get(idx..idx + channels) {
366 Some([red, green, blue]) => Color::rgb(*red, *green, *blue),
367 Some([red, green, blue, alpha]) => Color::rgba(*red, *green, *blue, *alpha),
368 _ => Color::TRANSPARENT,
369 }
370 }
371
372 #[inline]
374 pub fn set_pixel<C: Into<Color>>(&mut self, x: u32, y: u32, color: C) {
375 let color = color.into();
376 let idx = self.idx(x, y);
377 let channels = self.format.channels();
378 self.data[idx..(idx + channels)].clone_from_slice(&color.channels()[..channels]);
379 }
380
381 #[inline]
383 pub fn update_bytes<B: AsRef<[u8]>>(&mut self, bytes: B) {
384 self.data.clone_from_slice(bytes.as_ref());
385 }
386
387 #[inline]
389 pub const fn format(&self) -> PixelFormat {
390 self.format
391 }
392
393 #[cfg(not(target_arch = "wasm32"))]
417 pub fn save<P>(&self, path: P) -> PixResult<()>
418 where
419 P: AsRef<Path>,
420 {
421 let path = path.as_ref();
422 let png_file = BufWriter::new(File::create(path)?);
423 let mut png = png::Encoder::new(png_file, self.width, self.height);
424 png.set_color(self.format.into());
425 png.set_depth(png::BitDepth::Eight);
426 let mut writer = png
427 .write_header()
428 .with_context(|| format!("failed to write png header: {path:?}"))?;
429 writer
430 .write_image_data(self.as_bytes())
431 .with_context(|| format!("failed to write png data: {path:?}"))
432 }
433}
434
435impl Image {
436 #[inline]
438 const fn idx(&self, x: u32, y: u32) -> usize {
439 self.format.channels() * (x + y * self.width) as usize
440 }
441}
442
443impl PixState {
444 pub fn image<P>(&mut self, img: &Image, position: P) -> PixResult<()>
464 where
465 P: Into<Point<i32>>,
466 {
467 let pos = position.into();
468 let dst = img.bounding_rect_offset(pos);
469 self.image_transformed(img, None, dst, 0.0, None, None)
470 }
471
472 pub fn image_transformed<R1, R2, A, C, F>(
500 &mut self,
501 img: &Image,
502 src: R1,
503 dst: R2,
504 angle: A,
505 center: C,
506 flipped: F,
507 ) -> PixResult<()>
508 where
509 R1: Into<Option<Rect<i32>>>,
510 R2: Into<Option<Rect<i32>>>,
511 A: Into<Option<f64>>,
512 C: Into<Option<Point<i32>>>,
513 F: Into<Option<Flipped>>,
514 {
515 let s = &self.settings;
516 let mut dst = dst.into();
517 if s.image_mode == ImageMode::Center {
518 dst = dst.map(|dst| Rect::from_center(dst.top_left(), dst.width(), dst.height()));
519 };
520 let mut angle = angle.into().unwrap_or(0.0);
521 if s.angle_mode == AngleMode::Radians {
522 angle = angle.to_degrees();
523 };
524 self.renderer.image(
525 img,
526 src.into(),
527 dst,
528 angle,
529 center.into(),
530 flipped.into(),
531 s.image_tint,
532 )
533 }
534}
535
536impl fmt::Debug for Image {
537 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538 f.debug_struct("Image")
539 .field("width", &self.width)
540 .field("height", &self.height)
541 .field("size", &self.data.len())
542 .field("format", &self.format)
543 .finish()
544 }
545}
546
547#[derive(Debug, Clone)]
552#[must_use]
553pub struct Bytes<'a>(Copied<slice::Iter<'a, u8>>);
554
555impl Iterator for Bytes<'_> {
556 type Item = u8;
557 #[inline]
558 fn next(&mut self) -> Option<Self::Item> {
559 self.0.next()
560 }
561}
562
563#[derive(Debug, Clone)]
568#[must_use]
569pub struct Pixels<'a>(usize, Copied<slice::Iter<'a, u8>>);
570
571impl Iterator for Pixels<'_> {
572 type Item = Color;
573 #[inline]
574 fn next(&mut self) -> Option<Self::Item> {
575 let r = self.1.next()?;
576 let g = self.1.next()?;
577 let b = self.1.next()?;
578 let channels = self.0;
579 match channels {
580 3 => Some(Color::rgb(r, g, b)),
581 4 => {
582 let a = self.1.next()?;
583 Some(Color::rgba(r, g, b, a))
584 }
585 _ => Some(Color::TRANSPARENT),
586 }
587 }
588}
589
590#[derive(Debug, Clone)]
592pub enum Icon {
593 Image(Image),
595 #[cfg(not(target_arch = "wasm32"))]
596 Path(PathBuf),
598}
599
600#[cfg(not(target_arch = "wasm32"))]
601impl<T: Into<PathBuf>> From<T> for Icon {
602 fn from(value: T) -> Self {
603 Self::Path(value.into())
604 }
605}
606
607impl From<Image> for Icon {
608 fn from(img: Image) -> Self {
609 Self::Image(img)
610 }
611}