use crate::core::databuf::DataBuf;
use crate::core::ffi::{Color, Rectangle};
use crate::core::math::Vector2;
use crate::core::{RaylibHandle, RaylibThread};
use crate::ffi;
use std::convert::TryInto;
use std::ffi::CString;
use std::mem::{ManuallyDrop, MaybeUninit};
use super::error::{InvalidImageError, LoadTextureError, UpdateTextureError};
make_rslice!(ImagePalette, Color, ffi::UnloadImagePalette);
make_rslice!(ImageColors, Color, ffi::UnloadImageColors);
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct NPatchInfo {
pub source: Rectangle,
pub left: i32,
pub top: i32,
pub right: i32,
pub bottom: i32,
pub layout: crate::consts::NPatchLayout,
}
impl From<ffi::NPatchInfo> for NPatchInfo {
fn from(v: ffi::NPatchInfo) -> NPatchInfo {
unsafe { std::mem::transmute(v) }
}
}
impl From<NPatchInfo> for ffi::NPatchInfo {
fn from(v: NPatchInfo) -> Self {
unsafe { std::mem::transmute(v) }
}
}
impl From<&NPatchInfo> for ffi::NPatchInfo {
fn from(v: &NPatchInfo) -> Self {
ffi::NPatchInfo {
source: v.source,
left: v.left,
top: v.top,
right: v.right,
bottom: v.bottom,
layout: (v.layout as u32) as i32,
}
}
}
fn no_drop<T>(_thing: T) {}
make_thin_wrapper!(
Image,
ffi::Image,
ffi::UnloadImage
);
make_thin_wrapper!(
Texture2D,
ffi::Texture2D,
ffi::UnloadTexture
);
make_thin_wrapper!(WeakTexture2D, ffi::Texture2D, no_drop);
#[allow(clippy::derivable_impls)] impl Default for WeakTexture2D {
fn default() -> Self {
Self(ffi::Texture::default())
}
}
make_thin_wrapper!(
RenderTexture2D,
ffi::RenderTexture2D,
ffi::UnloadRenderTexture
);
make_thin_wrapper!(WeakRenderTexture2D, ffi::RenderTexture2D, no_drop);
impl Clone for WeakTexture2D {
fn clone(&self) -> WeakTexture2D {
WeakTexture2D(self.0)
}
}
impl Clone for WeakRenderTexture2D {
fn clone(&self) -> WeakRenderTexture2D {
WeakRenderTexture2D(self.0)
}
}
impl RaylibRenderTexture2D for WeakRenderTexture2D {}
impl RaylibRenderTexture2D for RenderTexture2D {}
impl AsRef<ffi::Texture2D> for RenderTexture2D {
#[inline]
fn as_ref(&self) -> &ffi::Texture2D {
self.texture()
}
}
impl AsMut<ffi::Texture2D> for RenderTexture2D {
#[inline]
fn as_mut(&mut self) -> &mut ffi::Texture2D {
self.texture_mut()
}
}
impl AsRef<ffi::Texture2D> for WeakRenderTexture2D {
#[inline]
fn as_ref(&self) -> &ffi::Texture2D {
self.texture()
}
}
impl AsMut<ffi::Texture2D> for WeakRenderTexture2D {
#[inline]
fn as_mut(&mut self) -> &mut ffi::Texture2D {
self.texture_mut()
}
}
impl RenderTexture2D {
#[inline]
#[must_use]
pub unsafe fn make_weak(self) -> WeakRenderTexture2D {
let m = WeakRenderTexture2D(self.0);
std::mem::forget(self);
m
}
#[inline]
#[must_use]
pub fn is_render_texture_valid(&self) -> bool {
unsafe { ffi::IsRenderTextureValid(self.0) }
}
}
pub trait RaylibRenderTexture2D: AsRef<ffi::RenderTexture2D> + AsMut<ffi::RenderTexture2D> {
#[inline]
#[must_use]
fn id(&self) -> u32 {
self.as_ref().id
}
#[inline]
#[must_use]
fn texture(&self) -> &WeakTexture2D {
unsafe { std::mem::transmute(&self.as_ref().texture) }
}
#[inline]
#[must_use]
fn texture_mut(&mut self) -> &mut WeakTexture2D {
unsafe { std::mem::transmute(&mut self.as_mut().texture) }
}
}
impl Clone for Image {
fn clone(&self) -> Image {
unsafe { Image(ffi::ImageCopy(self.0)) }
}
}
impl Image {
#[inline]
#[must_use]
pub fn width(&self) -> i32 {
self.0.width
}
#[inline]
#[must_use]
pub fn height(&self) -> i32 {
self.0.height
}
#[inline]
#[must_use]
pub fn mipmaps(&self) -> i32 {
self.0.mipmaps
}
#[inline]
#[must_use]
pub unsafe fn data(&self) -> *mut ::std::os::raw::c_void {
self.0.data
}
#[inline]
pub fn blur_gaussian(&mut self, blur_size: i32) {
unsafe { ffi::ImageBlurGaussian(&mut self.0, blur_size) }
}
#[inline]
pub fn rotate(&mut self, degrees: i32) {
unsafe { ffi::ImageRotate(&mut self.0, degrees) }
}
#[inline]
#[must_use]
pub fn get_color(&self, x: i32, y: i32) -> Color {
unsafe { ffi::GetImageColor(self.0, x, y) }
}
#[inline]
pub fn draw_circle_lines(&mut self, center_x: i32, center_y: i32, radius: i32, color: Color) {
unsafe { ffi::ImageDrawCircleLines(&mut self.0, center_x, center_y, radius, color) }
}
#[inline]
pub fn draw_circle_lines_v(&mut self, center: impl Into<Vector2>, center_y: i32, color: Color) {
unsafe { ffi::ImageDrawCircleLinesV(&mut self.0, center.into(), center_y, color) }
}
#[inline]
#[must_use]
pub fn format(&self) -> crate::consts::PixelFormat {
let i: u32 = self.format as u32;
unsafe { std::mem::transmute(i) }
}
#[must_use]
#[inline]
pub fn from_image(&self, rec: impl Into<ffi::Rectangle>) -> Image {
unsafe { Image(ffi::ImageFromImage(self.0, rec.into())) }
}
#[inline]
#[must_use]
pub fn from_channel(&self, selected_channel: i32) -> Image {
unsafe { Image(ffi::ImageFromChannel(self.0, selected_channel)) }
}
#[inline]
pub fn export_image(&self, filename: &str) {
let c_filename = CString::new(filename).unwrap();
unsafe {
ffi::ExportImage(self.0, c_filename.as_ptr());
}
}
#[inline]
pub fn export_image_as_code(&self, filename: &str) {
let c_filename = CString::new(filename).unwrap();
unsafe {
ffi::ExportImageAsCode(self.0, c_filename.as_ptr());
}
}
#[inline]
#[must_use]
pub fn get_pixel_data_size(&self) -> usize {
unsafe { ffi::GetPixelDataSize(self.width(), self.height(), self.format() as i32) as usize }
}
#[must_use]
pub fn get_image_data(&self) -> ImageColors {
unsafe {
let image_data = ffi::LoadImageColors(self.0);
let image_data_len = (self.width * self.height) as usize;
ImageColors(ManuallyDrop::new(Box::from_raw(
std::slice::from_raw_parts_mut(image_data as *mut _, image_data_len),
)))
}
}
#[must_use]
pub fn get_image_data_u8(&self, flip: bool) -> Vec<u8> {
let image_data_len = (self.width * self.height * 4) as usize;
let mut res = Vec::with_capacity(image_data_len);
if flip {
for y in (0..self.height).rev() {
for x in 0..self.width {
let color = self.get_color(x, y);
res.push(color.r);
res.push(color.g);
res.push(color.b);
res.push(color.a);
}
}
} else {
for y in 0..self.height {
for x in 0..self.width {
let color = self.get_color(x, y);
res.push(color.r);
res.push(color.g);
res.push(color.b);
res.push(color.a);
}
}
}
res
}
#[inline]
#[must_use]
pub fn extract_palette(&self, max_palette_size: u32) -> ImagePalette {
unsafe {
let mut palette_len = 0;
let image_data =
ffi::LoadImagePalette(self.0, max_palette_size as i32, &mut palette_len);
ImagePalette(ManuallyDrop::new(Box::from_raw(
std::slice::from_raw_parts_mut(image_data as *mut _, palette_len as usize),
)))
}
}
#[inline]
pub fn to_pot(&mut self, fill_color: impl Into<ffi::Color>) {
unsafe {
ffi::ImageToPOT(&mut self.0, fill_color.into());
}
}
#[inline]
pub fn set_format(&mut self, new_format: crate::consts::PixelFormat) {
unsafe {
ffi::ImageFormat(&mut self.0, (new_format as u32) as i32);
}
}
#[inline]
pub fn alpha_mask(&mut self, alpha_mask: &Image) {
unsafe {
ffi::ImageAlphaMask(&mut self.0, alpha_mask.0);
}
}
#[inline]
pub fn alpha_clear(&mut self, color: impl Into<ffi::Color>, threshold: f32) {
unsafe {
ffi::ImageAlphaClear(&mut self.0, color.into(), threshold);
}
}
#[inline]
pub fn alpha_crop(&mut self, threshold: f32) {
unsafe {
ffi::ImageAlphaCrop(&mut self.0, threshold);
}
}
#[inline]
pub fn alpha_premultiply(&mut self) {
unsafe {
ffi::ImageAlphaPremultiply(&mut self.0);
}
}
#[inline]
pub fn crop(&mut self, crop: impl Into<ffi::Rectangle>) {
unsafe {
ffi::ImageCrop(&mut self.0, crop.into());
}
}
#[inline]
pub fn resize(&mut self, new_width: i32, new_height: i32) {
unsafe {
ffi::ImageResize(&mut self.0, new_width, new_height);
}
}
#[inline]
pub fn resize_nn(&mut self, new_width: i32, new_height: i32) {
unsafe {
ffi::ImageResizeNN(&mut self.0, new_width, new_height);
}
}
#[inline]
pub fn resize_canvas(
&mut self,
new_width: i32,
new_height: i32,
offset_x: i32,
offset_y: i32,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageResizeCanvas(
&mut self.0,
new_width,
new_height,
offset_x,
offset_y,
color.into(),
);
}
}
#[inline]
pub fn gen_mipmaps(&mut self) {
unsafe {
ffi::ImageMipmaps(&mut self.0);
}
}
#[inline]
pub fn dither(&mut self, r_bpp: i32, g_bpp: i32, b_bpp: i32, a_bpp: i32) {
unsafe {
ffi::ImageDither(&mut self.0, r_bpp, g_bpp, b_bpp, a_bpp);
}
}
#[inline]
pub fn get_image_alpha_border(&self, threshold: f32) -> Rectangle {
unsafe { ffi::GetImageAlphaBorder(self.0, threshold) }
}
#[inline]
pub fn clear_background(&mut self, color: impl Into<ffi::Color>) {
unsafe { ffi::ImageClearBackground(&mut self.0, color.into()) }
}
#[inline]
pub fn draw(
&mut self,
src: &Image,
src_rec: Rectangle,
dst_rec: Rectangle,
tint: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDraw(&mut self.0, src.0, src_rec, dst_rec, tint.into());
}
}
#[inline]
pub fn draw_pixel(&mut self, pos_x: i32, pos_y: i32, color: impl Into<ffi::Color>) {
unsafe { ffi::ImageDrawPixel(&mut self.0, pos_x, pos_y, color.into()) }
}
#[inline]
pub fn draw_pixel_v(&mut self, position: impl Into<Vector2>, color: impl Into<ffi::Color>) {
unsafe { ffi::ImageDrawPixelV(&mut self.0, position.into(), color.into()) }
}
#[inline]
pub fn draw_line(
&mut self,
start_pos_x: i32,
start_pos_y: i32,
end_pos_x: i32,
end_pos_y: i32,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawLine(
&mut self.0,
start_pos_x,
start_pos_y,
end_pos_x,
end_pos_y,
color.into(),
)
}
}
#[inline]
pub fn draw_line_ex(
&mut self,
start_pos: impl Into<Vector2>,
end_pos: impl Into<Vector2>,
thick: i32,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawLineEx(
&mut self.0,
start_pos.into(),
end_pos.into(),
thick,
color.into(),
)
}
}
#[inline]
pub fn draw_line_v(
&mut self,
start: impl Into<Vector2>,
end: impl Into<Vector2>,
color: impl Into<ffi::Color>,
) {
unsafe { ffi::ImageDrawLineV(&mut self.0, start.into(), end.into(), color.into()) }
}
#[inline]
pub fn draw_triangle(
&mut self,
v1: impl Into<Vector2>,
v2: impl Into<Vector2>,
v3: impl Into<Vector2>,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawTriangle(&mut self.0, v1.into(), v2.into(), v3.into(), color.into())
}
}
#[inline]
pub fn draw_triangle_ex(
&mut self,
v1: impl Into<Vector2>,
v2: impl Into<Vector2>,
v3: impl Into<Vector2>,
c1: impl Into<ffi::Color>,
c2: impl Into<ffi::Color>,
c3: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawTriangleEx(
&mut self.0,
v1.into(),
v2.into(),
v3.into(),
c1.into(),
c2.into(),
c3.into(),
)
}
}
#[inline]
pub fn draw_triangle_lines(
&mut self,
v1: impl Into<Vector2>,
v2: impl Into<Vector2>,
v3: impl Into<Vector2>,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawTriangleLines(&mut self.0, v1.into(), v2.into(), v3.into(), color.into())
}
}
pub fn draw_triangle_fan(
&mut self,
points: &mut [crate::math::Vector2],
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawTriangleFan(
&mut self.0,
points.as_ptr() as *mut Vector2,
points.len() as i32,
color.into(),
)
}
}
pub fn draw_triangle_strip(
&mut self,
points: &mut [crate::math::Vector2],
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawTriangleStrip(
&mut self.0,
points.as_ptr() as *mut Vector2,
points.len() as i32,
color.into(),
)
}
}
#[inline]
pub fn draw_circle(
&mut self,
center_x: i32,
center_y: i32,
radius: i32,
color: impl Into<ffi::Color>,
) {
unsafe { ffi::ImageDrawCircle(&mut self.0, center_x, center_y, radius, color.into()) }
}
#[inline]
pub fn draw_circle_v(
&mut self,
center: impl Into<Vector2>,
radius: i32,
color: impl Into<ffi::Color>,
) {
unsafe { ffi::ImageDrawCircleV(&mut self.0, center.into(), radius, color.into()) }
}
#[inline]
pub fn draw_rectangle(
&mut self,
pos_x: i32,
pos_y: i32,
width: i32,
height: i32,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawRectangle(&mut self.0, pos_x, pos_y, width, height, color.into());
}
}
#[inline]
pub fn draw_rectangle_v(
&mut self,
position: impl Into<Vector2>,
size: impl Into<Vector2>,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawRectangleV(&mut self.0, position.into(), size.into(), color.into());
}
}
#[inline]
pub fn draw_rectangle_rec(
&mut self,
rectangle: impl Into<ffi::Rectangle>,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawRectangleRec(&mut self.0, rectangle.into(), color.into());
}
}
#[inline]
pub fn draw_rectangle_lines(
&mut self,
rec: Rectangle,
thickness: i32,
color: impl Into<ffi::Color>,
) {
unsafe {
ffi::ImageDrawRectangleLines(&mut self.0, rec, thickness, color.into());
}
}
#[inline]
pub fn draw_text(
&mut self,
text: &str,
pos_x: i32,
pos_y: i32,
font_size: i32,
color: impl Into<ffi::Color>,
) {
let c_text = CString::new(text).unwrap();
unsafe {
ffi::ImageDrawText(
&mut self.0,
c_text.as_ptr(),
pos_x,
pos_y,
font_size,
color.into(),
);
}
}
#[inline]
pub fn draw_text_ex(
&mut self,
font: impl AsRef<ffi::Font>,
text: &str,
position: impl Into<Vector2>,
font_size: f32,
spacing: f32,
color: impl Into<ffi::Color>,
) {
let c_text = CString::new(text).unwrap();
unsafe {
ffi::ImageDrawTextEx(
&mut self.0,
*font.as_ref(),
c_text.as_ptr(),
position.into(),
font_size,
spacing,
color.into(),
);
}
}
#[inline]
pub fn flip_vertical(&mut self) {
unsafe {
ffi::ImageFlipVertical(&mut self.0);
}
}
#[inline]
pub fn flip_horizontal(&mut self) {
unsafe {
ffi::ImageFlipHorizontal(&mut self.0);
}
}
#[inline]
pub fn rotate_cw(&mut self) {
unsafe {
ffi::ImageRotateCW(&mut self.0);
}
}
#[inline]
pub fn rotate_ccw(&mut self) {
unsafe {
ffi::ImageRotateCCW(&mut self.0);
}
}
#[inline]
pub fn color_tint(&mut self, color: impl Into<ffi::Color>) {
unsafe {
ffi::ImageColorTint(&mut self.0, color.into());
}
}
#[inline]
pub fn color_invert(&mut self) {
unsafe {
ffi::ImageColorInvert(&mut self.0);
}
}
#[inline]
pub fn color_grayscale(&mut self) {
unsafe {
ffi::ImageColorGrayscale(&mut self.0);
}
}
#[inline]
pub fn color_contrast(&mut self, contrast: f32) {
unsafe {
ffi::ImageColorContrast(&mut self.0, contrast);
}
}
#[inline]
pub fn color_brightness(&mut self, brightness: i32) {
unsafe {
ffi::ImageColorBrightness(&mut self.0, brightness);
}
}
#[inline]
pub fn color_replace(&mut self, color: impl Into<ffi::Color>, replace: impl Into<ffi::Color>) {
unsafe {
ffi::ImageColorReplace(&mut self.0, color.into(), replace.into());
}
}
pub fn export_image_to_memory(
&self,
file_type: &str,
) -> Result<DataBuf<[u8]>, InvalidImageError> {
if self.width == 0 {
return Err(InvalidImageError::ZeroWidth);
}
if self.height == 0 {
return Err(InvalidImageError::ZeroHeight);
}
if self.data.is_null() {
return Err(InvalidImageError::NullData);
}
let c_filetype = CString::new(file_type).unwrap();
let mut data_size = MaybeUninit::uninit();
let data = unsafe {
ffi::ExportImageToMemory(self.0, c_filetype.as_ptr(), data_size.as_mut_ptr())
};
let buf = unsafe { DataBuf::slice_from_raw(data, data_size) };
buf.ok_or(InvalidImageError::UnsupportedFormat)
}
pub fn kernel_convolution(&mut self, kernel: &[f32]) -> Result<(), InvalidImageError> {
if self.width == 0 {
return Err(InvalidImageError::ZeroWidth);
}
if self.height == 0 {
return Err(InvalidImageError::ZeroHeight);
}
if self.data.is_null() {
return Err(InvalidImageError::NullData);
}
let kernel_width = (kernel.len() as f32).sqrt() as i32;
if (kernel_width * kernel_width) as usize != kernel.len() {
return Err(InvalidImageError::NonSquareKernel);
}
unsafe { ffi::ImageKernelConvolution(&mut self.0, kernel.as_ptr(), kernel.len() as i32) }
Ok(())
}
#[inline]
#[must_use]
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_color(width: i32, height: i32, color: impl Into<ffi::Color>) -> Image {
unsafe { Image(ffi::GenImageColor(width, height, color.into())) }
}
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_perlin_noise(
&self,
width: i32,
height: i32,
offset_x: i32,
offset_y: i32,
scale: f32,
) -> Image {
Image(unsafe { ffi::GenImagePerlinNoise(width, height, offset_x, offset_y, scale) })
}
#[inline]
#[must_use]
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_gradient_radial(
width: i32,
height: i32,
density: f32,
inner: impl Into<ffi::Color>,
outer: impl Into<ffi::Color>,
) -> Image {
unsafe {
Image(ffi::GenImageGradientRadial(
width,
height,
density,
inner.into(),
outer.into(),
))
}
}
#[inline]
#[must_use]
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_checked(
width: i32,
height: i32,
checks_x: i32,
checks_y: i32,
col1: impl Into<ffi::Color>,
col2: impl Into<ffi::Color>,
) -> Image {
unsafe {
Image(ffi::GenImageChecked(
width,
height,
checks_x,
checks_y,
col1.into(),
col2.into(),
))
}
}
#[must_use]
#[inline]
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_gradient_linear(
width: i32,
height: i32,
direction: i32,
start: Color,
end: Color,
) -> Image {
unsafe {
Image(ffi::GenImageGradientLinear(
width, height, direction, start, end,
))
}
}
#[must_use]
#[inline]
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_gradient_square(
width: i32,
height: i32,
density: f32,
start: Color,
end: Color,
) -> Image {
unsafe {
Image(ffi::GenImageGradientSquare(
width, height, density, start, end,
))
}
}
#[must_use]
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_text(width: i32, height: i32, text: &str) -> Image {
let c_str = CString::new(text).unwrap();
unsafe { Image(ffi::GenImageText(width, height, c_str.as_ptr())) }
}
#[inline]
#[must_use]
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_white_noise(width: i32, height: i32, factor: f32) -> Image {
unsafe { Image(ffi::GenImageWhiteNoise(width, height, factor)) }
}
#[inline]
#[must_use]
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
pub fn gen_image_cellular(width: i32, height: i32, tile_size: i32) -> Image {
unsafe { Image(ffi::GenImageCellular(width, height, tile_size)) }
}
#[cfg(target_os = "windows")]
pub fn get_clipboard_image(&mut self) -> Result<Image, InvalidImageError> {
let i = unsafe { ffi::GetClipboardImage() };
if i.data.is_null() {
return Err(InvalidImageError::NullData);
}
Ok(Image(i))
}
pub fn load_image(filename: &str) -> Result<Image, InvalidImageError> {
let c_filename = CString::new(filename).unwrap();
let i = unsafe { ffi::LoadImage(c_filename.as_ptr()) };
if i.data.is_null() {
return Err(InvalidImageError::NullDataFromFile);
}
Ok(Image(i))
}
pub fn load_image_from_mem(filetype: &str, bytes: &[u8]) -> Result<Image, InvalidImageError> {
let c_filetype = CString::new(filetype).unwrap();
let data_size = bytes.len().try_into().unwrap();
if data_size == 0 {
return Err(InvalidImageError::InvalidFile);
}
let i = unsafe { ffi::LoadImageFromMemory(c_filetype.as_ptr(), bytes.as_ptr(), data_size) };
if i.data.is_null() {
return Err(InvalidImageError::NullDataFromMemory);
};
Ok(Image(i))
}
#[must_use]
pub fn load_image_anim(filename: &str, frame_num: &mut i32) -> Self {
let c_filename = CString::new(filename).unwrap();
unsafe { Image(ffi::LoadImageAnim(c_filename.as_ptr(), frame_num)) }
}
#[must_use]
pub fn load_image_anim_from_memory(filetype: &str, data: &[u8], frame_num: &mut i32) -> Self {
let c_filetype = CString::new(filetype).unwrap();
unsafe {
Image(ffi::LoadImageAnimFromMemory(
c_filetype.as_ptr(),
data.as_ptr(),
data.len() as i32,
frame_num,
))
}
}
pub fn load_image_raw(
filename: &str,
width: i32,
height: i32,
format: i32,
header_size: i32,
) -> Result<Image, InvalidImageError> {
let c_filename = CString::new(filename).unwrap();
let i =
unsafe { ffi::LoadImageRaw(c_filename.as_ptr(), width, height, format, header_size) };
if i.data.is_null() {
return Err(InvalidImageError::NullDataFromFile);
}
Ok(Image(i))
}
#[inline]
#[must_use]
pub fn image_text(text: &str, font_size: i32, color: impl Into<ffi::Color>) -> Image {
let c_text = CString::new(text).unwrap();
unsafe { Image(ffi::ImageText(c_text.as_ptr(), font_size, color.into())) }
}
#[inline]
#[must_use]
pub fn image_text_ex(
font: impl std::convert::AsRef<ffi::Font>,
text: &str,
font_size: f32,
spacing: f32,
tint: impl Into<ffi::Color>,
) -> Image {
let c_text = CString::new(text).unwrap();
unsafe {
Image(ffi::ImageTextEx(
*font.as_ref(),
c_text.as_ptr(),
font_size,
spacing,
tint.into(),
))
}
}
#[inline]
#[must_use]
pub fn is_image_valid(&self) -> bool {
unsafe { ffi::IsImageValid(self.0) }
}
}
impl RaylibTexture2D for WeakTexture2D {}
impl RaylibTexture2D for Texture2D {}
impl RaylibTexture2D for WeakRenderTexture2D {}
impl RaylibTexture2D for RenderTexture2D {}
impl Texture2D {
pub unsafe fn make_weak(self) -> WeakTexture2D {
let m = WeakTexture2D(self.0);
std::mem::forget(self);
m
}
}
pub trait RaylibTexture2D: AsRef<ffi::Texture2D> + AsMut<ffi::Texture2D> {
#[inline]
#[must_use]
fn width(&self) -> i32 {
self.as_ref().width
}
#[inline]
#[must_use]
fn height(&self) -> i32 {
self.as_ref().height
}
#[inline]
#[must_use]
fn mipmaps(&self) -> i32 {
self.as_ref().mipmaps
}
#[inline]
#[must_use]
fn format(&self) -> i32 {
self.as_ref().format
}
#[inline]
fn update_texture(&mut self, pixels: &[u8]) -> Result<(), UpdateTextureError> {
let expected_len = unsafe {
get_pixel_data_size(
self.as_ref().width,
self.as_ref().height,
std::mem::transmute::<i32, ffi::PixelFormat>(self.as_ref().format),
) as usize
};
if pixels.len() != expected_len {
return Err(UpdateTextureError::WrongDataSize {
expect: expected_len,
actual: pixels.len(),
});
}
unsafe {
ffi::UpdateTexture(
*self.as_mut(),
pixels.as_ptr() as *const std::os::raw::c_void,
);
}
Ok(())
}
fn update_texture_rec(
&mut self,
rec: impl Into<ffi::Rectangle>,
pixels: &[u8],
) -> Result<(), UpdateTextureError> {
let rec = rec.into();
if (rec.x < 0.0)
|| (rec.y < 0.0)
|| ((rec.x as i32 + rec.width as i32) > (self.as_ref().width))
|| ((rec.y as i32 + rec.height as i32) > (self.as_ref().height))
{
return Err(UpdateTextureError::OutOfBounds);
}
if (rec.width < 0.0) || (rec.height < 0.0) {
return Err(UpdateTextureError::NegativeSize);
}
let expected_len = unsafe {
get_pixel_data_size(
rec.width as i32,
rec.height as i32,
std::mem::transmute::<i32, ffi::PixelFormat>(self.as_ref().format),
) as usize
};
if pixels.len() != expected_len {
return Err(UpdateTextureError::WrongDataSize {
expect: expected_len,
actual: pixels.len(),
});
}
unsafe {
ffi::UpdateTextureRec(
*self.as_ref(),
rec,
pixels.as_ptr() as *const std::os::raw::c_void,
)
}
Ok(())
}
#[inline]
fn load_image(&self) -> Result<Image, InvalidImageError> {
let i = unsafe { ffi::LoadImageFromTexture(*self.as_ref()) };
if i.data.is_null() {
return Err(InvalidImageError::NullDataFromTexture);
}
Ok(Image(i))
}
#[inline]
fn gen_texture_mipmaps(&mut self) {
unsafe {
ffi::GenTextureMipmaps(self.as_mut());
}
}
#[inline]
fn set_texture_filter(&self, _: &RaylibThread, filter_mode: crate::consts::TextureFilter) {
unsafe {
ffi::SetTextureFilter(*self.as_ref(), filter_mode as i32);
}
}
#[inline]
fn set_texture_wrap(&self, _: &RaylibThread, wrap_mode: crate::consts::TextureWrap) {
unsafe {
ffi::SetTextureWrap(*self.as_ref(), wrap_mode as i32);
}
}
#[inline]
fn is_texture_valid(&self) -> bool {
unsafe { ffi::IsTextureValid(*self.as_ref()) }
}
}
#[inline]
pub fn get_pixel_data_size(width: i32, height: i32, format: ffi::PixelFormat) -> i32 {
unsafe { ffi::GetPixelDataSize(width, height, format as i32) }
}
impl RaylibHandle {
pub fn load_texture(
&mut self,
_: &RaylibThread,
filename: &str,
) -> Result<Texture2D, LoadTextureError> {
let c_filename = CString::new(filename).unwrap();
let t = unsafe { ffi::LoadTexture(c_filename.as_ptr()) };
if t.id == 0 {
return Err(LoadTextureError::TextureFromFileFailed {
path: filename.into(),
});
}
Ok(Texture2D(t))
}
pub fn load_texture_cubemap(
&mut self,
_: &RaylibThread,
image: &Image,
layout: crate::consts::CubemapLayout,
) -> Result<Texture2D, LoadTextureError> {
let t = unsafe { ffi::LoadTextureCubemap(image.0, layout as i32) };
if t.id == 0 {
return Err(LoadTextureError::CubemapFromImageFailed);
}
Ok(Texture2D(t))
}
#[inline]
pub fn load_texture_from_image(
&mut self,
_: &RaylibThread,
image: &Image,
) -> Result<Texture2D, LoadTextureError> {
if image.width == 0 || image.height == 0 {
return Err(LoadTextureError::InvalidData);
}
let t = unsafe { ffi::LoadTextureFromImage(image.0) };
if t.id == 0 {
return Err(LoadTextureError::TextureFromImageFailed);
}
Ok(Texture2D(t))
}
pub fn load_render_texture(
&mut self,
_: &RaylibThread,
width: u32,
height: u32,
) -> Result<RenderTexture2D, LoadTextureError> {
let t = unsafe { ffi::LoadRenderTexture(width as i32, height as i32) };
if t.id == 0 {
return Err(LoadTextureError::CreateRenderTextureFailed);
}
Ok(RenderTexture2D(t))
}
}
impl RaylibHandle {
#[inline]
pub unsafe fn unload_texture(&mut self, _: &RaylibThread, texture: WeakTexture2D) {
unsafe { ffi::UnloadTexture(*texture.as_ref()) }
}
#[inline]
pub unsafe fn unload_render_texture(&mut self, _: &RaylibThread, texture: WeakRenderTexture2D) {
unsafe { ffi::UnloadRenderTexture(*texture.as_ref()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
use crate::ffi::Color;
#[test]
fn image_load_from_file_happy_and_error() {
#[cfg(feature = "SUPPORT_FILEFORMAT_PNG")]
{
let path = "tests/fixtures/billboard.png";
if std::path::Path::new(path).exists() {
let img = Image::load_image(path).expect("billboard.png loads");
assert!(img.width() > 0, "loaded image has non-zero width");
assert!(img.height() > 0, "loaded image has non-zero height");
} else {
eprintln!("SKIP: {path} not found (cargo invoked from a non-workspace-root cwd?)");
}
}
Image::load_image("tests/fixtures/does_not_exist.png")
.expect_err("nonexistent file should error");
}
#[cfg(feature = "SUPPORT_IMAGE_GENERATION")]
#[test]
fn image_manipulations_no_segfault() {
let mut i = Image::gen_image_color(32, 32, Color::new(230, 41, 55, 255));
let mut canvas = Image::gen_image_color(32, 32, Color::new(0, 0, 0, 0));
let mask = Image::gen_image_checked(
32,
32,
8,
8,
Color::new(255, 255, 255, 255),
Color::new(0, 0, 0, 0),
);
let mut c = i.clone();
c.alpha_mask(&mask);
c.alpha_clear(Color::new(0, 0, 255, 255), 0.5);
c.alpha_crop(0.5);
c.alpha_premultiply();
let mut blurry = c.clone();
blurry.resize(64, 64);
c.resize_nn(64, 64);
i.resize_canvas(64, 64, 10, 10, Color::new(0, 0, 255, 255));
c.gen_mipmaps();
blurry.dither(128, 128, 128, 128);
let colors = c.extract_palette(100);
assert_eq!(
colors.len(),
2,
"checker-masked single-color image has 2-color palette"
);
canvas.draw(
&i,
Rectangle::new(0.0, 0.0, 20.0, 20.0),
Rectangle::new(0.0, 0.0, 20.0, 20.0),
Color::new(255, 255, 255, 255),
);
canvas.draw_rectangle_lines(
Rectangle::new(20.0, 0.0, 20.0, 20.0),
4,
Color::new(0, 228, 48, 255),
);
canvas.draw_rectangle(40, 0, 20, 20, Color::new(255, 161, 0, 255));
canvas.flip_vertical();
canvas.flip_horizontal();
canvas.rotate_cw();
canvas.rotate_ccw();
canvas.color_tint(Color::new(255, 109, 194, 255));
canvas.color_invert();
canvas.color_contrast(0.5);
canvas.color_brightness(128);
canvas.color_replace(Color::new(0, 228, 48, 255), Color::new(230, 41, 55, 255));
}
}