Prevent colorspace conversion when reading from JPEG if possible
diff --git a/apps/shared/avifjpeg.c b/apps/shared/avifjpeg.c
index 208a7ca..d77d601 100644
--- a/apps/shared/avifjpeg.c
+++ b/apps/shared/avifjpeg.c
@@ -12,6 +12,9 @@
 
 #include "iccjpeg.h"
 
+#define AVIF_MIN(a, b) (((a) < (b)) ? (a) : (b))
+#define AVIF_MAX(a, b) (((a) > (b)) ? (a) : (b))
+
 struct my_error_mgr
 {
     struct jpeg_error_mgr pub;
@@ -25,6 +28,176 @@
     longjmp(myerr->setjmp_buffer, 1);
 }
 
+#if JPEG_LIB_VERSION >= 70
+#define AVIF_LIBJPEG_DCT_v_scaled_size DCT_v_scaled_size
+#define AVIF_LIBJPEG_DCT_h_scaled_size DCT_h_scaled_size
+#else
+#define AVIF_LIBJPEG_DCT_h_scaled_size DCT_scaled_size
+#define AVIF_LIBJPEG_DCT_v_scaled_size DCT_scaled_size
+#endif
+static void avifJPEGCopyPixels(avifImage * avif, struct jpeg_decompress_struct * cinfo)
+{
+    cinfo->raw_data_out = TRUE;
+    jpeg_start_decompress(cinfo);
+
+    avif->width = cinfo->image_width;
+    avif->height = cinfo->image_height;
+
+    int workComponents = avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400 ? 1 : cinfo->num_components;
+
+    JSAMPIMAGE buffer = (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_IMAGE, sizeof(JSAMPARRAY) * workComponents);
+
+    // lines of output image attempt to read per jpeg_read_raw_data call
+    int readLines = 0;
+    // lines of sample to read per call for each channel
+    int linesPerCall[3] = { 0, 0, 0 };
+    // lines of sample expect to get for each channel
+    int targetRead[3] = { 0, 0, 0 };
+    // already read lines for each channel
+    int alreadyRead[3] = { 0, 0, 0 };
+    // which avif channel to write to for each jpeg channel
+    enum avifChannelIndex targetChannel[3] = { AVIF_CHAN_R, AVIF_CHAN_R, AVIF_CHAN_R };
+    for (int i = 0; i < workComponents; ++i) {
+        jpeg_component_info * comp = &cinfo->comp_info[i];
+
+        linesPerCall[i] = comp->v_samp_factor * comp->AVIF_LIBJPEG_DCT_v_scaled_size;
+        targetRead[i] = comp->downsampled_height;
+        buffer[i] = (*cinfo->mem->alloc_sarray)((j_common_ptr)cinfo,
+                                                JPOOL_IMAGE,
+                                                comp->width_in_blocks * comp->AVIF_LIBJPEG_DCT_h_scaled_size,
+                                                linesPerCall[i]);
+        readLines = AVIF_MAX(readLines, linesPerCall[i]);
+    }
+
+    avifImageAllocatePlanes(avif, AVIF_PLANES_YUV);
+
+    if (cinfo->jpeg_color_space == JCS_YCbCr) {
+        targetChannel[0] = AVIF_CHAN_Y;
+        targetChannel[1] = AVIF_CHAN_U;
+        targetChannel[2] = AVIF_CHAN_V;
+    } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) {
+        targetChannel[0] = AVIF_CHAN_Y;
+    } else /* cinfo->jpeg_color_space == JCS_RGB */ {
+        targetChannel[0] = AVIF_CHAN_V;
+        targetChannel[1] = AVIF_CHAN_Y;
+        targetChannel[2] = AVIF_CHAN_U;
+    }
+
+    while (cinfo->output_scanline < cinfo->output_height) {
+        jpeg_read_raw_data(cinfo, buffer, readLines);
+
+        for (int i = 0; i < workComponents; ++i) {
+            int linesRead = AVIF_MIN(targetRead[i] - alreadyRead[i], linesPerCall[i]);
+            for (int j = 0; j < linesRead; ++j) {
+                memcpy(&avif->yuvPlanes[targetChannel[i]][avif->yuvRowBytes[targetChannel[i]] * (alreadyRead[i] + j)],
+                       buffer[i][j],
+                       avif->yuvRowBytes[targetChannel[i]]);
+            }
+            alreadyRead[i] += linesPerCall[i];
+        }
+    }
+
+    jpeg_finish_decompress(cinfo);
+}
+
+static avifBool avifJPEGReadCopy(avifImage * avif, struct jpeg_decompress_struct * cinfo)
+{
+    if (avif->depth != 8) {
+        return AVIF_FALSE;
+    }
+
+    if (cinfo->jpeg_color_space == JCS_YCbCr) {
+        // Import from YUV: must using compatible matrixCoefficients.
+        if ((avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_BT601) ||
+            (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL &&
+             avif->colorPrimaries == AVIF_COLOR_PRIMARIES_BT470M)) {
+            // 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);
+
+                return AVIF_TRUE;
+            }
+
+            // YUV->Grayscale: subsample Y plane not allowed.
+            if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) && (cinfo->comp_info[0].h_samp_factor == cinfo->max_h_samp_factor &&
+                                                                  cinfo->comp_info[0].v_samp_factor == cinfo->max_v_samp_factor)) {
+                cinfo->out_color_space = JCS_YCbCr;
+                avifJPEGCopyPixels(avif, cinfo);
+
+                return AVIF_TRUE;
+            }
+        }
+    } else if (cinfo->jpeg_color_space == JCS_GRAYSCALE) {
+        // Import from Grayscale: subsample not allowed.
+        if ((cinfo->comp_info[0].h_samp_factor == cinfo->max_h_samp_factor &&
+             cinfo->comp_info[0].v_samp_factor == cinfo->max_v_samp_factor)) {
+            // Import to YUV/Grayscale: must using compatible matrixCoefficients.
+            if (((avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_BT601) ||
+                 (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL &&
+                  avif->colorPrimaries == AVIF_COLOR_PRIMARIES_BT470M))) {
+                // Grayscale->Grayscale: direct copy.
+                if (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
+                    cinfo->out_color_space = JCS_GRAYSCALE;
+                    avifJPEGCopyPixels(avif, cinfo);
+
+                    return AVIF_TRUE;
+                }
+
+                // Grayscale->YUV: copy Y, fill UV with monochrome value.
+                if ((avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV444) || (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV422) ||
+                    (avif->yuvFormat == AVIF_PIXEL_FORMAT_YUV420)) {
+                    cinfo->out_color_space = JCS_GRAYSCALE;
+                    avifJPEGCopyPixels(avif, cinfo);
+
+                    avifPixelFormatInfo info;
+                    avifGetPixelFormatInfo(avif->yuvFormat, &info);
+                    uint32_t uvHeight = (avif->height + info.chromaShiftY) >> info.chromaShiftY;
+                    memset(avif->yuvPlanes[AVIF_CHAN_U], 128, avif->yuvRowBytes[AVIF_CHAN_U] * uvHeight);
+                    memset(avif->yuvPlanes[AVIF_CHAN_V], 128, avif->yuvRowBytes[AVIF_CHAN_V] * uvHeight);
+
+                    return AVIF_TRUE;
+                }
+            }
+
+            // Grayscale->RGB: copy Y to G, duplicate to R and B.
+            if (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY) {
+                cinfo->out_color_space = JCS_GRAYSCALE;
+                avifJPEGCopyPixels(avif, cinfo);
+
+                memcpy(avif->yuvPlanes[AVIF_CHAN_U], avif->yuvPlanes[AVIF_CHAN_Y], avif->yuvRowBytes[AVIF_CHAN_U] * avif->height);
+                memcpy(avif->yuvPlanes[AVIF_CHAN_V], avif->yuvPlanes[AVIF_CHAN_Y], avif->yuvRowBytes[AVIF_CHAN_V] * avif->height);
+
+                return AVIF_TRUE;
+            }
+        }
+    } else if (cinfo->jpeg_color_space == JCS_RGB) {
+        // RGB->RGB: subsample not allowed.
+        if (avif->matrixCoefficients == AVIF_MATRIX_COEFFICIENTS_IDENTITY &&
+            (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)) {
+            cinfo->out_color_space = JCS_RGB;
+            avifJPEGCopyPixels(avif, cinfo);
+
+            return AVIF_TRUE;
+        }
+    }
+
+    return AVIF_FALSE;
+}
+
 // Note on setjmp() and volatile variables:
 //
 // K & R, The C Programming Language 2nd Ed, p. 254 says:
