Add new default setting for avifenc's --yuv: "auto"
This new setting behaves similarly to the previous default of "444", except when reading in a JPEG
which is internally YUV420 or YUV400 (grayscale), in which it will adopt the format without
performing any YUV conversion before encoding.
diff --git a/apps/avifenc.c b/apps/avifenc.c
index f499427..e1f9bf5 100644
--- a/apps/avifenc.c
+++ b/apps/avifenc.c
@@ -8,6 +8,7 @@
#include "avifutil.h"
#include "y4m.h"
+#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
@@ -54,7 +55,8 @@
printf(" -o,--output FILENAME : Instead of using the last filename given as output, use this filename\n");
printf(" -l,--lossless : Set all defaults to encode losslessly, and emit warnings when settings/input don't allow for it\n");
printf(" -d,--depth D : Output depth [8,10,12]. (JPEG/PNG only; For y4m or stdin, depth is retained)\n");
- printf(" -y,--yuv FORMAT : Output format [default=444, 422, 420, 400]. (JPEG/PNG only; For y4m or stdin, format is retained)\n");
+ printf(" -y,--yuv FORMAT : Output format [default=auto, 444, 422, 420, 400]. Ignored for y4m or stdin (y4m format is retained)\n");
+ printf(" For JPEG, auto honors the JPEG's internal format, if possible. For all other cases, auto defaults to 444\n");
printf(" -p,--premultiply : Premultiply color by the alpha channel and signal this in the AVIF\n");
printf(" --stdin : Read y4m frames from stdin instead of files; no input filenames allowed, must set before offering output filename\n");
printf(" --cicp,--nclx P/T/M : Set CICP values (nclx colr box) (3 raw numbers, use -r to set range flag)\n");
@@ -230,6 +232,7 @@
if (!y4mRead(NULL, image, sourceTiming, &input->frameIter)) {
return AVIF_APP_FILE_FORMAT_UNKNOWN;
}
+ assert(image->yuvFormat != AVIF_PIXEL_FORMAT_NONE);
return AVIF_APP_FILE_FORMAT_Y4M;
}
@@ -264,6 +267,8 @@
if (!input->frameIter) {
++input->fileIndex;
}
+
+ assert(image->yuvFormat != AVIF_PIXEL_FORMAT_NONE);
return nextInputFormat;
}
@@ -376,7 +381,7 @@
avifInput input;
memset(&input, 0, sizeof(input));
input.files = malloc(sizeof(avifInputFile) * argc);
- input.requestedFormat = AVIF_PIXEL_FORMAT_YUV444;
+ input.requestedFormat = AVIF_PIXEL_FORMAT_NONE; // AVIF_PIXEL_FORMAT_NONE is used as a sentinel for "auto"
// See here for the discussion on the semi-arbitrary defaults for speed/min/max:
// https://github.com/AOMediaCodec/libavif/issues/440
diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c
index b0c7944..599ec4d 100644
--- a/apps/shared/avifjpeg.c
+++ b/apps/shared/avifjpeg.c
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: BSD-2-Clause
#include "avifjpeg.h"
+#include "avifutil.h"
#include <assert.h>
#include <setjmp.h>
@@ -125,22 +126,31 @@
// Import from YUV: must using compatible matrixCoefficients.
if (avifJPEGHasCompatibleMatrixCoefficients(avif->matrixCoefficients)) {
// YUV->YUV: require precise match for pixel format.
- if (((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) &&
- (cinfo->comp_info[0].h_samp_factor == 1 && cinfo->comp_info[0].v_samp_factor == 1 &&
- cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
- cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1)) ||
- ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) &&
- (cinfo->comp_info[0].h_samp_factor == 2 && cinfo->comp_info[0].v_samp_factor == 1 &&
- cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
- cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1)) ||
- ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420) &&
- (cinfo->comp_info[0].h_samp_factor == 2 && cinfo->comp_info[0].v_samp_factor == 2 &&
- cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
- cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1))) {
- cinfo->out_color_space = JCS_YCbCr;
- avifJPEGCopyPixels(avif, cinfo);
+ avifPixelFormat jpegFormat = AVIF_PIXEL_FORMAT_NONE;
+ if (cinfo->comp_info[0].h_samp_factor == 1 && cinfo->comp_info[0].v_samp_factor == 1 &&
+ cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
+ cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1) {
+ jpegFormat = AVIF_PIXEL_FORMAT_YUV444;
+ } else if (cinfo->comp_info[0].h_samp_factor == 2 && cinfo->comp_info[0].v_samp_factor == 1 &&
+ cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
+ cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1) {
+ jpegFormat = AVIF_PIXEL_FORMAT_YUV422;
+ } else if (cinfo->comp_info[0].h_samp_factor == 2 && cinfo->comp_info[0].v_samp_factor == 2 &&
+ cinfo->comp_info[1].h_samp_factor == 1 && cinfo->comp_info[1].v_samp_factor == 1 &&
+ cinfo->comp_info[2].h_samp_factor == 1 && cinfo->comp_info[2].v_samp_factor == 1) {
+ jpegFormat = AVIF_PIXEL_FORMAT_YUV420;
+ }
+ if (jpegFormat != AVIF_PIXEL_FORMAT_NONE) {
+ if (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE) {
+ // The requested format is "auto": Adopt JPEG's internal format.
+ avif->yuvFormat = jpegFormat;
+ }
+ if (avif->yuvFormat == jpegFormat) {
+ cinfo->out_color_space = JCS_YCbCr;
+ avifJPEGCopyPixels(avif, cinfo);
- return AVIF_TRUE;
+ return AVIF_TRUE;
+ }
}
// YUV->Grayscale: subsample Y plane not allowed.
@@ -159,7 +169,8 @@
// Import to YUV/Grayscale: must using compatible matrixCoefficients.
if (avifJPEGHasCompatibleMatrixCoefficients(avif->matrixCoefficients)) {
// Grayscale->Grayscale: direct copy.
- if (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
+ if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_NONE)) {
+ avif->yuvFormat = AVIF_PIXEL_FORMAT_YUV400;
cinfo->out_color_space = JCS_GRAYSCALE;
avifJPEGCopyPixels(avif, cinfo);
@@ -259,7 +270,7 @@
avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen);
}
- avif->yuvFormat = requestedFormat;
+ avif->yuvFormat = requestedFormat; // This may be AVIF_PIXEL_FORMAT_NONE, which is "auto" to avifJPEGReadCopy()
avif->depth = requestedDepth ? requestedDepth : 8;
// JPEG doesn't have alpha. Prevent confusion.
avif->alphaPremultiplied = AVIF_FALSE;
@@ -284,7 +295,7 @@
avif->width = cinfo.output_width;
avif->height = cinfo.output_height;
- avif->yuvFormat = requestedFormat;
+ avif->yuvFormat = (requestedFormat == AVIF_PIXEL_FORMAT_NONE) ? AVIF_APP_DEFAULT_PIXEL_FORMAT : requestedFormat;
avif->depth = requestedDepth ? requestedDepth : 8;
avifRGBImageSetDefaults(&rgb, avif);
rgb.format = AVIF_RGB_FORMAT_RGB;
diff --git a/apps/shared/avifpng.c b/apps/shared/avifpng.c
index 7d5c5f4..0dce383 100644
--- a/apps/shared/avifpng.c
+++ b/apps/shared/avifpng.c
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: BSD-2-Clause
#include "avifpng.h"
+#include "avifutil.h"
#include "png.h"
@@ -117,7 +118,7 @@
avif->width = rawWidth;
avif->height = rawHeight;
- avif->yuvFormat = requestedFormat;
+ avif->yuvFormat = (requestedFormat == AVIF_PIXEL_FORMAT_NONE) ? AVIF_APP_DEFAULT_PIXEL_FORMAT : requestedFormat;
avif->depth = requestedDepth;
if (avif->depth == 0) {
if (imgBitDepth == 8) {
diff --git a/apps/shared/avifutil.h b/apps/shared/avifutil.h
index 568604b..e189571 100644
--- a/apps/shared/avifutil.h
+++ b/apps/shared/avifutil.h
@@ -46,4 +46,8 @@
uint64_t timescale; // timescale of the media (Hz)
} avifAppSourceTiming;
+// Used by image decoders when the user doesn't explicitly choose a format with --yuv
+// This must match the cited fallback for "--yuv auto" in avifenc.c's syntax() function.
+#define AVIF_APP_DEFAULT_PIXEL_FORMAT AVIF_PIXEL_FORMAT_YUV444
+
#endif // ifndef LIBAVIF_APPS_SHARED_AVIFUTIL_H