From 92d5c2078e5885ff645719c0072e008e7bd3531c Mon Sep 17 00:00:00 2001 From: Omar Emara Date: Fri, 25 Jul 2025 11:15:28 +0200 Subject: [PATCH] Render: Add Media Type option This patch adds a new Media Type option to image format settings, which is used in the Render Output panel, File Output node, and Image Saving operation. The option does not provide any new functionality, but improves the UX of selecting file types by categorizing the existing file type option into: - Image. - Multi-Layer EXR. - Video. Each option would then list only the file types that fit that media type. For Multi-Layer and Video, the file type option is no longer drawn for now since only one option exist for now, OpenEXR Multi-Layer and FFMPEG respectively. This also improves the experience by not listing technical terms like FFMPEG in the UI, but rather use "Video" instead. The original motivation for introducing this option is the recent redesign of the File Output node. The problem is that the distinction between images and multi-layers images is not at all clear, while the behavior of the node changes quite a bit when multi-layer is chosen. While now with the new option, the distinction is quite clear. Implementation-wise, the new option is mostly a UI layer that controls the available enum items for the file format and callbacks to set a default format if the existing format doesn't match the media type. However, core code is unaffected and still transparently reads the image format only. Pull Request: https://projects.blender.org/blender/blender/pulls/142955 --- .../blender/blenkernel/BKE_blender_version.h | 2 +- source/blender/blenkernel/BKE_image_format.hh | 10 ++ .../blender/blenkernel/intern/image_format.cc | 57 ++++++++ .../blenloader/intern/versioning_500.cc | 48 +++++++ .../editors/space_image/image_buttons.cc | 8 +- source/blender/makesdna/DNA_scene_types.h | 21 ++- source/blender/makesrna/RNA_enum_items.hh | 2 +- source/blender/makesrna/intern/rna_image.cc | 2 +- source/blender/makesrna/intern/rna_scene.cc | 124 ++++++++++++++---- 9 files changed, 242 insertions(+), 32 deletions(-) diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index ee836a032e8..6acfd32bf7b 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 41 +#define BLENDER_FILE_SUBVERSION 42 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/BKE_image_format.hh b/source/blender/blenkernel/BKE_image_format.hh index 347132cdfe2..5f2976eb834 100644 --- a/source/blender/blenkernel/BKE_image_format.hh +++ b/source/blender/blenkernel/BKE_image_format.hh @@ -35,6 +35,11 @@ void BKE_image_format_update_color_space_for_type(ImageFormatData *format); void BKE_image_format_blend_read_data(BlendDataReader *reader, ImageFormatData *imf); void BKE_image_format_blend_write(BlendWriter *writer, ImageFormatData *imf); +/* Sets the media type of the given format that belongs to the given ID. This involves updating the + * imtype to a default format if it does not match the newly set media type. */ +void BKE_image_format_media_type_set(ImageFormatData *format, + ID *owner_id, + const MediaType media_type); void BKE_image_format_set(ImageFormatData *imf, ID *owner_id, const char imtype); /* File Paths */ @@ -97,6 +102,11 @@ int BKE_image_path_ext_from_imtype_ensure(char *filepath, size_t filepath_maxncp char BKE_ftype_to_imtype(int ftype, const ImbFormatOptions *options); int BKE_imtype_to_ftype(char imtype, ImbFormatOptions *r_options); +/* Returns true if the given imtype represents an image. This excludes multi-layer images, use + * BKE_imtype_is_multi_layer_image to detect those images. */ +bool BKE_imtype_is_image(char imtype); +/* Returns true if the given imtype represents a multi-layer image. */ +bool BKE_imtype_is_multi_layer_image(char imtype); bool BKE_imtype_is_movie(char imtype); bool BKE_imtype_supports_compress(char imtype); bool BKE_imtype_supports_quality(char imtype); diff --git a/source/blender/blenkernel/intern/image_format.cc b/source/blender/blenkernel/intern/image_format.cc index e640d7c8076..7ac3bf95d96 100644 --- a/source/blender/blenkernel/intern/image_format.cc +++ b/source/blender/blenkernel/intern/image_format.cc @@ -90,10 +90,53 @@ void BKE_image_format_blend_write(BlendWriter *writer, ImageFormatData *imf) BKE_color_managed_view_settings_blend_write(writer, &imf->view_settings); } +void BKE_image_format_media_type_set(ImageFormatData *format, + ID *owner_id, + const MediaType media_type) +{ + format->media_type = media_type; + + switch (media_type) { + case MEDIA_TYPE_IMAGE: + if (!BKE_imtype_is_image(format->imtype)) { + BKE_image_format_set(format, owner_id, R_IMF_IMTYPE_PNG); + } + break; + case MEDIA_TYPE_MULTI_LAYER_IMAGE: + if (!BKE_imtype_is_multi_layer_image(format->imtype)) { + BKE_image_format_set(format, owner_id, R_IMF_IMTYPE_MULTILAYER); + } + break; + case MEDIA_TYPE_VIDEO: + if (!BKE_imtype_is_movie(format->imtype)) { + BKE_image_format_set(format, owner_id, R_IMF_IMTYPE_FFMPEG); + } + break; + } +} + void BKE_image_format_set(ImageFormatData *imf, ID *owner_id, const char imtype) { imf->imtype = imtype; + /* Update media type in case it doesn't match the new imtype. Note that normally, one would use + * the BKE_image_format_media_type_set function to set the media type, but that function itself + * calls this function to update the imtype, and while this wouldn't case recursion since the + * imtype is already conforming, it is better to err on the side of caution and set the media + * type manually. */ + if (BKE_imtype_is_image(imf->imtype)) { + imf->media_type = MEDIA_TYPE_IMAGE; + } + else if (BKE_imtype_is_multi_layer_image(imf->imtype)) { + imf->media_type = MEDIA_TYPE_MULTI_LAYER_IMAGE; + } + else if (BKE_imtype_is_movie(imf->imtype)) { + imf->media_type = MEDIA_TYPE_VIDEO; + } + else { + BLI_assert_unreachable(); + } + const bool is_render = (owner_id && GS(owner_id->name) == ID_SCE); /* see note below on why this is */ const char chan_flag = BKE_imtype_valid_channels(imf->imtype) | @@ -253,6 +296,20 @@ char BKE_ftype_to_imtype(const int ftype, const ImbFormatOptions *options) return R_IMF_IMTYPE_JPEG90; } +bool BKE_imtype_is_image(const char imtype) +{ + return !BKE_imtype_is_multi_layer_image(imtype) && !BKE_imtype_is_movie(imtype); +} + +bool BKE_imtype_is_multi_layer_image(const char imtype) +{ + switch (imtype) { + case R_IMF_IMTYPE_MULTILAYER: + return true; + } + return false; +} + bool BKE_imtype_is_movie(const char imtype) { switch (imtype) { diff --git a/source/blender/blenloader/intern/versioning_500.cc b/source/blender/blenloader/intern/versioning_500.cc index 86802de8836..6a93d38aa32 100644 --- a/source/blender/blenloader/intern/versioning_500.cc +++ b/source/blender/blenloader/intern/versioning_500.cc @@ -35,6 +35,7 @@ #include "BKE_colortools.hh" #include "BKE_curves.hh" #include "BKE_idprop.hh" +#include "BKE_image_format.hh" #include "BKE_lib_id.hh" #include "BKE_main.hh" #include "BKE_mesh_legacy_convert.hh" @@ -1217,6 +1218,23 @@ static void do_version_composite_node_in_scene_tree(bNodeTree &node_tree, bNode version_node_remove(node_tree, node); } +/* Updates the media type of the given format to match its imtype. */ +static void update_format_media_type(ImageFormatData *format) +{ + if (BKE_imtype_is_image(format->imtype)) { + format->media_type = MEDIA_TYPE_IMAGE; + } + else if (BKE_imtype_is_multi_layer_image(format->imtype)) { + format->media_type = MEDIA_TYPE_MULTI_LAYER_IMAGE; + } + else if (BKE_imtype_is_movie(format->imtype)) { + format->media_type = MEDIA_TYPE_VIDEO; + } + else { + BLI_assert_unreachable(); + } +} + void do_versions_after_linking_500(FileData *fd, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 9)) { @@ -1643,6 +1661,36 @@ void blo_do_versions_500(FileData * /*fd*/, Library * /*lib*/, Main *bmain) } } + /* ImageFormatData gained a new media type which we need to be set according to the existing + * imtype. */ + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 500, 42)) { + LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) { + update_format_media_type(&scene->r.im_format); + } + + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + if (node_tree->type != NTREE_COMPOSIT) { + continue; + } + + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy != CMP_NODE_OUTPUT_FILE) { + continue; + } + + NodeImageMultiFile *storage = static_cast(node->storage); + update_format_media_type(&storage->format); + + LISTBASE_FOREACH (bNodeSocket *, input, &node->inputs) { + NodeImageMultiFileSocket *input_storage = static_cast( + input->storage); + update_format_media_type(&input_storage->format); + } + } + } + FOREACH_NODETREE_END; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. diff --git a/source/blender/editors/space_image/image_buttons.cc b/source/blender/editors/space_image/image_buttons.cc index 53981f925c1..ce58262479b 100644 --- a/source/blender/editors/space_image/image_buttons.cc +++ b/source/blender/editors/space_image/image_buttons.cc @@ -993,7 +993,13 @@ void uiTemplateImageSettings(uiLayout *layout, PointerRNA *imfptr, bool color_ma col->use_property_split_set(true); col->use_property_decorate_set(false); - col->prop(imfptr, "file_format", UI_ITEM_NONE, std::nullopt, ICON_NONE); + col->prop(imfptr, "media_type", UI_ITEM_NONE, std::nullopt, ICON_NONE); + + /* Multi layer images and video media types only have a single supported format, so we needn't + * draw the format format enum. */ + if (imf->media_type == MEDIA_TYPE_IMAGE) { + col->prop(imfptr, "file_format", UI_ITEM_NONE, std::nullopt, ICON_NONE); + } /* Multi-layer always saves raw unmodified channels. */ if (imf->imtype != R_IMF_IMTYPE_MULTILAYER) { diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 47a6be02614..6f223b7f1e3 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -462,6 +462,8 @@ typedef enum eStereo3dInterlaceType { * RNA ensures these enum's are only selectable for render output. */ typedef struct ImageFormatData { + /** MediaType. */ + char media_type; /** * R_IMF_IMTYPE_PNG, R_... * \note Video types should only ever be set from this structure when used from #RenderData. @@ -487,11 +489,6 @@ typedef struct ImageFormatData { /** OpenEXR: R_IMF_EXR_CODEC_* values in low OPENEXR_CODEC_MASK bits. */ char exr_codec; - /** CINEON. */ - char cineon_flag; - short cineon_white, cineon_black; - float cineon_gamma; - /** Jpeg2000. */ char jp2_flag; char jp2_codec; @@ -499,7 +496,12 @@ typedef struct ImageFormatData { /** TIFF. */ char tiff_codec; - char _pad[4]; + /** CINEON. */ + char cineon_flag; + short cineon_white, cineon_black; + float cineon_gamma; + + char _pad[3]; /** Multi-view. */ char views_format; @@ -514,6 +516,13 @@ typedef struct ImageFormatData { ColorManagedColorspaceSettings linear_colorspace_settings; } ImageFormatData; +/** #ImageFormatData::media_type */ +typedef enum MediaType { + MEDIA_TYPE_IMAGE = 0, + MEDIA_TYPE_MULTI_LAYER_IMAGE = 1, + MEDIA_TYPE_VIDEO = 2, +} MediaType; + /** #ImageFormatData::imtype */ enum { R_IMF_IMTYPE_TARGA = 0, diff --git a/source/blender/makesrna/RNA_enum_items.hh b/source/blender/makesrna/RNA_enum_items.hh index 3211c16f06f..e471fc9404a 100644 --- a/source/blender/makesrna/RNA_enum_items.hh +++ b/source/blender/makesrna/RNA_enum_items.hh @@ -56,7 +56,7 @@ DEF_ENUM(rna_enum_shrinkwrap_type_items) DEF_ENUM(rna_enum_shrinkwrap_face_cull_items) DEF_ENUM(rna_enum_node_warning_type_items) -DEF_ENUM(rna_enum_image_type_items) +DEF_ENUM(rna_enum_image_type_all_items) DEF_ENUM(rna_enum_image_color_mode_items) DEF_ENUM(rna_enum_image_color_depth_items) DEF_ENUM(rna_enum_image_generated_type_items) diff --git a/source/blender/makesrna/intern/rna_image.cc b/source/blender/makesrna/intern/rna_image.cc index f62edfc6ab1..9650d24f4e4 100644 --- a/source/blender/makesrna/intern/rna_image.cc +++ b/source/blender/makesrna/intern/rna_image.cc @@ -1163,7 +1163,7 @@ static void rna_def_image(BlenderRNA *brna) RNA_def_property_ui_text(prop, "File Name", "Image/Movie file name (without data refreshing)"); prop = RNA_def_property(srna, "file_format", PROP_ENUM, PROP_NONE); - RNA_def_property_enum_items(prop, rna_enum_image_type_items); + RNA_def_property_enum_items(prop, rna_enum_image_type_all_items); RNA_def_property_enum_funcs( prop, "rna_Image_file_format_get", "rna_Image_file_format_set", nullptr); RNA_def_property_ui_text(prop, "File Format", "Format used for re-saving this file"); diff --git a/source/blender/makesrna/intern/rna_scene.cc b/source/blender/makesrna/intern/rna_scene.cc index cdad373950e..b47912f5ddb 100644 --- a/source/blender/makesrna/intern/rna_scene.cc +++ b/source/blender/makesrna/intern/rna_scene.cc @@ -255,6 +255,34 @@ const EnumPropertyItem rna_enum_curve_fit_method_items[] = { {0, nullptr, 0, nullptr, nullptr}, }; +#define MEDIA_TYPE_ENUM_IMAGE \ + { \ + MEDIA_TYPE_IMAGE, "IMAGE", ICON_NONE, "Image", "" \ + } +#define MEDIA_TYPE_ENUM_MULTI_LAYER_IMAGE \ + { \ + MEDIA_TYPE_MULTI_LAYER_IMAGE, "MULTI_LAYER_IMAGE", ICON_NONE, "Multi-Layer EXR", "" \ + } +#define MEDIA_TYPE_ENUM_VIDEO \ + { \ + MEDIA_TYPE_VIDEO, "VIDEO", ICON_NONE, "Video", "" \ + } + +static const EnumPropertyItem rna_enum_media_type_all_items[] = { + MEDIA_TYPE_ENUM_IMAGE, + MEDIA_TYPE_ENUM_MULTI_LAYER_IMAGE, + MEDIA_TYPE_ENUM_VIDEO, + {0, nullptr, 0, nullptr, nullptr}, +}; + +#ifdef RNA_RUNTIME +static const EnumPropertyItem rna_enum_media_type_image_items[] = { + MEDIA_TYPE_ENUM_IMAGE, + MEDIA_TYPE_ENUM_MULTI_LAYER_IMAGE, + {0, nullptr, 0, nullptr, nullptr}, +}; +#endif + /* workaround for duplicate enums, * have each enum line as a define then conditionally set it or not */ @@ -337,7 +365,13 @@ const EnumPropertyItem rna_enum_curve_fit_method_items[] = { # define R_IMF_ENUM_WEBP #endif -#define IMAGE_TYPE_ITEMS_IMAGE_ONLY \ +#ifdef WITH_FFMPEG +# define R_IMF_ENUM_FFMPEG {R_IMF_IMTYPE_FFMPEG, "FFMPEG", ICON_FILE_MOVIE, "FFmpeg Video", ""}, +#else +# define R_IMF_ENUM_FFMPEG +#endif + +#define IMAGE_TYPE_ITEMS_IMAGE \ R_IMF_ENUM_BMP \ /* DDS save not supported yet R_IMF_ENUM_DDS */ \ R_IMF_ENUM_IRIS \ @@ -346,27 +380,36 @@ const EnumPropertyItem rna_enum_curve_fit_method_items[] = { R_IMF_ENUM_JPEG2K \ R_IMF_ENUM_TAGA \ R_IMF_ENUM_TAGA_RAW \ - RNA_ENUM_ITEM_SEPR_COLUMN, R_IMF_ENUM_CINEON R_IMF_ENUM_DPX R_IMF_ENUM_EXR_MULTILAYER \ - R_IMF_ENUM_EXR R_IMF_ENUM_HDR R_IMF_ENUM_TIFF R_IMF_ENUM_WEBP + RNA_ENUM_ITEM_SEPR_COLUMN, R_IMF_ENUM_CINEON R_IMF_ENUM_DPX R_IMF_ENUM_EXR R_IMF_ENUM_HDR \ + R_IMF_ENUM_TIFF R_IMF_ENUM_WEBP + +#define IMAGE_TYPE_ITEMS_MULTI_LAYER_IMAGE R_IMF_ENUM_EXR_MULTILAYER + +#define IMAGE_TYPE_ITEMS_VIDEO R_IMF_ENUM_FFMPEG #ifdef RNA_RUNTIME -static const EnumPropertyItem image_only_type_items[] = { +static const EnumPropertyItem image_type_items[] = { + IMAGE_TYPE_ITEMS_IMAGE - IMAGE_TYPE_ITEMS_IMAGE_ONLY + {0, nullptr, 0, nullptr, nullptr}, +}; + +static const EnumPropertyItem multi_layer_image_type_items[] = { + IMAGE_TYPE_ITEMS_MULTI_LAYER_IMAGE + + {0, nullptr, 0, nullptr, nullptr}, +}; + +static const EnumPropertyItem video_image_type_items[] = { + IMAGE_TYPE_ITEMS_VIDEO {0, nullptr, 0, nullptr, nullptr}, }; #endif -const EnumPropertyItem rna_enum_image_type_items[] = { - RNA_ENUM_ITEM_HEADING(N_("Image"), nullptr), +const EnumPropertyItem rna_enum_image_type_all_items[] = { + IMAGE_TYPE_ITEMS_IMAGE IMAGE_TYPE_ITEMS_MULTI_LAYER_IMAGE IMAGE_TYPE_ITEMS_VIDEO - IMAGE_TYPE_ITEMS_IMAGE_ONLY - - RNA_ENUM_ITEM_HEADING(N_("Movie"), nullptr), -#ifdef WITH_FFMPEG - {R_IMF_IMTYPE_FFMPEG, "FFMPEG", ICON_FILE_MOVIE, "FFmpeg Video", ""}, -#endif {0, nullptr, 0, nullptr, nullptr}, }; @@ -1389,6 +1432,30 @@ static bool rna_RenderSettings_is_movie_format_get(PointerRNA *ptr) return BKE_imtype_is_movie(rd->im_format.imtype); } +static const EnumPropertyItem *rna_ImageFormatSettings_media_type_itemf(bContext * /*C*/, + PointerRNA *ptr, + PropertyRNA * /*prop*/, + bool * /*r_free*/) +{ + ID *id = ptr->owner_id; + /* Scene format setting include video, so we return all items. Otherwise, only image types are + * returned. */ + if (id && GS(id->name) == ID_SCE) { + return rna_enum_media_type_all_items; + } + else { + return rna_enum_media_type_image_items; + } +} + +/* If the existing imtype does not match the new media type, assign an appropriate default media + * type. */ +static void rna_ImageFormatSettings_media_type_set(PointerRNA *ptr, int value) +{ + ImageFormatData *format = ptr->data_as(); + BKE_image_format_media_type_set(format, ptr->owner_id, static_cast(value)); +} + static void rna_ImageFormatSettings_file_format_set(PointerRNA *ptr, int value) { BKE_image_format_set((ImageFormatData *)ptr->data, ptr->owner_id, value); @@ -1399,13 +1466,17 @@ static const EnumPropertyItem *rna_ImageFormatSettings_file_format_itemf(bContex PropertyRNA * /*prop*/, bool * /*r_free*/) { - ID *id = ptr->owner_id; - if (id && GS(id->name) == ID_SCE) { - return rna_enum_image_type_items; - } - else { - return image_only_type_items; + const ImageFormatData *format = ptr->data_as(); + switch (static_cast(format->media_type)) { + case MEDIA_TYPE_IMAGE: + return image_type_items; + case MEDIA_TYPE_MULTI_LAYER_IMAGE: + return multi_layer_image_type_items; + case MEDIA_TYPE_VIDEO: + return video_image_type_items; } + + return rna_enum_image_type_all_items; } static const EnumPropertyItem *rna_ImageFormatSettings_color_mode_itemf(bContext * /*C*/, @@ -6188,7 +6259,6 @@ static void rna_def_image_format_stereo3d_format(BlenderRNA *brna) static void rna_def_scene_image_format_data(BlenderRNA *brna) { - # ifdef WITH_IMAGE_OPENJPEG static const EnumPropertyItem jp2_codec_items[] = { {R_IMF_JP2_CODEC_JP2, "JP2", 0, "JP2", ""}, @@ -6222,9 +6292,19 @@ static void rna_def_scene_image_format_data(BlenderRNA *brna) RNA_def_struct_path_func(srna, "rna_ImageFormatSettings_path"); RNA_def_struct_ui_text(srna, "Image Format", "Settings for image formats"); + prop = RNA_def_property(srna, "media_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, nullptr, "media_type"); + RNA_def_property_enum_items(prop, rna_enum_media_type_all_items); + RNA_def_property_enum_funcs(prop, + nullptr, + "rna_ImageFormatSettings_media_type_set", + "rna_ImageFormatSettings_media_type_itemf"); + RNA_def_property_ui_text(prop, "Media Type", "The type of media to save"); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr); + prop = RNA_def_property(srna, "file_format", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "imtype"); - RNA_def_property_enum_items(prop, rna_enum_image_type_items); + RNA_def_property_enum_items(prop, rna_enum_image_type_all_items); RNA_def_property_enum_funcs(prop, nullptr, "rna_ImageFormatSettings_file_format_set", @@ -7165,7 +7245,7 @@ static void rna_def_scene_render_data(BlenderRNA *brna) # if 0 /* moved */ prop = RNA_def_property(srna, "file_format", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, nullptr, "imtype"); - RNA_def_property_enum_items(prop, rna_enum_image_type_items); + RNA_def_property_enum_items(prop, rna_enum_image_type_all_items); RNA_def_property_enum_funcs(prop, nullptr, "rna_RenderSettings_file_format_set", nullptr); RNA_def_property_ui_text(prop, "File Format", "File format to save the rendered images as"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, nullptr);