lexlib 2.0.1

library with miscellaneous stuff
Documentation
// Copyright 2023 alexevier <alexevier@proton.me>
// licensed under the zlib license <https://www.zlib.net/zlib_license.html>

//! image manipulation.
//!
//! <dl>
//! <dt>The formats currently supported are</dt>
//! 		<dd>· bmp</dd>
//! 		<dd>· png</dd>
//! </dl>

extern crate alloc;
use crate::c;
use crate::color;
use crate::color::Color8;
use crate::color::Color16;
use core::mem::MaybeUninit;

/// Supported Bmp headers.
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BmpHeader {
	Info = 0x28,
	InfoV3 = 0x38,
}

/// Errors which can occur manipulating images.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
	/// the file doesn't exist or user might not have permissions.
	CantOpen,
	/// can't read the file, might be corrupted/broken.
	CantRead,
	/// can't create/write the file.
	CantWrite,
	/// wrote only part of the file.
	PartialWrite,
	/// the file is not a image or a unknown format to lexlib.
	InvalidFileType,
	/// the filename did not have a extension.
	InvalidFileName,
	/// the image contains invalid data, might be corrupted.
	InvalidFileData,
	/// the image format/type is unsuported.
	Unsupported,
	/// failed to allocate space for the image or temporal buffers.
	OutOfMemory,
	/// an unknown fatal error occured, this should never happend.
	Unknown,
	/// access was out of bounds.
	OutOfBounds,
	/// the image is not valid.
	InvalidImage,
	/// a invalid value was passed.
	InvalidValue,
	/// a &str contains a null char.
	NullStr,
}

/// Stores and facilitates image manipulation.
///
/// the origin is 0,0 at the top left.
///
/// the raw data is compatible with opengl but it might be necessary to [`Self::flip_y`].
#[repr(C)]
pub struct Image {
	data: *mut u8,
	width: u32,
	height: u32,
	channels: u8,
	bpc: u8,
	bpp: u8,
	profile: color::Profile,
	flags: u8,
}

/// main functions
impl Image {
	/// `width`/`height` can't be 0.<br>
	/// `bits_per_color` should be 0, 8 or 16; if 0 the default of 8 is used.<br>
	/// [`color::Profile::RGB555`]/`565` are interpreted as [`color::Profile::RGB`].
	pub fn new(width: u32, height: u32, profile: color::Profile, bits_per_color: u8) -> Result<Self, Error> {
		let mut image = MaybeUninit::uninit();
		match unsafe{c::lexlibImageNew(image.as_mut_ptr(), width, height, profile as u8, bits_per_color)} {
			c::LEXLIB_OK => Ok(unsafe{image.assume_init()}),
			c::LEXLIB_OUT_OF_MEMORY => Err(Error::OutOfMemory),
			c::LEXLIB_INVALID_VALUE |
			c::LEXLIB_INVALID_ARG => Err(Error::InvalidValue),
			_ => panic!("lexlib::image::Image::new unknown error")
		}
	}

	/// checks if the current state of the image is valid.
	pub fn validate(&self) -> Result<(), Error> {
		if unsafe{c::lexlibImageValidate(self)} == c::LEXLIB_OK {
			return Ok(());
		}
		Err(Error::InvalidImage)
	}

