Skip to content

Commit

Permalink
plug-ins: new JPEG export option to value file size over encoding speed.
Browse files Browse the repository at this point in the history
This uses MozJPEG from Mozilla and is dependent on this patch to be merged:
mozilla/mozjpeg#383
This is an optional option (standard libjpeg API, e.g. with jpeg-turbo, is still
used when MozJPEG is not detected at build time).

When using MozJPEG, we have access to both the standard algorithm used by
libjpeg-turbo, since MozJPEG is a patched version of libjpeg-turbo, and their
own encoding algorithm. We can switch from one to another with a single call.

Here is what the maintainer says about MozJPEG goal:

> The point of MozJPEG is to improve quality/filesize ratio. It's a win-win: you
> get better quality for the same file size, or better file size for the same
> quality, or both. There is no downside in either quality or file size. MozJPEG
> tunes for these two aspects over speed. libjpeg-turbo's maintainer values speed
> over the other two variables.
>
> MozJPEG has a few techniques. Improved splitting of progressive scans gives
> smaller file size while being 100% visually identical with libjpeg-turbo.
>
> But MozJPEG also has trellis quantization and tuned quantization tables that
> give better visual quality, but on a microscopic scale they make different
> choices than libjpeg-turbo, so some pixels differ. The differences are
> relatively small and predictable, so there's no risk of unexpectedly ruining
> an image (especially that on average, you get better quality).

Cf. mozilla/mozjpeg#382 (comment)

Note that after several testing, I could indeed confirm that it seems to always
produce smaller files (as far as my testing went) for similarly looking quality,
but the speed cost can actually be quite important: on my computer, for some
random files where encoding would take 0.7 second, it took 3.5 secs with
mozjpeg; for much bigger file (~25MiB) where export with jpeg-turbo takes about
3.9 secs, it takes 30+ seconds with MozJPEG which is a huge difference and can
be very frustrating.
For small files only, this is less of a problem (I still timed an important
difference, but from 0.05 to 0.15 secs is actually bearable).

This is why this cannot be an option checked by default.

About naming: I hesitated to call it "Export for Web" because it's clearly one
of the big use cases (optimizing file size for images on websites), but I just
decided to go with a much more explicit name (even though it may resonate less
that the basic "Export for Web" which everyone asks for).
  • Loading branch information
Jehan committed Sep 7, 2023
1 parent 5e399eb commit 9ff73dc
Show file tree
Hide file tree
Showing 5 changed files with 42 additions and 4 deletions.
9 changes: 8 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,13 @@ libtiff = dependency('libtiff-4', version: '>=' + libtiff_minver)
MIMEtypes += 'image/tiff'


libjpeg = dependency('libjpeg')
mozjpeg = dependency('mozjpeg', required: get_option('mozjpeg'))
if mozjpeg.found()
libjpeg = mozjpeg
else
libjpeg = dependency('libjpeg')
endif
conf.set('HAVE_MOZJPEG', mozjpeg.found())
MIMEtypes += 'image/jpeg'


