UDIM: Improve tile format detection in filenames

There are some filenames where the UDIM pattern guessing would fail
unnecessarily. The user can fix these up afterwards but it would be
nicer if they would detect properly in the first place.

Examples:
`test.1001.ver0023.png` would guess wrong since it uses the image
sequence detection code which finds the first sequence from the end. It
would guess `filename.1001.ver<UDIM>.png`

`uv-test.u1_v2.png` would fail detection due to a bug in the processing.

Make this much more reliable and add tests for the most important tile
related get/set/detection functions.

Differential Revision: https://developer.blender.org/D14320
This commit is contained in:
Jesse Yurkovich 2022-03-23 21:01:36 -07:00
parent ead2c71a90
commit a1598d6835
3 changed files with 205 additions and 61 deletions

View File

@ -818,6 +818,7 @@ if(WITH_GTESTS)
intern/cryptomatte_test.cc
intern/curves_geometry_test.cc
intern/fcurve_test.cc
intern/image_test.cc
intern/idprop_serialize_test.cc
intern/image_partial_update_test.cc
intern/lattice_deform_test.cc

View File

@ -9,6 +9,7 @@
#include <cmath>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <fcntl.h>
#ifndef WIN32
# include <unistd.h>
@ -16,7 +17,8 @@
# include <io.h>
#endif
#include <ctime>
#include <regex>
#include <string>
#include "BLI_array.hh"
@ -3316,69 +3318,24 @@ void BKE_image_ensure_tile_token(char *filename)
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);
std::string path(filename);
std::smatch match;
/* General 4-digit "udim" pattern. As this format is susceptible to ambiguity
* with other digit sequences, we can leverage the supported range of roughly
* 1000 through 2000 to provide better detection.
*/
std::regex pattern("(^|.*?\\D)([12]\\d{3})(\\D.*)");
if (std::regex_search(path, match, pattern)) {
BLI_strncpy(filename, match.format("$1<UDIM>$3").c_str(), FILE_MAX);
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';
/* General u##_v### "uvtile" pattern. */
pattern = std::regex("(.*)(u\\d{1,2}_v\\d{1,3})(\\D.*)");
if (std::regex_search(path, match, pattern)) {
BLI_strncpy(filename, match.format("$1<UVTILE>$3").c_str(), FILE_MAX);
return;
}
}

View File

