Skip to content

Commit 72b4502

Browse files
author
Ole Gulbrandsen
committed
Add support for writing Gaussian forward and inverse transform as meta data
1 parent 71177ff commit 72b4502

File tree

3 files changed

+106
-0
lines changed

3 files changed

+106
-0
lines changed

src/libOpenImageIO/maketexture.cpp

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

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
@@ -4692,6 +4692,14 @@ prep_texture_config(ImageSpec& configspec, ParamValueList& fileoptions)
46924692
fileoptions.get_string("bumpformat", "auto"));
46934693
configspec.attribute("maketx:uvslopes_scale",
46944694
fileoptions.get_float("uvslopes_scale", 0.0f));
4695+
4696+
// The default values here should match the initialized values
4697+
// in src/maketx/maketx.cpp
4698+
configspec.attribute("maketx:cdf", fileoptions.get_int("cdf"));
4699+
configspec.attribute("maketx:cdfbits", fileoptions.get_int("cdfbits", 8));
4700+
configspec.attribute("maketx:cdfsigma",
4701+
fileoptions.get_float("cdfsigma", 1.0f / 6));
4702+
46954703
// if (mipimages.size())
46964704
// configspec.attribute ("maketx:mipimages", Strutil::join(mipimages,";"));
46974705

0 commit comments

Comments
 (0)