@@ -66,11 +239,6 @@
     setup_read_icc_profile(&cinfo);
     jpeg_stdio_src(&cinfo, f);
     jpeg_read_header(&cinfo, TRUE);
-    cinfo.out_color_space = JCS_RGB;
-    jpeg_start_decompress(&cinfo);
-
-    int row_stride = cinfo.output_width * cinfo.output_components;
-    JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
 
     uint8_t * iccDataTmp;
     unsigned int iccDataLen;
@@ -79,6 +247,25 @@
         avifImageSetProfileICC(avif, iccDataTmp, (size_t)iccDataLen);
     }
 
+    avif->yuvFormat = requestedFormat;
+    avif->depth = requestedDepth ? requestedDepth : 8;
+    // JPEG doesn't have alpha. Prevent confusion.
+    avif->alphaPremultiplied = AVIF_FALSE;
+
+    if (avifJPEGReadCopy(avif, &cinfo)) {
+        ret = AVIF_TRUE;
+        goto cleanup;
+    }
+
+    cinfo.out_color_space = JCS_RGB;
+    jpeg_start_decompress(&cinfo);
+
+    avif->width = cinfo.output_width;
+    avif->height = cinfo.output_height;
+
+    int row_stride = cinfo.output_width * cinfo.output_components;
+    JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
+
     avif->width = cinfo.output_width;
     avif->height = cinfo.output_height;
     avif->yuvFormat = requestedFormat;