	/// flips the image in the `x` axis.
	pub fn flip_x(&mut self) -> Result<(), Error> {
		match unsafe{c::lexlibImageFlip(self, c::LEXLIB_FLIP_X)}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_OUT_OF_MEMORY => Err(Error::OutOfMemory),
			c::LEXLIB_INVALID_VALUE => Err(Error::InvalidValue),
			_ => panic!("lexlib::image::Image::flip_x unknown error")
		}
	}

	/// flips the image in the `y` axis
	pub fn flip_y(&mut self) -> Result<(), Error> {
		match unsafe{c::lexlibImageFlip(self, c::LEXLIB_FLIP_Y)}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_OUT_OF_MEMORY => Err(Error::OutOfMemory),
			c::LEXLIB_INVALID_VALUE => Err(Error::InvalidValue),
			_ => panic!("lexlib::image::Image::flip_x unknown error")
		}
	}

	/// changes the color profile of the image
	pub fn change_profile(&mut self, profile: color::Profile) -> Result<(), Error> {
		match unsafe{c::lexlibImageProfileChange(self, profile as u8)}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_INVALID_VALUE => Err(Error::InvalidImage),
			c::LEXLIB_INVALID_ARG => Err(Error::InvalidValue),
			c::LEXLIB_OUT_OF_MEMORY => Err(Error::OutOfMemory),
			_ => panic!("lexlib::image::Image::change_profile unknown error")
		}
	}

	/// fills an area of the image.
	///
	/// x, y, w, h are counted from the origin.
	pub fn fill_area(&mut self, x: u32, y: u32, w: u32, h: u32, color: color::Color8, blend_mode: color::BlendMode) -> Result<(), Error> {
		match unsafe{c::lexlibImageFillArea(self, x, y, w, h, color, blend_mode as u8)}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_INVALID_OPERATION => Err(Error::OutOfBounds),
			_ => panic!("lexlib::image::Image::fill_area unknown error")
		}
	}

	/// gets a pixel.<br>
	/// if the pixel is OutOfBounds [`None`] is returned.
	pub fn pixel(&self, x: u32, y: u32 ) -> Option<Color8> {
		let mut color = MaybeUninit::uninit();
		if unsafe{c::lexlibImagePixel(self, x, y, color.as_mut_ptr())} != c::LEXLIB_OK {
			return None;
		}
		Some(unsafe{color.assume_init()})
	}

	/// gets a pixel with 16bpc.<br>
	/// if the pixel is OutOfBounds [`None`] is returned.
	pub fn pixel16(&self, x: u32, y: u32 ) -> Option<Color16> {
		let mut color = MaybeUninit::uninit();
		if unsafe{c::lexlibImagePixel16(self, x, y, color.as_mut_ptr())} != c::LEXLIB_OK {
			return None;
		}
		Some(unsafe{color.assume_init()})
	}

	/// sets a pixel.<br>
	/// transformations to the color are performed depending of the image profile.
	pub fn set_pixel(&mut self, x: u32, y: u32, color: Color8, blend_mode: color::BlendMode) -> Result<(), Error> {
		match unsafe{c::lexlibImagePixelSet(self, x, y, color, blend_mode as u8)}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_INVALID_OPERATION => Err(Error::OutOfBounds),
			c::LEXLIB_INVALID_VALUE => Err(Error::InvalidImage),
			_ => panic!("lexlib::image::Image::set_pixel unknown error")
		}
	}

	/// sets a pixel with 16bpc.<br>
	/// transformations to the color are performed depending of the image profile.
	pub fn set_pixel16(&mut self, x: u32, y: u32, color: Color16, blend_mode: color::BlendMode) -> Result<(), Error> {
		match unsafe{c::lexlibImagePixel16Set(self, x, y, color, blend_mode as u8)}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_INVALID_OPERATION => Err(Error::OutOfBounds),
			c::LEXLIB_INVALID_VALUE => Err(Error::InvalidImage),
			_ => panic!("lexlib::image::Image::set_pixel16 unknown error")
		}
	}

	/// loads a image file.
	pub fn load(filename: &str) -> Result<Image, Error> {
		let cstr = match alloc::ffi::CString::new(filename) {
			Ok(v) => { v }
			Err(_) => return Err(Error::NullStr)
		};

		let mut image = MaybeUninit::<Image>::uninit();
		return match unsafe{c::lexlibImageLoad(image.as_mut_ptr(), cstr.as_ptr())} {
			c::LEXLIB_OK => Ok(unsafe{image.assume_init()}),
			c::LEXLIB_CANT_OPEN => Err(Error::CantOpen),
			c::LEXLIB_CANT_READ => Err(Error::CantRead),
			c::LEXLIB_INVALID_TYPE => Err(Error::InvalidFileType),
			c::LEXLIB_INVALID_DATA => Err(Error::InvalidFileData),
			c::LEXLIB_UNSUPORTED => Err(Error::Unsupported),
			c::LEXLIB_OUT_OF_MEMORY => Err(Error::OutOfMemory),
			c::LEXLIB_ERROR => Err(Error::Unknown),
			_ => panic!("lexlib::image::Image::load unknown error")
		}
	}

	// its not necessary to wrap the other loaders.

	/// saves the Image as a file.<br>
	/// gets the format from the file extension in the filename.
	pub fn save(&self, filename: &str) -> Result<(), Error> {
		let cstr = match alloc::ffi::CString::new(filename) {
			Ok(v) => {v}
			Err(_) => return Err(Error::NullStr)
		};

		match unsafe{c::lexlibImageSave(self, cstr.as_ptr())}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_INVALID_NAME => Err(Error::InvalidFileName),
			c::LEXLIB_CANT_WRITE => Err(Error::CantWrite),
			c::LEXLIB_PARTIAL_WRITE => Err(Error::PartialWrite),
			c::LEXLIB_OUT_OF_MEMORY => Err(Error::OutOfMemory),
			c::LEXLIB_UNSUPORTED => Err(Error::Unsupported),
			_ => panic!("lexlib::image::Image::save unknown error")
		}
	}

	/// saves the Image as a bmp file.
	pub fn save_bmp(&self, filename: &str) -> Result<(), Error> {
		let cstr = match alloc::ffi::CString::new(filename) {
			Ok(v) => {v}
			Err(_) => return Err(Error::NullStr)
		};

		match unsafe{c::lexlibImageSaveBmp(self, cstr.as_ptr())}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_INVALID_VALUE => Err(Error::InvalidImage),
			c::LEXLIB_CANT_WRITE => Err(Error::CantWrite),
			c::LEXLIB_PARTIAL_WRITE => Err(Error::PartialWrite),
			_ => panic!("lexlib::image::Image::save_bmp unknown error")
		}
	}

	/// saves the Image as a bmp file allowing to specify the profile and header.
	pub fn save_bmp_ex(&self, filename: &str, profile: color::Profile, header: BmpHeader) -> Result<(), Error> {
		let cstr = match alloc::ffi::CString::new(filename) {
			Ok(v) => {v}
			Err(_) => return Err(Error::NullStr)
		};

		match unsafe{c::lexlibImageSaveBmpEx(self, cstr.as_ptr(), profile as u8, header as u8)}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_INVALID_ARG => Err(Error::InvalidValue),
			c::LEXLIB_UNSUPORTED => Err(Error::Unsupported),
			c::LEXLIB_CANT_WRITE => Err(Error::CantWrite),
			c::LEXLIB_PARTIAL_WRITE => Err(Error::PartialWrite),
			_ => panic!("lexlib::image::Image::save_bmp_ex unknown error")
		}
	}

	pub fn save_png(&self, filename: &str) -> Result<(), Error> {
		let cstr = match alloc::ffi::CString::new(filename) {
			Ok(v) => {v}
			Err(_) => return Err(Error::NullStr)
		};

		match unsafe{c::lexlibImageSavePng(self, cstr.as_ptr())}{
			c::LEXLIB_OK => Ok(()),
			c::LEXLIB_CANT_WRITE => Err(Error::CantWrite),
			c::LEXLIB_UNSUPORTED => Err(Error::Unsupported),
			c::LEXLIB_OUT_OF_MEMORY => Err(Error::OutOfMemory),
			_ => panic!("lexlib::image::Image::save_png, returned unknown error")
		}
	}
}

