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