use crate::slice::Slice;
use crate::{SharedString, SharedVector};
use super::{IntRect, IntSize};
#[derive(Debug, Clone)]
#[repr(C)]
pub struct SharedPixelBuffer<Pixel> {
width: usize,
height: usize,
stride: usize,
data: SharedVector<Pixel>,
}
impl<Pixel> SharedPixelBuffer<Pixel> {
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn stride(&self) -> usize {
self.stride
}
}
impl<Pixel: Clone> SharedPixelBuffer<Pixel> {
pub fn make_mut_slice(&mut self) -> &mut [Pixel] {
self.data.make_mut_slice()
}
}
impl<Pixel: Clone + rgb::Pod> SharedPixelBuffer<Pixel>
where
[Pixel]: rgb::ComponentBytes<u8>,
{
pub fn as_bytes(&self) -> &[u8] {
use rgb::ComponentBytes;
self.data.as_slice().as_bytes()
}
pub fn make_mut_bytes(&mut self) -> &mut [u8] {
use rgb::ComponentBytes;
self.data.make_mut_slice().as_bytes_mut()
}
}
impl<Pixel> SharedPixelBuffer<Pixel> {
pub fn as_slice(&self) -> &[Pixel] {
self.data.as_slice()
}
}
impl<Pixel: Clone + Default> SharedPixelBuffer<Pixel> {
pub fn new(width: usize, height: usize) -> Self {
Self {
width,
height,
stride: width,
data: core::iter::repeat(Pixel::default()).take(width * height).collect(),
}
}
}
impl<Pixel: Clone> SharedPixelBuffer<Pixel> {
pub fn clone_from_slice<SourcePixelType>(
pixel_slice: &[SourcePixelType],
width: usize,
height: usize,
) -> Self
where
[SourcePixelType]: rgb::AsPixels<Pixel>,
{
use rgb::AsPixels;
Self {
width,
height,
stride: width,
data: pixel_slice.as_pixels().iter().cloned().collect(),
}
}
}
pub type Rgb8Pixel = rgb::RGB8;
pub type Rgba8Pixel = rgb::RGBA8;
#[derive(Clone, Debug)]
#[repr(C)]
pub enum SharedImageBuffer {
RGB8(SharedPixelBuffer<Rgb8Pixel>),
RGBA8(SharedPixelBuffer<Rgba8Pixel>),
RGBA8Premultiplied(SharedPixelBuffer<Rgba8Pixel>),
}
impl SharedImageBuffer {
#[inline]
pub fn width(&self) -> usize {
match self {
Self::RGB8(buffer) => buffer.width(),
Self::RGBA8(buffer) => buffer.width(),
Self::RGBA8Premultiplied(buffer) => buffer.width(),
}
}
#[inline]
pub fn height(&self) -> usize {
match self {
Self::RGB8(buffer) => buffer.height(),
Self::RGBA8(buffer) => buffer.height(),
Self::RGBA8Premultiplied(buffer) => buffer.height(),
}
}
}
impl PartialEq for SharedImageBuffer {
fn eq(&self, other: &Self) -> bool {
match self {
Self::RGB8(lhs_buffer) => {
matches!(other, Self::RGB8(rhs_buffer) if lhs_buffer.data.as_ptr().eq(&rhs_buffer.data.as_ptr()))
}
Self::RGBA8(lhs_buffer) => {
matches!(other, Self::RGBA8(rhs_buffer) if lhs_buffer.data.as_ptr().eq(&rhs_buffer.data.as_ptr()))
}
Self::RGBA8Premultiplied(lhs_buffer) => {
matches!(other, Self::RGBA8Premultiplied(rhs_buffer) if lhs_buffer.data.as_ptr().eq(&rhs_buffer.data.as_ptr()))
}
}
}
}
#[repr(u8)]
#[derive(Clone, PartialEq, Debug, Copy)]
pub enum PixelFormat {
Rgb,
Rgba,
AlphaMap,
}
#[repr(C)]
#[derive(Clone, PartialEq, Debug)]
pub struct StaticTexture {
pub rect: IntRect,
pub format: PixelFormat,
pub color: crate::Color,
pub index: usize,
}
#[derive(Clone, PartialEq, Debug)]
#[repr(u8)]
#[allow(missing_docs)]
pub enum ImageInner {
None,
AbsoluteFilePath(SharedString),
EmbeddedData {
data: Slice<'static, u8>,
format: Slice<'static, u8>,
},
EmbeddedImage(SharedImageBuffer),
StaticTextures {
size: IntSize,
data: Slice<'static, u8>,
textures: Slice<'static, StaticTexture>,
},
}
impl Default for ImageInner {
fn default() -> Self {
ImageInner::None
}
}
impl ImageInner {
pub fn is_svg(&self) -> bool {
match self {
ImageInner::AbsoluteFilePath(path) => path.ends_with(".svg") || path.ends_with(".svgz"),
ImageInner::EmbeddedData { format, .. } => {
format.as_slice() == b"svg" || format.as_slice() == b"svgz"
}
_ => false,
}
}
}
impl<'a> From<&'a Image> for &'a ImageInner {
fn from(other: &'a Image) -> Self {
&other.0
}
}
#[derive(Default, Debug, PartialEq)]
pub struct LoadImageError(());
#[repr(transparent)]
#[derive(Default, Clone, Debug, PartialEq, derive_more::From)]
pub struct Image(ImageInner);
impl Image {
#[cfg(feature = "std")]
pub fn load_from_path(path: &std::path::Path) -> Result<Self, LoadImageError> {
Ok(Image(ImageInner::AbsoluteFilePath(path.to_str().ok_or(LoadImageError(()))?.into())))
}
pub fn from_rgb8(buffer: SharedPixelBuffer<Rgb8Pixel>) -> Self {
Image(ImageInner::EmbeddedImage(SharedImageBuffer::RGB8(buffer)))
}
pub fn from_rgba8(buffer: SharedPixelBuffer<Rgba8Pixel>) -> Self {
Image(ImageInner::EmbeddedImage(SharedImageBuffer::RGBA8(buffer)))
}
pub fn from_rgba8_premultiplied(buffer: SharedPixelBuffer<Rgba8Pixel>) -> Self {
Image(ImageInner::EmbeddedImage(SharedImageBuffer::RGBA8Premultiplied(buffer)))
}
pub fn size(&self) -> crate::graphics::Size {
match &self.0 {
ImageInner::None => Default::default(),
ImageInner::AbsoluteFilePath(_) | ImageInner::EmbeddedData { .. } => {
match crate::backend::instance() {
Some(backend) => backend.image_size(self),
None => panic!("sixtyfps::Image::size() called too early (before a graphics backend was chosen). You need to create a component first."),
}
},
ImageInner::EmbeddedImage(buffer) => [buffer.width() as _, buffer.height() as _].into(),
ImageInner::StaticTextures{size, ..} => size.cast(),
}
}
#[cfg(feature = "std")]
pub fn path(&self) -> Option<&std::path::Path> {
match &self.0 {
ImageInner::AbsoluteFilePath(path) => Some(std::path::Path::new(path.as_str())),
_ => None,
}
}
}
#[test]
fn test_image_size_from_buffer_without_backend() {
{
assert_eq!(Image::default().size(), Default::default());
}
{
let buffer = SharedPixelBuffer::<Rgb8Pixel>::new(320, 200);
let image = Image::from_rgb8(buffer);
assert_eq!(image.size(), [320., 200.].into())
}
}
#[cfg(feature = "ffi")]
pub(crate) mod ffi {
#![allow(unsafe_code)]
use super::super::Size;
use super::*;
#[cfg(cbindgen)]
#[repr(C)]
struct Rgb8Pixel {
r: u8,
g: u8,
b: u8,
}
#[cfg(cbindgen)]
#[repr(C)]
struct Rgba8Pixel {
r: u8,
g: u8,
b: u8,
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_image_size(image: &Image) -> Size {
image.size()
}
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_image_path(image: &Image) -> Option<&SharedString> {
match &image.0 {
ImageInner::AbsoluteFilePath(path) => Some(&path),
_ => None,
}
}
}