1use crate::{colorspace::ColorSpace, PixelDensity};
2use crate::colorspace::ColorSpaceExt;
3use crate::component::CompInfo;
4use crate::component::CompInfoExt;
5use crate::errormgr::unwinding_error_mgr;
6use crate::errormgr::ErrorMgr;
7use crate::fail;
8use crate::ffi;
9use crate::ffi::boolean;
10use crate::ffi::jpeg_compress_struct;
11use crate::ffi::DCTSIZE;
12use crate::ffi::JDIMENSION;
13use crate::ffi::JPEG_LIB_VERSION;
14use crate::ffi::J_BOOLEAN_PARAM;
15use crate::ffi::J_INT_PARAM;
16use crate::marker::Marker;
17use crate::qtable::QTable;
18use crate::writedst::DestinationMgr;
19use arrayvec::ArrayVec;
20use std::cmp::min;
21use std::io;
22use std::marker::PhantomPinned;
23use std::mem;
24use std::os::raw::{c_int, c_uchar, c_uint, c_ulong, c_void};
25use std::ptr;
26use std::ptr::addr_of_mut;
27use std::slice;
28
29pub const MAX_MCU_HEIGHT: usize = 16;
31pub const MAX_COMPONENTS: usize = 4;
33
34pub struct Compress {
38 cinfo: jpeg_compress_struct,
39
40 own_err: *mut ErrorMgr,
43 _it_is_self_referential: PhantomPinned,
44}
45
46#[derive(Copy, Clone)]
47pub enum ScanMode {
48 AllComponentsTogether = 0,
49 ScanPerComponent = 1,
51 Auto = 2,
52}
53
54pub struct CompressStarted<W> {
55 compress: Compress,
56 dest_mgr: DestinationMgr<W>,
58}
59
60impl Compress {
61 #[must_use]
70 pub fn new(color_space: ColorSpace) -> Self {
71 Self::new_err(unwinding_error_mgr(), color_space)
72 }
73
74 #[must_use]
81 pub fn new_err(err: Box<ErrorMgr>, color_space: ColorSpace) -> Self {
82 unsafe {
83 let mut newself = Self {
84 cinfo: mem::zeroed(),
85 own_err: Box::into_raw(err),
86 _it_is_self_referential: PhantomPinned,
87 };
88 newself.cinfo.common.err = addr_of_mut!(*newself.own_err);
89
90 let s = mem::size_of_val(&newself.cinfo);
91 ffi::jpeg_CreateCompress(&mut newself.cinfo, JPEG_LIB_VERSION, s);
92
93 newself.cinfo.in_color_space = color_space;
94 newself.cinfo.input_components = color_space.num_components() as c_int;
95 ffi::jpeg_set_defaults(&mut newself.cinfo);
96
97 newself
98 }
99 }
100
101 #[doc(hidden)]
102 #[deprecated(note = "Give a Vec to start_compress instead")]
103 pub fn set_mem_dest(&self) {
104 }
105
106 pub fn start_compress<W: io::Write>(self, writer: W) -> io::Result<CompressStarted<W>> {
112 if !self.components().iter().any(|c| c.h_samp_factor == 1) { return Err(io::Error::new(io::ErrorKind::InvalidInput, "at least one h_samp_factor must be 1")); }
113 if !self.components().iter().any(|c| c.v_samp_factor == 1) { return Err(io::Error::new(io::ErrorKind::InvalidInput, "at least one v_samp_factor must be 1")); }
114
115 let expected_file_size = (self.cinfo.image_width as usize * self.cinfo.image_height as usize / 8 + 4095) & !4095;
117 let write_buffer_capacity = expected_file_size.clamp(1 << 12, 1 << 16);
118
119 let mut started = CompressStarted {
120 compress: self,
121 dest_mgr: DestinationMgr::new(writer, write_buffer_capacity),
122 };
123 unsafe {
124 started.compress.cinfo.dest = started.dest_mgr.iface_c_ptr();
125 ffi::jpeg_start_compress(&mut started.compress.cinfo, boolean::from(true));
126 }
127 Ok(started)
128 }
129}
130
131impl<W> CompressStarted<W> {
132 pub fn write_marker(&mut self, marker: Marker, data: &[u8]) {
140 unsafe {
141 ffi::jpeg_write_marker(
142 &mut self.compress.cinfo,
143 marker.into(),
144 data.as_ptr(),
145 data.len() as c_uint,
146 );
147 }
148 }
149
150 pub fn write_icc_profile(&mut self, data: &[u8]) {
156 const OVERHEAD_LEN: usize = 14;
157 const MAX_BYTES_IN_MARKER: usize = 65533;
158 const MAX_DATA_BYTES_IN_MARKER: usize = MAX_BYTES_IN_MARKER - OVERHEAD_LEN;
159
160 if data.is_empty() {
161 fail(&mut self.compress.cinfo.common, ffi::JERR_BUFFER_SIZE);
162 }
163
164 let chunks = data.chunks(MAX_DATA_BYTES_IN_MARKER);
165 let num_chunks = chunks.len();
166
167 let mut buf = Vec::with_capacity(MAX_BYTES_IN_MARKER.min(data.len() + OVERHEAD_LEN));
168
169 chunks.enumerate().for_each(move |(current_marker, chunk)| {
170 buf.clear();
171 buf.extend_from_slice(b"ICC_PROFILE\0");
172 buf.extend([current_marker as u8, num_chunks as u8]);
173 buf.extend_from_slice(chunk);
174
175 self.write_marker(Marker::APP(2), &buf);
176 });
177 }
178
179 #[must_use]
181 pub fn components(&self) -> &[CompInfo] {
182 self.compress.components()
183 }
184
185 fn can_write_more_lines(&self) -> bool {
186 self.compress.cinfo.next_scanline < self.compress.cinfo.image_height
187 }
188}
189
190impl Compress {
191 pub fn components_mut(&mut self) -> &mut [CompInfo] {
193 if self.cinfo.comp_info.is_null() {
194 return &mut [];
195 }
196 unsafe {
197 slice::from_raw_parts_mut(self.cinfo.comp_info, self.cinfo.num_components as usize)
198 }
199 }
200
201 #[must_use]
203 pub fn components(&self) -> &[CompInfo] {
204 if self.cinfo.comp_info.is_null() {
205 return &[];
206 }
207 unsafe {
208 slice::from_raw_parts(self.cinfo.comp_info, self.cinfo.num_components as usize)
209 }
210 }
211}
212
213impl<W> CompressStarted<W> {
214 pub fn write_scanlines(&mut self, image_src: &[u8]) -> io::Result<()> {
220 if self.compress.cinfo.raw_data_in != 0 ||
221 self.compress.cinfo.input_components <= 0 ||
222 self.compress.cinfo.image_width == 0 {
223 return Err(io::ErrorKind::InvalidInput.into());
224 }
225
226 let byte_width = self.compress.cinfo.image_width as usize * self.compress.cinfo.input_components as usize;
227 for rows in image_src.chunks(MAX_MCU_HEIGHT * byte_width) {
228 let mut row_pointers = ArrayVec::<_, MAX_MCU_HEIGHT>::new();
229 for row in rows.chunks_exact(byte_width) {
230 row_pointers.push(row.as_ptr());
231 }
232
233 let mut rows_left = row_pointers.len() as u32;
234 let mut row_pointers = row_pointers.as_ptr();
235 while rows_left > 0 {
236 unsafe {
237 let rows_written = ffi::jpeg_write_scanlines(
238 &mut self.compress.cinfo,
239 row_pointers,
240 rows_left,
241 );
242 debug_assert!(rows_left >= rows_written);
243 if rows_written == 0 {
244 return Err(io::ErrorKind::UnexpectedEof.into());
245 }
246 rows_left -= rows_written;
247 row_pointers = row_pointers.add(rows_written as usize);
248 }
249 }
250 }
251 Ok(())
252 }
253
254 #[track_caller]
263 pub fn write_raw_data(&mut self, image_src: &[&[u8]]) -> bool {
264 if 0 == self.compress.cinfo.raw_data_in {
265 panic!("Raw data not set");
266 }
267
268 let mcu_height = self.compress.cinfo.max_v_samp_factor as usize * DCTSIZE;
269 if mcu_height > MAX_MCU_HEIGHT {
270 panic!("Subsampling factor too large");
271 }
272 assert!(mcu_height > 0);
273
274 let num_components = self.components().len();
275 if num_components > MAX_COMPONENTS || num_components > image_src.len() {
276 panic!("Too many components: declared {}, got {}", num_components, image_src.len());
277 }
278
279 for (ci, comp_info) in self.components().iter().enumerate() {
280 if comp_info.row_stride() * comp_info.col_stride() > image_src[ci].len() {
281 panic!("Bitmap too small. Expected {}x{}, got {}", comp_info.row_stride(), comp_info.col_stride(), image_src[ci].len());
282 }
283 }
284
285 let mut start_row = self.compress.cinfo.next_scanline as usize;
286 while self.can_write_more_lines() {
287 unsafe {
288 let mut row_ptrs = [[ptr::null::<u8>(); MAX_MCU_HEIGHT]; MAX_COMPONENTS];
289
290 for ((comp_info, &image_src), comp_row_ptrs) in self.components().iter().zip(image_src).zip(row_ptrs.iter_mut()) {
291 let row_stride = comp_info.row_stride();
292
293 let input_height = image_src.len() / row_stride;
294
295 let comp_start_row = start_row * comp_info.v_samp_factor as usize
296 / self.compress.cinfo.max_v_samp_factor as usize;
297 let comp_height = min(
298 input_height - comp_start_row,
299 DCTSIZE * comp_info.v_samp_factor as usize,
300 );
301 assert!(comp_height >= 8);
302
303 for (src_row, row_ptr) in image_src.chunks_exact(row_stride).skip(comp_start_row).take(comp_height).zip(comp_row_ptrs.iter_mut()) {
305 *row_ptr = src_row.as_ptr();
306 }
307 }
308
309 let comp_ptrs: [*const *const u8; MAX_COMPONENTS] = std::array::from_fn(|ci| row_ptrs[ci].as_ptr());
310 let rows_written = ffi::jpeg_write_raw_data(&mut self.compress.cinfo, comp_ptrs.as_ptr(), mcu_height as u32) as usize;
311 if 0 == rows_written {
312 return false;
313 }
314 start_row += rows_written;
315 }
316 }
317 true
318 }
319}
320
321impl Compress {
322 pub fn set_color_space(&mut self, color_space: ColorSpace) {
326 unsafe {
327 ffi::jpeg_set_colorspace(&mut self.cinfo, color_space);
328 }
329 }
330
331 pub fn set_size(&mut self, width: usize, height: usize) {
333 self.cinfo.image_width = width as JDIMENSION;
334 self.cinfo.image_height = height as JDIMENSION;
335 }
336
337 #[deprecated(note = "it doesn't do anything")]
339 pub fn set_gamma(&mut self, gamma: f64) {
340 self.cinfo.input_gamma = gamma;
341 }
342
343 pub fn set_pixel_density(&mut self, density: PixelDensity) {
350 self.cinfo.density_unit = density.unit as u8;
351 self.cinfo.X_density = density.x;
352 self.cinfo.Y_density = density.y;
353 }
354
355 pub fn set_optimize_scans(&mut self, opt: bool) {
357 unsafe {
358 ffi::jpeg_c_set_bool_param(&mut self.cinfo, J_BOOLEAN_PARAM::JBOOLEAN_OPTIMIZE_SCANS, boolean::from(opt));
359 }
360 if !opt {
361 self.cinfo.scan_info = ptr::null();
362 }
363 }
364
365 pub fn set_smoothing_factor(&mut self, smoothing_factor: u8) {
367 self.cinfo.smoothing_factor = c_int::from(smoothing_factor);
368 }
369
370 pub fn set_optimize_coding(&mut self, opt: bool) {
372 self.cinfo.optimize_coding = boolean::from(opt);
373 }
374
375 pub fn set_use_scans_in_trellis(&mut self, opt: bool) {
378 unsafe {
379 ffi::jpeg_c_set_bool_param(&mut self.cinfo, J_BOOLEAN_PARAM::JBOOLEAN_USE_SCANS_IN_TRELLIS, boolean::from(opt));
380 }
381 }
382
383 pub fn set_progressive_mode(&mut self) {
385 unsafe {
386 ffi::jpeg_simple_progression(&mut self.cinfo);
387 }
388 }
389
390 pub fn set_scan_optimization_mode(&mut self, mode: ScanMode) {
392 unsafe {
393 ffi::jpeg_c_set_int_param(&mut self.cinfo, J_INT_PARAM::JINT_DC_SCAN_OPT_MODE, mode as c_int);
394 ffi::jpeg_set_defaults(&mut self.cinfo);
395 }
396 }
397
398 pub fn set_fastest_defaults(&mut self) {
402 unsafe {
403 ffi::jpeg_c_set_int_param(&mut self.cinfo, J_INT_PARAM::JINT_COMPRESS_PROFILE, ffi::JINT_COMPRESS_PROFILE_VALUE::JCP_FASTEST as c_int);
404 ffi::jpeg_set_defaults(&mut self.cinfo);
405 }
406 }
407
408 pub fn set_raw_data_in(&mut self, opt: bool) {
410 self.cinfo.raw_data_in = boolean::from(opt);
411 }
412
413 pub fn set_quality(&mut self, quality: f32) {
415 unsafe {
416 ffi::jpeg_set_quality(&mut self.cinfo, quality as c_int, boolean::from(false));
417 }
418 }
419
420 pub fn set_luma_qtable(&mut self, qtable: &QTable) {
422 unsafe {
423 ffi::jpeg_add_quant_table(&mut self.cinfo, 0, qtable.as_ptr(), 100, 1);
424 }
425 }
426
427 pub fn set_chroma_qtable(&mut self, qtable: &QTable) {
429 unsafe {
430 ffi::jpeg_add_quant_table(&mut self.cinfo, 1, qtable.as_ptr(), 100, 1);
431 }
432 }
433
434 pub fn set_chroma_sampling_pixel_sizes(&mut self, cb: (u8, u8), cr: (u8, u8)) {
442 let max_sampling_h = cb.0.max(cr.0);
443 let max_sampling_v = cb.1.max(cr.1);
444
445 let px_sizes = [(1, 1), cb, cr];
446 for (c, (h, v)) in self.components_mut().iter_mut().zip(px_sizes) {
447 c.h_samp_factor = (max_sampling_h / h).into();
448 c.v_samp_factor = (max_sampling_v / v).into();
449 }
450 }
451}
452
453impl<W: io::Write> CompressStarted<W> {
454 #[inline]
461 pub fn finish(mut self) -> io::Result<W> {
462 unsafe {
463 ffi::jpeg_finish_compress(&mut self.compress.cinfo);
464 }
465 self.compress.cinfo.dest = ptr::null_mut();
466 drop(self.compress);
467 Ok(self.dest_mgr.into_inner())
468 }
469
470 #[doc(hidden)]
471 #[deprecated(note = "use finish(); it now returns a writer given to start_compress()")]
472 pub fn finish_compress(self) -> io::Result<W> {
473 self.finish()
474 }
475
476 #[cold]
478 pub fn abort(mut self) -> W {
479 self.compress.cinfo.dest = ptr::null_mut();
480 self.dest_mgr.into_inner()
481 }
482}
483
484impl Drop for Compress {
485 #[inline]
486 fn drop(&mut self) {
487 unsafe {
488 self.cinfo.dest = ptr::null_mut();
489 ffi::jpeg_destroy_compress(&mut self.cinfo);
490 let _ = Box::from_raw(self.own_err);
492 }
493 }
494}
495
496#[test]
497fn write_mem() {
498 let mut cinfo = Compress::new(ColorSpace::JCS_YCbCr);
499
500 assert_eq!(3, cinfo.components().len());
501
502 cinfo.set_size(17, 33);
503
504 #[allow(deprecated)] {
505 cinfo.set_gamma(1.0);
506 }
507
508 cinfo.set_progressive_mode();
509 cinfo.set_scan_optimization_mode(ScanMode::AllComponentsTogether);
510
511 cinfo.set_raw_data_in(true);
512
513 cinfo.set_quality(88.);
514
515 cinfo.set_chroma_sampling_pixel_sizes((1, 1), (1, 1));
516 for c in cinfo.components() {
517 assert_eq!(c.v_samp_factor, 1);
518 assert_eq!(c.h_samp_factor, 1);
519 }
520
521 cinfo.set_chroma_sampling_pixel_sizes((2, 2), (2, 2));
522 for (c, samp) in cinfo.components().iter().zip([2, 1, 1]) {
523 assert_eq!(c.v_samp_factor, samp);
524 assert_eq!(c.h_samp_factor, samp);
525 }
526
527 let mut cinfo = cinfo.start_compress(Vec::new()).unwrap();
528
529 cinfo.write_marker(Marker::APP(2), b"Hello World");
530
531 assert_eq!(24, cinfo.components()[0].row_stride());
532 assert_eq!(40, cinfo.components()[0].col_stride());
533 assert_eq!(16, cinfo.components()[1].row_stride());
534 assert_eq!(24, cinfo.components()[1].col_stride());
535 assert_eq!(16, cinfo.components()[2].row_stride());
536 assert_eq!(24, cinfo.components()[2].col_stride());
537
538 let bitmaps = cinfo
539 .components()
540 .iter()
541 .map(|c| vec![128u8; c.row_stride() * c.col_stride()])
542 .collect::<Vec<_>>();
543
544 assert!(cinfo.write_raw_data(&bitmaps.iter().map(|c| &c[..]).collect::<Vec<_>>()));
545
546 cinfo.finish().unwrap();
547}
548
549#[test]
550fn convert_colorspace() {
551 let mut cinfo = Compress::new(ColorSpace::JCS_RGB);
552 cinfo.set_color_space(ColorSpace::JCS_GRAYSCALE);
553 assert_eq!(1, cinfo.components().len());
554
555 cinfo.set_size(33, 15);
556 cinfo.set_quality(44.);
557
558 let mut cinfo = cinfo.start_compress(Vec::new()).unwrap();
559
560 let scanlines = vec![127u8; 33*15*3];
561 cinfo.write_scanlines(&scanlines).unwrap();
562
563 let res = cinfo.finish().unwrap();
564 assert!(!res.is_empty());
565}