Expand Down Expand Up @@ -1952,6 +1958,7 @@ final_message = [
''' Detailed backtraces: @0@'''.format(detailed_backtraces),
''' Binary symlinks: @0@'''.format(enable_default_bin),
''' OpenMP: @0@'''.format(have_openmp),
''' MozJPEG: @0@'''.format(mozjpeg.found()),
'',
'''Optional Plug-Ins:''',
''' Ascii Art: @0@'''.format(libaa.found()),
Expand Down
1 change: 1 addition & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ option('ilbm', type: 'feature', value: 'auto', description: 'Amiga
option('jpeg2000', type: 'feature', value: 'auto', description: 'Jpeg-2000 support')
option('jpeg-xl', type: 'feature', value: 'auto', description: 'JPEG XL support')
option('mng', type: 'feature', value: 'auto', description: 'Mng support')
option('mozjpeg', type: 'feature', value: 'auto', description: 'Build JPEG support with mozjpeg specifically')
option('openexr', type: 'feature', value: 'auto', description: 'Openexr support')
option('openmp', type: 'feature', value: 'auto', description: 'OpenMP support')
option('print', type: 'boolean', value: true, description: 'Print support')
Expand Down
24 changes: 24 additions & 0 deletions plug-ins/file-jpeg/jpeg-save.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ save_image (GFile *file,
GimpImage *image,
GimpDrawable *drawable,
GimpImage *orig_image,
GimpRunMode run_mode,
gboolean preview,
GError **error)
{
Expand Down Expand Up @@ -236,6 +237,7 @@ save_image (GFile *file,
gint orig_num_quant_tables = -1;
gboolean use_arithmetic_coding = FALSE;
gboolean use_restart = FALSE;
gboolean mozjpeg = FALSE;
gchar *comment;

g_object_get (config,
Expand All @@ -248,6 +250,7 @@ save_image (GFile *file,
"baseline", &baseline,
"restart", &restart,
"dct", &dct,
"use-mozjpeg", &mozjpeg,

/* Original quality settings. */
"use-original-quality", &use_orig_quality,
Expand All @@ -262,6 +265,16 @@ save_image (GFile *file,

NULL);

if (run_mode == GIMP_RUN_NONINTERACTIVE && mozjpeg)
{
#ifndef HAVE_MOZJPEG
g_set_error_literal (error, GIMP_PLUG_IN_ERROR, 0,
_("GIMP was not compiled with MozJPEG. "
"The argument 'use-mozjpeg' cannot be set to TRUE."));
return FALSE;
#endif
}

quality = (gint) (dquality * 100.0 + 0.5);

drawable_type = gimp_drawable_type (drawable);
Expand Down Expand Up @@ -489,6 +502,13 @@ save_image (GFile *file,
drawable_type == GIMP_RGBA_IMAGE)
? JCS_RGB : JCS_GRAYSCALE;
}

#ifdef HAVE_MOZJPEG
if (! mozjpeg)
/* Disable mozjpeg code path (upstream jpeg-turbo normal algorithm). */
jpeg_c_set_int_param (&cinfo, JINT_COMPRESS_PROFILE, JCP_FASTEST);
#endif

/* Now use the library's routine to set default compression parameters.
* (You must set at least cinfo.in_color_space before calling this,
* since the defaults depend on the source color space.)
Expand Down Expand Up @@ -795,6 +815,7 @@ make_preview (GimpProcedureConfig *config)
preview_image,
drawable_global,
orig_image_global,
GIMP_RUN_NONINTERACTIVE,
TRUE, NULL);

g_object_unref (file);
Expand Down Expand Up @@ -995,6 +1016,9 @@ save_dialog (GimpProcedure *procedure,
"restart-frame",
"sub-sampling",
"dct",
#ifdef HAVE_MOZJPEG
"use-mozjpeg",
#endif
NULL);
gimp_procedure_dialog_fill_frame (GIMP_PROCEDURE_DIALOG (dialog),
"advanced-frame", "advanced-title", FALSE,
Expand Down
3 changes: 2 additions & 1 deletion plug-ins/file-jpeg/jpeg-save.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ extern GimpDrawable *drawable_global;


gboolean save_image (GFile *file,
GimpProcedureConfig *config,
GimpProcedureConfig *config,
GimpImage *image,
GimpDrawable *drawable,
GimpImage *orig_image,
GimpRunMode run_mode,
gboolean preview,
GError **error);
gboolean save_dialog (GimpProcedure *procedure,
Expand Down
9 changes: 7 additions & 2 deletions plug-ins/file-jpeg/jpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,11 @@ jpeg_create_procedure (GimpPlugIn *plug_in,
_("Use restart mar_kers"),
NULL, FALSE,
G_PARAM_READWRITE);
GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "use-mozjpeg",
_("Value file size over encoding speed (using MozJPEG)"),
_("Use MozJPEG optimizations for better quality/filesize ratio, with slower encoding."),
FALSE,
G_PARAM_READWRITE);

gimp_save_procedure_set_support_exif (GIMP_SAVE_PROCEDURE (procedure), TRUE);
gimp_save_procedure_set_support_iptc (GIMP_SAVE_PROCEDURE (procedure), TRUE);
Expand Down Expand Up @@ -597,8 +602,8 @@ jpeg_save (GimpProcedure *procedure,
if (status == GIMP_PDB_SUCCESS)
{
if (! save_image (file, config,
image, drawables[0], orig_image, FALSE,
&error))
image, drawables[0], orig_image,
run_mode, FALSE, &error))
{
status = GIMP_PDB_EXECUTION_ERROR;
}
Expand Down

0 comments on commit 9ff73dc

Please sign in to comment.