/// getters
impl Image {
	/// gets a pointer to the raw image data
	#[inline(always)]
	pub fn raw_data(&self) -> *const u8 {
		self.data
	}

	/// gets a mutable pointer to the raw image data
	#[inline(always)]
	pub unsafe fn raw_data_mut(&mut self) -> *mut u8 {
		self.data
	}

	/// image width in pixels
	#[inline(always)]
	pub fn width(&self) -> u32 {
		self.width
	}

	/// image height in pixels
	#[inline(always)]
	pub fn height(&self) -> u32 {
		self.height
	}

	/// count of color channels
	#[inline(always)]
	pub fn channels(&self) -> u8 {
		self.channels
	}

	/// bits per color
	#[inline(always)]
	pub fn bpc(&self) -> u8 {
		self.bpc
	}

	/// bits per pixel
	#[inline(always)]
	pub fn bpp(&self) -> u8 {
		self.bpp
	}

	/// color profile
	#[inline(always)]
	pub fn profile(&self) -> color::Profile {
		self.profile
	}
}

impl Drop for Image {
	#[inline(always)]
	fn drop(&mut self){
		unsafe{c::lexlibImageDelete(self)}
	}
}

impl Clone for Image {
	#[inline(always)]
	fn clone(&self) -> Self {
		let mut copy = MaybeUninit::uninit();
		match unsafe{c::lexlibImageCopy(copy.as_mut_ptr(), self)} {
			c::LEXLIB_OK => unsafe{copy.assume_init()},
			c::LEXLIB_OUT_OF_MEMORY => panic!("out of memory"),
			_ => panic!("lexlib::image::Image::clone() unknown error")
		}
	}
}

#[cfg(test)]
use crate::color::Color;

#[test]
fn image_compat(){
	let imageog = Image::new(240, 160, color::Profile::RGBA, 8).expect("failed to create image");
	let mut image = imageog.clone();

	drop(imageog);

	assert!(!image.data.is_null());
	assert_eq!(image.width, 240);
	assert_eq!(image.height, 160);
	assert_eq!(image.channels, 4);
	assert_eq!(image.bpc, 8);
	assert_eq!(image.profile, color::Profile::RGBA);
	assert_eq!(image.flags, 0x80);

	image.fill_area(0,0,240,160, color::Color8::RED, color::BlendMode::Mod).unwrap();
	image.validate().expect("validation failed");
	image.flip_x().expect("failed to flip x");
	image.flip_y().expect("failed to flip y");
	image.set_pixel(0, 0, color::Color8::YELLOW, color::BlendMode::None).unwrap();
	image.set_pixel(0, 1, color::Color8::new_gray(0x00), color::BlendMode::None).unwrap();
	image.change_profile(color::Profile::RGB).unwrap();
	image.save("resources/out/img.bmp").unwrap();
}

impl core::fmt::Display for Error {
	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
		write!(f, "lexlib::image::Error::{:?}", self)
	}
}

impl std::error::Error for Error {}

impl core::fmt::Display for BmpHeader {
	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
		write!(f, "BmpHeader::{:?}", self)
	}
}