Skip to content

Commit 78a67fe

Browse files
authored
Add support for writing Gaussian forward and inverse transform lookup tables (#3159)
1 parent f0ac3e1 commit 78a67fe

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed

src/libOpenImageIO/maketexture.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,88 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input,
11841184
mode = ImageBufAlgo::MakeTxTexture;
11851185
src = bumpslopes;
11861186
}
1187+
1188+
if (configspec.get_int_attribute("maketx:cdf")) {
1189+
// Writes Gaussian CDF and Inverse Gaussian CDF as per-channel
1190+
// metadata. We provide both the inverse transfrom and forward
1191+
// transfrom, so in theory we're free to change the distribution.
1192+
//
1193+
// References:
1194+
//
1195+
// Brent Burley, On Histogram-Preserving Blending for Randomized
1196+
// Texture Tiling, Journal of Computer Graphics Techniques (JCGT),
1197+
// vol. 8, no. 4, 31-53, 2019
1198+
//
1199+
// Eric Heitz and Fabrice Neyret, High-Performance By-Example Noise
1200+
// using a Histogram-Preserving Blending Operator,
1201+
// https://hal.inria.fr/hal-01824773}, Proceedings of the ACM on
1202+
// Computer Graphics and Interactive Techniques, ACM SIGGRAPH /
1203+
// Eurographics Symposium on High-Performance Graphics 2018,
1204+
//
1205+
// Benedikt Bitterli
1206+
// https://benedikt-bitterli.me/histogram-tiling/
1207+
1208+
const float cdf_sigma = configspec.get_float_attribute(
1209+
"maketx:cdfsigma");
1210+
const int cdf_bits = configspec.get_int_attribute("maketx:cdfbits");
1211+
const uint64_t bins = 1 << cdf_bits;
1212+
1213+
// Normalization coefficient for the truncated normal distribution
1214+
const float c_sigma_inv = fast_erf(1.0f / (2.0f * M_SQRT2 * cdf_sigma));
1215+
1216+
// If there are channels other than R,G,B,A, we probably shouldn't do
1217+
// anything to them.
1218+
const int channels = std::min(4, src->spec().nchannels);
1219+
1220+
std::vector<float> invCDF(bins);
1221+
std::vector<float> CDF(bins);
1222+
std::vector<imagesize_t> hist;
1223+
1224+
for (int i = 0; i < channels; i++) {
1225+
hist = ImageBufAlgo::histogram(*src, i, bins, 0.0f, 1.0f);
1226+
1227+
// Turn the histogram into a non-normalized CDF
1228+
for (uint64_t j = 1; j < bins; j++) {
1229+
hist[j] += hist[j - 1];
1230+
}
1231+
1232+
// Store the inverse CDF as a lookup-table which we'll use to
1233+
// transform the image data to a Guassian distribution. As
1234+
// mentioned in Burley [2019] we're combining two steps here when
1235+
// using the invCDF lookup table: we first "look up" the image
1236+
// value through its CDF (the normalized histogram) which gives us
1237+
// a uniformly distributed value, which we're then feeding in to
1238+
// the Gaussian inverse CDF to transfrom the unifrom distribution
1239+
// to Gaussian.
1240+
for (uint64_t j = 0; j < bins; j++) {
1241+
float u = float(hist[j]) / hist[bins - 1];
1242+
float g = 0.5f
1243+
+ cdf_sigma * M_SQRT2
1244+
* fast_ierf(c_sigma_inv * (2.0f * u - 1.0f));
1245+
invCDF[j] = std::min(1.0f, std::max(0.0f, g));
1246+
}
1247+
configspec.attribute("invCDF_" + std::to_string(i),
1248+
TypeDesc(TypeDesc::FLOAT, bins),
1249+
invCDF.data());
1250+
1251+
// Store the forward CDF as a lookup table to transform back to
1252+
// the original image distribution from a Gaussian distribution.
1253+
for (uint64_t j = 0; j < bins; j++) {
1254+
auto upper = std::upper_bound(invCDF.begin(), invCDF.end(),
1255+
float(j) / (float(bins - 1)));
1256+
CDF[j] = clamp(float(upper - invCDF.begin()) / float(bins - 1),
1257+
0.0f, 1.0f);
1258+
}
1259+
1260+
configspec.attribute("CDF_" + std::to_string(i),
1261+
TypeDesc(TypeDesc::FLOAT, bins), CDF.data());
1262+
}
1263+
1264+
configspec["CDF_bits"] = cdf_bits;
1265+
1266+
mode = ImageBufAlgo::MakeTxTexture;
1267+
}
1268+
11871269
double misc_time_2 = alltime.lap();
11881270
STATUS("misc2", misc_time_2);
11891271

