Closed JosefWN closed 2 years ago
First, let me explain the differences in the commandlines you've provided.
Encoding an AVIF is a two-step process, each of which can introduce loss:
The first step is to convert from the source image's RGB(A) into some destination color space, quite often YCbCr, but other options are available. Converting from 8bpc RGB to 8bpc YCbCr (for example) can introduce up to one codepoint of loss/drift in various channels when converting back to RGB on decode. The only choice during step 1 that doesn't introduce loss (and doesn't increase bit depth) is simply performing no conversion, also known as "Identity", or simply keeping the channels as RGB. The downside to this is modern video codecs such as AV1 benefit heavily from the luma and chroma of a given pixel being as de-correlated as possible, so handing raw RGB values to the video codec is often much more inefficient.
The second step is (of course) AV1 encoding the converted pixels. This is where the quantization parameters (--min
, --max
) are used. In order for AV1 to not introduce loss, quantization must be forced to zero, and a special flag must be set on the AV1 encoder which demands that the lossless variant of one of the steps must be used (less efficient). This is automatically engaged by libavif when quantization is set to 0-0
.
When you perform this command:
avifenc --min 0 --max 0 40.png 40.avif
You're asking avifenc to use its defaults for step 1, which will convert your RGB data to YCbCr using BT601 coefficients, and then force 0 quantization and lossless mode at the AV1 encoding step.
This command's --lossless
argument is a bit of an alias / shorthand:
avifenc --lossless 40.png 40.avif
... for something like...
avifenc --min 0 --max 0 --cicp 1/13/0 --yuv 444 --range full --codec aom 40.png 40.avif
This asks avifenc to avoid step 1 and simply hand over the RGB values (reordered as GBR) directly to the AV1 encoder, configured for lossless. The other settings from this alias I believe are also the defaults for non-lossless, so they're not super interesting.
Okay ... with that out of the way ...
As a general rule, giving GBR to an AV1 encoder instead of YCbCr is much, much more likely to be less efficient. I'd say if you took 500 random images of different styles (photos, drawings, etc), the vast majority will encode better as YCbCr. That said, there are certainly going to be cases where specific images will just happen to play quite nice with AV1 encoders in their raw RGB form, and you may have found one. Certainly an almost solid-color / loose gradient would be a good candidate for this kind of thing.
Despite being the author of the libavif library/tools and its --lossless
argument, I don't really recommend using AVIF for lossless in its current state. I believe there are still ongoing discussions on ideas to improve its efficiency, but without some extra work/tooling on the AV1 encoder side, or using a new MC value not currently offered, or some additional trickery on the container side, I don't find it to be competitive. AVIF shines at high quality ("near lossless") values, its ability to pack and signal HDR@10bpc, alpha channels, ... not necessarily lossless. 😄
Thank you for a very exhaustive answer @joedrago! I'll close this ticket 🙂
@joedrago Sorry to bother you, but did something change recently regarding avifenc/aomenc and its handling of lossless RGB?
It seems like I can't get pure RGB lossless with aomenc anymore:
butteraugli_main /home/bluezakm/Videos/2048x1320_david-marcu-441_png_original.png /home/bluezakm/Videos/2048x1320_david-marcu-441_png_original-avif.png
2.8017091751
3-norm: 1.348538
If it was lossless, it would be 0.000.
Here is the source image and the supposedely lossless AV1 image converted to png: https://slow.pics/c/AlqqgdC3
I just used this command: avifenc --lossless -s 5 input.png output.avif
I'm on git aomenc from Thursday(287ee40d89f1464dae62d73124648bd69f933ed1)
https://aomedia.googlesource.com/aom/+/287ee40d89f1464dae62d73124648bd69f933ed1
@BlueSwordM Hi Zak,
I don't have the butteraugli_main
program that you used. But as far as I can tell, avifenc --lossless is working correctly.
For libavif, I tested both the tip of the source tree (commit a2e109f98cfdeb90747fb4aedc7e1cfac14fc6ec) and the v0.10.1 tag.
For libaom I tested commit 287ee40d89f1464dae62d73124648bd69f933ed1 that you used.
Here is a transcript of my experiments. Note that the YUV format is YUV444
, color range is Full
, and matrix coefficients is 0
(the identity matrix). The mean squared error (mse) calculated using Python is 0.
$ ./avifenc --lossless -s 5 F2mBUt8A.png output.avif
Successfully loaded: F2mBUt8A.png
AVIF to be written: (Lossless)
* Resolution : 2048x1320
* Bit Depth : 8
* Format : YUV444
* Alpha : Not premultiplied
* Range : Full
* Color Primaries: 1
* Transfer Char. : 13
* Matrix Coeffs. : 0
* ICC Profile : Absent (0 bytes)
* XMP Metadata : Absent (0 bytes)
* EXIF Metadata : Absent (0 bytes)
* Transformations: None
* Progressive : Unavailable
Encoding with AV1 codec 'aom' speed [5], color QP [0 (Lossless) <-> 0 (Lossless)], alpha QP [0 (Lossless) <-> 0 (Lossless)], tileRowsLog2 [0], tileColsLog2 [0], 1 worker thread(s), please wait...
Encoded successfully.
* Color AV1 total size: 3981637 bytes
* Alpha AV1 total size: 0 bytes
Wrote AVIF: output.avif
$ ./avifdec output.avif output.png
Decoding with AV1 codec 'dav1d' (1 worker thread), please wait...
Image decoded: output.avif
Image details:
* Resolution : 2048x1320
* Bit Depth : 8
* Format : YUV444
* Alpha : Absent
* Range : Full
* Color Primaries: 1
* Transfer Char. : 13
* Matrix Coeffs. : 0
* ICC Profile : Absent (0 bytes)
* XMP Metadata : Absent (0 bytes)
* EXIF Metadata : Absent (0 bytes)
* Transformations: None
* Progressive : Unavailable
Wrote PNG: output.png
$ python3
Python 3.9.12 (main, Mar 24 2022, 13:02:21)
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import cv2
>>> import numpy as np
>>> img1 = cv2.imread('F2mBUt8A.png')
>>> img2 = cv2.imread('output.png')
>>> mse = np.mean((np.array(img1, dtype=np.float32) - np.array(img2, dtype=np.float32)) ** 2)
>>> print(mse == 0)
True
>>>
@wantehchang Thank you wantecchang.
Yes, I use butteraugli_main and ssimulacra_main to compare lossless and lossy encoding performance for intra. They are programs from the libjxl library, and are extremely useful. I recommend them a lot.
Anyway, the reason the program didn't think it was lossless is that somewhere during the transfer, the included color profile was removed, and the tools mentioned before take that into account.
I checked by removing the source's color profile and bam! Lossless in all tools.
On that note, because of this, so I might have to open an issue report as not transferring color profiles might be considered a bug.
Have a good day.
It seems that:
Produces a lossy AVIF (21 171 bytes), whereas the following produces a lossless (21 021 bytes):
Notably the lossless image is smaller than the lossy one.
Is this a bug or intended behavior? Attached an admittedly poor example image for input, can't submit the converted image because GitHub doesn't support AVIF.
If you squint you can see the difference, original: