From 8ffa3eac04212ebfbfcdca8aeb1d193006d8acc3 Mon Sep 17 00:00:00 2001 From: Aleksandr Kadykov Date: Sun, 26 Oct 2025 23:07:04 +0100 Subject: [PATCH 1/3] feat(avif): add chroma subsampling control for AVIF export Add a new chroma subsampling option to the AVIF export module, allowing users to manually select subsampling modes (4:4:4, 4:2:2, 4:2:0) or use auto mode based on quality thresholds. This provides finer control over compression and quality trade-offs in AVIF images. --- src/imageio/format/avif.c | 100 ++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 14 deletions(-) diff --git a/src/imageio/format/avif.c b/src/imageio/format/avif.c index 57b26357f850..c9194c7a00c0 100644 --- a/src/imageio/format/avif.c +++ b/src/imageio/format/avif.c @@ -51,6 +51,14 @@ enum avif_tiling_e AVIF_TILING_OFF }; +enum avif_subsample_e +{ + AVIF_SUBSAMPLE_AUTO = 0, + AVIF_SUBSAMPLE_444, + AVIF_SUBSAMPLE_422, + AVIF_SUBSAMPLE_420 +}; + enum avif_color_mode_e { AVIF_COLOR_MODE_RGB = 0, @@ -65,6 +73,7 @@ typedef struct dt_imageio_avif_t uint32_t compression_type; uint32_t quality; uint32_t tiling; + uint32_t subsample; } dt_imageio_avif_t; typedef struct dt_imageio_avif_gui_t @@ -74,6 +83,7 @@ typedef struct dt_imageio_avif_gui_t GtkWidget *compression_type; GtkWidget *quality; GtkWidget *tiling; + GtkWidget *subsample; } dt_imageio_avif_gui_t; static const struct @@ -207,6 +217,28 @@ void init(dt_imageio_module_format_t *self) dt_imageio_avif_t, quality, int); + + /* subsample */ + luaA_enum(darktable.lua_state.state, + enum avif_subsample_e); + luaA_enum_value(darktable.lua_state.state, + enum avif_subsample_e, + AVIF_SUBSAMPLE_AUTO); + luaA_enum_value(darktable.lua_state.state, + enum avif_subsample_e, + AVIF_SUBSAMPLE_444); + luaA_enum_value(darktable.lua_state.state, + enum avif_subsample_e, + AVIF_SUBSAMPLE_422); + luaA_enum_value(darktable.lua_state.state, + enum avif_subsample_e, + AVIF_SUBSAMPLE_420); + + dt_lua_register_module_member(darktable.lua_state.state, + self, + dt_imageio_avif_t, + subsample, + enum avif_subsample_e); #endif } @@ -250,17 +282,33 @@ int write_image(struct dt_imageio_module_data_t *data, format = AVIF_PIXEL_FORMAT_YUV444; break; case AVIF_COMP_LOSSY: - if(d->quality > 90) + // Determine pixel format based on subsample setting + switch(d->subsample) { + case AVIF_SUBSAMPLE_AUTO: + // Auto mode: use quality thresholds + if(d->quality > 90) + { + format = AVIF_PIXEL_FORMAT_YUV444; + } + else if(d->quality > 80) + { + format = AVIF_PIXEL_FORMAT_YUV422; + } + else + { + format = AVIF_PIXEL_FORMAT_YUV420; + } + break; + case AVIF_SUBSAMPLE_444: format = AVIF_PIXEL_FORMAT_YUV444; - } - else if(d->quality > 80) - { + break; + case AVIF_SUBSAMPLE_422: format = AVIF_PIXEL_FORMAT_YUV422; - } - else - { - format = AVIF_PIXEL_FORMAT_YUV420; + break; + case AVIF_SUBSAMPLE_420: + format = AVIF_PIXEL_FORMAT_YUV420; + break; } break; } @@ -703,6 +751,7 @@ void *get_params(dt_imageio_module_format_t *self) } d->tiling = !dt_conf_get_bool("plugins/imageio/format/avif/tiling"); + d->subsample = dt_conf_get_int("plugins/imageio/format/avif/subsample"); return d; } @@ -721,6 +770,7 @@ int set_params(dt_imageio_module_format_t *self, dt_bauhaus_combobox_set(g->tiling, d->tiling); dt_bauhaus_combobox_set(g->compression_type, d->compression_type); dt_bauhaus_slider_set(g->quality, d->quality); + dt_bauhaus_combobox_set(g->subsample, d->subsample); return 0; } @@ -818,6 +868,12 @@ static void quality_changed(GtkWidget *slider, gpointer user_data) dt_conf_set_int("plugins/imageio/format/avif/quality", quality); } +static void subsample_changed(GtkWidget *widget, gpointer user_data) +{ + const enum avif_subsample_e subsample = dt_bauhaus_combobox_get(widget); + dt_conf_set_int("plugins/imageio/format/avif/subsample", subsample); +} + void gui_init(dt_imageio_module_format_t *self) { dt_imageio_avif_gui_t *gui = malloc(sizeof(dt_imageio_avif_gui_t)); @@ -904,17 +960,31 @@ void gui_init(dt_imageio_module_format_t *self) dt_bauhaus_widget_set_label(gui->quality, NULL, N_("quality")); gtk_widget_set_tooltip_text(gui->quality, - _("the quality of an image, less quality means fewer details.\n" - "\n" - "pixel format is controlled by quality:\n" - "\n" - "5-80: YUV420, 81-90: YUV422, 91-100: YUV444")); + _("the quality of an image, less quality means fewer details")); dt_bauhaus_slider_set(gui->quality, quality); gtk_widget_set_visible(gui->quality, compression_type != AVIF_COMP_LOSSLESS); gtk_widget_set_no_show_all(gui->quality, TRUE); + /* + * Chroma subsampling combo box + */ + const enum avif_subsample_e subsample = dt_conf_get_int("plugins/imageio/format/avif/subsample"); + + DT_BAUHAUS_COMBOBOX_NEW_FULL(gui->subsample, self, NULL, N_("chroma subsampling"), + _("chroma subsampling setting for AVIF encoder.\n" + "auto - use subsampling determined by the quality value\n" + " (5-80: YUV420, 81-90: YUV422, 91-100: YUV444)\n" + "4:4:4 - no chroma subsampling\n" + "4:2:2 - color sampling rate halved horizontally\n" + "4:2:0 - color sampling rate halved horizontally and vertically"), + subsample, subsample_changed, self, + N_("auto"), N_("4:4:4"), N_("4:2:2"), N_("4:2:0")); + + dt_bauhaus_combobox_set_default(gui->subsample, + dt_confgen_get_int("plugins/imageio/format/avif/subsample", DT_DEFAULT)); + g_signal_connect(G_OBJECT(gui->bit_depth), "value-changed", G_CALLBACK(bit_depth_changed), @@ -929,7 +999,7 @@ void gui_init(dt_imageio_module_format_t *self) NULL); self->widget = dt_gui_vbox(gui->bit_depth, gui->color_mode, gui->tiling, - gui->compression_type, gui->quality); + gui->compression_type, gui->quality, gui->subsample); } void gui_cleanup(dt_imageio_module_format_t *self) @@ -946,6 +1016,7 @@ void gui_reset(dt_imageio_module_format_t *self) const enum avif_tiling_e tiling = !dt_confgen_get_bool("plugins/imageio/format/avif/tiling", DT_DEFAULT); const enum avif_compression_type_e compression_type = dt_confgen_get_int("plugins/imageio/format/avif/compression_type", DT_DEFAULT); const uint32_t quality = dt_confgen_get_int("plugins/imageio/format/avif/quality", DT_DEFAULT); + const enum avif_subsample_e subsample = dt_confgen_get_int("plugins/imageio/format/avif/subsample", DT_DEFAULT); size_t idx = 0; for(size_t i = 0; avif_bit_depth[i].name != NULL; ++i) @@ -961,6 +1032,7 @@ void gui_reset(dt_imageio_module_format_t *self) dt_bauhaus_combobox_set(gui->tiling, tiling); dt_bauhaus_combobox_set(gui->compression_type, compression_type); dt_bauhaus_slider_set(gui->quality, quality); + dt_bauhaus_combobox_set(gui->subsample, subsample); } // clang-format off From 78c9b7e7436c449f99b3001bf907f49279e053da Mon Sep 17 00:00:00 2001 From: Aleksandr Kadykov Date: Mon, 27 Oct 2025 16:15:22 +0100 Subject: [PATCH 2/3] feat(avif): add chroma subsampling configuration Add a new configuration option for AVIF chroma subsampling in the imageio plugin settings. This allows users to control subsampling levels (0-3) for AVIF exports, defaulting to 0. --- data/darktableconfig.xml.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data/darktableconfig.xml.in b/data/darktableconfig.xml.in index ad9ed6b1be58..c639bac04b43 100644 --- a/data/darktableconfig.xml.in +++ b/data/darktableconfig.xml.in @@ -2907,6 +2907,13 @@ + + plugins/imageio/format/avif/subsample + int + 0 + AVIF chroma subsampling + + plugins/imageio/format/xcf/bpp From 8cb0db4acff3069c3834af67500c8bc5e5593499 Mon Sep 17 00:00:00 2001 From: Aleksandr Kadykov Date: Tue, 28 Oct 2025 20:33:44 +0100 Subject: [PATCH 3/3] feat(avif): conditionally disable chroma subsampling UI for lossless mode When AVIF compression is set to lossless, the subsample control is now hidden from the GUI to prevent irrelevant options, and marked with no-show-all for better layout management. This improves user experience by simplifying the interface during lossless exports. --- src/imageio/format/avif.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/imageio/format/avif.c b/src/imageio/format/avif.c index c9194c7a00c0..125e0772f077 100644 --- a/src/imageio/format/avif.c +++ b/src/imageio/format/avif.c @@ -860,6 +860,7 @@ static void compression_type_changed(GtkWidget *widget, gpointer user_data) dt_conf_set_int("plugins/imageio/format/avif/compression_type", compression_type); gtk_widget_set_visible(gui->quality, compression_type != AVIF_COMP_LOSSLESS); + gtk_widget_set_visible(gui->subsample, compression_type != AVIF_COMP_LOSSLESS); } static void quality_changed(GtkWidget *slider, gpointer user_data) @@ -985,6 +986,9 @@ void gui_init(dt_imageio_module_format_t *self) dt_bauhaus_combobox_set_default(gui->subsample, dt_confgen_get_int("plugins/imageio/format/avif/subsample", DT_DEFAULT)); + gtk_widget_set_visible(gui->subsample, compression_type != AVIF_COMP_LOSSLESS); + gtk_widget_set_no_show_all(gui->subsample, TRUE); + g_signal_connect(G_OBJECT(gui->bit_depth), "value-changed", G_CALLBACK(bit_depth_changed),