summaryrefslogtreecommitdiffstats
path: root/Scripts
diff options
context:
space:
mode:
authoryum <yum.food.vr@gmail.com>2026-03-30 18:43:30 -0700
committeryum <yum.food.vr@gmail.com>2026-03-30 18:43:34 -0700
commitc7c1f7733eac8d28faed999ee0dc4796d16a6ade (patch)
tree7b67125cef30f104ac7fefbafeb7eca82de4f5b4 /Scripts
parent46265149d719c0ebb61b0b72d9884f8bb5a76f4b (diff)
Add heightmaps to burley tiling
Also add --homo flag to gaussianize. This "homogenizes" the input by removing low frequency energy from the DFT of the image luma.
Diffstat (limited to 'Scripts')
-rwxr-xr-xScripts/gaussianize61
1 files changed, 61 insertions, 0 deletions
diff --git a/Scripts/gaussianize b/Scripts/gaussianize
index 2ca915d..620e05d 100755
--- a/Scripts/gaussianize
+++ b/Scripts/gaussianize
@@ -72,6 +72,58 @@ def save_image(image: np.ndarray, output_path: Path):
raise ValueError(f"Unsupported output format '{output_path.suffix}'. Use .exr or .png.")
+def rgb_to_ycrcb(image: np.ndarray) -> np.ndarray:
+ """Convert RGB in [0, 1] to full-range YCrCb in [0, 1]."""
+ rgb = np.clip(image, 0.0, 1.0)
+ r = rgb[:, :, 0]
+ g = rgb[:, :, 1]
+ b = rgb[:, :, 2]
+
+ y = 0.299 * r + 0.587 * g + 0.114 * b
+ cb = 0.5 + (b - y) * 0.564
+ cr = 0.5 + (r - y) * 0.713
+ return np.stack((y, cr, cb), axis=-1)
+
+
+def ycrcb_to_rgb(image: np.ndarray) -> np.ndarray:
+ """Convert full-range YCrCb in [0, 1] back to RGB in [0, 1]."""
+ ycrcb = np.asarray(image, dtype=np.float64)
+ y = ycrcb[:, :, 0]
+ cr = ycrcb[:, :, 1] - 0.5
+ cb = ycrcb[:, :, 2] - 0.5
+
+ r = y + 1.402 * cr
+ g = y - 0.714136 * cr - 0.344136 * cb
+ b = y + 1.772 * cb
+ return np.clip(np.stack((r, g, b), axis=-1), 0.0, 1.0)
+
+
+def subtract_low_frequencies(channel: np.ndarray, cutoff_cycles: float = 8.0) -> np.ndarray:
+ """Subtract a smooth periodic low-frequency component from a scalar image."""
+ if cutoff_cycles <= 0.0:
+ raise ValueError(f"cutoff_cycles must be > 0, got {cutoff_cycles}")
+
+ channel = np.asarray(channel, dtype=np.float64)
+ height, width = channel.shape
+
+ fy = np.fft.fftfreq(height)
+ fx = np.fft.fftfreq(width)
+ radius = np.sqrt((fy[:, np.newaxis] * height) ** 2 + (fx[np.newaxis, :] * width) ** 2)
+
+ low_mask = np.exp(-((radius / cutoff_cycles) ** 2))
+ low_pass = np.fft.ifft2(np.fft.fft2(channel) * low_mask).real
+
+ # Remove only the spatial trend and keep the original average luminance.
+ return channel - low_pass + float(np.mean(low_pass))
+
+
+def homogenize_luminance(image: np.ndarray, cutoff_cycles: float = 8.0) -> np.ndarray:
+ """Reduce very-low-frequency luminance variation while preserving chroma."""
+ ycrcb = rgb_to_ycrcb(image)
+ ycrcb[:, :, 0] = subtract_low_frequencies(ycrcb[:, :, 0], cutoff_cycles=cutoff_cycles)
+ return ycrcb_to_rgb(ycrcb)
+
+
class TruncatedGaussian:
"""Truncated Gaussian distribution for histogram transformation."""
@@ -450,6 +502,11 @@ def main():
action="store_true",
help="Print detailed progress information"
)
+ parser.add_argument(
+ "--homo",
+ action="store_true",
+ help="Homogenize luminance before gaussianizing by removing very-low-frequency Y in YCrCb space"
+ )
args = parser.parse_args()
@@ -467,6 +524,10 @@ def main():
image = load_image(args.input)
quantization_step = 0.0 if args.input.suffix.lower() == ".exr" else (1.0 / 255.0)
+ if args.homo:
+ print("Homogenizing luminance in YCrCb space...")
+ image = homogenize_luminance(image)
+
# Gaussianize the texture
print("Applying per-channel Gaussianization...")
gaussianized, inverse_luts = gaussianize_texture(