1use crate::color::{ChromaSampling, Depth, MatrixCoefficients, Range};
3use crate::{Config, Decoder, FrameTempRef, Planes};
4use crate::{Error, Result};
5use imgref::{Img, ImgRef, ImgVec};
6use rgb::prelude::*;
7use rgb::{Rgb, Rgba};
8use std::borrow::Cow;
9use yuv::{YuvPlanarImage, YuvRange, YuvStandardMatrix};
10
11pub struct Avif {
13 decoder: Decoder,
14 avif: ParsedAvifData,
15}
16
17pub enum Image {
19 RGB8(ImgVec<Rgb<u8>>),
20 RGBA8(ImgVec<Rgba<u8>>),
21 RGB16(ImgVec<Rgb<u16>>),
22 RGBA16(ImgVec<Rgba<u16>>),
23 Gray8(ImgVec<u8>),
24 Gray16(ImgVec<u16>),
25}
26
27pub use avif_parse::AvifData as ParsedAvifData;
29pub use avif_parse::AV1Metadata;
32pub use avif_parse::Error as AvifParseError;
33pub use yuv::YuvError;
34
35impl Avif {
36 pub fn decode(data: &[u8], config: &Config) -> Result<Self> {
37 Self::from_parsed_avif_data(Self::parse_avif(data)?, config)
38 }
39
40 pub fn convert(&mut self) -> Result<Image> {
41 let has_alpha = self.avif.alpha_item.is_some();
44 let color = self.raw_color_data()?;
45 let mut img = match color.planes()? {
46 Planes::YuvPlanes8 { y, u, v, chroma_sampling } => {
47 yuv_to_rgb8(&color, y, chroma_sampling, u, v, has_alpha)?
48 },
49 Planes::Mono8(y) => yuv_to_gray8(&color, y, has_alpha),
50 Planes::Mono16(y, depth) => yuv_to_gray16(&color, depth, y, has_alpha),
51 Planes::YuvPlanes16 { y, u, v, chroma_sampling, depth } => {
52 yuv_to_rgb16(&color, depth, y, chroma_sampling, u, v, has_alpha)?
53 },
54 };
55 let color_mc = color.matrix_coefficients().unwrap_or(MatrixCoefficients::Identity);
56
57 let premultiplied_alpha = self.avif.premultiplied_alpha;
58 if let Some(alpha) = self.raw_alpha_data()? {
59 let range = alpha.range();
60 if let Some(alpha_mc) = alpha.matrix_coefficients().filter(|&mc| mc != MatrixCoefficients::Identity)
61 && color_mc != alpha_mc {
62 return Err(Error::Unsupported("alpha image has color info"));
63 }
64 match alpha.planes()? {
65 Planes::YuvPlanes8 { y, .. } | Planes::Mono8(y) => {
66 add_alpha8(&mut img, y, range, premultiplied_alpha)?;
67 },
68 Planes::YuvPlanes16 { y, depth, .. } | Planes::Mono16(y, depth) => {
69 add_alpha16(&mut img, y, depth, range, premultiplied_alpha)?;
70 },
71 }
72 } else if has_alpha {
73 return Err(Error::Unsupported("invalid alpha"));
74 }
75 Ok(img)
76 }
77
78 pub fn parse_avif(data: &[u8]) -> Result<ParsedAvifData> {
79 Ok(avif_parse::read_avif(&mut &data[..])?)
80 }
81
82 pub fn from_parsed_avif_data(avif: ParsedAvifData, config: &Config) -> Result<Self> {
83 let decoder = Decoder::new(config)?;
84 Ok(Self { decoder, avif })
85 }
86
87 pub fn raw_color_data(&mut self) -> Result<FrameTempRef<'_>> {
88 self.decoder.decode_frame(&self.avif.primary_item)
89 }
90
91 pub fn raw_alpha_data(&mut self) -> Result<Option<FrameTempRef<'_>>> {
92 Ok(if let Some(alpha) = &self.avif.alpha_item {
93 Some(self.decoder.decode_frame(alpha)?)
94 } else {
95 None
96 })
97 }
98}
99
100fn add_alpha16(img: &mut Image, y: ImgRef<'_, [u8; 2]>, depth: Depth, range: Range, premultiplied_alpha: bool) -> Result<()> {
101 match img {
102 Image::RGBA8(img) => {
103 for (y_row, img_row) in y.rows().zip(img.rows_mut()) {
104 if y_row.len() != img_row.len() {
105 return Err(Error::Unsupported("invalid alpha size"));
106 }
107 for (y, px) in y_row.iter().copied().zip(img_row.iter_mut()) {
108 px.a = (luma16(u16::from_ne_bytes(y), depth, range) >> 8) as u8;
109 }
110 if premultiplied_alpha {
111 unpremultiply8(img_row);
112 }
113 }
114 },
115 Image::RGBA16(img) => {
116 for (y_row, img_row) in y.rows().zip(img.rows_mut()) {
117 if y_row.len() != img_row.len() {
118 return Err(Error::Unsupported("invalid alpha size"));
119 }
120 for (y, px) in y_row.iter().copied().zip(img_row.iter_mut()) {
121 px.a = luma16(u16::from_ne_bytes(y), depth, range);
122 }
123 if premultiplied_alpha {
124 unpremultiply16(img_row);
125 }
126 }
127 },
128 _ => return Err(Error::Unsupported("internal error")),
129 }
130 Ok(())
131}
132
133fn add_alpha8(img: &mut Image, y: ImgRef<'_, u8>, range: Range, premultiplied_alpha: bool) -> Result<()> {
134 match img {
135 Image::RGBA8(img) => {
136 for (y_row, img_row) in y.rows().zip(img.rows_mut()) {
137 if y_row.len() != img_row.len() {
138 return Err(Error::Unsupported("invalid alpha size"));
139 }
140 for (y, px) in y_row.iter().copied().zip(img_row.iter_mut()) {
141 px.a = luma8(y, range);
142 }
143 if premultiplied_alpha {
144 unpremultiply8(img_row);
145 }
146 }
147 },
148 Image::RGBA16(img) => {
149 for (y_row, img_row) in y.rows().zip(img.rows_mut()) {
150 if y_row.len() != img_row.len() {
151 return Err(Error::Unsupported("invalid alpha size"));
152 }
153 for (y, px) in y_row.iter().copied().zip(img_row.iter_mut()) {
154 px.a = luma16(u16::from(y), Depth::Depth8, range);
155 }
156 if premultiplied_alpha {
157 unpremultiply16(img_row);
158 }
159 }
160 },
161 _ => return Err(Error::Unsupported("internal error")),
162 }
163 Ok(())
164}
165
166#[inline(never)]
167fn unpremultiply8(img_row: &mut [Rgba<u8>]) {
168 for px in img_row.iter_mut() {
169 if px.a != 255 && px.a != 0 {
170 *px.rgb_mut() = px.rgb().map(|c| (u16::from(c) * 255 / u16::from(px.a)).min(255) as u8);
171 }
172 }
173}
174
175#[inline(never)]
176fn unpremultiply16(img_row: &mut [Rgba<u16>]) {
177 for px in img_row.iter_mut() {
178 if px.a != 0xFFFF && px.a != 0 {
179 *px.rgb_mut() = px.rgb().map(|c| (u32::from(c) * 0xFFFF / u32::from(px.a)).min(0xFFFF) as u16);
180 }
181 }
182}
183
184fn imgref_align_to_u16(plane: ImgRef<'_, [u8; 2]>) -> Img<Cow<'_, [u16]>> {
185 plane.new_buf(match bytemuck::try_cast_slice(plane.buf()) {
186 Ok(samples) => Cow::Borrowed(samples),
187 Err(_) => Cow::Owned(plane.buf().iter().map(|b| u16::from_ne_bytes(*b)).collect()),
188 })
189}
190
191fn yuv_to_rgb16(color: &FrameTempRef, depth: Depth, y: ImgRef<'_, [u8; 2]>, chroma_sampling: ChromaSampling, u: ImgRef<'_, [u8; 2]>, v: ImgRef<'_, [u8; 2]>, has_alpha: bool) -> Result<Image, Error> {
192 let mc = color.matrix_coefficients().unwrap_or(MatrixCoefficients::BT601);
193 let conv = conversion(mc, color.range())?;
194 let range = to_yuv_range(color.range());
195 let width = y.width();
196 let height = y.height();
197 let (y_plane, u_plane, v_plane) = (imgref_align_to_u16(y), imgref_align_to_u16(u), imgref_align_to_u16(v));
199 let planar = YuvPlanarImage {
200 y_plane: y_plane.buf(),
201 y_stride: y_plane.stride() as u32,
202 u_plane: u_plane.buf(),
203 u_stride: u_plane.stride() as u32,
204 v_plane: v_plane.buf(),
205 v_stride: v_plane.stride() as u32,
206 width: width as u32,
207 height: height as u32,
208 };
209 if has_alpha {
210 let mut out = vec![Rgba::<u16>::new(0, 0, 0, 0); width * height];
211 convert16(&planar, bytemuck::cast_slice_mut(out.as_mut_slice()), (width * 4) as u32, chroma_sampling, depth, conv, range, true)?;
212 Ok(Image::RGBA16(ImgVec::new(out, width, height)))
213 } else {
214 let mut out = vec![Rgb::<u16>::new(0, 0, 0); width * height];
215 convert16(&planar, bytemuck::cast_slice_mut(out.as_mut_slice()), (width * 3) as u32, chroma_sampling, depth, conv, range, false)?;
216 Ok(Image::RGB16(ImgVec::new(out, width, height)))
217 }
218}
219
220fn yuv_to_gray16(color: &FrameTempRef, depth: Depth, y: ImgRef<'_, [u8; 2]>, has_alpha: bool) -> Image {
221 let range = color.range();
222 let width = y.width();
223 let height = y.height();
224 if has_alpha {
225 let mut out = Vec::with_capacity(width * height);
226 out.extend(y.rows().flat_map(|row| {
227 row.iter().copied().map(|y| {
228 let g = luma16(u16::from_ne_bytes(y), depth, range);
229 Rgba::new(g, g, g, 0)
230 })
231 }));
232 Image::RGBA16(ImgVec::new(out, width, height))
233 } else {
234 let mut out = Vec::with_capacity(width * height);
235 out.extend(y.rows().flat_map(|row| {
236 row.iter()
237 .copied()
238 .map(|y| luma16(u16::from_ne_bytes(y), depth, range))
239 }));
240 Image::Gray16(ImgVec::new(out, width, height))
241 }
242}
243
244fn yuv_to_gray8(color: &FrameTempRef, y: ImgRef<'_, u8>, has_alpha: bool) -> Image {
245 let range = color.range();
246 let width = y.width();
247 let height = y.height();
248 if has_alpha {
249 let mut out = Vec::with_capacity(width * height);
250 out.extend(y.rows().flat_map(|row| {
251 row.iter().copied().map(|y| {
252 let g = luma8(y, range);
253 Rgba::new(g, g, g, 0)
254 })
255 }));
256 Image::RGBA8(ImgVec::new(out, width, height))
257 } else {
258 let mut out = Vec::with_capacity(width * height);
259 out.extend(y.rows().flat_map(|row| {
260 row.iter()
261 .copied()
262 .map(|y| luma8(y, range))
263 }));
264 Image::Gray8(ImgVec::new(out, width, height))
265 }
266}
267
268fn yuv_to_rgb8(color: &FrameTempRef, y: ImgRef<'_, u8>, chroma_sampling: ChromaSampling, u: ImgRef<'_, u8>, v: ImgRef<'_, u8>, has_alpha: bool) -> Result<Image, Error> {
269 let mc = color.matrix_coefficients().unwrap_or(MatrixCoefficients::BT601);
270 let conv = conversion(mc, color.range())?;
271 let range = to_yuv_range(color.range());
272 let width = y.width();
273 let height = y.height();
274 let planar = YuvPlanarImage {
275 y_plane: y.buf(),
276 y_stride: y.stride() as u32,
277 u_plane: u.buf(),
278 u_stride: u.stride() as u32,
279 v_plane: v.buf(),
280 v_stride: v.stride() as u32,
281 width: width as u32,
282 height: height as u32,
283 };
284 if has_alpha {
285 let mut out = vec![Rgba::<u8>::new(0, 0, 0, 0); width * height];
286 convert8(&planar, bytemuck::cast_slice_mut(out.as_mut_slice()), (width * 4) as u32, chroma_sampling, conv, range, true)?;
287 Ok(Image::RGBA8(ImgVec::new(out, width, height)))
288 } else {
289 let mut out = vec![Rgb::<u8>::new(0, 0, 0); width * height];
290 convert8(&planar, bytemuck::cast_slice_mut(out.as_mut_slice()), (width * 3) as u32, chroma_sampling, conv, range, false)?;
291 Ok(Image::RGB8(ImgVec::new(out, width, height)))
292 }
293}
294
295#[derive(Debug, Copy, Clone, PartialEq)]
297enum Conversion {
298 Gbr,
300 YCgCo,
302 Matrix(YuvStandardMatrix),
304}
305
306fn conversion(mc: MatrixCoefficients, range: Range) -> Result<Conversion, Error> {
307 Ok(match mc {
308 MatrixCoefficients::Identity => Conversion::Gbr,
309 MatrixCoefficients::YCgCo => Conversion::YCgCo,
310 MatrixCoefficients::BT709 => Conversion::Matrix(YuvStandardMatrix::Bt709),
311 MatrixCoefficients::FCC => Conversion::Matrix(YuvStandardMatrix::Fcc),
312 MatrixCoefficients::BT470BG |
315 MatrixCoefficients::BT601 => Conversion::Matrix(YuvStandardMatrix::Bt601),
316 MatrixCoefficients::SMPTE240 => Conversion::Matrix(YuvStandardMatrix::Smpte240),
317 MatrixCoefficients::BT2020NCL => Conversion::Matrix(YuvStandardMatrix::Bt2020),
318 _ => {
319 log::debug!("Unsupported matrix coefficients: {mc:?} ({range:?})");
320 return Err(Error::Unsupported("matrix coefficients"));
321 },
322 })
323}
324
325#[inline]
326fn to_yuv_range(range: Range) -> YuvRange {
327 match range {
328 Range::Full => YuvRange::Full,
329 Range::Limited => YuvRange::Limited,
330 }
331}
332
333fn convert8(planar: &YuvPlanarImage<u8>, dst: &mut [u8], dst_stride: u32, chroma_sampling: ChromaSampling, conv: Conversion, range: YuvRange, has_alpha: bool) -> Result<(), Error> {
335 use ChromaSampling::{Cs420, Cs422, Cs444, Monochrome};
336 use Conversion::{Gbr, Matrix, YCgCo};
337 let res = match (conv, chroma_sampling) {
338 (Gbr, Cs444) => if has_alpha {
339 yuv::gbr_to_rgba(planar, dst, dst_stride, range)
340 } else {
341 yuv::gbr_to_rgb(planar, dst, dst_stride, range)
342 },
343 (Gbr, _) => return Err(Error::Unsupported("identity matrix with chroma subsampling")),
344 (YCgCo, Cs444) => if has_alpha {
345 yuv::ycgco444_to_rgba(planar, dst, dst_stride, range)
346 } else {
347 yuv::ycgco444_to_rgb(planar, dst, dst_stride, range)
348 },
349 (YCgCo, Cs422) => if has_alpha {
350 yuv::ycgco422_to_rgba(planar, dst, dst_stride, range)
351 } else {
352 yuv::ycgco422_to_rgb(planar, dst, dst_stride, range)
353 },
354 (YCgCo, Cs420) => if has_alpha {
355 yuv::ycgco420_to_rgba(planar, dst, dst_stride, range)
356 } else {
357 yuv::ycgco420_to_rgb(planar, dst, dst_stride, range)
358 },
359 (YCgCo, Monochrome) => unreachable!(),
360 (Matrix(m), Cs444) => if has_alpha {
361 yuv::yuv444_to_rgba(planar, dst, dst_stride, range, m)
362 } else {
363 yuv::yuv444_to_rgb(planar, dst, dst_stride, range, m)
364 },
365 (Matrix(m), Cs422) => if has_alpha {
366 yuv::yuv422_to_rgba(planar, dst, dst_stride, range, m)
367 } else {
368 yuv::yuv422_to_rgb(planar, dst, dst_stride, range, m)
369 },
370 (Matrix(m), Cs420) => if has_alpha {
371 yuv::yuv420_to_rgba(planar, dst, dst_stride, range, m)
372 } else {
373 yuv::yuv420_to_rgb(planar, dst, dst_stride, range, m)
374 },
375 (Matrix(_), Monochrome) => unreachable!(),
376 };
377 res.inspect_err(|e| log::debug!("{e} (conversion={conv:?}, range={range:?})"))?;
378 Ok(())
379}
380
381fn convert16(planar: &YuvPlanarImage<u16>, dst: &mut [u16], dst_stride: u32, chroma_sampling: ChromaSampling, depth: Depth, conv: Conversion, range: YuvRange, has_alpha: bool) -> Result<(), Error> {
383 use ChromaSampling::{Cs420, Cs422, Cs444, Monochrome};
384 use Conversion::{Gbr, Matrix, YCgCo};
385 let res = match (conv, chroma_sampling, depth) {
386 (Gbr, Cs444, Depth::Depth10) => if has_alpha {
387 yuv::gb10_to_rgba10(planar, dst, dst_stride, range)
388 } else {
389 yuv::gb10_to_rgb10(planar, dst, dst_stride, range)
390 },
391 (Gbr, Cs444, Depth::Depth12) => if has_alpha {
392 yuv::gb12_to_rgba12(planar, dst, dst_stride, range)
393 } else {
394 yuv::gb12_to_rgb12(planar, dst, dst_stride, range)
395 },
396 (Gbr, Cs444, _) => if has_alpha {
397 yuv::gb16_to_rgba16(planar, dst, dst_stride, range)
398 } else {
399 yuv::gb16_to_rgb16(planar, dst, dst_stride, range)
400 },
401 (Gbr, _, _) => return Err(Error::Unsupported("identity matrix with chroma subsampling")),
402 (YCgCo, Cs444, Depth::Depth10) => if has_alpha {
403 yuv::icgc410_to_rgba10(planar, dst, dst_stride, range)
404 } else {
405 yuv::icgc410_to_rgb10(planar, dst, dst_stride, range)
406 },
407 (YCgCo, Cs422, Depth::Depth10) => if has_alpha {
408 yuv::icgc210_to_rgba10(planar, dst, dst_stride, range)
409 } else {
410 yuv::icgc210_to_rgb10(planar, dst, dst_stride, range)
411 },
412 (YCgCo, Cs420, Depth::Depth10) => if has_alpha {
413 yuv::icgc010_to_rgba10(planar, dst, dst_stride, range)
414 } else {
415 yuv::icgc010_to_rgb10(planar, dst, dst_stride, range)
416 },
417 (YCgCo, Cs444, Depth::Depth12) => if has_alpha {
418 yuv::icgc412_to_rgba12(planar, dst, dst_stride, range)
419 } else {
420 yuv::icgc412_to_rgb12(planar, dst, dst_stride, range)
421 },
422 (YCgCo, Cs422, Depth::Depth12) => if has_alpha {
423 yuv::icgc212_to_rgba12(planar, dst, dst_stride, range)
424 } else {
425 yuv::icgc212_to_rgb12(planar, dst, dst_stride, range)
426 },
427 (YCgCo, Cs420, Depth::Depth12) => if has_alpha {
428 yuv::icgc012_to_rgba12(planar, dst, dst_stride, range)
429 } else {
430 yuv::icgc012_to_rgb12(planar, dst, dst_stride, range)
431 },
432 (YCgCo, _, _) => return Err(Error::Unsupported("YCgCo at this depth")),
433 (Matrix(m), Cs444, Depth::Depth10) => if has_alpha {
434 yuv::i410_to_rgba10(planar, dst, dst_stride, range, m)
435 } else {
436 yuv::i410_to_rgb10(planar, dst, dst_stride, range, m)
437 },
438 (Matrix(m), Cs444, Depth::Depth12) => if has_alpha {
439 yuv::i412_to_rgba12(planar, dst, dst_stride, range, m)
440 } else {
441 yuv::i412_to_rgb12(planar, dst, dst_stride, range, m)
442 },
443 (Matrix(m), Cs444, _) => if has_alpha {
444 yuv::i416_to_rgba16(planar, dst, dst_stride, range, m)
445 } else {
446 yuv::i416_to_rgb16(planar, dst, dst_stride, range, m)
447 },
448 (Matrix(m), Cs422, Depth::Depth10) => if has_alpha {
449 yuv::i210_to_rgba10(planar, dst, dst_stride, range, m)
450 } else {
451 yuv::i210_to_rgb10(planar, dst, dst_stride, range, m)
452 },
453 (Matrix(m), Cs422, Depth::Depth12) => if has_alpha {
454 yuv::i212_to_rgba12(planar, dst, dst_stride, range, m)
455 } else {
456 yuv::i212_to_rgb12(planar, dst, dst_stride, range, m)
457 },
458 (Matrix(m), Cs422, _) => if has_alpha {
459 yuv::i216_to_rgba16(planar, dst, dst_stride, range, m)
460 } else {
461 yuv::i216_to_rgb16(planar, dst, dst_stride, range, m)
462 },
463 (Matrix(m), Cs420, Depth::Depth10) => if has_alpha {
464 yuv::i010_to_rgba10(planar, dst, dst_stride, range, m)
465 } else {
466 yuv::i010_to_rgb10(planar, dst, dst_stride, range, m)
467 },
468 (Matrix(m), Cs420, Depth::Depth12) => if has_alpha {
469 yuv::i012_to_rgba12(planar, dst, dst_stride, range, m)
470 } else {
471 yuv::i012_to_rgb12(planar, dst, dst_stride, range, m)
472 },
473 (Matrix(m), Cs420, _) => if has_alpha {
474 yuv::i016_to_rgba16(planar, dst, dst_stride, range, m)
475 } else {
476 yuv::i016_to_rgb16(planar, dst, dst_stride, range, m)
477 },
478 (Matrix(_), Monochrome, _) => return Err(Error::Unsupported("unreachable")),
479 };
480 res.inspect_err(|e| log::debug!("{e} (conversion={conv:?}, range={range:?})"))?;
481
482 if depth != Depth::Depth16 {
484 let shift = 16 - depth.bits();
485 let low_shift = depth.bits() - shift;
486 for v in dst.iter_mut() {
487 *v = (*v << shift) | (*v >> low_shift);
488 }
489 }
490 Ok(())
491}
492
493#[inline]
495fn luma8(v: u8, range: Range) -> u8 {
496 (luma16(u16::from(v), Depth::Depth8, range) >> 8) as u8
497}
498
499#[inline]
501fn luma16(v: u16, depth: Depth, range: Range) -> u16 {
502 let bits = depth.bits();
503 let (min, span) = match range {
504 Range::Full => (0, (1 << bits) - 1),
505 Range::Limited => (16 << (bits - 8), 219 << (bits - 8)),
506 };
507 let v = u32::from(v).saturating_sub(min).min(span);
508 ((v * 65535 + span / 2) / span) as u16
509}