From 0a7c6c096da6f7b74e2c8aea7eeefa010dd1ccf3 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 14:12:04 -0700 Subject: [PATCH] feat(editor): add --gen-texture-halftone gradient-modulated dots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 80th procedural texture: regular grid of dots whose radii grow with a configurable gradient direction. Three modes: • v (vertical) — radii grow top-to-bottom • h (horizontal) — radii grow left-to-right • r (radial) — radii grow from texture center outward Mimics the comic-print / newspaper image-reproduction trick of varying dot size to encode grayscale. Distinct from --gen-texture-dots (uniform radius across the grid) and --gen-texture-studs (uniform with derived inner highlight) — halftone is the gradient-modulated variant. Useful for retro-comic / newspaper-aesthetic surfaces, vintage- poster overlays, sci-fi monitor screens (radial mode reads as CRT vignette), banner gradient detail, dawn/dusk sky overlays. Default 16-stride / maxR=7 / vertical reads as classic comic shading at 256x256. --- tools/editor/cli_arg_required.cpp | 2 +- tools/editor/cli_gen_texture.cpp | 85 +++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 + 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index c64393d1..63388fd4 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -119,7 +119,7 @@ const char* const kArgRequired[] = { "--gen-texture-embroidery", "--gen-texture-lightbeam", "--gen-texture-dewdrops", "--gen-texture-pinwheel", "--gen-texture-scratched-metal", "--gen-texture-crackle", - "--gen-texture-star", + "--gen-texture-star", "--gen-texture-halftone", "--validate-glb", "--info-glb", "--info-glb-tree", "--info-glb-bytes", "--validate-jsondbc", "--check-glb-bounds", "--validate-stl", "--validate-png", "--validate-blp", diff --git a/tools/editor/cli_gen_texture.cpp b/tools/editor/cli_gen_texture.cpp index b7668e88..252c96fb 100644 --- a/tools/editor/cli_gen_texture.cpp +++ b/tools/editor/cli_gen_texture.cpp @@ -3500,6 +3500,90 @@ int handleKnit(int& i, int argc, char** argv) { return 0; } +int handleHalftone(int& i, int argc, char** argv) { + // Halftone: regular grid of dots whose radii grow with a + // configurable gradient direction (vertical, horizontal, or + // radial-from-center). Mimics the comic-print / newspaper + // image-reproduction trick of varying dot size to encode + // grayscale. Distinct from --gen-texture-dots (uniform dot + // radius) and --gen-texture-studs (uniform with inner + // highlight) — halftone is the gradient-modulated variant. + std::string outPath = argv[++i]; + std::string bgHex = argv[++i]; + std::string dotHex = argv[++i]; + int stride = 16; + int maxR = 7; + char dir = 'v'; // 'v' vertical, 'h' horizontal, 'r' radial + int W = 256, H = 256; + parseOptInt(i, argc, argv, stride); + parseOptInt(i, argc, argv, maxR); + if (i + 1 < argc && argv[i + 1][0] != '-') { + const char* a = argv[++i]; + if (a[0] == 'h' || a[0] == 'H') dir = 'h'; + else if (a[0] == 'r' || a[0] == 'R') dir = 'r'; + else dir = 'v'; + } + parseOptInt(i, argc, argv, W); + parseOptInt(i, argc, argv, H); + if (W < 1 || H < 1 || W > 8192 || H > 8192 || + stride < 4 || stride > 1024 || + maxR < 1 || maxR * 2 >= stride) { + std::fprintf(stderr, + "gen-texture-halftone: invalid dims (W/H 1..8192, " + "stride 4..1024, maxR 1..stride/2)\n"); + return 1; + } + uint8_t br_, bg_, bb_, dr, dg, db; + if (!parseHexOrError(bgHex, br_, bg_, bb_, + "gen-texture-halftone")) return 1; + if (!parseHexOrError(dotHex, dr, dg, db, + "gen-texture-halftone")) return 1; + std::vector pixels(static_cast(W) * H * 3, 0); + const float cx = W * 0.5f; + const float cy = H * 0.5f; + const float maxDist = std::sqrt(cx * cx + cy * cy); + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + // Find nearest grid center. + int col = (x + stride / 2) / stride; + int row = (y + stride / 2) / stride; + float gx = col * stride; + float gy = row * stride; + // Gradient value t in [0, 1] at the cell center based + // on direction. + float t; + if (dir == 'h') { + t = gx / (W - 1); + } else if (dir == 'r') { + float dxc = gx - cx, dyc = gy - cy; + t = std::sqrt(dxc * dxc + dyc * dyc) / maxDist; + } else { + t = gy / (H - 1); + } + float r = maxR * t; + float ddx = x - gx; + float ddy = y - gy; + float d = std::sqrt(ddx * ddx + ddy * ddy); + uint8_t outR, outG, outB; + if (d < r) { + outR = dr; outG = dg; outB = db; + } else { + outR = br_; outG = bg_; outB = bb_; + } + setPixelRGB(pixels, W, x, y, outR, outG, outB); + } + } + if (!savePngOrError(outPath, W, H, pixels, + "gen-texture-halftone")) return 1; + printPngWrote(outPath, W, H); + std::printf(" bg/dot : %s / %s\n", bgHex.c_str(), dotHex.c_str()); + std::printf(" grid : stride=%d, maxR=%d, dir=%s\n", + stride, maxR, + dir == 'h' ? "horizontal" : + (dir == 'r' ? "radial" : "vertical")); + return 0; +} + int handleStar(int& i, int argc, char** argv) { // N-pointed star polygon centered on the texture. Each pixel // computes its polar (r, θ); the star boundary at any θ @@ -5831,6 +5915,7 @@ constexpr TextureEntry kTextureTable[] = { {"--gen-texture-scratched-metal",3, handleScratchedMetal}, {"--gen-texture-crackle", 3, handleCrackle}, {"--gen-texture-star", 3, handleStar}, + {"--gen-texture-halftone", 3, handleHalftone}, }; } // namespace diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 1c6a484a..542aba89 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -193,6 +193,8 @@ void printUsage(const char* argv0) { std::printf(" Crackle: fine Voronoi cell-boundary cracks (dried mud / parched earth / aged leather)\n"); std::printf(" --gen-texture-star [points] [innerFrac] [W H]\n"); std::printf(" Star: solid N-pointed star polygon centered (medallions / shields / religious symbols)\n"); + std::printf(" --gen-texture-halftone [stride] [maxR] [v|h|r] [W H]\n"); + std::printf(" Halftone: grid of dots whose radii grow with a v/h/r gradient (comic / newspaper print look)\n"); std::printf(" --add-texture-to-zone [renameTo]\n"); std::printf(" Copy an existing PNG into (optionally renaming it on the way in)\n"); std::printf(" --gen-mesh [size]\n");