Skip to main content

ff_decode/image/
decoder_inner.rs

1//! Internal image decoder implementation using FFmpeg.
2//!
3//! This module contains the low-level decoder logic that directly interacts
4//! with FFmpeg's C API through the ff-sys crate. It is not exposed publicly.
5
6// Allow unsafe code in this module as it's necessary for FFmpeg FFI
7#![allow(unsafe_code)]
8// Allow specific clippy lints for FFmpeg FFI code
9#![allow(clippy::similar_names)]
10#![allow(clippy::too_many_lines)]
11#![allow(clippy::cast_sign_loss)]
12#![allow(clippy::cast_possible_truncation)]
13#![allow(clippy::cast_possible_wrap)]
14#![allow(clippy::module_name_repetitions)]
15#![allow(clippy::ptr_as_ptr)]
16#![allow(clippy::doc_markdown)]
17#![allow(clippy::unnecessary_cast)]
18#![allow(clippy::cast_precision_loss)]
19#![allow(clippy::cast_lossless)]
20
21use std::path::Path;
22use std::ptr;
23
24use ff_format::time::{Rational, Timestamp};
25use ff_format::{PixelFormat, PooledBuffer, VideoFrame};
26use ff_sys::{
27    AVCodecContext, AVCodecID, AVFormatContext, AVFrame, AVMediaType_AVMEDIA_TYPE_VIDEO, AVPacket,
28    AVPixelFormat,
29};
30
31use crate::error::DecodeError;
32
33// ── RAII guards ────────────────────────────────────────────────────────────────
34
35/// RAII guard for `AVFormatContext` to ensure proper cleanup.
36struct AvFormatContextGuard(*mut AVFormatContext);
37
38impl AvFormatContextGuard {
39    unsafe fn new(path: &Path) -> Result<Self, DecodeError> {
40        // SAFETY: Caller ensures FFmpeg is initialized and path is valid
41        let format_ctx = unsafe {
42            ff_sys::avformat::open_input(path).map_err(|e| {
43                DecodeError::Ffmpeg(format!(
44                    "Failed to open file: {}",
45                    ff_sys::av_error_string(e)
46                ))
47            })?
48        };
49        Ok(Self(format_ctx))
50    }
51
52    const fn as_ptr(&self) -> *mut AVFormatContext {
53        self.0
54    }
55
56    fn into_raw(self) -> *mut AVFormatContext {
57        let ptr = self.0;
58        std::mem::forget(self);
59        ptr
60    }
61}
62
63impl Drop for AvFormatContextGuard {
64    fn drop(&mut self) {
65        if !self.0.is_null() {
66            // SAFETY: self.0 is valid and owned by this guard
67            unsafe {
68                ff_sys::avformat::close_input(&mut (self.0 as *mut _));
69            }
70        }
71    }
72}
73
74/// RAII guard for `AVCodecContext` to ensure proper cleanup.
75struct AvCodecContextGuard(*mut AVCodecContext);
76
77impl AvCodecContextGuard {
78    unsafe fn new(codec: *const ff_sys::AVCodec) -> Result<Self, DecodeError> {
79        // SAFETY: Caller ensures codec pointer is valid
80        let codec_ctx = unsafe {
81            ff_sys::avcodec::alloc_context3(codec).map_err(|e| {
82                DecodeError::Ffmpeg(format!("Failed to allocate codec context: {e}"))
83            })?
84        };
85        Ok(Self(codec_ctx))
86    }
87
88    const fn as_ptr(&self) -> *mut AVCodecContext {
89        self.0
90    }
91
92    fn into_raw(self) -> *mut AVCodecContext {
93        let ptr = self.0;
94        std::mem::forget(self);
95        ptr
96    }
97}
98
99impl Drop for AvCodecContextGuard {
100    fn drop(&mut self) {
101        if !self.0.is_null() {
102            // SAFETY: self.0 is valid and owned by this guard
103            unsafe {
104                ff_sys::avcodec::free_context(&mut (self.0 as *mut _));
105            }
106        }
107    }
108}
109
110// ── ImageDecoderInner ─────────────────────────────────────────────────────────
111
112/// Internal state for the image decoder.
113///
114/// Holds raw FFmpeg pointers and is responsible for proper cleanup in `Drop`.
115pub(crate) struct ImageDecoderInner {
116    /// Format context for reading the image file.
117    format_ctx: *mut AVFormatContext,
118    /// Codec context for decoding the image.
119    codec_ctx: *mut AVCodecContext,
120    /// Video stream index in the format context.
121    stream_index: usize,
122    /// Reusable packet for reading from file.
123    packet: *mut AVPacket,
124    /// Reusable frame for decoding.
125    frame: *mut AVFrame,
126}
127
128// SAFETY: `ImageDecoderInner` owns all FFmpeg contexts exclusively.
129//         FFmpeg contexts are not safe for concurrent access (not Sync),
130//         but ownership transfer between threads is safe.
131unsafe impl Send for ImageDecoderInner {}
132
133impl ImageDecoderInner {
134    /// Opens an image file and prepares the decoder.
135    ///
136    /// Performs the full FFmpeg initialization sequence:
137    /// 1. `avformat_open_input`
138    /// 2. `avformat_find_stream_info`
139    /// 3. `av_find_best_stream(AVMEDIA_TYPE_VIDEO)`
140    /// 4. `avcodec_find_decoder`
141    /// 5. `avcodec_alloc_context3`
142    /// 6. `avcodec_parameters_to_context`
143    /// 7. `avcodec_open2`
144    pub(crate) fn new(path: &Path) -> Result<Self, DecodeError> {
145        ff_sys::ensure_initialized();
146
147        // 1. avformat_open_input
148        // SAFETY: Path is valid; AvFormatContextGuard ensures cleanup on error.
149        let format_ctx_guard = unsafe { AvFormatContextGuard::new(path)? };
150        let format_ctx = format_ctx_guard.as_ptr();
151
152        // 2. avformat_find_stream_info
153        // SAFETY: format_ctx is valid and owned by the guard.
154        unsafe {
155            ff_sys::avformat::find_stream_info(format_ctx).map_err(|e| {
156                DecodeError::Ffmpeg(format!(
157                    "Failed to find stream info: {}",
158                    ff_sys::av_error_string(e)
159                ))
160            })?;
161        }
162
163        // 3. Find the video stream.
164        // SAFETY: format_ctx is valid.
165        let (stream_index, codec_id) =
166            unsafe { Self::find_video_stream(format_ctx) }.ok_or_else(|| {
167                DecodeError::NoVideoStream {
168                    path: path.to_path_buf(),
169                }
170            })?;
171
172        // 4. avcodec_find_decoder
173        // SAFETY: codec_id comes from FFmpeg.
174        let codec = unsafe {
175            ff_sys::avcodec::find_decoder(codec_id).ok_or_else(|| {
176                DecodeError::UnsupportedCodec {
177                    codec: format!("codec_id={codec_id:?}"),
178                }
179            })?
180        };
181
182        // 5. avcodec_alloc_context3
183        // SAFETY: codec pointer is valid; AvCodecContextGuard ensures cleanup.
184        let codec_ctx_guard = unsafe { AvCodecContextGuard::new(codec)? };
185        let codec_ctx = codec_ctx_guard.as_ptr();
186
187        // 6. avcodec_parameters_to_context
188        // SAFETY: All pointers are valid; stream_index was validated above.
189        unsafe {
190            let stream = (*format_ctx).streams.add(stream_index);
191            let codecpar = (*(*stream)).codecpar;
192            ff_sys::avcodec::parameters_to_context(codec_ctx, codecpar).map_err(|e| {
193                DecodeError::Ffmpeg(format!(
194                    "Failed to copy codec parameters: {}",
195                    ff_sys::av_error_string(e)
196                ))
197            })?;
198        }
199
200        // 7. avcodec_open2
201        // SAFETY: codec_ctx and codec are valid; no hardware acceleration for images.
202        unsafe {
203            ff_sys::avcodec::open2(codec_ctx, codec, ptr::null_mut()).map_err(|e| {
204                DecodeError::Ffmpeg(format!(
205                    "Failed to open codec: {}",
206                    ff_sys::av_error_string(e)
207                ))
208            })?;
209        }
210
211        // Allocate packet and frame.
212        // SAFETY: FFmpeg is initialized.
213        let packet = unsafe { ff_sys::av_packet_alloc() };
214        if packet.is_null() {
215            return Err(DecodeError::Ffmpeg("Failed to allocate packet".to_string()));
216        }
217        let frame = unsafe { ff_sys::av_frame_alloc() };
218        if frame.is_null() {
219            unsafe { ff_sys::av_packet_free(&mut (packet as *mut _)) };
220            return Err(DecodeError::Ffmpeg("Failed to allocate frame".to_string()));
221        }
222
223        Ok(Self {
224            format_ctx: format_ctx_guard.into_raw(),
225            codec_ctx: codec_ctx_guard.into_raw(),
226            stream_index,
227            packet,
228            frame,
229        })
230    }
231
232    /// Returns the image width in pixels.
233    pub(crate) fn width(&self) -> u32 {
234        // SAFETY: codec_ctx is valid for the lifetime of `self`.
235        unsafe { (*self.codec_ctx).width as u32 }
236    }
237
238    /// Returns the image height in pixels.
239    pub(crate) fn height(&self) -> u32 {
240        // SAFETY: codec_ctx is valid for the lifetime of `self`.
241        unsafe { (*self.codec_ctx).height as u32 }
242    }
243
244    /// Decodes the image, consuming `self` and returning a [`VideoFrame`].
245    ///
246    /// Follows the sequence:
247    /// 1. `av_read_frame`
248    /// 2. `avcodec_send_packet`
249    /// 3. `avcodec_receive_frame`
250    /// 4. Convert to [`VideoFrame`]
251    pub(crate) fn decode(self) -> Result<VideoFrame, DecodeError> {
252        // 1. av_read_frame
253        // SAFETY: format_ctx and packet are valid.
254        let ret = unsafe { ff_sys::av_read_frame(self.format_ctx, self.packet) };
255        if ret < 0 {
256            return Err(DecodeError::Ffmpeg(format!(
257                "Failed to read frame: {}",
258                ff_sys::av_error_string(ret)
259            )));
260        }
261
262        // 2. avcodec_send_packet
263        // SAFETY: codec_ctx and packet are valid; packet contains image data.
264        let ret = unsafe { ff_sys::avcodec_send_packet(self.codec_ctx, self.packet) };
265        unsafe { ff_sys::av_packet_unref(self.packet) };
266        if ret < 0 {
267            return Err(DecodeError::Ffmpeg(format!(
268                "Failed to send packet to decoder: {}",
269                ff_sys::av_error_string(ret)
270            )));
271        }
272
273        // 3. avcodec_receive_frame
274        // SAFETY: codec_ctx and frame are valid.
275        let ret = unsafe { ff_sys::avcodec_receive_frame(self.codec_ctx, self.frame) };
276        if ret < 0 {
277            return Err(DecodeError::Ffmpeg(format!(
278                "Failed to receive decoded frame: {}",
279                ff_sys::av_error_string(ret)
280            )));
281        }
282
283        // 4. Convert to VideoFrame.
284        // SAFETY: frame is valid and contains decoded image data.
285        let video_frame = unsafe { self.av_frame_to_video_frame(self.frame)? };
286        Ok(video_frame)
287    }
288
289    /// Finds the first video stream in the format context.
290    ///
291    /// # Safety
292    ///
293    /// `format_ctx` must be a valid, fully initialized `AVFormatContext`.
294    unsafe fn find_video_stream(format_ctx: *mut AVFormatContext) -> Option<(usize, AVCodecID)> {
295        // SAFETY: Caller ensures format_ctx is valid.
296        unsafe {
297            let nb_streams = (*format_ctx).nb_streams as usize;
298            for i in 0..nb_streams {
299                let stream = (*format_ctx).streams.add(i);
300                let codecpar = (*(*stream)).codecpar;
301                if (*codecpar).codec_type == AVMediaType_AVMEDIA_TYPE_VIDEO {
302                    return Some((i, (*codecpar).codec_id));
303                }
304            }
305        }
306        None
307    }
308
309    /// Maps an `AVPixelFormat` value to our [`PixelFormat`] enum.
310    ///
311    /// Image decoders commonly produce YUVJ formats (full-range YUV), which
312    /// have the same plane layout as the corresponding YUV formats but with a
313    /// different color range flag.  We map them to their YUV equivalents here
314    /// and rely on the colour-range metadata to distinguish them if needed.
315    fn convert_pixel_format(fmt: AVPixelFormat) -> PixelFormat {
316        if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUV420P
317            || fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUVJ420P
318        {
319            PixelFormat::Yuv420p
320        } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUV422P
321            || fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUVJ422P
322        {
323            PixelFormat::Yuv422p
324        } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUV444P
325            || fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_YUVJ444P
326        {
327            PixelFormat::Yuv444p
328        } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_RGB24 {
329            PixelFormat::Rgb24
330        } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_BGR24 {
331            PixelFormat::Bgr24
332        } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_RGBA {
333            PixelFormat::Rgba
334        } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_BGRA {
335            PixelFormat::Bgra
336        } else if fmt == ff_sys::AVPixelFormat_AV_PIX_FMT_GRAY8 {
337            PixelFormat::Gray8
338        } else {
339            log::warn!(
340                "pixel_format unsupported, falling back to Rgb24 requested={fmt} fallback=Rgb24"
341            );
342            PixelFormat::Rgb24
343        }
344    }
345
346    /// Converts a decoded `AVFrame` to a [`VideoFrame`].
347    ///
348    /// # Safety
349    ///
350    /// `frame` must be a valid, fully decoded `AVFrame` owned by `self`.
351    unsafe fn av_frame_to_video_frame(
352        &self,
353        frame: *const AVFrame,
354    ) -> Result<VideoFrame, DecodeError> {
355        // SAFETY: Caller ensures frame is valid.
356        unsafe {
357            let width = (*frame).width as u32;
358            let height = (*frame).height as u32;
359            let format = Self::convert_pixel_format((*frame).format);
360
361            // Extract timestamp (images often have no meaningful PTS).
362            let pts = (*frame).pts;
363            let timestamp = if pts == ff_sys::AV_NOPTS_VALUE {
364                Timestamp::default()
365            } else {
366                let stream = (*self.format_ctx).streams.add(self.stream_index);
367                let time_base = (*(*stream)).time_base;
368                Timestamp::new(
369                    pts as i64,
370                    Rational::new(time_base.num as i32, time_base.den as i32),
371                )
372            };
373
374            let (planes, strides) = Self::extract_planes_and_strides(frame, width, height, format)?;
375
376            // Images are always key frames.
377            VideoFrame::new(planes, strides, width, height, format, timestamp, true)
378                .map_err(|e| DecodeError::Ffmpeg(format!("Failed to create VideoFrame: {e}")))
379        }
380    }
381
382    /// Extracts pixel data from an `AVFrame` into [`PooledBuffer`] planes.
383    ///
384    /// Copies data row-by-row to strip any FFmpeg padding from line strides.
385    ///
386    /// # Safety
387    ///
388    /// `frame` must be a valid, fully decoded `AVFrame` with `format` matching
389    /// the actual pixel format of the frame.
390    unsafe fn extract_planes_and_strides(
391        frame: *const AVFrame,
392        width: u32,
393        height: u32,
394        format: PixelFormat,
395    ) -> Result<(Vec<PooledBuffer>, Vec<usize>), DecodeError> {
396        // SAFETY: Caller ensures frame is valid and format matches.
397        unsafe {
398            let w = width as usize;
399            let h = height as usize;
400            let mut planes: Vec<PooledBuffer> = Vec::new();
401            let mut strides: Vec<usize> = Vec::new();
402
403            match format {
404                PixelFormat::Rgba | PixelFormat::Bgra => {
405                    let bytes_per_pixel = 4_usize;
406                    let stride = (*frame).linesize[0] as usize;
407                    let row_w = w * bytes_per_pixel;
408                    let mut buf = vec![0u8; row_w * h];
409                    let src = (*frame).data[0];
410                    if src.is_null() {
411                        return Err(DecodeError::Ffmpeg(
412                            "Null plane data for packed format".to_string(),
413                        ));
414                    }
415                    for row in 0..h {
416                        ptr::copy_nonoverlapping(
417                            src.add(row * stride),
418                            buf[row * row_w..].as_mut_ptr(),
419                            row_w,
420                        );
421                    }
422                    planes.push(PooledBuffer::standalone(buf));
423                    strides.push(row_w);
424                }
425                PixelFormat::Rgb24 | PixelFormat::Bgr24 => {
426                    let bytes_per_pixel = 3_usize;
427                    let stride = (*frame).linesize[0] as usize;
428                    let row_w = w * bytes_per_pixel;
429                    let mut buf = vec![0u8; row_w * h];
430                    let src = (*frame).data[0];
431                    if src.is_null() {
432                        return Err(DecodeError::Ffmpeg(
433                            "Null plane data for packed format".to_string(),
434                        ));
435                    }
436                    for row in 0..h {
437                        ptr::copy_nonoverlapping(
438                            src.add(row * stride),
439                            buf[row * row_w..].as_mut_ptr(),
440                            row_w,
441                        );
442                    }
443                    planes.push(PooledBuffer::standalone(buf));
444                    strides.push(row_w);
445                }
446                PixelFormat::Gray8 => {
447                    let stride = (*frame).linesize[0] as usize;
448                    let mut buf = vec![0u8; w * h];
449                    let src = (*frame).data[0];
450                    if src.is_null() {
451                        return Err(DecodeError::Ffmpeg("Null plane data for Gray8".to_string()));
452                    }
453                    for row in 0..h {
454                        ptr::copy_nonoverlapping(
455                            src.add(row * stride),
456                            buf[row * w..].as_mut_ptr(),
457                            w,
458                        );
459                    }
460                    planes.push(PooledBuffer::standalone(buf));
461                    strides.push(w);
462                }
463                PixelFormat::Yuv420p | PixelFormat::Nv12 | PixelFormat::Nv21 => {
464                    // Y plane (full size).
465                    let y_stride = (*frame).linesize[0] as usize;
466                    let mut y_buf = vec![0u8; w * h];
467                    let y_src = (*frame).data[0];
468                    if y_src.is_null() {
469                        return Err(DecodeError::Ffmpeg("Null Y plane".to_string()));
470                    }
471                    for row in 0..h {
472                        ptr::copy_nonoverlapping(
473                            y_src.add(row * y_stride),
474                            y_buf[row * w..].as_mut_ptr(),
475                            w,
476                        );
477                    }
478                    planes.push(PooledBuffer::standalone(y_buf));
479                    strides.push(w);
480
481                    if matches!(format, PixelFormat::Nv12 | PixelFormat::Nv21) {
482                        // Interleaved UV plane (half height).
483                        let uv_h = h / 2;
484                        let uv_stride = (*frame).linesize[1] as usize;
485                        let mut uv_buf = vec![0u8; w * uv_h];
486                        let uv_src = (*frame).data[1];
487                        if !uv_src.is_null() {
488                            for row in 0..uv_h {
489                                ptr::copy_nonoverlapping(
490                                    uv_src.add(row * uv_stride),
491                                    uv_buf[row * w..].as_mut_ptr(),
492                                    w,
493                                );
494                            }
495                        }
496                        planes.push(PooledBuffer::standalone(uv_buf));
497                        strides.push(w);
498                    } else {
499                        // YUV 4:2:0 — separate U and V planes (half width, half height).
500                        let uv_w = w / 2;
501                        let uv_h = h / 2;
502                        for plane_idx in 1..=2usize {
503                            let uv_stride = (*frame).linesize[plane_idx] as usize;
504                            let mut uv_buf = vec![0u8; uv_w * uv_h];
505                            let uv_src = (*frame).data[plane_idx];
506                            if !uv_src.is_null() {
507                                for row in 0..uv_h {
508                                    ptr::copy_nonoverlapping(
509                                        uv_src.add(row * uv_stride),
510                                        uv_buf[row * uv_w..].as_mut_ptr(),
511                                        uv_w,
512                                    );
513                                }
514                            }
515                            planes.push(PooledBuffer::standalone(uv_buf));
516                            strides.push(uv_w);
517                        }
518                    }
519                }
520                PixelFormat::Yuv422p => {
521                    // Y plane (full size), U and V planes (half width, full height).
522                    let uv_w = w / 2;
523                    let plane_dims = [(w, h), (uv_w, h), (uv_w, h)];
524                    for (plane_idx, (pw, ph)) in plane_dims.iter().enumerate() {
525                        let stride = (*frame).linesize[plane_idx] as usize;
526                        let mut buf = vec![0u8; pw * ph];
527                        let src = (*frame).data[plane_idx];
528                        if !src.is_null() {
529                            for row in 0..*ph {
530                                ptr::copy_nonoverlapping(
531                                    src.add(row * stride),
532                                    buf[row * pw..].as_mut_ptr(),
533                                    *pw,
534                                );
535                            }
536                        }
537                        planes.push(PooledBuffer::standalone(buf));
538                        strides.push(*pw);
539                    }
540                }
541                PixelFormat::Yuv444p => {
542                    // All three planes are full size.
543                    for plane_idx in 0..3usize {
544                        let stride = (*frame).linesize[plane_idx] as usize;
545                        let mut buf = vec![0u8; w * h];
546                        let src = (*frame).data[plane_idx];
547                        if !src.is_null() {
548                            for row in 0..h {
549                                ptr::copy_nonoverlapping(
550                                    src.add(row * stride),
551                                    buf[row * w..].as_mut_ptr(),
552                                    w,
553                                );
554                            }
555                        }
556                        planes.push(PooledBuffer::standalone(buf));
557                        strides.push(w);
558                    }
559                }
560                _ => {
561                    return Err(DecodeError::Ffmpeg(format!(
562                        "Unsupported pixel format for image decoding: {format:?}"
563                    )));
564                }
565            }
566
567            Ok((planes, strides))
568        }
569    }
570}
571
572impl Drop for ImageDecoderInner {
573    fn drop(&mut self) {
574        // SAFETY: All pointers are exclusively owned by this struct and were
575        // allocated by the corresponding FFmpeg alloc functions.
576        unsafe {
577            if !self.frame.is_null() {
578                ff_sys::av_frame_free(&mut (self.frame as *mut _));
579            }
580            if !self.packet.is_null() {
581                ff_sys::av_packet_free(&mut (self.packet as *mut _));
582            }
583            if !self.codec_ctx.is_null() {
584                ff_sys::avcodec::free_context(&mut (self.codec_ctx as *mut _));
585            }
586            if !self.format_ctx.is_null() {
587                ff_sys::avformat::close_input(&mut (self.format_ctx as *mut _));
588            }
589        }
590    }
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596
597    #[test]
598    fn convert_pixel_format_yuv420p_should_map_to_yuv420p() {
599        assert_eq!(
600            ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_YUV420P),
601            PixelFormat::Yuv420p
602        );
603    }
604
605    #[test]
606    fn convert_pixel_format_yuvj420p_should_map_to_yuv420p() {
607        assert_eq!(
608            ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_YUVJ420P),
609            PixelFormat::Yuv420p
610        );
611    }
612
613    #[test]
614    fn convert_pixel_format_rgb24_should_map_to_rgb24() {
615        assert_eq!(
616            ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_RGB24),
617            PixelFormat::Rgb24
618        );
619    }
620
621    #[test]
622    fn convert_pixel_format_rgba_should_map_to_rgba() {
623        assert_eq!(
624            ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_RGBA),
625            PixelFormat::Rgba
626        );
627    }
628
629    #[test]
630    fn convert_pixel_format_gray8_should_map_to_gray8() {
631        assert_eq!(
632            ImageDecoderInner::convert_pixel_format(ff_sys::AVPixelFormat_AV_PIX_FMT_GRAY8),
633            PixelFormat::Gray8
634        );
635    }
636}