src/maketx/maketx.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ getargs(int argc, char* argv[], ImageSpec& configspec)
177177
bool sansattrib = false;
178178
float sharpen = 0.0f;
179179
float uvslopes_scale = 0.0f;
180+
bool cdf = false;
181+
float cdfsigma = 1.0f / 6;
182+
int cdfbits = 8;
180183
std::string incolorspace;
181184
std::string outcolorspace;
182185
std::string colorconfigname;
@@ -285,6 +288,13 @@ getargs(int argc, char* argv[], ImageSpec& configspec)
285288
.hidden(); // DEPRECATED 1.6
286289
ap.arg("--mipimage %L:FILENAME", &mipimages)
287290
.help("Specify an individual MIP level");
291+
ap.arg("--cdf %d:N", &cdf)
292+
.help("Store the forward and inverse Gaussian CDF as a lookup-table. The variance is set by cdfsigma (1/6 by default), and the number of buckets \
293+
in the lookup table is determined by cdfbits (8 bit - 256 buckets by default)");
294+
ap.arg("--cdfsigma %f:N", &cdfsigma)
295+
.help("Specify the Gaussian sigma parameter when writing the forward and inverse Gaussian CDF data. The default vale is 1/6 (0.1667)");
296+
ap.arg("--cdfbits %d:N", &cdfbits)
297+
.help("Specify the number of bits used to store the forward and inverse Guassian CDF. The default value is 8 bits");
288298

289299
ap.separator("Basic modes (default is plain texture):");
290300
ap.arg("--shadow", &shadowmode)
@@ -426,6 +436,9 @@ getargs(int argc, char* argv[], ImageSpec& configspec)
426436
configspec.attribute("maketx:mipimages", Strutil::join(mipimages, ";"));
427437
if (bumpslopesmode)
428438
configspec.attribute("maketx:bumpformat", bumpformat);
439+
configspec.attribute("maketx:cdf", cdf);
440+
configspec.attribute("maketx:cdfsigma", cdfsigma);
441+
configspec.attribute("maketx:cdfbits", cdfbits);
429442

430443
std::string cmdline
431444
= Strutil::sprintf("OpenImageIO %s : %s", OIIO_VERSION_STRING,

src/oiiotool/oiiotool.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4839,6 +4839,14 @@ prep_texture_config(ImageSpec& configspec, ParamValueList& fileoptions)
48394839
fileoptions.get_string("bumpformat", "auto"));
48404840
configspec.attribute("maketx:uvslopes_scale",
48414841
fileoptions.get_float("uvslopes_scale", 0.0f));
4842+
4843+
// The default values here should match the initialized values
4844+
// in src/maketx/maketx.cpp
4845+
configspec.attribute("maketx:cdf", fileoptions.get_int("cdf"));
4846+
configspec.attribute("maketx:cdfbits", fileoptions.get_int("cdfbits", 8));
4847+
configspec.attribute("maketx:cdfsigma",
4848+
fileoptions.get_float("cdfsigma", 1.0f / 6));
4849+
48424850
// if (mipimages.size())
48434851
// configspec.attribute ("maketx:mipimages", Strutil::join(mipimages,";"));
48444852

0 commit comments

Comments
 (0)