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