rfb_encodings/jpeg/
turbojpeg.rs1use std::ffi::c_void;
21use std::os::raw::{c_char, c_int, c_uchar, c_ulong};
22
23pub const TJPF_RGB: c_int = 0;
26#[allow(dead_code)]
28pub const TJPF_BGR: c_int = 1;
29#[allow(dead_code)]
31pub const TJPF_RGBX: c_int = 2;
32#[allow(dead_code)]
34pub const TJPF_BGRX: c_int = 3;
35#[allow(dead_code)]
37pub const TJPF_XBGR: c_int = 4;
38#[allow(dead_code)]
40pub const TJPF_XRGB: c_int = 5;
41#[allow(dead_code)]
43pub const TJPF_GRAY: c_int = 6;
44
45#[allow(dead_code)]
48pub const TJSAMP_444: c_int = 0;
49pub const TJSAMP_422: c_int = 1;
51#[allow(dead_code)]
53pub const TJSAMP_420: c_int = 2;
54#[allow(dead_code)]
56pub const TJSAMP_GRAY: c_int = 3;
57
58type TjHandle = *mut c_void;
60
61#[link(name = "turbojpeg")]
63extern "C" {
64 fn tjInitCompress() -> TjHandle;
65 fn tjDestroy(handle: TjHandle) -> c_int;
66 fn tjCompress2(
67 handle: TjHandle,
68 src_buf: *const c_uchar,
69 width: c_int,
70 pitch: c_int,
71 height: c_int,
72 pixel_format: c_int,
73 jpeg_buf: *mut *mut c_uchar,
74 jpeg_size: *mut c_ulong,
75 jpeg_subsamp: c_int,
76 jpeg_qual: c_int,
77 flags: c_int,
78 ) -> c_int;
79 fn tjFree(buffer: *mut c_uchar);
80 fn tjGetErrorStr2(handle: TjHandle) -> *const c_char;
81}
82
83pub struct TurboJpegEncoder {
85 handle: TjHandle,
86}
87
88impl TurboJpegEncoder {
89 pub fn new() -> Result<Self, String> {
95 let handle = unsafe { tjInitCompress() };
96 if handle.is_null() {
97 return Err("Failed to initialize TurboJPEG compressor".to_string());
98 }
99 Ok(Self { handle })
100 }
101
102 #[allow(clippy::cast_possible_truncation)] pub fn compress_rgb(
119 &mut self,
120 rgb_data: &[u8],
121 width: u16,
122 height: u16,
123 quality: u8,
124 ) -> Result<Vec<u8>, String> {
125 let expected_size = (width as usize) * (height as usize) * 3;
126 if rgb_data.len() != expected_size {
127 return Err(format!(
128 "Invalid RGB data size: expected {}, got {}",
129 expected_size,
130 rgb_data.len()
131 ));
132 }
133
134 let mut jpeg_buf: *mut c_uchar = std::ptr::null_mut();
135 let mut jpeg_size: c_ulong = 0;
136
137 let result = unsafe {
138 tjCompress2(
139 self.handle,
140 rgb_data.as_ptr(),
141 c_int::from(width),
142 0, c_int::from(height),
144 TJPF_RGB,
145 &raw mut jpeg_buf,
146 &raw mut jpeg_size,
147 TJSAMP_422, c_int::from(quality),
149 0, )
151 };
152
153 if result != 0 {
154 let error_msg = self.get_error_string();
155 return Err(format!("TurboJPEG compression failed: {error_msg}"));
156 }
157
158 if jpeg_buf.is_null() {
159 return Err("TurboJPEG returned null buffer".to_string());
160 }
161
162 let jpeg_data =
164 unsafe { std::slice::from_raw_parts(jpeg_buf, jpeg_size as usize).to_vec() };
165
166 unsafe {
168 tjFree(jpeg_buf);
169 }
170
171 Ok(jpeg_data)
172 }
173
174 fn get_error_string(&self) -> String {
176 unsafe {
177 let c_str = tjGetErrorStr2(self.handle);
178 if c_str.is_null() {
179 return "Unknown error".to_string();
180 }
181 std::ffi::CStr::from_ptr(c_str)
182 .to_string_lossy()
183 .into_owned()
184 }
185 }
186}
187
188impl Drop for TurboJpegEncoder {
189 fn drop(&mut self) {
190 unsafe {
191 tjDestroy(self.handle);
192 }
193 }
194}
195
196unsafe impl Send for TurboJpegEncoder {}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_encoder_creation() {
204 let encoder = TurboJpegEncoder::new();
205 assert!(encoder.is_ok());
206 }
207
208 #[test]
209 fn test_compress_rgb() {
210 let mut encoder = TurboJpegEncoder::new().unwrap();
211
212 let rgb_data = vec![255, 0, 0, 255, 0, 0, 255, 0, 0, 255, 0, 0];
214
215 let result = encoder.compress_rgb(&rgb_data, 2, 2, 90);
216 assert!(result.is_ok());
217
218 let jpeg_data = result.unwrap();
219 assert!(!jpeg_data.is_empty());
220 assert_eq!(jpeg_data[0], 0xFF);
222 assert_eq!(jpeg_data[1], 0xD8);
223 }
224}