use std::os::raw::c_int;
use crate::{
AVPixelFormat, SwsContext, ensure_initialized, sws_freeContext as ffi_sws_freeContext,
sws_getContext as ffi_sws_getContext,
sws_isSupportedEndiannessConversion as ffi_sws_isSupportedEndiannessConversion,
sws_isSupportedInput as ffi_sws_isSupportedInput,
sws_isSupportedOutput as ffi_sws_isSupportedOutput, sws_scale as ffi_sws_scale,
};
pub mod scale_flags {
#[cfg(ffmpeg8)]
pub const FAST_BILINEAR: i32 = crate::SwsFlags_SWS_FAST_BILINEAR as i32;
#[cfg(not(ffmpeg8))]
pub const FAST_BILINEAR: i32 = crate::SWS_FAST_BILINEAR as i32;
#[cfg(ffmpeg8)]
pub const BILINEAR: i32 = crate::SwsFlags_SWS_BILINEAR as i32;
#[cfg(not(ffmpeg8))]
pub const BILINEAR: i32 = crate::SWS_BILINEAR as i32;
#[cfg(ffmpeg8)]
pub const BICUBIC: i32 = crate::SwsFlags_SWS_BICUBIC as i32;
#[cfg(not(ffmpeg8))]
pub const BICUBIC: i32 = crate::SWS_BICUBIC as i32;
#[cfg(ffmpeg8)]
pub const X: i32 = crate::SwsFlags_SWS_X as i32;
#[cfg(not(ffmpeg8))]
pub const X: i32 = crate::SWS_X as i32;
#[cfg(ffmpeg8)]
pub const POINT: i32 = crate::SwsFlags_SWS_POINT as i32;
#[cfg(not(ffmpeg8))]
pub const POINT: i32 = crate::SWS_POINT as i32;
#[cfg(ffmpeg8)]
pub const AREA: i32 = crate::SwsFlags_SWS_AREA as i32;
#[cfg(not(ffmpeg8))]
pub const AREA: i32 = crate::SWS_AREA as i32;
#[cfg(ffmpeg8)]
pub const BICUBLIN: i32 = crate::SwsFlags_SWS_BICUBLIN as i32;
#[cfg(not(ffmpeg8))]
pub const BICUBLIN: i32 = crate::SWS_BICUBLIN as i32;
#[cfg(ffmpeg8)]
pub const GAUSS: i32 = crate::SwsFlags_SWS_GAUSS as i32;
#[cfg(not(ffmpeg8))]
pub const GAUSS: i32 = crate::SWS_GAUSS as i32;
#[cfg(ffmpeg8)]
pub const SINC: i32 = crate::SwsFlags_SWS_SINC as i32;
#[cfg(not(ffmpeg8))]
pub const SINC: i32 = crate::SWS_SINC as i32;
#[cfg(ffmpeg8)]
pub const LANCZOS: i32 = crate::SwsFlags_SWS_LANCZOS as i32;
#[cfg(not(ffmpeg8))]
pub const LANCZOS: i32 = crate::SWS_LANCZOS as i32;
#[cfg(ffmpeg8)]
pub const SPLINE: i32 = crate::SwsFlags_SWS_SPLINE as i32;
#[cfg(not(ffmpeg8))]
pub const SPLINE: i32 = crate::SWS_SPLINE as i32;
}
pub unsafe fn get_context(
src_w: c_int,
src_h: c_int,
src_fmt: AVPixelFormat,
dst_w: c_int,
dst_h: c_int,
dst_fmt: AVPixelFormat,
flags: c_int,
) -> Result<*mut SwsContext, c_int> {
ensure_initialized();
if src_w <= 0 || src_h <= 0 || dst_w <= 0 || dst_h <= 0 {
return Err(crate::error_codes::EINVAL);
}
let ctx = ffi_sws_getContext(
src_w,
src_h,
src_fmt,
dst_w,
dst_h,
dst_fmt,
flags,
std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null(), );
if ctx.is_null() {
Err(crate::error_codes::ENOMEM)
} else {
Ok(ctx)
}
}
pub unsafe fn free_context(ctx: *mut SwsContext) {
if !ctx.is_null() {
ffi_sws_freeContext(ctx);
}
}
pub unsafe fn scale(
ctx: *mut SwsContext,
src: *const *const u8,
src_stride: *const c_int,
src_slice_y: c_int,
src_slice_h: c_int,
dst: *const *mut u8,
dst_stride: *const c_int,
) -> Result<c_int, c_int> {
if ctx.is_null() || src.is_null() || dst.is_null() {
return Err(crate::error_codes::EINVAL);
}
if src_stride.is_null() || dst_stride.is_null() {
return Err(crate::error_codes::EINVAL);
}
let ret = ffi_sws_scale(
ctx,
src,
src_stride,
src_slice_y,
src_slice_h,
dst,
dst_stride,
);
if ret < 0 { Err(ret) } else { Ok(ret) }
}
pub unsafe fn is_supported_input(pix_fmt: AVPixelFormat) -> bool {
ensure_initialized();
ffi_sws_isSupportedInput(pix_fmt) != 0
}
pub unsafe fn is_supported_output(pix_fmt: AVPixelFormat) -> bool {
ensure_initialized();
ffi_sws_isSupportedOutput(pix_fmt) != 0
}
pub unsafe fn is_supported_endianness_conversion(pix_fmt: AVPixelFormat) -> bool {
ensure_initialized();
ffi_sws_isSupportedEndiannessConversion(pix_fmt) != 0
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
AVPixelFormat_AV_PIX_FMT_RGB24, AVPixelFormat_AV_PIX_FMT_RGBA,
AVPixelFormat_AV_PIX_FMT_YUV420P,
};
#[test]
fn test_scale_flags_values() {
assert!(scale_flags::FAST_BILINEAR > 0);
assert!(scale_flags::BILINEAR > 0);
assert!(scale_flags::BICUBIC > 0);
assert!(scale_flags::LANCZOS > 0);
assert_ne!(scale_flags::BILINEAR, scale_flags::BICUBIC);
assert_ne!(scale_flags::BILINEAR, scale_flags::LANCZOS);
assert_ne!(scale_flags::BICUBIC, scale_flags::LANCZOS);
}
#[test]
fn test_get_context_and_free() {
unsafe {
let ctx_result = get_context(
640,
480,
AVPixelFormat_AV_PIX_FMT_RGB24,
320,
240,
AVPixelFormat_AV_PIX_FMT_RGB24,
scale_flags::BILINEAR,
);
assert!(ctx_result.is_ok(), "Context creation should succeed");
let ctx = ctx_result.unwrap();
assert!(!ctx.is_null());
free_context(ctx);
}
}
#[test]
fn test_get_context_yuv_to_rgb() {
unsafe {
let ctx_result = get_context(
1920,
1080,
AVPixelFormat_AV_PIX_FMT_YUV420P,
1920,
1080,
AVPixelFormat_AV_PIX_FMT_RGB24,
scale_flags::BICUBIC,
);
assert!(ctx_result.is_ok(), "YUV to RGB context should succeed");
let ctx = ctx_result.unwrap();
free_context(ctx);
}
}
#[test]
fn test_get_context_with_lanczos() {
unsafe {
let ctx_result = get_context(
3840,
2160,
AVPixelFormat_AV_PIX_FMT_YUV420P,
1920,
1080,
AVPixelFormat_AV_PIX_FMT_YUV420P,
scale_flags::LANCZOS,
);
assert!(ctx_result.is_ok(), "Lanczos context should succeed");
let ctx = ctx_result.unwrap();
free_context(ctx);
}
}
#[test]
fn test_get_context_invalid_dimensions() {
unsafe {
let result = get_context(
0,
480,
AVPixelFormat_AV_PIX_FMT_RGB24,
320,
240,
AVPixelFormat_AV_PIX_FMT_RGB24,
scale_flags::BILINEAR,
);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
let result = get_context(
640,
0,
AVPixelFormat_AV_PIX_FMT_RGB24,
320,
240,
AVPixelFormat_AV_PIX_FMT_RGB24,
scale_flags::BILINEAR,
);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
let result = get_context(
-640,
480,
AVPixelFormat_AV_PIX_FMT_RGB24,
320,
240,
AVPixelFormat_AV_PIX_FMT_RGB24,
scale_flags::BILINEAR,
);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
}
}
#[test]
fn test_free_context_null() {
unsafe {
free_context(std::ptr::null_mut());
}
}
#[test]
fn test_scale_null_context() {
unsafe {
let src: [*const u8; 4] = [std::ptr::null(); 4];
let dst: [*mut u8; 4] = [std::ptr::null_mut(); 4];
let src_stride: [c_int; 4] = [0; 4];
let dst_stride: [c_int; 4] = [0; 4];
let result = scale(
std::ptr::null_mut(),
src.as_ptr(),
src_stride.as_ptr(),
0,
480,
dst.as_ptr(),
dst_stride.as_ptr(),
);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
}
}
#[test]
fn test_scale_null_src() {
unsafe {
let ctx = get_context(
640,
480,
AVPixelFormat_AV_PIX_FMT_RGB24,
320,
240,
AVPixelFormat_AV_PIX_FMT_RGB24,
scale_flags::BILINEAR,
)
.unwrap();
let dst: [*mut u8; 4] = [std::ptr::null_mut(); 4];
let dst_stride: [c_int; 4] = [0; 4];
let result = scale(
ctx,
std::ptr::null(),
std::ptr::null(),
0,
480,
dst.as_ptr(),
dst_stride.as_ptr(),
);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
free_context(ctx);
}
}
#[test]
fn test_scale_null_dst() {
unsafe {
let ctx = get_context(
640,
480,
AVPixelFormat_AV_PIX_FMT_RGB24,
320,
240,
AVPixelFormat_AV_PIX_FMT_RGB24,
scale_flags::BILINEAR,
)
.unwrap();
let src: [*const u8; 4] = [std::ptr::null(); 4];
let src_stride: [c_int; 4] = [0; 4];
let result = scale(
ctx,
src.as_ptr(),
src_stride.as_ptr(),
0,
480,
std::ptr::null(),
std::ptr::null(),
);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), crate::error_codes::EINVAL);
free_context(ctx);
}
}
#[test]
fn test_is_supported_input() {
unsafe {
assert!(
is_supported_input(AVPixelFormat_AV_PIX_FMT_YUV420P),
"YUV420P should be supported as input"
);
assert!(
is_supported_input(AVPixelFormat_AV_PIX_FMT_RGB24),
"RGB24 should be supported as input"
);
}
}
#[test]
fn test_is_supported_output() {
unsafe {
assert!(
is_supported_output(AVPixelFormat_AV_PIX_FMT_YUV420P),
"YUV420P should be supported as output"
);
assert!(
is_supported_output(AVPixelFormat_AV_PIX_FMT_RGB24),
"RGB24 should be supported as output"
);
}
}
#[test]
fn test_is_supported_endianness_conversion() {
unsafe {
let _rgb24 = is_supported_endianness_conversion(AVPixelFormat_AV_PIX_FMT_RGB24);
let _yuv420p = is_supported_endianness_conversion(AVPixelFormat_AV_PIX_FMT_YUV420P);
}
}
#[test]
fn test_context_multiple_algorithms() {
unsafe {
let algorithms = [
scale_flags::FAST_BILINEAR,
scale_flags::BILINEAR,
scale_flags::BICUBIC,
scale_flags::POINT,
scale_flags::AREA,
scale_flags::LANCZOS,
];
for &algo in &algorithms {
let ctx_result = get_context(
640,
480,
AVPixelFormat_AV_PIX_FMT_YUV420P,
320,
240,
AVPixelFormat_AV_PIX_FMT_YUV420P,
algo,
);
assert!(
ctx_result.is_ok(),
"Algorithm {algo} should create valid context"
);
free_context(ctx_result.unwrap());
}
}
}
fn load_test_image() -> (u32, u32, Vec<u8>) {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let image_path = format!("{}/../../assets/img/hello-triangle.png", manifest_dir);
let img = image::open(&image_path)
.unwrap_or_else(|e| panic!("Failed to load test image at {}: {}", image_path, e));
let rgba = img.to_rgba8();
let (width, height) = rgba.dimensions();
(width, height, rgba.into_raw())
}
#[test]
fn test_scale_actual_image_downscale() {
let (src_width, src_height, src_data) = load_test_image();
let dst_width = src_width / 2;
let dst_height = src_height / 2;
unsafe {
let ctx = get_context(
src_width as c_int,
src_height as c_int,
AVPixelFormat_AV_PIX_FMT_RGBA,
dst_width as c_int,
dst_height as c_int,
AVPixelFormat_AV_PIX_FMT_RGBA,
scale_flags::BILINEAR,
)
.unwrap_or_else(|e| panic!("Context creation failed with error code: {}", e));
let src_stride: [c_int; 4] = [(src_width * 4) as c_int, 0, 0, 0];
let src_ptrs: [*const u8; 4] = [
src_data.as_ptr(),
std::ptr::null(),
std::ptr::null(),
std::ptr::null(),
];
let dst_size = (dst_width * dst_height * 4) as usize;
let mut dst_data: Vec<u8> = vec![0u8; dst_size];
let dst_stride: [c_int; 4] = [(dst_width * 4) as c_int, 0, 0, 0];
let dst_ptrs: [*mut u8; 4] = [
dst_data.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
];
let result = scale(
ctx,
src_ptrs.as_ptr(),
src_stride.as_ptr(),
0,
src_height as c_int,
dst_ptrs.as_ptr(),
dst_stride.as_ptr(),
);
assert!(result.is_ok(), "Scaling should succeed");
assert_eq!(
result.unwrap(),
dst_height as c_int,
"Should process all output rows"
);
let non_zero_count = dst_data.iter().filter(|&&b| b != 0).count();
assert!(
non_zero_count > dst_size / 2,
"Output image should contain significant data"
);
free_context(ctx);
}
}
#[test]
fn test_scale_actual_image_upscale() {
let (src_width, src_height, src_data) = load_test_image();
let dst_width = (src_width as f32 * 1.5) as u32;
let dst_height = (src_height as f32 * 1.5) as u32;
unsafe {
let ctx = get_context(
src_width as c_int,
src_height as c_int,
AVPixelFormat_AV_PIX_FMT_RGBA,
dst_width as c_int,
dst_height as c_int,
AVPixelFormat_AV_PIX_FMT_RGBA,
scale_flags::LANCZOS,
)
.unwrap_or_else(|e| panic!("Context creation failed with error code: {}", e));
let src_stride: [c_int; 4] = [(src_width * 4) as c_int, 0, 0, 0];
let src_ptrs: [*const u8; 4] = [
src_data.as_ptr(),
std::ptr::null(),
std::ptr::null(),
std::ptr::null(),
];
let dst_size = (dst_width * dst_height * 4) as usize;
let mut dst_data: Vec<u8> = vec![0u8; dst_size];
let dst_stride: [c_int; 4] = [(dst_width * 4) as c_int, 0, 0, 0];
let dst_ptrs: [*mut u8; 4] = [
dst_data.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
];
let result = scale(
ctx,
src_ptrs.as_ptr(),
src_stride.as_ptr(),
0,
src_height as c_int,
dst_ptrs.as_ptr(),
dst_stride.as_ptr(),
);
assert!(result.is_ok(), "Upscaling should succeed");
assert_eq!(
result.unwrap(),
dst_height as c_int,
"Should process all output rows"
);
free_context(ctx);
}
}
#[test]
fn test_scale_rgba_to_rgb24_conversion() {
let (src_width, src_height, src_data) = load_test_image();
unsafe {
let ctx = get_context(
src_width as c_int,
src_height as c_int,
AVPixelFormat_AV_PIX_FMT_RGBA,
src_width as c_int,
src_height as c_int,
AVPixelFormat_AV_PIX_FMT_RGB24,
scale_flags::POINT,
)
.unwrap_or_else(|e| panic!("Context creation failed with error code: {}", e));
let src_stride: [c_int; 4] = [(src_width * 4) as c_int, 0, 0, 0];
let src_ptrs: [*const u8; 4] = [
src_data.as_ptr(),
std::ptr::null(),
std::ptr::null(),
std::ptr::null(),
];
let dst_size = (src_width * src_height * 3) as usize;
let mut dst_data: Vec<u8> = vec![0u8; dst_size];
let dst_stride: [c_int; 4] = [(src_width * 3) as c_int, 0, 0, 0];
let dst_ptrs: [*mut u8; 4] = [
dst_data.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
];
let result = scale(
ctx,
src_ptrs.as_ptr(),
src_stride.as_ptr(),
0,
src_height as c_int,
dst_ptrs.as_ptr(),
dst_stride.as_ptr(),
);
assert!(result.is_ok(), "Format conversion should succeed");
assert_eq!(
result.unwrap(),
src_height as c_int,
"Should process all output rows"
);
let src_r = src_data[0];
let src_g = src_data[1];
let src_b = src_data[2];
let dst_r = dst_data[0];
let dst_g = dst_data[1];
let dst_b = dst_data[2];
assert_eq!(src_r, dst_r, "Red channel should match");
assert_eq!(src_g, dst_g, "Green channel should match");
assert_eq!(src_b, dst_b, "Blue channel should match");
free_context(ctx);
}
}
#[test]
fn test_scale_multiple_algorithms_on_image() {
let (src_width, src_height, src_data) = load_test_image();
let dst_width = 256;
let dst_height = 256;
let algorithms = [
("FAST_BILINEAR", scale_flags::FAST_BILINEAR),
("BILINEAR", scale_flags::BILINEAR),
("BICUBIC", scale_flags::BICUBIC),
("POINT", scale_flags::POINT),
("AREA", scale_flags::AREA),
("LANCZOS", scale_flags::LANCZOS),
];
for (name, algo) in algorithms {
unsafe {
let ctx = get_context(
src_width as c_int,
src_height as c_int,
AVPixelFormat_AV_PIX_FMT_RGBA,
dst_width,
dst_height,
AVPixelFormat_AV_PIX_FMT_RGBA,
algo,
)
.unwrap_or_else(|e| panic!("{} context creation failed: {}", name, e));
let src_stride: [c_int; 4] = [(src_width * 4) as c_int, 0, 0, 0];
let src_ptrs: [*const u8; 4] = [
src_data.as_ptr(),
std::ptr::null(),
std::ptr::null(),
std::ptr::null(),
];
let dst_size = (dst_width * dst_height * 4) as usize;
let mut dst_data: Vec<u8> = vec![0u8; dst_size];
let dst_stride: [c_int; 4] = [(dst_width * 4) as c_int, 0, 0, 0];
let dst_ptrs: [*mut u8; 4] = [
dst_data.as_mut_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
];
let result = scale(
ctx,
src_ptrs.as_ptr(),
src_stride.as_ptr(),
0,
src_height as c_int,
dst_ptrs.as_ptr(),
dst_stride.as_ptr(),
);
assert!(result.is_ok(), "{} scaling should succeed", name);
assert_eq!(
result.unwrap(),
dst_height,
"{} should process all output rows",
name
);
free_context(ctx);
}
}
}
}