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
diff --git a/src/imageio/format/avif.c b/src/imageio/format/avif.c
index 57b26357f850..125e0772f077 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;
}
@@ -810,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)
@@ -818,6 +869,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 +961,34 @@ 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));
+
+ 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),
@@ -929,7 +1003,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 +1020,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 +1036,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