Commit 3044c6c2f for imagemagick.org

commit 3044c6c2f0d253b22f786dcba66844e8f4934339
Author: DRC <information@virtualgl.org>
Date:   Sun Nov 16 11:53:20 2025 -0500

    JPEG: Support 2-to-16-bit lossless JPEG images with libjpeg-turbo 3.1+; Support additional IDCT scaling factors with libjpeg-turbo and libjpeg v7+ (#8445)

    * Support additional JPEG scaling factors w/ libjpeg-turbo or libjpeg v7+

    * Support 12-bit lossy and 2-bit to 16-bit lossless JPEG images

    - If compiled with libjpeg-turbo 3.1.x or later and lossless JPEG
      compression is selected, directly encode pixel data with 2 to 16 bits
      per channel into a lossless JPEG image with the same data precision,
      rather than upconverting to an 8-bit-per-sample, 12-bit-per-sample, or
      16-bit-per-sample lossless JPEG image.  (If compiled with
      libjpeg-turbo 3.0.x, upconverting is still performed.)

    - If compiled with libjpeg-turbo 3.1.x or later, directly decode
      lossless JPEG images with 2 to 16 bits per sample into pixel data with
      the same data precision, rather than upconverting to
      8-bit-per-channel, 12-bit-per-channel, or 16-bit-per-channel pixel
      data.

    - If compiled with libjpeg-turbo 3.0.x or later and lossy JPEG
      compression is selected, upconvert or downconvert pixel data with 9 to
      16 bits per channel into a 12-bit-per-sample lossy JPEG image.

    - If compiled with libjpeg-turbo 2.1.x or earlier (or libjpeg),
      upconvert or downconvert pixel data with 2 to 16 bits per channel into
      an 8-bit-per-sample lossy JPEG image.

diff --git a/coders/jpeg.c b/coders/jpeg.c
index bd20cd2b9..f917f3e64 100644
--- a/coders/jpeg.c
+++ b/coders/jpeg.c
@@ -256,12 +256,23 @@ static MagickBooleanType IsJPEG(const unsigned char *magick,const size_t length)
 %
 */

-static inline int JPEGGetSample(const struct jpeg_decompress_struct *jpeg_info,
-  const JSAMPLE *p)
+static inline Quantum JPEGGetQuantum(
+  const struct jpeg_decompress_struct *jpeg_info,const JSAMPLE *p)
 {
-  if (jpeg_info->data_precision > 8)
-    return((int) (*(unsigned short *) p));
-  return((int) (*(JSAMPLE *) p));
+  if (jpeg_info->data_precision == 8)
+    return ScaleCharToQuantum(*p);
+  else if (jpeg_info->data_precision == 16)
+    return ScaleShortToQuantum(*(unsigned short *) p);
+  else if (jpeg_info->data_precision < 8)
+    {
+      QuantumAny range=GetQuantumRange(jpeg_info->data_precision);
+      return ScaleAnyToQuantum(*p,range);
+    }
+  else
+    {
+      QuantumAny range=GetQuantumRange(jpeg_info->data_precision);
+      return ScaleAnyToQuantum(*(unsigned short *) p,range);
+    }
 }

 static boolean FillInputBuffer(j_decompress_ptr compress_info)
@@ -1093,7 +1104,6 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,

   ssize_t
     i,
-    scale,
     y;

   struct jpeg_error_mgr
@@ -1224,8 +1234,17 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
       if ((geometry_info.sigma != 0.0) &&
           (scale_factor > (jpeg_info->output_height/geometry_info.sigma)))
         scale_factor=jpeg_info->output_height/geometry_info.sigma;
+#if defined(LIBJPEG_TURBO_VERSION_NUMBER) || (JPEG_LIB_VERSION >= 70)
+      jpeg_info->scale_num=(unsigned int) (8.0/scale_factor+0.5);
+      if (jpeg_info->scale_num > 16U)
+        jpeg_info->scale_num=16U;
+      if (jpeg_info->scale_num < 1U)
+        jpeg_info->scale_num=1U;
+      jpeg_info->scale_denom=8U;
+#else
       jpeg_info->scale_num=1U;
       jpeg_info->scale_denom=(unsigned int) scale_factor;
+#endif
       jpeg_calc_output_dimensions(jpeg_info);
       if (image->debug != MagickFalse)
         (void) LogMagickEvent(CoderEvent,GetMagickModule(),
@@ -1447,8 +1466,6 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
           image->colormap[i].alpha=(MagickRealType) OpaqueAlpha;
         }
     }
