1 | /* |
2 | * Copyright (c) 2023. |
3 | * |
4 | * This software is free software; |
5 | * |
6 | * You can redistribute it or modify it under terms of the MIT, Apache License or Zlib license |
7 | */ |
8 | |
9 | //! Main image logic. |
10 | #![allow (clippy::doc_markdown)] |
11 | |
12 | use alloc::string::ToString; |
13 | use alloc::vec::Vec; |
14 | use alloc::{format, vec}; |
15 | |
16 | use zune_core::bytestream::{ZByteReader, ZReaderTrait}; |
17 | use zune_core::colorspace::ColorSpace; |
18 | use zune_core::log::{error, trace, warn}; |
19 | use zune_core::options::DecoderOptions; |
20 | |
21 | use crate::color_convert::choose_ycbcr_to_rgb_convert_func; |
22 | use crate::components::{Components, SampleRatios}; |
23 | use crate::errors::{DecodeErrors, UnsupportedSchemes}; |
24 | use crate::headers::{ |
25 | parse_app1, parse_app14, parse_app2, parse_dqt, parse_huffman, parse_sos, parse_start_of_frame |
26 | }; |
27 | use crate::huffman::HuffmanTable; |
28 | use crate::idct::choose_idct_func; |
29 | use crate::marker::Marker; |
30 | use crate::misc::SOFMarkers; |
31 | use crate::upsampler::{ |
32 | choose_horizontal_samp_function, choose_hv_samp_function, choose_v_samp_function, |
33 | upsample_no_op |
34 | }; |
35 | |
36 | /// Maximum components |
37 | pub(crate) const MAX_COMPONENTS: usize = 4; |
38 | |
39 | /// Maximum image dimensions supported. |
40 | pub(crate) const MAX_DIMENSIONS: usize = 1 << 27; |
41 | |
42 | /// Color conversion function that can convert YCbCr colorspace to RGB(A/X) for |
43 | /// 16 values |
44 | /// |
45 | /// The following are guarantees to the following functions |
46 | /// |
47 | /// 1. The `&[i16]` slices passed contain 16 items |
48 | /// |
49 | /// 2. The slices passed are in the following order |
50 | /// `y,cb,cr` |
51 | /// |
52 | /// 3. `&mut [u8]` is zero initialized |
53 | /// |
54 | /// 4. `&mut usize` points to the position in the array where new values should |
55 | /// be used |
56 | /// |
57 | /// The pointer should |
58 | /// 1. Carry out color conversion |
59 | /// 2. Update `&mut usize` with the new position |
60 | |
61 | pub type ColorConvert16Ptr = fn(&[i16; 16], &[i16; 16], &[i16; 16], &mut [u8], &mut usize); |
62 | |
63 | /// IDCT function prototype |
64 | /// |
65 | /// This encapsulates a dequantize and IDCT function which will carry out the |
66 | /// following functions |
67 | /// |
68 | /// Multiply each 64 element block of `&mut [i16]` with `&Aligned32<[i32;64]>` |
69 | /// Carry out IDCT (type 3 dct) on ach block of 64 i16's |
70 | pub type IDCTPtr = fn(&mut [i32; 64], &mut [i16], usize); |
71 | |
72 | /// An encapsulation of an ICC chunk |
73 | pub(crate) struct ICCChunk { |
74 | pub(crate) seq_no: u8, |
75 | pub(crate) num_markers: u8, |
76 | pub(crate) data: Vec<u8> |
77 | } |
78 | |
79 | /// A JPEG Decoder Instance. |
80 | #[allow (clippy::upper_case_acronyms, clippy::struct_excessive_bools)] |
81 | pub struct JpegDecoder<T: ZReaderTrait> { |
82 | /// Struct to hold image information from SOI |
83 | pub(crate) info: ImageInfo, |
84 | /// Quantization tables, will be set to none and the tables will |
85 | /// be moved to `components` field |
86 | pub(crate) qt_tables: [Option<[i32; 64]>; MAX_COMPONENTS], |
87 | /// DC Huffman Tables with a maximum of 4 tables for each component |
88 | pub(crate) dc_huffman_tables: [Option<HuffmanTable>; MAX_COMPONENTS], |
89 | /// AC Huffman Tables with a maximum of 4 tables for each component |
90 | pub(crate) ac_huffman_tables: [Option<HuffmanTable>; MAX_COMPONENTS], |
91 | /// Image components, holds information like DC prediction and quantization |
92 | /// tables of a component |
93 | pub(crate) components: Vec<Components>, |
94 | /// maximum horizontal component of all channels in the image |
95 | pub(crate) h_max: usize, |
96 | // maximum vertical component of all channels in the image |
97 | pub(crate) v_max: usize, |
98 | /// mcu's width (interleaved scans) |
99 | pub(crate) mcu_width: usize, |
100 | /// MCU height(interleaved scans |
101 | pub(crate) mcu_height: usize, |
102 | /// Number of MCU's in the x plane |
103 | pub(crate) mcu_x: usize, |
104 | /// Number of MCU's in the y plane |
105 | pub(crate) mcu_y: usize, |
106 | /// Is the image interleaved? |
107 | pub(crate) is_interleaved: bool, |
108 | pub(crate) sub_sample_ratio: SampleRatios, |
109 | /// Image input colorspace, should be YCbCr for a sane image, might be |
110 | /// grayscale too |
111 | pub(crate) input_colorspace: ColorSpace, |
112 | // Progressive image details |
113 | /// Is the image progressive? |
114 | pub(crate) is_progressive: bool, |
115 | |
116 | /// Start of spectral scan |
117 | pub(crate) spec_start: u8, |
118 | /// End of spectral scan |
119 | pub(crate) spec_end: u8, |
120 | /// Successive approximation bit position high |
121 | pub(crate) succ_high: u8, |
122 | /// Successive approximation bit position low |
123 | pub(crate) succ_low: u8, |
124 | /// Number of components. |
125 | pub(crate) num_scans: u8, |
126 | // Function pointers, for pointy stuff. |
127 | /// Dequantize and idct function |
128 | // This is determined at runtime which function to run, statically it's |
129 | // initialized to a platform independent one and during initialization |
130 | // of this struct, we check if we can switch to a faster one which |
131 | // depend on certain CPU extensions. |
132 | pub(crate) idct_func: IDCTPtr, |
133 | // Color convert function which acts on 16 YCbCr values |
134 | pub(crate) color_convert_16: ColorConvert16Ptr, |
135 | pub(crate) z_order: [usize; MAX_COMPONENTS], |
136 | /// restart markers |
137 | pub(crate) restart_interval: usize, |
138 | pub(crate) todo: usize, |
139 | // decoder options |
140 | pub(crate) options: DecoderOptions, |
141 | // byte-stream |
142 | pub(crate) stream: ZByteReader<T>, |
143 | // Indicate whether headers have been decoded |
144 | pub(crate) headers_decoded: bool, |
145 | pub(crate) seen_sof: bool, |
146 | // exif data, lifted from app2 |
147 | pub(crate) exif_data: Option<Vec<u8>>, |
148 | |
149 | pub(crate) icc_data: Vec<ICCChunk>, |
150 | pub(crate) is_mjpeg: bool, |
151 | pub(crate) coeff: usize // Solves some weird bug :) |
152 | } |
153 | |
154 | impl<T> JpegDecoder<T> |
155 | where |
156 | T: ZReaderTrait |
157 | { |
158 | #[allow (clippy::redundant_field_names)] |
159 | fn default(options: DecoderOptions, buffer: T) -> Self { |
160 | let color_convert = choose_ycbcr_to_rgb_convert_func(ColorSpace::RGB, &options).unwrap(); |
161 | JpegDecoder { |
162 | info: ImageInfo::default(), |
163 | qt_tables: [None, None, None, None], |
164 | dc_huffman_tables: [None, None, None, None], |
165 | ac_huffman_tables: [None, None, None, None], |
166 | components: vec![], |
167 | // Interleaved information |
168 | h_max: 1, |
169 | v_max: 1, |
170 | mcu_height: 0, |
171 | mcu_width: 0, |
172 | mcu_x: 0, |
173 | mcu_y: 0, |
174 | is_interleaved: false, |
175 | sub_sample_ratio: SampleRatios::None, |
176 | is_progressive: false, |
177 | spec_start: 0, |
178 | spec_end: 0, |
179 | succ_high: 0, |
180 | succ_low: 0, |
181 | num_scans: 0, |
182 | idct_func: choose_idct_func(&options), |
183 | color_convert_16: color_convert, |
184 | input_colorspace: ColorSpace::YCbCr, |
185 | z_order: [0; MAX_COMPONENTS], |
186 | restart_interval: 0, |
187 | todo: 0x7fff_ffff, |
188 | options: options, |
189 | stream: ZByteReader::new(buffer), |
190 | headers_decoded: false, |
191 | seen_sof: false, |
192 | exif_data: None, |
193 | icc_data: vec![], |
194 | is_mjpeg: false, |
195 | coeff: 1 |
196 | } |
197 | } |
198 | /// Decode a buffer already in memory |
199 | /// |
200 | /// The buffer should be a valid jpeg file, perhaps created by the command |
201 | /// `std:::fs::read()` or a JPEG file downloaded from the internet. |
202 | /// |
203 | /// # Errors |
204 | /// See DecodeErrors for an explanation |
205 | pub fn decode(&mut self) -> Result<Vec<u8>, DecodeErrors> { |
206 | self.decode_headers()?; |
207 | let size = self.output_buffer_size().unwrap(); |
208 | let mut out = vec![0; size]; |
209 | self.decode_into(&mut out)?; |
210 | Ok(out) |
211 | } |
212 | |
213 | /// Create a new Decoder instance |
214 | /// |
215 | /// # Arguments |
216 | /// - `stream`: The raw bytes of a jpeg file. |
217 | #[must_use ] |
218 | #[allow (clippy::new_without_default)] |
219 | pub fn new(stream: T) -> JpegDecoder<T> { |
220 | JpegDecoder::default(DecoderOptions::default(), stream) |
221 | } |
222 | |
223 | /// Returns the image information |
224 | /// |
225 | /// This **must** be called after a subsequent call to [`decode`] or [`decode_headers`] |
226 | /// it will return `None` |
227 | /// |
228 | /// # Returns |
229 | /// - `Some(info)`: Image information,width, height, number of components |
230 | /// - None: Indicates image headers haven't been decoded |
231 | /// |
232 | /// [`decode`]: JpegDecoder::decode |
233 | /// [`decode_headers`]: JpegDecoder::decode_headers |
234 | #[must_use ] |
235 | pub fn info(&self) -> Option<ImageInfo> { |
236 | // we check for fails to that call by comparing what we have to the default, if |
237 | // it's default we assume that the caller failed to uphold the |
238 | // guarantees. We can be sure that an image cannot be the default since |
239 | // its a hard panic in-case width or height are set to zero. |
240 | if !self.headers_decoded { |
241 | return None; |
242 | } |
243 | |
244 | return Some(self.info.clone()); |
245 | } |
246 | |
247 | /// Return the number of bytes required to hold a decoded image frame |
248 | /// decoded using the given input transformations |
249 | /// |
250 | /// # Returns |
251 | /// - `Some(usize)`: Minimum size for a buffer needed to decode the image |
252 | /// - `None`: Indicates the image was not decoded, or image dimensions would overflow a usize |
253 | /// |
254 | #[must_use ] |
255 | pub fn output_buffer_size(&self) -> Option<usize> { |
256 | return if self.headers_decoded { |
257 | Some( |
258 | usize::from(self.width()) |
259 | .checked_mul(usize::from(self.height()))? |
260 | .checked_mul(self.options.jpeg_get_out_colorspace().num_components())? |
261 | ) |
262 | } else { |
263 | None |
264 | }; |
265 | } |
266 | |
267 | /// Get a mutable reference to the decoder options |
268 | /// for the decoder instance |
269 | /// |
270 | /// This can be used to modify options before actual decoding |
271 | /// but after initial creation |
272 | /// |
273 | /// # Example |
274 | /// ```no_run |
275 | /// use zune_jpeg::JpegDecoder; |
276 | /// |
277 | /// let mut decoder = JpegDecoder::new(&[]); |
278 | /// // get current options |
279 | /// let mut options = decoder.get_options(); |
280 | /// // modify it |
281 | /// let new_options = options.set_max_width(10); |
282 | /// // set it back |
283 | /// decoder.set_options(new_options); |
284 | /// |
285 | /// ``` |
286 | #[must_use ] |
287 | pub const fn get_options(&self) -> &DecoderOptions { |
288 | &self.options |
289 | } |
290 | /// Return the input colorspace of the image |
291 | /// |
292 | /// This indicates the colorspace that is present in |
293 | /// the image, but this may be different to the colorspace that |
294 | /// the output will be transformed to |
295 | /// |
296 | /// # Returns |
297 | /// -`Some(Colorspace)`: Input colorspace |
298 | /// - None : Indicates the headers weren't decoded |
299 | #[must_use ] |
300 | pub fn get_input_colorspace(&self) -> Option<ColorSpace> { |
301 | return if self.headers_decoded { Some(self.input_colorspace) } else { None }; |
302 | } |
303 | /// Set decoder options |
304 | /// |
305 | /// This can be used to set new options even after initialization |
306 | /// but before decoding. |
307 | /// |
308 | /// This does not bear any significance after decoding an image |
309 | /// |
310 | /// # Arguments |
311 | /// - `options`: New decoder options |
312 | /// |
313 | /// # Example |
314 | /// Set maximum jpeg progressive passes to be 4 |
315 | /// |
316 | /// ```no_run |
317 | /// use zune_jpeg::JpegDecoder; |
318 | /// let mut decoder =JpegDecoder::new(&[]); |
319 | /// // this works also because DecoderOptions implements `Copy` |
320 | /// let options = decoder.get_options().jpeg_set_max_scans(4); |
321 | /// // set the new options |
322 | /// decoder.set_options(options); |
323 | /// // now decode |
324 | /// decoder.decode().unwrap(); |
325 | /// ``` |
326 | pub fn set_options(&mut self, options: DecoderOptions) { |
327 | self.options = options; |
328 | } |
329 | /// Decode Decoder headers |
330 | /// |
331 | /// This routine takes care of parsing supported headers from a Decoder |
332 | /// image |
333 | /// |
334 | /// # Supported Headers |
335 | /// - APP(0) |
336 | /// - SOF(O) |
337 | /// - DQT -> Quantization tables |
338 | /// - DHT -> Huffman tables |
339 | /// - SOS -> Start of Scan |
340 | /// # Unsupported Headers |
341 | /// - SOF(n) -> Decoder images which are not baseline/progressive |
342 | /// - DAC -> Images using Arithmetic tables |
343 | /// - JPG(n) |
344 | fn decode_headers_internal(&mut self) -> Result<(), DecodeErrors> { |
345 | if self.headers_decoded { |
346 | trace!("Headers decoded!" ); |
347 | return Ok(()); |
348 | } |
349 | // match output colorspace here |
350 | // we know this will only be called once per image |
351 | // so makes sense |
352 | // We only care for ycbcr to rgb/rgba here |
353 | // in case one is using another colorspace. |
354 | // May god help you |
355 | let out_colorspace = self.options.jpeg_get_out_colorspace(); |
356 | |
357 | if matches!( |
358 | out_colorspace, |
359 | ColorSpace::BGR | ColorSpace::BGRA | ColorSpace::RGB | ColorSpace::RGBA |
360 | ) { |
361 | self.color_convert_16 = choose_ycbcr_to_rgb_convert_func( |
362 | self.options.jpeg_get_out_colorspace(), |
363 | &self.options |
364 | ) |
365 | .unwrap(); |
366 | } |
367 | // First two bytes should be jpeg soi marker |
368 | let magic_bytes = self.stream.get_u16_be_err()?; |
369 | |
370 | let mut last_byte = 0; |
371 | let mut bytes_before_marker = 0; |
372 | |
373 | if magic_bytes != 0xffd8 { |
374 | return Err(DecodeErrors::IllegalMagicBytes(magic_bytes)); |
375 | } |
376 | |
377 | loop { |
378 | // read a byte |
379 | let mut m = self.stream.get_u8_err()?; |
380 | |
381 | // AND OF COURSE some images will have fill bytes in their marker |
382 | // bitstreams because why not. |
383 | // |
384 | // I am disappointed as a man. |
385 | if (m == 0xFF || m == 0) && last_byte == 0xFF { |
386 | // This handles the edge case where |
387 | // images have markers with fill bytes(0xFF) |
388 | // or byte stuffing (0) |
389 | // I.e 0xFF 0xFF 0xDA |
390 | // and |
391 | // 0xFF 0 0xDA |
392 | // It should ignore those fill bytes and take 0xDA |
393 | // I don't know why such images exist |
394 | // but they do. |
395 | // so this is for you (with love) |
396 | while m == 0xFF || m == 0x0 { |
397 | last_byte = m; |
398 | m = self.stream.get_u8_err()?; |
399 | } |
400 | } |
401 | // Last byte should be 0xFF to confirm existence of a marker since markers look |
402 | // like OxFF(some marker data) |
403 | if last_byte == 0xFF { |
404 | let marker = Marker::from_u8(m); |
405 | if let Some(n) = marker { |
406 | if bytes_before_marker > 3 { |
407 | if self.options.get_strict_mode() |
408 | /*No reason to use this*/ |
409 | { |
410 | return Err(DecodeErrors::FormatStatic( |
411 | "[strict-mode]: Extra bytes between headers" |
412 | )); |
413 | } |
414 | |
415 | error!( |
416 | "Extra bytes {} before marker 0xFF{:X}" , |
417 | bytes_before_marker - 3, |
418 | m |
419 | ); |
420 | } |
421 | |
422 | bytes_before_marker = 0; |
423 | |
424 | self.parse_marker_inner(n)?; |
425 | |
426 | if n == Marker::SOS { |
427 | self.headers_decoded = true; |
428 | trace!("Input colorspace {:?}" , self.input_colorspace); |
429 | return Ok(()); |
430 | } |
431 | } else { |
432 | bytes_before_marker = 0; |
433 | |
434 | warn!("Marker 0xFF{:X} not known" , m); |
435 | |
436 | let length = self.stream.get_u16_be_err()?; |
437 | |
438 | if length < 2 { |
439 | return Err(DecodeErrors::Format(format!( |
440 | "Found a marker with invalid length : {length}" |
441 | ))); |
442 | } |
443 | |
444 | warn!("Skipping {} bytes" , length - 2); |
445 | self.stream.skip((length - 2) as usize); |
446 | } |
447 | } |
448 | last_byte = m; |
449 | bytes_before_marker += 1; |
450 | } |
451 | } |
452 | #[allow (clippy::too_many_lines)] |
453 | pub(crate) fn parse_marker_inner(&mut self, m: Marker) -> Result<(), DecodeErrors> { |
454 | match m { |
455 | Marker::SOF(0..=2) => { |
456 | let marker = { |
457 | // choose marker |
458 | if m == Marker::SOF(0) || m == Marker::SOF(1) { |
459 | SOFMarkers::BaselineDct |
460 | } else { |
461 | self.is_progressive = true; |
462 | SOFMarkers::ProgressiveDctHuffman |
463 | } |
464 | }; |
465 | |
466 | trace!("Image encoding scheme =`{:?}`" , marker); |
467 | // get components |
468 | parse_start_of_frame(marker, self)?; |
469 | } |
470 | // Start of Frame Segments not supported |
471 | Marker::SOF(v) => { |
472 | let feature = UnsupportedSchemes::from_int(v); |
473 | |
474 | if let Some(feature) = feature { |
475 | return Err(DecodeErrors::Unsupported(feature)); |
476 | } |
477 | |
478 | return Err(DecodeErrors::Format("Unsupported image format" .to_string())); |
479 | } |
480 | //APP(0) segment |
481 | Marker::APP(0) => { |
482 | let mut length = self.stream.get_u16_be_err()?; |
483 | |
484 | if length < 2 { |
485 | return Err(DecodeErrors::Format(format!( |
486 | "Found a marker with invalid length: {length}\n" |
487 | ))); |
488 | } |
489 | // skip for now |
490 | if length > 5 && self.stream.has(5) { |
491 | let mut buffer = [0u8; 5]; |
492 | self.stream.read_exact(&mut buffer).unwrap(); |
493 | if &buffer == b"AVI1 \0" { |
494 | self.is_mjpeg = true; |
495 | } |
496 | length -= 5; |
497 | } |
498 | self.stream.skip(length.saturating_sub(2) as usize); |
499 | |
500 | //parse_app(buf, m, &mut self.info)?; |
501 | } |
502 | Marker::APP(1) => { |
503 | parse_app1(self)?; |
504 | } |
505 | |
506 | Marker::APP(2) => { |
507 | parse_app2(self)?; |
508 | } |
509 | // Quantization tables |
510 | Marker::DQT => { |
511 | parse_dqt(self)?; |
512 | } |
513 | // Huffman tables |
514 | Marker::DHT => { |
515 | parse_huffman(self)?; |
516 | } |
517 | // Start of Scan Data |
518 | Marker::SOS => { |
519 | parse_sos(self)?; |
520 | |
521 | // break after reading the start of scan. |
522 | // what follows is the image data |
523 | return Ok(()); |
524 | } |
525 | Marker::EOI => return Err(DecodeErrors::FormatStatic("Premature End of image" )), |
526 | |
527 | Marker::DAC | Marker::DNL => { |
528 | return Err(DecodeErrors::Format(format!( |
529 | "Parsing of the following header ` {m:?}` is not supported,\ |
530 | cannot continue" |
531 | ))); |
532 | } |
533 | Marker::DRI => { |
534 | trace!("DRI marker present" ); |
535 | |
536 | if self.stream.get_u16_be_err()? != 4 { |
537 | return Err(DecodeErrors::Format( |
538 | "Bad DRI length, Corrupt JPEG" .to_string() |
539 | )); |
540 | } |
541 | |
542 | self.restart_interval = usize::from(self.stream.get_u16_be_err()?); |
543 | self.todo = self.restart_interval; |
544 | } |
545 | Marker::APP(14) => { |
546 | parse_app14(self)?; |
547 | } |
548 | _ => { |
549 | warn!( |
550 | "Capabilities for processing marker \"{:?} \" not implemented" , |
551 | m |
552 | ); |
553 | |
554 | let length = self.stream.get_u16_be_err()?; |
555 | |
556 | if length < 2 { |
557 | return Err(DecodeErrors::Format(format!( |
558 | "Found a marker with invalid length: {length}\n" |
559 | ))); |
560 | } |
561 | warn!("Skipping {} bytes" , length - 2); |
562 | self.stream.skip((length - 2) as usize); |
563 | } |
564 | } |
565 | Ok(()) |
566 | } |
567 | /// Get the embedded ICC profile if it exists |
568 | /// and is correct |
569 | /// |
570 | /// One needs not to decode the whole image to extract this, |
571 | /// calling [`decode_headers`] for an image with an ICC profile |
572 | /// allows you to decode this |
573 | /// |
574 | /// # Returns |
575 | /// - `Some(Vec<u8>)`: The raw ICC profile of the image |
576 | /// - `None`: May indicate an error in the ICC profile , non-existence of |
577 | /// an ICC profile, or that the headers weren't decoded. |
578 | /// |
579 | /// [`decode_headers`]:Self::decode_headers |
580 | #[must_use ] |
581 | pub fn icc_profile(&self) -> Option<Vec<u8>> { |
582 | let mut marker_present: [Option<&ICCChunk>; 256] = [None; 256]; |
583 | |
584 | if !self.headers_decoded { |
585 | return None; |
586 | } |
587 | let num_markers = self.icc_data.len(); |
588 | |
589 | if num_markers == 0 || num_markers >= 255 { |
590 | return None; |
591 | } |
592 | // check validity |
593 | for chunk in &self.icc_data { |
594 | if usize::from(chunk.num_markers) != num_markers { |
595 | // all the lengths must match |
596 | return None; |
597 | } |
598 | if chunk.seq_no == 0 { |
599 | warn!("Zero sequence number in ICC, corrupt ICC chunk" ); |
600 | return None; |
601 | } |
602 | if marker_present[usize::from(chunk.seq_no)].is_some() { |
603 | // duplicate seq_no |
604 | warn!("Duplicate sequence number in ICC, corrupt chunk" ); |
605 | return None; |
606 | } |
607 | |
608 | marker_present[usize::from(chunk.seq_no)] = Some(chunk); |
609 | } |
610 | let mut data = Vec::with_capacity(1000); |
611 | // assemble the data now |
612 | for chunk in marker_present.get(1..=num_markers).unwrap() { |
613 | if let Some(ch) = chunk { |
614 | data.extend_from_slice(&ch.data); |
615 | } else { |
616 | warn!("Missing icc sequence number, corrupt ICC chunk " ); |
617 | return None; |
618 | } |
619 | } |
620 | |
621 | Some(data) |
622 | } |
623 | /// Return the exif data for the file |
624 | /// |
625 | /// This returns the raw exif data starting at the |
626 | /// TIFF header |
627 | /// |
628 | /// # Returns |
629 | /// -`Some(data)`: The raw exif data, if present in the image |
630 | /// - None: May indicate the following |
631 | /// |
632 | /// 1. The image doesn't have exif data |
633 | /// 2. The image headers haven't been decoded |
634 | #[must_use ] |
635 | pub fn exif(&self) -> Option<&Vec<u8>> { |
636 | return self.exif_data.as_ref(); |
637 | } |
638 | /// Get the output colorspace the image pixels will be decoded into |
639 | /// |
640 | /// |
641 | /// # Note. |
642 | /// This field can only be regarded after decoding headers, |
643 | /// as markers such as Adobe APP14 may dictate different colorspaces |
644 | /// than requested. |
645 | /// |
646 | /// Calling `decode_headers` is sufficient to know what colorspace the |
647 | /// output is, if this is called after `decode` it indicates the colorspace |
648 | /// the output is currently in |
649 | /// |
650 | /// Additionally not all input->output colorspace mappings are supported |
651 | /// but all input colorspaces can map to RGB colorspace, so that's a safe bet |
652 | /// if one is handling image formats |
653 | /// |
654 | ///# Returns |
655 | /// - `Some(Colorspace)`: If headers have been decoded, the colorspace the |
656 | ///output array will be in |
657 | ///- `None |
658 | #[must_use ] |
659 | pub fn get_output_colorspace(&self) -> Option<ColorSpace> { |
660 | return if self.headers_decoded { |
661 | Some(self.options.jpeg_get_out_colorspace()) |
662 | } else { |
663 | None |
664 | }; |
665 | } |
666 | |
667 | /// Decode into a pre-allocated buffer |
668 | /// |
669 | /// It is an error if the buffer size is smaller than |
670 | /// [`output_buffer_size()`](Self::output_buffer_size) |
671 | /// |
672 | /// If the buffer is bigger than expected, we ignore the end padding bytes |
673 | /// |
674 | /// # Example |
675 | /// |
676 | /// - Read headers and then alloc a buffer big enough to hold the image |
677 | /// |
678 | /// ```no_run |
679 | /// use zune_jpeg::JpegDecoder; |
680 | /// let mut decoder = JpegDecoder::new(&[]); |
681 | /// // before we get output, we must decode the headers to get width |
682 | /// // height, and input colorspace |
683 | /// decoder.decode_headers().unwrap(); |
684 | /// |
685 | /// let mut out = vec![0;decoder.output_buffer_size().unwrap()]; |
686 | /// // write into out |
687 | /// decoder.decode_into(&mut out).unwrap(); |
688 | /// ``` |
689 | /// |
690 | /// |
691 | pub fn decode_into(&mut self, out: &mut [u8]) -> Result<(), DecodeErrors> { |
692 | self.decode_headers_internal()?; |
693 | |
694 | let expected_size = self.output_buffer_size().unwrap(); |
695 | |
696 | if out.len() < expected_size { |
697 | // too small of a size |
698 | return Err(DecodeErrors::TooSmallOutput(expected_size, out.len())); |
699 | } |
700 | |
701 | // ensure we don't touch anyone else's scratch space |
702 | let out_len = core::cmp::min(out.len(), expected_size); |
703 | let out = &mut out[0..out_len]; |
704 | |
705 | if self.is_progressive { |
706 | self.decode_mcu_ycbcr_progressive(out) |
707 | } else { |
708 | self.decode_mcu_ycbcr_baseline(out) |
709 | } |
710 | } |
711 | |
712 | /// Read only headers from a jpeg image buffer |
713 | /// |
714 | /// This allows you to extract important information like |
715 | /// image width and height without decoding the full image |
716 | /// |
717 | /// # Examples |
718 | /// ```no_run |
719 | /// use zune_jpeg::{JpegDecoder}; |
720 | /// |
721 | /// let img_data = std::fs::read("a_valid.jpeg" ).unwrap(); |
722 | /// let mut decoder = JpegDecoder::new(&img_data); |
723 | /// decoder.decode_headers().unwrap(); |
724 | /// |
725 | /// println!("Total decoder dimensions are : {:?} pixels" ,decoder.dimensions()); |
726 | /// println!("Number of components in the image are {}" , decoder.info().unwrap().components); |
727 | /// ``` |
728 | /// # Errors |
729 | /// See DecodeErrors enum for list of possible errors during decoding |
730 | pub fn decode_headers(&mut self) -> Result<(), DecodeErrors> { |
731 | self.decode_headers_internal()?; |
732 | Ok(()) |
733 | } |
734 | /// Create a new decoder with the specified options to be used for decoding |
735 | /// an image |
736 | /// |
737 | /// # Arguments |
738 | /// - `buf`: The input buffer from where we will pull in compressed jpeg bytes from |
739 | /// - `options`: Options specific to this decoder instance |
740 | #[must_use ] |
741 | pub fn new_with_options(buf: T, options: DecoderOptions) -> JpegDecoder<T> { |
742 | JpegDecoder::default(options, buf) |
743 | } |
744 | |
745 | /// Set up-sampling routines in case an image is down sampled |
746 | pub(crate) fn set_upsampling(&mut self) -> Result<(), DecodeErrors> { |
747 | // no sampling, return early |
748 | // check if horizontal max ==1 |
749 | if self.h_max == self.v_max && self.h_max == 1 { |
750 | return Ok(()); |
751 | } |
752 | match (self.h_max, self.v_max) { |
753 | (1, 1) => { |
754 | self.sub_sample_ratio = SampleRatios::None; |
755 | } |
756 | (1, 2) => { |
757 | self.sub_sample_ratio = SampleRatios::V; |
758 | } |
759 | (2, 1) => { |
760 | self.sub_sample_ratio = SampleRatios::H; |
761 | } |
762 | (2, 2) => { |
763 | self.sub_sample_ratio = SampleRatios::HV; |
764 | } |
765 | _ => { |
766 | return Err(DecodeErrors::Format( |
767 | "Unknown down-sampling method, cannot continue" .to_string() |
768 | )) |
769 | } |
770 | } |
771 | |
772 | for comp in self.components.iter_mut() { |
773 | let hs = self.h_max / comp.horizontal_sample; |
774 | let vs = self.v_max / comp.vertical_sample; |
775 | |
776 | let samp_factor = match (hs, vs) { |
777 | (1, 1) => { |
778 | comp.sample_ratio = SampleRatios::None; |
779 | upsample_no_op |
780 | } |
781 | (2, 1) => { |
782 | comp.sample_ratio = SampleRatios::H; |
783 | choose_horizontal_samp_function(self.options.get_use_unsafe()) |
784 | } |
785 | (1, 2) => { |
786 | comp.sample_ratio = SampleRatios::V; |
787 | choose_v_samp_function(self.options.get_use_unsafe()) |
788 | } |
789 | (2, 2) => { |
790 | comp.sample_ratio = SampleRatios::HV; |
791 | choose_hv_samp_function(self.options.get_use_unsafe()) |
792 | } |
793 | _ => { |
794 | return Err(DecodeErrors::Format( |
795 | "Unknown down-sampling method, cannot continue" .to_string() |
796 | )) |
797 | } |
798 | }; |
799 | comp.setup_upsample_scanline(); |
800 | comp.up_sampler = samp_factor; |
801 | } |
802 | |
803 | return Ok(()); |
804 | } |
805 | #[must_use ] |
806 | /// Get the width of the image as a u16 |
807 | /// |
808 | /// The width lies between 1 and 65535 |
809 | pub(crate) fn width(&self) -> u16 { |
810 | self.info.width |
811 | } |
812 | |
813 | /// Get the height of the image as a u16 |
814 | /// |
815 | /// The height lies between 1 and 65535 |
816 | #[must_use ] |
817 | pub(crate) fn height(&self) -> u16 { |
818 | self.info.height |
819 | } |
820 | |
821 | /// Get image dimensions as a tuple of width and height |
822 | /// or `None` if the image hasn't been decoded. |
823 | /// |
824 | /// # Returns |
825 | /// - `Some(width,height)`: Image dimensions |
826 | /// - None : The image headers haven't been decoded |
827 | #[must_use ] |
828 | pub const fn dimensions(&self) -> Option<(usize, usize)> { |
829 | return if self.headers_decoded { |
830 | Some((self.info.width as usize, self.info.height as usize)) |
831 | } else { |
832 | None |
833 | }; |
834 | } |
835 | } |
836 | |
837 | /// A struct representing Image Information |
838 | #[derive (Default, Clone, Eq, PartialEq)] |
839 | #[allow (clippy::module_name_repetitions)] |
840 | pub struct ImageInfo { |
841 | /// Width of the image |
842 | pub width: u16, |
843 | /// Height of image |
844 | pub height: u16, |
845 | /// PixelDensity |
846 | pub pixel_density: u8, |
847 | /// Start of frame markers |
848 | pub sof: SOFMarkers, |
849 | /// Horizontal sample |
850 | pub x_density: u16, |
851 | /// Vertical sample |
852 | pub y_density: u16, |
853 | /// Number of components |
854 | pub components: u8 |
855 | } |
856 | |
857 | impl ImageInfo { |
858 | /// Set width of the image |
859 | /// |
860 | /// Found in the start of frame |
861 | |
862 | pub(crate) fn set_width(&mut self, width: u16) { |
863 | self.width = width; |
864 | } |
865 | |
866 | /// Set height of the image |
867 | /// |
868 | /// Found in the start of frame |
869 | |
870 | pub(crate) fn set_height(&mut self, height: u16) { |
871 | self.height = height; |
872 | } |
873 | |
874 | /// Set the image density |
875 | /// |
876 | /// Found in the start of frame |
877 | |
878 | pub(crate) fn set_density(&mut self, density: u8) { |
879 | self.pixel_density = density; |
880 | } |
881 | |
882 | /// Set image Start of frame marker |
883 | /// |
884 | /// found in the Start of frame header |
885 | |
886 | pub(crate) fn set_sof_marker(&mut self, marker: SOFMarkers) { |
887 | self.sof = marker; |
888 | } |
889 | |
890 | /// Set image x-density(dots per pixel) |
891 | /// |
892 | /// Found in the APP(0) marker |
893 | #[allow (dead_code)] |
894 | pub(crate) fn set_x(&mut self, sample: u16) { |
895 | self.x_density = sample; |
896 | } |
897 | |
898 | /// Set image y-density |
899 | /// |
900 | /// Found in the APP(0) marker |
901 | #[allow (dead_code)] |
902 | pub(crate) fn set_y(&mut self, sample: u16) { |
903 | self.y_density = sample; |
904 | } |
905 | } |
906 | |