ombre 0.6.7

Shadowy game and graphics library for Rust
Documentation
//! Image encoding and decoding.
use std::io;
use std::sync::Arc;

use super::{pixels, Color as _, Point2D, Rect, Rgba8, Size};
use crate::math::Zero;

/// File extension for RGBA images.
pub const FILE_EXTENSION: &str = "rgba";

///////////////////////////////////////////////////////////////////////////
// Image
///////////////////////////////////////////////////////////////////////////

/// An RGBA image. Can be created from an `.rgba` file buffer using the `TryFrom`
/// instance.
///
/// .---------.
/// |  MAGIC  | "RGBA" (4 bytes)
/// +---------+
/// |  WIDTH  |        (4 bytes) (Big Endian)
/// +---------+
/// |  HEIGHT |        (4 bytes) (Big Endian)
/// +---------+
/// |  DATA   |        (WIDTH * HEIGHT * 4 bytes)
/// |  ....   |        (RGBA format)
/// |  ....   |
/// |  ....   |
/// '---------'
///
#[derive(Debug, Clone)]
pub struct Image {
    pub size: Size<u32>,
    pub pixels: Arc<[Rgba8]>,
}

impl Eq for Image {}

impl PartialEq for Image {
    fn eq(&self, other: &Self) -> bool {
        (Arc::ptr_eq(&self.pixels, &other.pixels) && self.size == other.size)
            || self.pixels == other.pixels
    }
}

/// Image error.
pub enum ImageError {
    Decoding,
}

impl Image {
    pub fn new(pixels: impl Into<Arc<[Rgba8]>>, size: impl Into<Size<u32>>) -> Self {
        let size = size.into();
        let pixels = pixels.into();

        assert_eq!(size.area() as usize, pixels.len());

        Self { size, pixels }
    }

    /// An empty image.
    pub fn empty() -> Self {
        Self {
            size: Size::ZERO,
            pixels: Arc::new([]),
        }
    }

    /// Return a new scaled image.
    pub fn scaled(&self, factor: u32) -> Self {
        let scaled = pixels::scale(&self.pixels, self.size.w, self.size.h, factor);

        Self {
            size: self.size * factor,
            pixels: scaled.into(),
        }
    }

    /// Get the image area rectangle.
    pub fn rect(&self) -> Rect<u32> {
        Rect::origin(self.size)
    }

    /// Create a blank image of the given size.
    pub fn blank(size: impl Into<Size<u32>>) -> Self {
        let size = size.into();

        Self::new(vec![Rgba8::TRANSPARENT; size.area() as usize], size)
    }

    /// Write the image to the writer in `.rgba` format.
    pub fn write(&self, mut w: impl io::Write) -> Result<usize, io::Error> {
        let mut n = 0;
        let (head, texels, tail) = unsafe { self.pixels.align_to::<u8>() };

        assert!(head.is_empty() && tail.is_empty());

        n += w.write(&[b'R', b'G', b'B', b'A'])?;
        n += w.write(&u32::to_be_bytes(self.size.w))?;
        n += w.write(&u32::to_be_bytes(self.size.h))?;
        n += w.write(texels)?;

        w.flush()?;

        Ok(n)
    }

    /// Get the texel color at the given position in the image.
    pub fn sample(&self, point: Point2D<u32>) -> Option<&Rgba8> {
        let offset = self.size.w * point.y + point.x;
        self.pixels.get(offset as usize)
    }
}

impl TryFrom<&[u8]> for Image {
    type Error = ImageError;

    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
        let tail = if let &[b'R', b'G', b'B', b'A', ref tail @ ..] = bytes {
            tail
        } else {
            return Err(ImageError::Decoding);
        };

        let (tail, width) = if let &[a, b, c, d, ref tail @ ..] = tail {
            (tail, u32::from_be_bytes([a, b, c, d]))
        } else {
            return Err(ImageError::Decoding);
        };

        let (tail, height) = if let &[a, b, c, d, ref tail @ ..] = tail {
            (tail, u32::from_be_bytes([a, b, c, d]))
        } else {
            return Err(ImageError::Decoding);
        };

        let (head, texels, tail) = unsafe { tail.align_to::<Rgba8>() };

        if !head.is_empty() {
            return Err(ImageError::Decoding);
        }
        if !tail.is_empty() {
            return Err(ImageError::Decoding);
        }
        assert_eq!(texels.len(), (width * height) as usize);

        Ok(Self::new(texels.to_vec(), Size::new(width, height)))
    }
}