-  scale=65535U/(unsigned int) GetQuantumRange((size_t)
-    jpeg_info->data_precision);
   for (y=0; y < (ssize_t) image->rows; y++)
   {
     JDIMENSION
@@ -1463,31 +1480,25 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
     /*
       Uncompress one JPEG scanline.
     */
-    switch (jpeg_info->data_precision)
-    {
-      case 16:
+    if (jpeg_info->data_precision > 12)
       {
 #if defined(MAGICKCORE_HAVE_JPEG16_READ_SCANLINES)
         number_scanlines=jpeg16_read_scanlines(jpeg_info,(J16SAMPROW *)
           &jpeg_pixels,1);
-        break;
 #endif
       }
-      case 12:
+    else if (jpeg_info->data_precision > 8)
       {
 #if defined(MAGICKCORE_HAVE_JPEG12_READ_SCANLINES)
         number_scanlines=jpeg12_read_scanlines(jpeg_info,(J12SAMPROW *)
           &jpeg_pixels,1);
-        break;
 #endif
       }
-      default:
+    else
       {
         number_scanlines=jpeg_read_scanlines(jpeg_info,(JSAMPROW *)
           &jpeg_pixels,1);
-        break;
       }
-    }
     if (number_scanlines != 1)
       (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
         "AnErrorHasOccurredReadingFromFile","`%s'",image->filename);
@@ -1510,8 +1521,12 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
           Quantum
             index;

-          unsigned short
-            pixel = (unsigned short) JPEGGetSample(jpeg_info,p);
+          unsigned short pixel;
+
+          if (jpeg_info->data_precision > 8)
+            pixel=(*(unsigned short *) p);
+          else
+            pixel=(*(JSAMPLE *) p);

           index=(Quantum) ConstrainColormapIndex(image,pixel,exception);
           SetPixelViaPixelInfo(image,image->colormap+(ssize_t) index,q);
@@ -1528,17 +1543,13 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
         */
         for (x=0; x < (ssize_t) image->columns; x++)
         {
-          SetPixelCyan(image,QuantumRange-ScaleShortToQuantum((unsigned short)
-            (scale*JPEGGetSample(jpeg_info,p))),q);
+          SetPixelCyan(image,QuantumRange-JPEGGetQuantum(jpeg_info,p),q);
           p+=(ptrdiff_t) bytes_per_pixel;
-          SetPixelMagenta(image,QuantumRange-ScaleShortToQuantum(
-            (unsigned short) (scale*JPEGGetSample(jpeg_info,p))),q);
+          SetPixelMagenta(image,QuantumRange-JPEGGetQuantum(jpeg_info,p),q);
           p+=(ptrdiff_t) bytes_per_pixel;
-          SetPixelYellow(image,QuantumRange-ScaleShortToQuantum((unsigned short)
-            (scale*JPEGGetSample(jpeg_info,p))),q);
+          SetPixelYellow(image,QuantumRange-JPEGGetQuantum(jpeg_info,p),q);
           p+=(ptrdiff_t) bytes_per_pixel;
-          SetPixelBlack(image,QuantumRange-ScaleShortToQuantum((unsigned short)
-            (scale*JPEGGetSample(jpeg_info,p))),q);
+          SetPixelBlack(image,QuantumRange-JPEGGetQuantum(jpeg_info,p),q);
           p+=(ptrdiff_t) bytes_per_pixel;
           SetPixelAlpha(image,OpaqueAlpha,q);
           q+=(ptrdiff_t) GetPixelChannels(image);
@@ -1552,14 +1563,11 @@ static Image *ReadOneJPEGImage(const ImageInfo *image_info,
         */
         for (x=0; x < (ssize_t) image->columns; x++)
         {
-          SetPixelRed(image,ScaleShortToQuantum((unsigned short) (scale*
-            JPEGGetSample(jpeg_info,p))),q);
+          SetPixelRed(image,JPEGGetQuantum(jpeg_info,p),q);
           p+=(ptrdiff_t) bytes_per_pixel;
-          SetPixelGreen(image,ScaleShortToQuantum((unsigned short) (scale*
-            JPEGGetSample(jpeg_info,p))),q);
+          SetPixelGreen(image,JPEGGetQuantum(jpeg_info,p),q);
           p+=(ptrdiff_t) bytes_per_pixel;
-          SetPixelBlue(image,ScaleShortToQuantum((unsigned short) (scale*
-            JPEGGetSample(jpeg_info,p))),q);
+          SetPixelBlue(image,JPEGGetQuantum(jpeg_info,p),q);
           p+=(ptrdiff_t) bytes_per_pixel;
           SetPixelAlpha(image,OpaqueAlpha,q);
           q+=(ptrdiff_t) GetPixelChannels(image);
@@ -2351,11 +2359,22 @@ static char **SamplingFactorToList(const char *text)
 }

 static inline void JPEGSetSample(const struct jpeg_compress_struct *jpeg_info,
-  const unsigned int scale,const Quantum pixel,JSAMPLE *q)
+  const Quantum pixel,JSAMPLE *q)
 {
-  if (jpeg_info->data_precision > 8)
-    (*(unsigned short *) q)=(unsigned short) (ScaleQuantumToShort(pixel)/scale);
-  *q=(JSAMPLE) ScaleQuantumToChar(pixel);
+  if (jpeg_info->data_precision == 8)
+    *q=(JSAMPLE) ScaleQuantumToChar(pixel);
+  else if (jpeg_info->data_precision == 16)
+    (*(unsigned short *) q)=(unsigned short) ScaleQuantumToShort(pixel);
+  else if (jpeg_info->data_precision < 8)
+    {
+      QuantumAny range=GetQuantumRange(jpeg_info->data_precision);
+      *q=(JSAMPLE) ScaleQuantumToAny(pixel,range);
+    }
+  else
+    {
+      QuantumAny range=GetQuantumRange(jpeg_info->data_precision);
+      (*(unsigned short *) q)=(unsigned short) ScaleQuantumToAny(pixel,range);
+    }
 }

 static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
@@ -2405,9 +2424,6 @@ static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
   struct jpeg_error_mgr
     jpeg_error;

-  unsigned int
-    scale;
-
   /*
     Open image file.
   */
@@ -2473,12 +2489,23 @@ static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
 #if defined(C_LOSSLESS_SUPPORTED)
   if (image_info->compression == LosslessJPEGCompression)
     {
+#if defined(LIBJPEG_TURBO_VERSION_NUMBER) && LIBJPEG_TURBO_VERSION_NUMBER >= 3000090
+      jpeg_info->data_precision=(int) image->depth;
+#else
       if (image->depth > 12)
         jpeg_info->data_precision=16;
       else if (image->depth > 8)
         jpeg_info->data_precision=12;
+#endif
     }
+  else
+#endif
+    {
+#if defined(MAGICKCORE_HAVE_JPEG12_WRITE_SCANLINES)
+      if (image->depth > 8)
+        jpeg_info->data_precision=12;
 #endif
+    }
   jpeg_info->in_color_space=JCS_RGB;
   switch (image->colorspace)
   {
@@ -2990,8 +3017,6 @@ static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
         jps_image=DestroyImage(jps_image);
       return(MagickFalse);
     }
-  scale=65535U/(unsigned short) GetQuantumRange((size_t)
-    jpeg_info->data_precision);
   for (y=0; y < (ssize_t) image->rows; y++)
   {
     const Quantum
@@ -3019,7 +3044,7 @@ static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
         */
         for (x=0; x < (ssize_t) image->columns; x++)
         {
-          JPEGSetSample(jpeg_info,scale,ClampToQuantum(GetPixelLuma(image,p)),
+          JPEGSetSample(jpeg_info,ClampToQuantum(GetPixelLuma(image,p)),
             q);
           q+=(ptrdiff_t) bytes_per_pixel;
           p+=(ptrdiff_t) GetPixelChannels(image);
@@ -3036,16 +3061,16 @@ static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
           /*
             Convert DirectClass packets to contiguous CMYK scanlines.
           */
-          JPEGSetSample(jpeg_info,scale,(Quantum) ((QuantumRange-
+          JPEGSetSample(jpeg_info,(Quantum) ((QuantumRange-
             GetPixelCyan(image,p))),q);
           q+=(ptrdiff_t) bytes_per_pixel;
-          JPEGSetSample(jpeg_info,scale,(Quantum) ((QuantumRange-
+          JPEGSetSample(jpeg_info,(Quantum) ((QuantumRange-
             GetPixelMagenta(image,p))),q);
           q+=(ptrdiff_t) bytes_per_pixel;
-          JPEGSetSample(jpeg_info,scale,(Quantum) ((QuantumRange-
+          JPEGSetSample(jpeg_info,(Quantum) ((QuantumRange-
             GetPixelYellow(image,p))),q);
           q+=(ptrdiff_t) bytes_per_pixel;
-          JPEGSetSample(jpeg_info,scale,(Quantum) ((QuantumRange-
+          JPEGSetSample(jpeg_info,(Quantum) ((QuantumRange-
             GetPixelBlack(image,p))),q);
           q+=(ptrdiff_t) bytes_per_pixel;
           p+=(ptrdiff_t) GetPixelChannels(image);
@@ -3059,11 +3084,11 @@ static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
         */
         for (x=0; x < (ssize_t) image->columns; x++)
         {
-          JPEGSetSample(jpeg_info,scale,GetPixelRed(image,p),q);
+          JPEGSetSample(jpeg_info,GetPixelRed(image,p),q);
           q+=(ptrdiff_t) bytes_per_pixel;
-          JPEGSetSample(jpeg_info,scale,GetPixelGreen(image,p),q);
+          JPEGSetSample(jpeg_info,GetPixelGreen(image,p),q);
           q+=(ptrdiff_t) bytes_per_pixel;
-          JPEGSetSample(jpeg_info,scale,GetPixelBlue(image,p),q);
+          JPEGSetSample(jpeg_info,GetPixelBlue(image,p),q);
           q+=(ptrdiff_t) bytes_per_pixel;
           p+=(ptrdiff_t) GetPixelChannels(image);
         }
@@ -3073,31 +3098,25 @@ static MagickBooleanType WriteJPEGImage_(const ImageInfo *image_info,
     /*
       Compress one JPEG scanline.
     */
-    switch (jpeg_info->data_precision)
-    {
-      case 16:
+    if (jpeg_info->data_precision > 12)
       {
 #if defined(MAGICKCORE_HAVE_JPEG16_WRITE_SCANLINES)
         number_scanlines=jpeg16_write_scanlines(jpeg_info,(J16SAMPROW *)
           &jpeg_pixels,1);
-        break;
 #endif
       }
-      case 12:
+    else if (jpeg_info->data_precision > 8)
       {
 #if defined(MAGICKCORE_HAVE_JPEG12_WRITE_SCANLINES)
         number_scanlines=jpeg12_write_scanlines(jpeg_info,(J12SAMPROW *)
           &jpeg_pixels,1);
-        break;
 #endif
       }
-      default:
+    else
       {
         number_scanlines=jpeg_write_scanlines(jpeg_info,(JSAMPROW *)
           &jpeg_pixels,1);
-        break;
       }
-    }
     if (number_scanlines != 1)
       (void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
         "AnErrorHasOccurredWritingToFile","`%s'",image->filename);