1use crate::error::EncodeError;
2use crate::filter;
3use crate::format::{self, ChunkType};
4use crate::simd;
5
6#[derive(Debug, Clone)]
8pub struct EncodeOptions {
9 pub compression_level: i32,
13 pub has_alpha: bool,
15 pub premultiplied: bool,
17 pub metadata: Option<String>,
19}
20
21impl Default for EncodeOptions {
22 fn default() -> Self {
23 Self {
24 compression_level: 9,
25 has_alpha: true,
26 premultiplied: false,
27 metadata: None,
28 }
29 }
30}
31
32pub fn encode(width: u32, height: u32, rgba: &[u8]) -> Result<Vec<u8>, EncodeError> {
37 encode_with_opts(width, height, rgba, &EncodeOptions::default())
38}
39
40pub fn encode_with_opts(
50 width: u32,
51 height: u32,
52 rgba: &[u8],
53 opts: &EncodeOptions,
54) -> Result<Vec<u8>, EncodeError> {
55 let expected_bytes = (width as usize) * (height as usize) * 4;
56 if rgba.len() != expected_bytes {
57 return Err(EncodeError::BufferSizeMismatch {
58 expected: expected_bytes,
59 actual: rgba.len(),
60 });
61 }
62
63 let planar = simd::deinterleave_rgba(rgba);
65 let unfiltered = zstd::encode_all(planar.as_slice(), opts.compression_level)
66 .map_err(EncodeError::ZstdError)?;
67
68 let (pixel_data, pixel_uncompressed_size, filtered) = if width > 0 && height > 0 {
72 let filtered_planar = filter::filter_planes(&planar, width as usize, height as usize, 4);
73 let filtered_compressed =
74 zstd::encode_all(filtered_planar.as_slice(), opts.compression_level)
75 .map_err(EncodeError::ZstdError)?;
76 if filtered_compressed.len() < unfiltered.len() {
77 (filtered_compressed, filtered_planar.len(), true)
78 } else {
79 (unfiltered, expected_bytes, false)
80 }
81 } else {
82 (unfiltered, expected_bytes, false)
83 };
84
85 let mut flags = format::FLAG_PLANAR;
86 if opts.has_alpha {
87 flags |= format::FLAG_HAS_ALPHA;
88 }
89 if opts.premultiplied {
90 flags |= format::FLAG_PREMULTIPLIED;
91 }
92 if opts.metadata.is_some() {
93 flags |= format::FLAG_HAS_METADATA;
94 }
95 if filtered {
96 flags |= format::FLAG_FILTERED;
97 }
98
99 let mut out = Vec::with_capacity(expected_bytes / 2);
100 format::write_header(&mut out, width, height, flags);
101
102 format::ChunkHeader::write(
103 &mut out,
104 ChunkType::PixelData,
105 u32::try_from(pixel_uncompressed_size).unwrap_or(u32::MAX),
106 u32::try_from(pixel_data.len()).unwrap_or(u32::MAX),
107 0,
108 );
109 out.extend_from_slice(&pixel_data);
110
111 if let Some(meta) = &opts.metadata {
112 let meta_bytes = meta.as_bytes();
113 let compressed_meta =
114 zstd::encode_all(meta_bytes, opts.compression_level).map_err(EncodeError::ZstdError)?;
115 format::ChunkHeader::write(
116 &mut out,
117 ChunkType::Metadata,
118 u32::try_from(meta_bytes.len()).unwrap_or(u32::MAX),
119 u32::try_from(compressed_meta.len()).unwrap_or(u32::MAX),
120 0,
121 );
122 out.extend_from_slice(&compressed_meta);
123 }
124
125 Ok(out)
126}