#![cfg(feature = "encode")]
use webpx::*;
#[cfg(feature = "streaming")]
fn rgba_fixture(width: u32, height: u32) -> Vec<u8> {
let mut rgba = Vec::with_capacity((width * height * 4) as usize);
for y in 0..height {
for x in 0..width {
rgba.extend_from_slice(&[
(x * 40 + 10) as u8,
(y * 50 + 20) as u8,
(x * 17 + y * 19 + 30) as u8,
255,
]);
}
}
rgba
}
#[cfg(feature = "streaming")]
fn encode_lossless_rgba(rgba: &[u8], width: u32, height: u32) -> Vec<u8> {
Encoder::new_rgba(rgba, width, height)
.lossless(true)
.encode(Unstoppable)
.expect("lossless encode should succeed")
}
#[test]
fn argb_lossless_encode_must_not_mutate_shared_input() {
let argb = vec![0x00_ff_00_00u32];
let original = argb.clone();
Encoder::new_argb(&argb, 1, 1)
.lossless(true)
.encode(Unstoppable)
.expect("lossless argb encode should succeed");
assert_eq!(
argb, original,
"safe ARGB encoding mutated data passed through an immutable slice"
);
}
#[test]
fn yuv_encode_must_not_mutate_shared_planes() {
let width = 8;
let height = 8;
let y: Vec<u8> = (0..(width * height)).map(|v| v as u8).collect();
let u: Vec<u8> = (0..((width / 2) * (height / 2)))
.map(|v| (64 + v) as u8)
.collect();
let v: Vec<u8> = (0..((width / 2) * (height / 2)))
.map(|v| (128 + v) as u8)
.collect();
let a = vec![0u8; (width * height) as usize];
let original_y = y.clone();
let original_u = u.clone();
let original_v = v.clone();
let planes = YuvPlanesRef {
y: &y,
y_stride: width as usize,
u: &u,
u_stride: (width / 2) as usize,
v: &v,
v_stride: (width / 2) as usize,
a: Some(&a),
a_stride: width as usize,
width,
height,
};
Encoder::new_yuv(planes)
.encode(Unstoppable)
.expect("yuv encode should succeed");
assert_eq!(
y, original_y,
"safe YUV encoding mutated the Y plane passed through an immutable slice"
);
assert_eq!(
u, original_u,
"safe YUV encoding mutated the U plane passed through an immutable slice"
);
assert_eq!(
v, original_v,
"safe YUV encoding mutated the V plane passed through an immutable slice"
);
}
#[test]
fn yuv_encode_must_reject_planes_shorter_than_stride_height() {
let width = 2;
let height = 2;
let hidden_tail = [16u8, 235, 235, 235];
let u = [128u8];
let v = [128u8];
let planes = YuvPlanesRef {
y: &hidden_tail[..1],
y_stride: width as usize,
u: &u,
u_stride: 1,
v: &v,
v_stride: 1,
a: None,
a_stride: 0,
width,
height,
};
let result = Encoder::new_yuv(planes).encode(Unstoppable);
assert!(
result.is_err(),
"safe YUV encoding accepted a Y plane shorter than y_stride * height"
);
}
mod stride_overflow {
use super::*;
const NEGATIVE_BOUNDARY: u32 = (i32::MAX as u32) + 1;
#[test]
fn argb_zero_copy_rejects_stride_above_i32_max() {
let argb: Vec<u32> = vec![0; 64];
let r = Encoder::new_argb_stride(&argb, 1, 1, NEGATIVE_BOUNDARY).encode(Unstoppable);
assert!(
r.is_err(),
"ARGB stride > i32::MAX must be rejected before the cast",
);
}
#[test]
fn rgba_stride_above_i32_max_is_rejected() {
let data = vec![0u8; 16];
let r = Encoder::new_rgba_stride(&data, 1, 1, NEGATIVE_BOUNDARY).encode(Unstoppable);
assert!(
r.is_err(),
"RGBA stride > i32::MAX must be rejected before the cast",
);
}
#[test]
fn yuv_stride_above_i32_max_is_rejected() {
if usize::BITS < 64 {
return;
}
let big_stride = (i32::MAX as usize) + 1;
let y = vec![0u8; big_stride * 2];
let u = vec![0u8; 1];
let v = vec![0u8; 1];
let planes = YuvPlanesRef {
y: &y,
y_stride: big_stride,
u: &u,
u_stride: 1,
v: &v,
v_stride: 1,
a: None,
a_stride: 0,
width: 2,
height: 2,
};
let r = Encoder::new_yuv(planes).encode(Unstoppable);
assert!(
r.is_err(),
"Y stride > i32::MAX must be rejected by validate_yuv_planes",
);
}
#[cfg(feature = "streaming")]
#[cfg(all(feature = "streaming", feature = "decode"))]
#[test]
fn streaming_with_buffer_rejects_stride_above_i32_max() {
let mut buf = vec![0u8; 64];
let r = StreamingDecoder::with_buffer(&mut buf, (i32::MAX as usize) + 1, ColorMode::Rgba);
assert!(
r.is_err(),
"StreamingDecoder::with_buffer must reject stride > i32::MAX",
);
}
}
#[cfg(all(feature = "streaming", feature = "decode"))]
#[test]
fn streaming_decoder_with_buffer_safe_usage() {
let width = 16;
let height = 16;
let rgba = rgba_fixture(width, height);
let webp = encode_lossless_rgba(&rgba, width, height);
let stride = (width * 4) as usize;
let mut output = vec![0u8; stride * height as usize];
let mut decoder = StreamingDecoder::with_buffer(&mut output, stride, ColorMode::Rgba)
.expect("streaming decoder with external buffer");
decoder.append(&webp).expect("streaming decode append");
let (decoded, decoded_width, decoded_height) =
decoder.finish().expect("finish on caller-owned buffer");
assert_eq!((decoded_width, decoded_height), (width, height));
assert_eq!(decoded, rgba);
}
#[cfg(all(feature = "streaming", feature = "decode"))]
#[test]
fn streaming_get_partial_respects_external_buffer_minimum_extent() {
let width = 1;
let height = 2;
let rgba = rgba_fixture(width, height);
let webp = encode_lossless_rgba(&rgba, width, height);
let stride = 8;
let row_bytes = width as usize * 4;
let expected_partial_len = stride * (height as usize - 1) + row_bytes;
let mut output = vec![0xaa; expected_partial_len];
let mut decoder = StreamingDecoder::with_buffer(&mut output, stride, ColorMode::Rgba)
.expect("streaming decoder with tightly-sized external buffer");
decoder.append(&webp).expect("streaming decode append");
let (partial, partial_width, partial_rows) = decoder
.get_partial()
.expect("complete decode should be visible");
assert_eq!((partial_width, partial_rows), (width, height));
assert_eq!(partial.len(), expected_partial_len);
assert_eq!(partial[partial.len() - 1], rgba[rgba.len() - 1]);
let (decoded, decoded_width, decoded_height) =
decoder.finish().expect("finish on caller-owned buffer");
assert_eq!((decoded_width, decoded_height), (width, height));
assert_eq!(decoded, rgba);
}
#[cfg(all(feature = "streaming", feature = "decode"))]
#[test]
fn streaming_decoder_new_rejects_yuv_modes() {
for mode in [ColorMode::Yuv420, ColorMode::Yuva420] {
let r = StreamingDecoder::new(mode);
match r {
Err(at) => {
let (err, _) = at.decompose();
assert!(
matches!(err, Error::InvalidInput(_)),
"{:?} must surface InvalidInput, not OutOfMemory",
mode,
);
}
Ok(_) => panic!("StreamingDecoder::new accepted {:?}", mode),
}
}
}