@ -0,0 +1,186 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. */
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BKE_image.h"
#include "MEM_guardedalloc.h"
#include "testing/testing.h"
namespace blender::bke::tests {
TEST(udim, image_ensure_tile_token)
{
auto verify = [](const char *original, const char *expected) {
char result[FILE_MAX];
BLI_strncpy(result, original, sizeof(result));
BKE_image_ensure_tile_token(result);
EXPECT_STREQ(result, expected);
};
/* Already present tokens. */
verify("test.<UDIM>.png", "test.<UDIM>.png");
verify("test.<UVTILE>.png", "test.<UVTILE>.png");
/* UDIM pattern detection. */
verify("test.1002.png", "test.<UDIM>.png");
verify("test-1002-ao.png", "test-<UDIM>-ao.png");
verify("test_1002_ao.png", "test_<UDIM>_ao.png");
verify("test.1002.ver0023.png", "test.<UDIM>.ver0023.png");
verify("test.ver0023.1002.png", "test.ver0023.<UDIM>.png");
verify("1002test.png", "<UDIM>test.png");
verify("test1002.png", "test<UDIM>.png");
/* UVTILE pattern detection. */
verify("uv-test.u2_v10.png", "uv-test.<UVTILE>.png");
verify("uv-test-u2_v10-ao.png", "uv-test-<UVTILE>-ao.png");
verify("uv-test_u2_v10_ao.png", "uv-test_<UVTILE>_ao.png");
verify("uv-test.u10_v100.png", "uv-test.<UVTILE>.png");
verify("u_v-test.u2_v10.png", "u_v-test.<UVTILE>.png");
verify("u2_v10uv-test.png", "<UVTILE>uv-test.png");
verify("u2_v10u_v-test.png", "<UVTILE>u_v-test.png");
/* Incorrect patterns. */
for (const char *incorrect : {"test.123.png",
"test.12345.png",
"test.uv.png",
"test.u1v.png",
"test.uv1.png",
"test.u_v.png",
"test.u1_v.png",
"test.u_v2.png",
"test.u2v3.png",
"test.u123_v1.png",
"test.u1_v12345.png"}) {
/* These should not result in modifications happening. */
verify(incorrect, incorrect);
}
}
TEST(udim, image_get_tile_strformat)
{
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern;
/* Parameter validation. */
udim_pattern = BKE_image_get_tile_strformat(nullptr, &tile_format);
EXPECT_EQ(udim_pattern, nullptr);
udim_pattern = BKE_image_get_tile_strformat("", nullptr);
EXPECT_EQ(udim_pattern, nullptr);
/* Typical usage. */
udim_pattern = BKE_image_get_tile_strformat("", &tile_format);
EXPECT_EQ(tile_format, UDIM_TILE_FORMAT_NONE);
EXPECT_EQ(udim_pattern, nullptr);
udim_pattern = BKE_image_get_tile_strformat("test.<UNKNOWN>.png", &tile_format);
EXPECT_EQ(tile_format, UDIM_TILE_FORMAT_NONE);
EXPECT_EQ(udim_pattern, nullptr);
udim_pattern = BKE_image_get_tile_strformat("test.<UDIM>.png", &tile_format);
EXPECT_EQ(tile_format, UDIM_TILE_FORMAT_UDIM);
EXPECT_STREQ(udim_pattern, "test.%d.png");
MEM_freeN(udim_pattern);
udim_pattern = BKE_image_get_tile_strformat("test.<UVTILE>.png", &tile_format);
EXPECT_EQ(tile_format, UDIM_TILE_FORMAT_UVTILE);
EXPECT_STREQ(udim_pattern, "test.u%d_v%d.png");
MEM_freeN(udim_pattern);
}
TEST(udim, image_get_tile_number_from_filepath)
{
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern;
int tile_number;
udim_pattern = BKE_image_get_tile_strformat("test.<UDIM>.png", &tile_format);
EXPECT_EQ(tile_format, UDIM_TILE_FORMAT_UDIM);
EXPECT_NE(udim_pattern, nullptr);
/* Parameter validation. */
EXPECT_FALSE(
BKE_image_get_tile_number_from_filepath(nullptr, udim_pattern, tile_format, &tile_number));
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"test.1004.png", nullptr, tile_format, &tile_number));
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"test.1004.png", udim_pattern, UDIM_TILE_FORMAT_NONE, &tile_number));
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"test.1004.png", udim_pattern, tile_format, nullptr));
/* UDIM tile format tests. */
EXPECT_TRUE(BKE_image_get_tile_number_from_filepath(
"test.1004.png", udim_pattern, tile_format, &tile_number));
EXPECT_EQ(tile_number, 1004);
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"has_no_number.png", udim_pattern, tile_format, &tile_number));
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"test.X.png", udim_pattern, tile_format, &tile_number));
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"wrong.1004.png", udim_pattern, tile_format, &tile_number));
MEM_freeN(udim_pattern);
/* UVTILE tile format tests. */
udim_pattern = BKE_image_get_tile_strformat("test.<UVTILE>.png", &tile_format);
EXPECT_EQ(tile_format, UDIM_TILE_FORMAT_UVTILE);
EXPECT_NE(udim_pattern, nullptr);
EXPECT_TRUE(BKE_image_get_tile_number_from_filepath(
"test.u2_v2.png", udim_pattern, tile_format, &tile_number));
EXPECT_EQ(tile_number, 1012);
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"has_no_number.png", udim_pattern, tile_format, &tile_number));
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"test.u1_vX.png", udim_pattern, tile_format, &tile_number));
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"test.uX_v1.png", udim_pattern, tile_format, &tile_number));
EXPECT_FALSE(BKE_image_get_tile_number_from_filepath(
"wrong.u2_v2.png", udim_pattern, tile_format, &tile_number));
MEM_freeN(udim_pattern);
}
TEST(udim, image_set_filepath_from_tile_number)
{
eUDIM_TILE_FORMAT tile_format;
char *udim_pattern;
udim_pattern = BKE_image_get_tile_strformat("test.<UDIM>.png", &tile_format);
EXPECT_EQ(tile_format, UDIM_TILE_FORMAT_UDIM);
EXPECT_NE(udim_pattern, nullptr);
char filepath[FILE_MAX];
/* Parameter validation. */
BLI_strncpy(filepath, "xxxx", FILE_MAX);
BKE_image_set_filepath_from_tile_number(nullptr, udim_pattern, tile_format, 1028);
BKE_image_set_filepath_from_tile_number(filepath, nullptr, tile_format, 1028);
EXPECT_STREQ(filepath, "xxxx");
BKE_image_set_filepath_from_tile_number(filepath, udim_pattern, UDIM_TILE_FORMAT_NONE, 1028);
EXPECT_STREQ(filepath, "xxxx");
/* UDIM tile format tests. */
BKE_image_set_filepath_from_tile_number(filepath, udim_pattern, tile_format, 1028);
EXPECT_STREQ(filepath, "test.1028.png");
MEM_freeN(udim_pattern);
/* UVTILE tile format tests. */
udim_pattern = BKE_image_get_tile_strformat("test.<UVTILE>.png", &tile_format);
EXPECT_EQ(tile_format, UDIM_TILE_FORMAT_UVTILE);
EXPECT_NE(udim_pattern, nullptr);
BKE_image_set_filepath_from_tile_number(filepath, udim_pattern, tile_format, 1028);
EXPECT_STREQ(filepath, "test.u8_v3.png");
MEM_freeN(udim_pattern);
}
} // namespace blender::bke::tests