GraphicsMagick: Support writing lossy/lossless 12 bits, and loss...
GraphicsMagick Commits <[email protected]> Sun, 11 Feb 2024 14:28:25 -0600
| Newsgroups | gmane.comp.video.graphicsmagick.cvs |
|---|---|
| Message-ID | <mailman.10894.1707683316.16032.graphicsmagick-commit@lists.sourceforge.net> |
changeset f232b4698ba9 in /hg/GraphicsMagick details: http://hg.GraphicsMagick.org/hg/GraphicsMagick?cmd=changeset;node=f232b4698ba9 summary: Support writing lossy/lossless 12 bits, and lossless 16-bit JPEG using libjpeg-turbo-3.0 diffstat: ChangeLog | 14 + VisualMagick/installer/inc/version.isx | 4 +- coders/jpeg.c | 570 +++++++++++++++++--------------- configure | 5 +- configure.ac | 5 +- doc/options.imdoc | 27 + magick/enum_strings.c | 4 +- magick/magick_config.h.in | 3 + magick/version.h | 4 +- utilities/gm.1 | 37 ++- www/ChangeLog.html | 15 + www/GraphicsMagick.html | 20 + www/gm.html | 20 + 13 files changed, 456 insertions(+), 272 deletions(-) diffs (truncated from 969 to 500 lines): diff -r 0bdf6df7faf2 -r f232b4698ba9 ChangeLog --- a/ChangeLog Sat Feb 10 14:54:39 2024 -0600 +++ b/ChangeLog Sun Feb 11 14:28:22 2024 -0600 @@ -1,3 +1,17 @@ +2024-02-11 Bob Friesenhahn <[email protected]> + + * magick/enum_strings.c (StringToCompressionType): Recognize + "LosslessJPEG" as indicating LosslessJPEGCompression. + + * coders/jpeg.c (WriteJPEGImage): Added support for writing lossy + or lossless 12 bits, and lossless 16-bits using libjpeg-turbo-3.0. + Added the -define options 'jpeg:data-precision', + 'jpeg:lossless-precision', and 'jpeg:lossless-predictor', to + provide control over the parameters used. Please note that most + applications do not support lossless JPEG compression (and only + support a sample size of 8) so take care not to use these features + if it not known that all involved software can support them! + 2024-02-10 Bob Friesenhahn <[email protected]> * coders/jpeg.c (WriteJPEGImage): Do some preparation to implement diff -r 0bdf6df7faf2 -r f232b4698ba9 VisualMagick/installer/inc/version.isx --- a/VisualMagick/installer/inc/version.isx Sat Feb 10 14:54:39 2024 -0600 +++ b/VisualMagick/installer/inc/version.isx Sun Feb 11 14:28:22 2024 -0600 @@ -10,5 +10,5 @@ #define public MagickPackageName "GraphicsMagick" #define public MagickPackageVersion "1.4" -#define public MagickPackageVersionAddendum ".020240210" -#define public MagickPackageReleaseDate "snapshot-20240210" +#define public MagickPackageVersionAddendum ".020240211" +#define public MagickPackageReleaseDate "snapshot-20240211" diff -r 0bdf6df7faf2 -r f232b4698ba9 coders/jpeg.c --- a/coders/jpeg.c Sat Feb 10 14:54:39 2024 -0600 +++ b/coders/jpeg.c Sun Feb 11 14:28:22 2024 -0600 @@ -1129,15 +1129,10 @@ return save_quality; } -/* - Format JPEG color space to a string. -*/ -static void -FormatJPEGColorSpace(const J_COLOR_SPACE colorspace, - char *colorspace_name) +static const char *JPEGColorSpaceToString(const J_COLOR_SPACE colorspace) { const char - *s = NULL; + *s; switch (colorspace) { @@ -1161,7 +1156,17 @@ s = "YCCK"; break; } - (void) strlcpy(colorspace_name,s,MaxTextExtent); + return s; +} + +/* + Format JPEG color space to a string buffer of length MaxTextExtent. +*/ +static void +FormatJPEGColorSpace(const J_COLOR_SPACE colorspace, + char *colorspace_name) +{ + (void) strlcpy(colorspace_name,JPEGColorSpaceToString(colorspace),MaxTextExtent); } /* @@ -2661,9 +2666,6 @@ magick_jpeg_pixels_t jpeg_pixels; /* Contents freed by FreeMagickClientData() */ - JSAMPROW - scanline[1]; - char *sampling_factors, *preserve_settings; @@ -2677,9 +2679,6 @@ register const PixelPacket *p; - register JSAMPLE - *q; - register long x; @@ -2744,6 +2743,7 @@ */ if ((value=AccessDefinition(image_info,"jpeg","max-warnings"))) client_data->max_warning_count=strtol(value,(char **) NULL, 10); + client_data->jpeg_pixels = &jpeg_pixels; jpeg_info.client_data=(void *) client_data; if (setjmp(client_data->error_recovery) != 0) { @@ -2917,14 +2917,6 @@ jpeg_set_defaults(&jpeg_info); -#if defined(HAVE_JPEG12_WRITE_SCANLINES) && HAVE_JPEG12_WRITE_SCANLINES -#endif /* if defined(HAVE_JPEG12_WRITE_SCANLINES) && HAVE_JPEG12_WRITE_SCANLINES */ - -#if defined(HAVE_JPEG16_WRITE_SCANLINES) && HAVE_JPEG16_WRITE_SCANLINES -#if defined(HAVE_JPEG_ENABLE_LOSSLESS) && HAVE_JPEG_ENABLE_LOSSLESS -#endif /* if defined(HAVE_JPEG_ENABLE_LOSSLESS) && HAVE_JPEG_ENABLE_LOSSLESS */ -#endif /* if defined(HAVE_JPEG16_WRITE_SCANLINES) && HAVE_JPEG16_WRITE_SCANLINES */ - /* Determine bit depth (valid range in 8-16). */ @@ -2940,6 +2932,38 @@ sample_size=8; jpeg_info.data_precision=sample_size; } + + /* + Allow the user to set/override the data precision (8/12/16) + */ + if ((value=AccessDefinition(image_info,"jpeg","data-precision"))) + { + unsigned int data_precision_prop = 0; + if (sscanf(value,"%u",&data_precision_prop) == 1) + { + switch(data_precision_prop) + { + default: + case 8: + jpeg_info.data_precision=8; + break; +#if defined(HAVE_JPEG12_WRITE_SCANLINES) && HAVE_JPEG12_WRITE_SCANLINES + case 12: + jpeg_info.data_precision=12; + break; +#endif /* if defined(HAVE_JPEG12_WRITE_SCANLINES) && HAVE_JPEG12_WRITE_SCANLINES */ +#if defined(HAVE_JPEG16_WRITE_SCANLINES) && HAVE_JPEG16_WRITE_SCANLINES +#if defined(HAVE_JPEG_ENABLE_LOSSLESS) && HAVE_JPEG_ENABLE_LOSSLESS +#if defined(C_LOSSLESS_SUPPORTED) + case 16: + jpeg_info.data_precision=16; + break; +#endif /* if defined(C_LOSSLESS_SUPPORTED) */ +#endif /* if defined(HAVE_JPEG_ENABLE_LOSSLESS) && HAVE_JPEG_ENABLE_LOSSLESS */ +#endif /* if defined(HAVE_JPEG16_WRITE_SCANLINES) && HAVE_JPEG16_WRITE_SCANLINES */ + } + } + } if ((image->x_resolution == 0) || (image->y_resolution == 0)) { image->x_resolution=72.0; @@ -3006,7 +3030,7 @@ huffman_memory = 0; -#ifdef C_ARITH_CODING_SUPPORTED +#if defined(C_ARITH_CODING_SUPPORTED) && C_ARITH_CODING_SUPPORTED /* Allow the user to turn on/off arithmetic coder. */ @@ -3018,7 +3042,7 @@ jpeg_info.arith_code = True; } if (!jpeg_info.arith_code) /* jpeg_info.optimize_coding must not be set to enable arithmetic. */ -#endif +#endif /* if defined(C_ARITH_CODING_SUPPORTED) && C_ARITH_CODING_SUPPORTED */ { if ((value=AccessDefinition(image_info,"jpeg","optimize-coding"))) { @@ -3066,38 +3090,61 @@ (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Interlace: nonprogressive"); #endif - if ((image->compression == LosslessJPEGCompression) || - (quality > 100)) + if (image->logging) + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "Compression: %s", + CompressionTypeToString(image->compression)); + if (image->compression == LosslessJPEGCompression) { -#if !defined(LIBJPEG_TURBO_VERSION_NUMBER) && defined(C_LOSSLESS_SUPPORTED) - if (quality < 100) - ThrowException(&image->exception,CoderWarning, - LosslessToLossyJPEGConversion,(char *) NULL); - else +#if defined(C_LOSSLESS_SUPPORTED) { int point_transform, predictor; - predictor=quality/100; /* range 1-7 */ - point_transform=quality % 20; /* range 0-15 */ - jpeg_simple_lossless(&jpeg_info,predictor,point_transform); + predictor=1; /* range 1-7 */ + point_transform=0; /* range 0 to precision-1 */ + + /* + Right-shift the input samples by the specified number of + bits as a form of color quantization. Useful range of 0 + to precision-1. Use zero for true lossless compression! + */ + if ((value=AccessDefinition(image_info,"jpeg","lossless-precision"))) + { + int point_transform_v = 0; + if ((sscanf(value,"%u",&point_transform_v) == 1) && (point_transform_v >= 0)) + point_transform = point_transform_v; + } + + if ((value=AccessDefinition(image_info,"jpeg","lossless-predictor"))) + { + int predictor_v = predictor; + if ((sscanf(value,"%u",&predictor_v) == 1) && (predictor_v >= 0)) + predictor = predictor_v; + } + if (image->logging) { (void) LogMagickEvent(CoderEvent,GetMagickModule(), - "Compression: lossless"); + "Compression: lossless"); + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "DPCM Predictor: %d",predictor); (void) LogMagickEvent(CoderEvent,GetMagickModule(), - "Predictor: %d",predictor); - (void) LogMagickEvent(CoderEvent,GetMagickModule(), - "Point Transform: %d",point_transform); + "DPCM Point Transform: %d",point_transform); } +#if !defined(LIBJPEG_TURBO_VERSION_NUMBER) + jpeg_simple_lossless(&jpeg_info, predictor, point_transform); +#elif defined(LIBJPEG_TURBO_VERSION_NUMBER) + jpeg_enable_lossless(&jpeg_info, predictor, point_transform); +#endif } #else - { - jpeg_set_quality(&jpeg_info,100,True); - if (image->logging) - (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: 100"); - } + { + jpeg_set_quality(&jpeg_info,100,True); + if (image->logging) + (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: 100"); + } #endif } else @@ -3105,8 +3152,9 @@ jpeg_set_quality(&jpeg_info,(int) quality,True); if (image->logging) (void) LogMagickEvent(CoderEvent,GetMagickModule(),"Quality: %lu", - quality); + quality); } + if (sampling_factors != (char *) NULL) { double @@ -3269,8 +3317,39 @@ /* Convert MIFF to JPEG raster pixels. */ - jpeg_pixels.t.j=MagickAllocateResourceLimitedArray(JSAMPLE *, - (size_t)jpeg_info.input_components*image->columns,sizeof(JSAMPLE)); + switch (jpeg_info.data_precision) + { +#if defined(HAVE_JPEG16_WRITE_SCANLINES) && HAVE_JPEG16_WRITE_SCANLINES +#if defined(HAVE_JPEG_ENABLE_LOSSLESS) && HAVE_JPEG_ENABLE_LOSSLESS +#if defined(C_LOSSLESS_SUPPORTED) + case 16: + jpeg_pixels.t.j16 = + MagickAllocateResourceLimitedClearedArray(J16SAMPLE *, + jpeg_info.input_components, + MagickArraySize(image->columns, + sizeof(J16SAMPLE))); + break; +#endif /* if defined(C_LOSSLESS_SUPPORTED) */ +#endif /* if defined(HAVE_JPEG_ENABLE_LOSSLESS) && HAVE_JPEG_ENABLE_LOSSLESS */ +#endif /* if defined(HAVE_JPEG16_WRITE_SCANLINES) && HAVE_JPEG16_WRITE_SCANLINES */ +#if defined(HAVE_JPEG12_WRITE_SCANLINES) && HAVE_JPEG12_WRITE_SCANLINES + case 12: + jpeg_pixels.t.j12 = + MagickAllocateResourceLimitedClearedArray(J12SAMPLE *, + jpeg_info.input_components, + MagickArraySize(image->columns, + sizeof(J12SAMPLE))); + break; +#endif /* if defined(HAVE_JPEG12_WRITE_SCANLINES) && HAVE_JPEG12_WRITE_SCANLINES */ + default: + { + jpeg_pixels.t.j= + MagickAllocateResourceLimitedArray(JSAMPLE *, + jpeg_info.input_components, + MagickArraySize(image->columns, + sizeof(JSAMPLE))); + } + } if (jpeg_pixels.t.v == (JSAMPLE *) NULL) { if (huffman_memory) @@ -3278,233 +3357,200 @@ ThrowJPEGWriterException(ResourceLimitError,MemoryAllocationFailed,image); } client_data->jpeg_pixels = &jpeg_pixels; - scanline[0]=(JSAMPROW) jpeg_pixels.t.j; - if (jpeg_info.data_precision > 8 && jpeg_info.data_precision <= 16) - { /* Deep pixels */ - unsigned int - scale_short; - - scale_short=65535U/MaxValueGivenBits(jpeg_info.data_precision); - - if (jpeg_info.in_color_space == JCS_GRAYSCALE) - { /* Start deep JCS_GRAYSCALE */ - if (image->logging) - (void) LogMagickEvent(CoderEvent,GetMagickModule(), - "Writing %d bit JCS_GRAYSCALE samples", - jpeg_info.data_precision); - for (y=0; y < (long) image->rows; y++) - { - p=AcquireImagePixels(image,0,y,image->columns,1, - &image->exception); - if (p == (const PixelPacket *) NULL) - break; - q=jpeg_pixels.t.j; - if (image->is_grayscale) - { - for (x=0; x < (long) image->columns; x++) - { - *q++=(JSAMPLE) (ScaleQuantumToShort(GetGraySample(p))/ - scale_short); - p++; - } - } - else - { - for (x=0; x < (long) image->columns; x++) - { - *q++=(JSAMPLE) - (ScaleQuantumToShort(PixelIntensityToQuantum(p))/scale_short); - p++; - } - } - (void) jpeg_write_scanlines(&jpeg_info,scanline,1); -#if !USE_LIBJPEG_PROGRESS - if (QuantumTick(y,image->rows)) - if (!MagickMonitorFormatted(y,image->rows,&image->exception, - SaveImageText,image->filename, - image->columns,image->rows)) - break; -#endif /* !USE_LIBJPEG_PROGRESS */ - } - } /* End deep JCS_GRAYSCALE */ - else - if ((jpeg_info.in_color_space == JCS_RGB) || - (jpeg_info.in_color_space == JCS_YCbCr)) - { /* Start deep JCS_RGB || JCS_YCbCr */ - if (image->logging) - (void) LogMagickEvent(CoderEvent,GetMagickModule(), - "Writing %d bit JCS_RGB or " - "JCS_YCbCr samples", - jpeg_info.data_precision); - for (y=0; y < (long) image->rows; y++) - { - p=AcquireImagePixels(image,0,y,image->columns,1, - &image->exception); - if (p == (const PixelPacket *) NULL) - break; - q=jpeg_pixels.t.j; + + if (image->logging) + (void) LogMagickEvent(CoderEvent,GetMagickModule(), + "Writing %u bit %s samples...", + jpeg_info.data_precision, + JPEGColorSpaceToString(jpeg_info.in_color_space)); + + for (y=0; y < (long) image->rows; y++) + { + p=AcquireImagePixels(image,0,y,image->columns,1,&image->exception); + if (p == (const PixelPacket *) NULL) + break; + +#if defined(HAVE_JPEG16_WRITE_SCANLINES) && HAVE_JPEG16_WRITE_SCANLINES +#if defined(HAVE_JPEG_ENABLE_LOSSLESS) && HAVE_JPEG_ENABLE_LOSSLESS +#if defined(C_LOSSLESS_SUPPORTED) + if (jpeg_info.data_precision == 16) + { + { + J16SAMPROW + scanline[1]; + + if (jpeg_info.in_color_space == JCS_GRAYSCALE) + { /* Start deep JCS_GRAYSCALE */ + if (image->is_grayscale) + { + i=0; + for (x=0; x < (long) image->columns; x++) + { + jpeg_pixels.t.j16[i++]=(J16SAMPLE)(ScaleQuantumToShort(GetGraySample(p))); + p++; + } + } + else + { + i=0; + for (x=0; x < (long) image->columns; x++) + { + jpeg_pixels.t.j16[i++] = (J16SAMPLE)(ScaleQuantumToShort(PixelIntensityToQuantum(p))); + p++; + } + } + } /* End deep JCS_GRAYSCALE */ + else if ((jpeg_info.in_color_space == JCS_RGB) || + (jpeg_info.in_color_space == JCS_YCbCr)) + { /* Start deep JCS_RGB || JCS_YCbCr */ + i=0; for (x=0; x < (long) image->columns; x++) { - *q++=(JSAMPLE) (ScaleQuantumToShort(p->red)/16); - *q++=(JSAMPLE) (ScaleQuantumToShort(p->green)/16); - *q++=(JSAMPLE) (ScaleQuantumToShort(p->blue)/16); + jpeg_pixels.t.j16[i++] = (J16SAMPLE)(ScaleQuantumToShort(p->red)); + jpeg_pixels.t.j16[i++] = (J16SAMPLE)(ScaleQuantumToShort(p->green)); + jpeg_pixels.t.j16[i++] = (J16SAMPLE)(ScaleQuantumToShort(p->blue)); p++; } - (void) jpeg_write_scanlines(&jpeg_info,scanline,1); -#if !USE_LIBJPEG_PROGRESS - if (QuantumTick(y,image->rows)) - if (!MagickMonitorFormatted(y,image->rows,&image->exception, - SaveImageText,image->filename, - image->columns,image->rows)) - break; -#endif /* !USE_LIBJPEG_PROGRESS */ - } - } /* End deep JCS_RGB || JCS_YCbCr */ - else - { /* Start deep JCS_CMYK */ - if (image->logging) - (void) LogMagickEvent(CoderEvent,GetMagickModule(), - "Writing %d bit JCS_CMYK samples", - jpeg_info.data_precision); - for (y=0; y < (long) image->rows; y++) - { - p=AcquireImagePixels(image,0,y,image->columns,1, - &image->exception); - if (p == (const PixelPacket *) NULL) - break; - q=jpeg_pixels.t.j; + } /* End deep JCS_RGB || JCS_YCbCr */ + else if (jpeg_info.in_color_space == JCS_CMYK) + { /* Start deep JCS_CMYK */ + i=0; for (x=0; x < (long) image->columns; x++) { - /* - Convert DirectClass packets to contiguous CMYK scanlines. - */ - *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->red)/16); - *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->green)/16); - *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->blue)/16); - *q++=(JSAMPLE) (4095-ScaleQuantumToShort(p->opacity)/16); + jpeg_pixels.t.j16[i++] = (J16SAMPLE)(ScaleQuantumToShort(p->red)); + jpeg_pixels.t.j16[i++] = (J16SAMPLE)(ScaleQuantumToShort(p->green)); + jpeg_pixels.t.j16[i++] = (J16SAMPLE)(ScaleQuantumToShort(p->blue)); + jpeg_pixels.t.j16[i++] = (J16SAMPLE)(ScaleQuantumToShort(p->opacity)); p++; } - (void) jpeg_write_scanlines(&jpeg_info,scanline,1); + } /* End deep JCS_CMYK */ + + scanline[0]=(J16SAMPROW) jpeg_pixels.t.j16; + (void) jpeg16_write_scanlines(&jpeg_info,scanline,1); + } + } else +#endif /* if defined(C_LOSSLESS_SUPPORTED) */ +#endif /* if defined(HAVE_JPEG_ENABLE_LOSSLESS) && HAVE_JPEG_ENABLE_LOSSLESS */ +#endif /* if defined(HAVE_JPEG16_WRITE_SCANLINES) && HAVE_JPEG16_WRITE_SCANLINES */ + +#if defined(HAVE_JPEG12_WRITE_SCANLINES) && HAVE_JPEG12_WRITE_SCANLINES + if (jpeg_info.data_precision == 12) + { + J12SAMPROW + scanline[1]; + + if (jpeg_info.in_color_space == JCS_GRAYSCALE) + { /* Start deep JCS_GRAYSCALE */ + if (image->is_grayscale) + { + i=0; + for (x=0; x < (long) image->columns; x++) + { + jpeg_pixels.t.j12[i++] = (J12SAMPLE)(ScaleQuantumToShort(GetGraySample(p))/16); + p++;