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
This commit is contained in:
Omar Emara
2025-07-25 11:15:28 +02:00
committed by Omar Emara
parent c1f52b8e91
commit 92d5c2078e
9 changed files with 242 additions and 32 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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<NodeImageMultiFile *>(node->storage);
update_format_media_type(&storage->format);
LISTBASE_FOREACH (bNodeSocket *, input, &node->inputs) {
NodeImageMultiFileSocket *input_storage = static_cast<NodeImageMultiFileSocket *>(
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.

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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)

View File

@@ -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");

View File

@@ -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<ImageFormatData>();
BKE_image_format_media_type_set(format, ptr->owner_id, static_cast<MediaType>(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<ImageFormatData>();
switch (static_cast<MediaType>(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);