// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #[allow(unused_imports)] use crate::image::*; use crate::*; use std::fs::File; use std::io::prelude::*; use super::Writer; #[derive(Default)] pub(crate) struct Y4MWriter { header_written: bool, write_alpha: bool, skip_headers: bool, } impl Y4MWriter { #[allow(unused)] pub(crate) fn create(skip_headers: bool) -> Self { Self { skip_headers, ..Default::default() } } fn write_header(&mut self, file: &mut File, image: &Image) -> AvifResult<()> { if self.header_written { return Ok(()); } self.write_alpha = false; if image.alpha_present && (image.depth != 8 || image.yuv_format != PixelFormat::Yuv444) { println!("WARNING: writing alpha is currently only supported in 8bpc YUV444, ignoring alpha channel"); } let y4m_format = match image.depth { 8 => match image.yuv_format { PixelFormat::None | PixelFormat::AndroidP010 | PixelFormat::AndroidNv12 | PixelFormat::AndroidNv21 => "", PixelFormat::Yuv444 => { if image.alpha_present { self.write_alpha = true; "C444alpha XYSCSS=444" } else { "C444 XYSCSS=444" } } PixelFormat::Yuv422 => "C422 XYSCSS=422", PixelFormat::Yuv420 => "C420jpeg XYSCSS=420JPEG", PixelFormat::Yuv400 => "Cmono XYSCSS=400", }, 10 => match image.yuv_format { PixelFormat::None | PixelFormat::AndroidP010 | PixelFormat::AndroidNv12 | PixelFormat::AndroidNv21 => "", PixelFormat::Yuv444 => "C444p10 XYSCSS=444P10", PixelFormat::Yuv422 => "C422p10 XYSCSS=422P10", PixelFormat::Yuv420 => "C420p10 XYSCSS=420P10", PixelFormat::Yuv400 => "Cmono10 XYSCSS=400", }, 12 => match image.yuv_format { PixelFormat::None | PixelFormat::AndroidP010 | PixelFormat::AndroidNv12 | PixelFormat::AndroidNv21 => "", PixelFormat::Yuv444 => "C444p12 XYSCSS=444P12", PixelFormat::Yuv422 => "C422p12 XYSCSS=422P12", PixelFormat::Yuv420 => "C420p12 XYSCSS=420P12", PixelFormat::Yuv400 => "Cmono12 XYSCSS=400", }, _ => { return Err(AvifError::NotImplemented); } }; let y4m_color_range = if image.yuv_range == YuvRange::Limited { "XCOLORRANGE=LIMITED" } else { "XCOLORRANGE=FULL" }; let header = format!( "YUV4MPEG2 W{} H{} F25:1 Ip A0:0 {y4m_format} {y4m_color_range}\n", image.width, image.height ); file.write_all(header.as_bytes()) .or(Err(AvifError::IoError))?; self.header_written = true; Ok(()) } } impl Writer for Y4MWriter { fn write_frame(&mut self, file: &mut File, image: &Image) -> AvifResult<()> { if !self.skip_headers { self.write_header(file, image)?; let frame_marker = "FRAME\n"; file.write_all(frame_marker.as_bytes()) .or(Err(AvifError::IoError))?; } let planes: &[Plane] = if self.write_alpha { &ALL_PLANES } else { &YUV_PLANES }; for plane in planes { let plane = *plane; if !image.has_plane(plane) { continue; } if image.depth == 8 { for y in 0..image.height(plane) { let row = image.row(plane, y as u32)?; let pixels = &row[..image.width(plane)]; file.write_all(pixels).or(Err(AvifError::IoError))?; } } else { for y in 0..image.height(plane) { let row16 = image.row16(plane, y as u32)?; let pixels16 = &row16[..image.width(plane)]; let mut pixels: Vec = Vec::new(); // y4m is always little endian. for &pixel16 in pixels16 { pixels.extend_from_slice(&pixel16.to_le_bytes()); } file.write_all(&pixels[..]).or(Err(AvifError::IoError))?; } } } Ok(()) } }