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