diff options
author | Jesse Yurkovich <jesse.y@gmail.com> | 2021-12-31 09:06:23 +0300 |
---|---|---|
committer | Jesse Yurkovich <jesse.y@gmail.com> | 2022-01-03 07:48:59 +0300 |
commit | 180b66ae8a1ffc0a1bb7d3993028240b4c7f7246 (patch) | |
tree | cbc9405b97f02826d181829859b12ce82a672def /source/blender/blenkernel/intern | |
parent | 367fc69dc1c5ebb6e30ef74488a041502ea8a370 (diff) |
UDIM: Support virtual filenames
This implements the design detailed in T92696 to support virtual
filenames for UDIM textures. Currently, the following 2 substitution
tokens are supported:
| Token | Meaning |
| ----- | ---- |
| <UDIM> | 1001 + u-tile + v-tile * 10 |
| <UVTILE> | Equivalent to u<u-tile + 1>_v<v-tile + 1> |
Example for u-tile of 3 and v-tile of 1:
filename.<UDIM>_ver0023.png --> filename.1014_ver0023.png
filename.<UVTILE>_ver0023.png --> filename.u4_v2_ver0023.png
For image loading, the existing workflow is unchanged. A user can select
one or more image files, belonging to one or more UDIM tile sets, and
have Blender load them all as it does today. Now the <UVTILE> format is
"guessed" just as the <UDIM> format was guessed before.
If guessing fails, the user can simply go into the Image Editor and type
the proper substitution in the filename. Once typing is complete,
Blender will reload the files and correctly fill the tiles. This
workflow is new as attempting to fix the guessing in current versions
did not really work, and the user was often stuck with a confusing
situation.
For image saving, the existing workflow is changed slightly. Currently,
when saving, a user has to be sure to type the filename of the first
tile (e.g. filename.1001.png) to save the entire UDIM set. The number
could differ if they start at a different tile etc. This is confusing.
Now, the user should type a filename containing the appropriate
substitution token. By default Blender will fill in a default name using
the <UDIM> token but the user is free to save out images using <UVTILE>
if they wish.
Differential Revision: https://developer.blender.org/D13057
Diffstat (limited to 'source/blender/blenkernel/intern')
-rw-r--r-- | source/blender/blenkernel/intern/bpath.c | 6 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/image.c | 321 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/image_save.c | 53 |
3 files changed, 343 insertions, 37 deletions
diff --git a/source/blender/blenkernel/intern/bpath.c b/source/blender/blenkernel/intern/bpath.c index 85e49774dfd..44ef3ec96ff 100644 --- a/source/blender/blenkernel/intern/bpath.c +++ b/source/blender/blenkernel/intern/bpath.c @@ -236,7 +236,8 @@ void BKE_bpath_missing_files_check(Main *bmain, ReportList *reports) BKE_bpath_foreach_path_main(&(BPathForeachPathData){ .bmain = bmain, .callback_function = check_missing_files_foreach_path_cb, - .flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_SKIP_PACKED, + .flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_SKIP_PACKED | + BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN, .user_data = reports}); } @@ -384,7 +385,8 @@ void BKE_bpath_missing_files_find(Main *bmain, const bool find_all) { struct BPathFind_Data data = {NULL}; - const int flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_RELOAD_EDITED; + const int flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_RELOAD_EDITED | + BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN; data.basedir = BKE_main_blendfile_path(bmain); data.reports = reports; diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index 3fec5db422d..2d4366846aa 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -21,6 +21,7 @@ * \ingroup bke */ +#include <ctype.h> #include <fcntl.h> #include <math.h> #include <stdio.h> @@ -272,7 +273,33 @@ static void image_foreach_path(ID *id, BPathForeachPathData *bpath_data) return; } - if (BKE_bpath_foreach_path_fixed_process(bpath_data, ima->filepath)) { + /* If this is a tiled image, and we're asked to resolve the tokens in the virtual + * filepath, use the first tile to generate a concrete path for use during processing. */ + bool result = false; + if (ima->source == IMA_SRC_TILED && (flag & BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN) != 0) { + char temp_path[FILE_MAX], orig_file[FILE_MAXFILE]; + BLI_strncpy(temp_path, ima->filepath, sizeof(temp_path)); + BLI_split_file_part(temp_path, orig_file, sizeof(orig_file)); + + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = BKE_image_get_tile_strformat(temp_path, &tile_format); + BKE_image_set_filepath_from_tile_number( + temp_path, udim_pattern, tile_format, ((ImageTile *)ima->tiles.first)->tile_number); + MEM_SAFE_FREE(udim_pattern); + + result = BKE_bpath_foreach_path_fixed_process(bpath_data, temp_path); + if (result) { + /* Put the filepath back together using the new directory and the original file name. */ + char new_dir[FILE_MAXDIR]; + BLI_split_dir_part(temp_path, new_dir, sizeof(new_dir)); + BLI_join_dirfile(ima->filepath, sizeof(ima->filepath), new_dir, orig_file); + } + } + else { + result = BKE_bpath_foreach_path_fixed_process(bpath_data, ima->filepath); + } + + if (result) { if (flag & BKE_BPATH_FOREACH_PATH_RELOAD_EDITED) { if (!BKE_image_has_packedfile(ima) && /* Image may have been painted onto (and not saved, T44543). */ @@ -888,9 +915,13 @@ Image *BKE_image_load(Main *bmain, const char *filepath) /* exists? */ file = BLI_open(str, O_BINARY | O_RDONLY, 0); if (file == -1) { - return NULL; + if (!BKE_image_tile_filepath_exists(str)) { + return NULL; + } + } + else { + close(file); } - close(file); ima = image_alloc(bmain, BLI_path_basename(filepath), IMA_SRC_FILE, IMA_TYPE_IMAGE); STRNCPY(ima->filepath, filepath); @@ -3699,6 +3730,43 @@ void BKE_image_signal(Main *bmain, Image *ima, ImageUser *iuser, int signal) BKE_image_free_buffers(ima); } + if (ima->source == IMA_SRC_TILED) { + ListBase new_tiles = {NULL, NULL}; + int new_start, new_range; + + char filepath[FILE_MAX]; + BLI_strncpy(filepath, ima->filepath, sizeof(filepath)); + BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&ima->id)); + bool result = BKE_image_get_tile_info(filepath, &new_tiles, &new_start, &new_range); + if (result) { + /* Because the prior and new list of tiles are both sparse sequences, we need to be sure + * to account for how the two sets might or might not overlap. To be complete, we start + * the refresh process by clearing all existing tiles, stopping when there's only 1 tile + * left. */ + while (BKE_image_remove_tile(ima, ima->tiles.last)) { + ; + } + + int remaining_tile_number = ((ImageTile *)ima->tiles.first)->tile_number; + bool needs_final_cleanup = true; + + /* Add in all the new tiles. */ + LISTBASE_FOREACH (LinkData *, new_tile, &new_tiles) { + int new_tile_number = POINTER_AS_INT(new_tile->data); + BKE_image_add_tile(ima, new_tile_number, NULL); + if (new_tile_number == remaining_tile_number) { + needs_final_cleanup = false; + } + } + + /* Final cleanup if the prior remaining tile was never encountered in the new list. */ + if (needs_final_cleanup) { + BKE_image_remove_tile(ima, BKE_image_get_tile(ima, remaining_tile_number)); + } + } + BLI_freelistN(&new_tiles); + } + if (iuser) { image_tag_reload(ima, NULL, iuser, ima); } @@ -3782,6 +3850,57 @@ void BKE_image_get_tile_label(Image *ima, ImageTile *tile, char *label, int len_ } } +bool BKE_image_get_tile_info(char *filepath, + ListBase *udim_tiles, + int *udim_start, + int *udim_range) +{ + char filename[FILE_MAXFILE], dirname[FILE_MAXDIR]; + BLI_split_dirfile(filepath, dirname, filename, sizeof(dirname), sizeof(filename)); + + BKE_image_ensure_tile_token(filename); + + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = BKE_image_get_tile_strformat(filename, &tile_format); + + bool is_udim = true; + int min_udim = IMA_UDIM_MAX + 1; + int max_udim = 0; + int id; + + struct direntry *dir; + uint totfile = BLI_filelist_dir_contents(dirname, &dir); + for (int i = 0; i < totfile; i++) { + if (!(dir[i].type & S_IFREG)) { + continue; + } + + if (!BKE_image_get_tile_number_from_filepath(dir[i].relname, udim_pattern, tile_format, &id)) { + continue; + } + + if (id < 1001 || id > IMA_UDIM_MAX) { + is_udim = false; + break; + } + + BLI_addtail(udim_tiles, BLI_genericNodeN(POINTER_FROM_INT(id))); + min_udim = min_ii(min_udim, id); + max_udim = max_ii(max_udim, id); + } + BLI_filelist_free(dir, totfile); + MEM_SAFE_FREE(udim_pattern); + + if (is_udim && min_udim <= IMA_UDIM_MAX) { + BLI_join_dirfile(filepath, FILE_MAX, dirname, filename); + + *udim_start = min_udim; + *udim_range = max_udim - min_udim + 1; + return true; + } + return false; +} + ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *label) { if (ima->source != IMA_SRC_TILED) { @@ -3941,6 +4060,185 @@ bool BKE_image_fill_tile(struct Image *ima, return false; } +void BKE_image_ensure_tile_token(char *filename) +{ + if (filename == NULL) { + return; + } + + /* Is there a '<' character in the filename? Assume tokens already present. */ + if (strstr(filename, "<") != NULL) { + return; + } + + /* Is there a sequence of digits in the filename? */ + ushort digits; + char head[FILE_MAX], tail[FILE_MAX]; + BLI_path_sequence_decode(filename, head, tail, &digits); + if (digits == 4) { + sprintf(filename, "%s<UDIM>%s", head, tail); + return; + } + + /* Is there a sequence like u##_v#### in the filename? */ + uint cur = 0; + uint name_end = strlen(filename); + uint u_digits = 0; + uint v_digits = 0; + uint u_start = (uint)-1; + bool u_found = false; + bool v_found = false; + bool sep_found = false; + while (cur < name_end) { + if (filename[cur] == 'u') { + u_found = true; + u_digits = 0; + u_start = cur; + } + else if (filename[cur] == 'v') { + v_found = true; + v_digits = 0; + } + else if (u_found && !v_found) { + if (isdigit(filename[cur]) && u_digits < 2) { + u_digits++; + } + else if (filename[cur] == '_') { + sep_found = true; + } + else { + u_found = false; + } + } + else if (u_found && u_digits > 0 && v_found) { + if (isdigit(filename[cur])) { + if (v_digits < 4) { + v_digits++; + } + else { + u_found = false; + v_found = false; + } + } + else if (v_digits > 0) { + break; + } + } + + cur++; + } + + if (u_found && sep_found && v_found && (u_digits + v_digits > 1)) { + const char *token = "<UVTILE>"; + const size_t token_length = strlen(token); + memmove(filename + u_start + token_length, filename + cur, name_end - cur); + memcpy(filename + u_start, token, token_length); + filename[u_start + token_length + (name_end - cur)] = '\0'; + } +} + +bool BKE_image_tile_filepath_exists(const char *filepath) +{ + BLI_assert(!BLI_path_is_rel(filepath)); + + char dirname[FILE_MAXDIR]; + BLI_split_dir_part(filepath, dirname, sizeof(dirname)); + + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = BKE_image_get_tile_strformat(filepath, &tile_format); + + bool found = false; + struct direntry *dir; + uint totfile = BLI_filelist_dir_contents(dirname, &dir); + for (int i = 0; i < totfile; i++) { + if (!(dir[i].type & S_IFREG)) { + continue; + } + + int id; + if (!BKE_image_get_tile_number_from_filepath(dir[i].path, udim_pattern, tile_format, &id)) { + continue; + } + + if (id < 1001 || id > IMA_UDIM_MAX) { + continue; + } + + found = true; + break; + } + BLI_filelist_free(dir, totfile); + MEM_SAFE_FREE(udim_pattern); + + return found; +} + +char *BKE_image_get_tile_strformat(const char *filepath, eUDIM_TILE_FORMAT *r_tile_format) +{ + if (filepath == NULL || r_tile_format == NULL) { + return NULL; + } + + if (strstr(filepath, "<UDIM>") != NULL) { + *r_tile_format = UDIM_TILE_FORMAT_UDIM; + return BLI_str_replaceN(filepath, "<UDIM>", "%d"); + } + if (strstr(filepath, "<UVTILE>") != NULL) { + *r_tile_format = UDIM_TILE_FORMAT_UVTILE; + return BLI_str_replaceN(filepath, "<UVTILE>", "u%d_v%d"); + } + + *r_tile_format = UDIM_TILE_FORMAT_NONE; + return NULL; +} + +bool BKE_image_get_tile_number_from_filepath(const char *filepath, + const char *pattern, + eUDIM_TILE_FORMAT tile_format, + int *r_tile_number) +{ + if (filepath == NULL || pattern == NULL || r_tile_number == NULL) { + return false; + } + + int u, v; + bool result = false; + + if (tile_format == UDIM_TILE_FORMAT_UDIM) { + if (sscanf(filepath, pattern, &u) == 1) { + *r_tile_number = u; + result = true; + } + } + else if (tile_format == UDIM_TILE_FORMAT_UVTILE) { + if (sscanf(filepath, pattern, &u, &v) == 2) { + *r_tile_number = 1001 + (u - 1) + ((v - 1) * 10); + result = true; + } + } + + return result; +} + +void BKE_image_set_filepath_from_tile_number(char *filepath, + const char *pattern, + eUDIM_TILE_FORMAT tile_format, + int tile_number) +{ + if (filepath == NULL || pattern == NULL) { + return; + } + + if (tile_format == UDIM_TILE_FORMAT_UDIM) { + sprintf(filepath, pattern, tile_number); + } + else if (tile_format == UDIM_TILE_FORMAT_UVTILE) { + int u = ((tile_number - 1001) % 10); + int v = ((tile_number - 1001) / 10); + sprintf(filepath, pattern, u + 1, v + 1); + } +} + /* if layer or pass changes, we need an index for the imbufs list */ /* note it is called for rendered results, but it doesn't use the index! */ RenderPass *BKE_image_multilayer_index(RenderResult *rr, ImageUser *iuser) @@ -5513,6 +5811,11 @@ void BKE_image_user_id_eval_animation(Depsgraph *depsgraph, ID *id) void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath) { + BKE_image_user_file_path_ex(iuser, ima, filepath, true); +} + +void BKE_image_user_file_path_ex(ImageUser *iuser, Image *ima, char *filepath, bool resolve_udim) +{ if (BKE_image_is_multiview(ima)) { ImageView *iv = BLI_findlink(&ima->views, iuser->view); if (iv->filepath[0]) { @@ -5533,13 +5836,17 @@ void BKE_image_user_file_path(ImageUser *iuser, Image *ima, char *filepath) int index; if (ima->source == IMA_SRC_SEQUENCE) { index = iuser ? iuser->framenr : ima->lastframe; + BLI_path_sequence_decode(filepath, head, tail, &numlen); + BLI_path_sequence_encode(filepath, head, tail, numlen, index); } - else { + else if (resolve_udim) { index = image_get_tile_number_from_iuser(ima, iuser); - } - BLI_path_sequence_decode(filepath, head, tail, &numlen); - BLI_path_sequence_encode(filepath, head, tail, numlen, index); + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = BKE_image_get_tile_strformat(filepath, &tile_format); + BKE_image_set_filepath_from_tile_number(filepath, udim_pattern, tile_format, index); + MEM_SAFE_FREE(udim_pattern); + } } BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&ima->id)); diff --git a/source/blender/blenkernel/intern/image_save.c b/source/blender/blenkernel/intern/image_save.c index f93ede517a9..329bc7b498b 100644 --- a/source/blender/blenkernel/intern/image_save.c +++ b/source/blender/blenkernel/intern/image_save.c @@ -30,6 +30,8 @@ #include "DNA_image_types.h" +#include "MEM_guardedalloc.h" + #include "IMB_colormanagement.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -402,15 +404,17 @@ bool BKE_image_save( bool colorspace_changed = false; + eUDIM_TILE_FORMAT tile_format; + char *udim_pattern = NULL; + if (ima->source == IMA_SRC_TILED) { - /* Verify filepath for tiles images. */ - ImageTile *first_tile = ima->tiles.first; - if (BLI_path_sequence_decode(opts->filepath, NULL, NULL, NULL) != first_tile->tile_number) { + /* Verify filepath for tiled images contains a valid UDIM marker. */ + udim_pattern = BKE_image_get_tile_strformat(opts->filepath, &tile_format); + if (tile_format == UDIM_TILE_FORMAT_NONE) { BKE_reportf(reports, RPT_ERROR, - "When saving a tiled image, the path '%s' must contain the UDIM tile number %d", - opts->filepath, - first_tile->tile_number); + "When saving a tiled image, the path '%s' must contain a valid UDIM marker", + opts->filepath); return false; } @@ -420,36 +424,29 @@ bool BKE_image_save( } } - /* Save image - or, for tiled images, the first tile. */ - bool ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed); - - if (ok && ima->source == IMA_SRC_TILED) { + /* Save images */ + bool ok = false; + if (ima->source != IMA_SRC_TILED) { + ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed); + } + else { char filepath[FILE_MAX]; BLI_strncpy(filepath, opts->filepath, sizeof(filepath)); - char head[FILE_MAX], tail[FILE_MAX]; - unsigned short numlen; - BLI_path_sequence_decode(filepath, head, tail, &numlen); - - /* Save all other tiles. */ - int index; - LISTBASE_FOREACH_INDEX (ImageTile *, tile, &ima->tiles, index) { - /* First tile was already saved before the loop. */ - if (index == 0) { - continue; - } + /* Save all the tiles. */ + LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { + BKE_image_set_filepath_from_tile_number( + opts->filepath, udim_pattern, tile_format, tile->tile_number); + iuser->tile = tile->tile_number; + ok = image_save_single(reports, ima, iuser, opts, &colorspace_changed); if (!ok) { - continue; + break; } - - /* Build filepath of the tile. */ - BLI_path_sequence_encode(opts->filepath, head, tail, numlen, tile->tile_number); - - iuser->tile = tile->tile_number; - ok = ok && image_save_single(reports, ima, iuser, opts, &colorspace_changed); } + BLI_strncpy(ima->filepath, filepath, sizeof(ima->filepath)); BLI_strncpy(opts->filepath, filepath, sizeof(opts->filepath)); + MEM_freeN(udim_pattern); } if (colorspace_changed) { |