diff options
Diffstat (limited to 'pixpat-native/src/pattern.h')
| -rw-r--r-- | pixpat-native/src/pattern.h | 597 |
1 files changed, 0 insertions, 597 deletions
diff --git a/pixpat-native/src/pattern.h b/pixpat-native/src/pattern.h deleted file mode 100644 index fbee683..0000000 --- a/pixpat-native/src/pattern.h +++ /dev/null @@ -1,597 +0,0 @@ -#pragma once - -#include <cmath> -#include <cstdint> - -#include "color.h" -#include "layout.h" -#include "params.h" - -namespace pixpat::patterns -{ - -// Patterns emit opaque pixels (a=kNormMax) unless they encode their -// own alpha (e.g. `plain`'s ARGB form). Alpha-bearing sinks -// (ARGB8888 etc) therefore see the pattern's chosen alpha; convert -// paths propagate the source's actual `a` instead (a=0 for X-only -// sources). -// -// A pattern is an instance with: -// using Pixel = RGB16 | YUV16; -// explicit Pat(const Params&) noexcept; -// Pixel sample(size_t x, size_t y, size_t W, size_t H) const noexcept; -// bool ready() const noexcept; // optional, default true -// Patterns that don't read params ignore the constructor argument. - -namespace detail -{ -// 8-bit -> normalized 16 byte-replication. e.g. 255 -> 0xFFFF, -// 1 -> 0x0101. -constexpr RGB16 rgb8(uint8_t r, uint8_t g, uint8_t b) noexcept -{ - return RGB16{ - uint16_t((uint16_t(r) << 8) | r), - uint16_t((uint16_t(g) << 8) | g), - uint16_t((uint16_t(b) << 8) | b), - kNormMax, - }; -} - -// 12-bit -> normalized 16 bit-replication. -constexpr YUV16 yuv12(uint16_t y, uint16_t u, uint16_t v) noexcept -{ - return YUV16{ - uint16_t((y << 4) | (y >> 8)), - uint16_t((u << 4) | (u >> 8)), - uint16_t((v << 4) | (v >> 8)), - kNormMax, - }; -} -} // namespace detail - -// "kmstest" default pattern: white border + diagonals; blue rails on -// the top/left edges; red rails on the bottom/right; an 8-step color -// gradient block in the center. -struct Kmstest { - using Pixel = RGB16; - - explicit Kmstest(const Params&) noexcept { - } - - RGB16 sample(size_t x, size_t y, size_t W, size_t H) const noexcept - { - using detail::rgb8; - const size_t mw = 20; - const size_t xm1 = mw; - const size_t xm2 = W - mw - 1; - const size_t ym1 = mw; - const size_t ym2 = H - mw - 1; - - if (x == xm1 || x == xm2 || y == ym1 || y == ym2) - return rgb8(255, 255, 255); - if (x < xm1 && y < ym1) - return rgb8(255, 255, 255); - if ((x == 0 || x == W - 1) && (y < ym1 || y > ym2)) - return rgb8(255, 255, 255); - if ((y == 0 || y == H - 1) && (x < xm1 || x > xm2)) - return rgb8(255, 255, 255); - if (x < xm1 && (y > ym1 && y < ym2)) - return rgb8(0, 0, 255); - if (y < ym1 && (x > xm1 && x < xm2)) - return rgb8(0, 0, 255); - if (x > xm2 && (y > ym1 && y < ym2)) - return rgb8(255, 0, 0); - if (y > ym2 && (x > xm1 && x < xm2)) - return rgb8(255, 0, 0); - if (x > xm1 && x < xm2 && y > ym1 && y < ym2) { - if (x == y || W - x == H - y) - return rgb8(255, 255, 255); - if (W - x - 1 == y || x == H - y - 1) - return rgb8(255, 255, 255); - const int t = int((x - xm1 - 1) * 8 / (xm2 - xm1 - 1)); - const unsigned c = unsigned((y - ym1 - 1) % 256); - unsigned r = 0, g = 0, b = 0; - switch (t) { - case 0: r = c; break; - case 1: g = c; break; - case 2: b = c; break; - case 3: g = b = c; break; - case 4: r = b = c; break; - case 5: r = g = c; break; - case 6: r = g = b = c; break; - case 7: break; - } - return rgb8(uint8_t(r), uint8_t(g), uint8_t(b)); - } - return rgb8(0, 0, 0); - } -}; - -// SMPTE RP 219-1:2014 color bar pattern. Emits YUV directly with -// pixel values defined by the spec in BT.709 / Limited range. Pass -// `rec=BT709, range=Limited` for spec-correct output; other ColorSpec -// settings produce visibly-wrong colors when the sink crosses to RGB -// (the matrix the caller picked is applied to BT.709-encoded values). -// Callers are trusted — pixpat does not override the spec for them. -struct Smpte { - using Pixel = YUV16; - - explicit Smpte(const Params&) noexcept { - } - - YUV16 sample(size_t x, size_t y, size_t W, size_t H) const noexcept - { - using detail::yuv12; - constexpr YUV16 gray40 = yuv12(1658, 2048, 2048); - constexpr YUV16 white75 = yuv12(2884, 2048, 2048); - constexpr YUV16 yellow75 = yuv12(2694, 704, 2171); - constexpr YUV16 cyan75 = yuv12(2325, 2356, 704); - constexpr YUV16 green75 = yuv12(2136, 1012, 827); - constexpr YUV16 magenta75 = yuv12(1004, 3084, 3269); - constexpr YUV16 red75 = yuv12( 815, 1740, 3392); - constexpr YUV16 blue75 = yuv12( 446, 3392, 1925); - constexpr YUV16 cyan100 = yuv12(3015, 2459, 256); - constexpr YUV16 blue100 = yuv12( 509, 3840, 1884); - constexpr YUV16 yellow100 = yuv12(3507, 256, 2212); - constexpr YUV16 black = yuv12( 256, 2048, 2048); - constexpr YUV16 white100 = yuv12(3760, 2048, 2048); - constexpr YUV16 red100 = yuv12(1001, 1637, 3840); - constexpr YUV16 gray15 = yuv12( 782, 2048, 2048); - - constexpr YUV16 black_m2 = yuv12( 186, 2048, 2048); - constexpr YUV16 black_p2 = yuv12( 326, 2048, 2048); - constexpr YUV16 black_p4 = yuv12( 396, 2048, 2048); - - constexpr size_t M = 1024; - const size_t xs = x * M; - const size_t a = W * M; - const size_t c = (a * 3 / 4) / 7; - const size_t d = a / 8; - - const size_t pattern1_height = (H * 7) / 12; - const size_t pattern2_height = pattern1_height + (H / 12); - const size_t pattern3_height = pattern2_height + (H / 12); - - if (y < pattern1_height) { - if (xs < d || xs >= (a - d)) - return gray40; - const size_t bar = (xs - d) / c; - switch (bar) { - case 0: return white75; - case 1: return yellow75; - case 2: return cyan75; - case 3: return green75; - case 4: return magenta75; - case 5: return red75; - default: return blue75; - } - } - - if (y < pattern2_height) { - if (xs < d) return cyan100; - if (xs >= (a - d)) return blue100; - return white75; - } - - if (y < pattern3_height) { - if (xs < d) return yellow100; - if (xs >= (a - d)) return red100; - const size_t ramp_w = a - 2 * d; - const size_t ramp_x = xs - d; - const uint16_t y_val = uint16_t(256 + (3760 - 256) * ramp_x / ramp_w); - return yuv12(y_val, 2048, 2048); - } - - // pattern4 (PLUGE) - const size_t c0 = d; - const size_t c1 = c0 + c * 3 / 2; - const size_t c2 = c1 + 2 * c; - const size_t c3 = c2 + c * 5 / 6; - - if (xs < c0) return gray15; - if (xs < c1) return black; - if (xs < c2) return white100; - if (xs < c3) return black; - if (xs >= a - d) return gray15; - if (xs >= a - d - c) return black; - - const size_t step = (xs - c3) / (c / 3); - switch (step) { - case 0: return black_m2; - case 1: return black; - case 2: return black_p2; - case 3: return black; - default: return black_p4; - } - } -}; - -// Solid fill from a hex color string. Reads `color=<hex>` from -// params; the value is parsed by Params::get_hex_color (8/16-bpc, -// alpha-first if present, optional `0x` prefix). Missing or -// malformed `color` leaves ready()=false and the dispatcher fails -// the call. -struct Plain { - using Pixel = RGB16; - - explicit Plain(const Params& p) noexcept - { - if (auto c = p.get_hex_color("color")) { - color_ = *c; - ready_ = true; - } - } - - bool ready() const noexcept { - return ready_; - } - - RGB16 sample(size_t, size_t, size_t, size_t) const noexcept - { - return color_; - } - -private: - RGB16 color_{}; - bool ready_{ false }; -}; - -namespace detail -{ -// Linear ramp 0..kNormMax across [0, span-1]. span<=1 returns kNormMax. -constexpr uint16_t ramp16(size_t pos, size_t span) noexcept -{ - if (span <= 1) - return kNormMax; - return uint16_t((uint64_t(pos) * kNormMax) / (span - 1)); -} -} // namespace detail - -// Black/white checkerboard. Reads optional `cell=<N>` (positive -// integer; default 8) for cell size in pixels. -struct Checker { - using Pixel = RGB16; - - explicit Checker(const Params& p) noexcept - { - if (p.get("cell")) { - auto n = p.get_int("cell"); - if (!n || *n <= 0) { - ready_ = false; - return; - } - cell_ = size_t(*n); - } - } - - bool ready() const noexcept { - return ready_; - } - - RGB16 sample(size_t x, size_t y, size_t, size_t) const noexcept - { - const bool dark = (((x / cell_) ^ (y / cell_)) & 1u) != 0; - return dark ? RGB16{ 0, 0, 0, kNormMax } - : RGB16{ kNormMax, kNormMax, kNormMax, kNormMax }; - } - -private: - size_t cell_{ 8 }; - bool ready_{ true }; -}; - -namespace detail -{ -// Pick one of (R, G, B, gray) given a stripe index in [0, 4) and a -// scalar ramp value. Used by hramp/vramp. -constexpr RGB16 rgb_gray_stripe(size_t stripe, uint16_t v) noexcept -{ - switch (stripe) { - case 0: return RGB16{ v, 0, 0, kNormMax }; - case 1: return RGB16{ 0, v, 0, kNormMax }; - case 2: return RGB16{ 0, 0, v, kNormMax }; - default: return RGB16{ v, v, v, kNormMax }; - } -} -} // namespace detail - -// Four horizontal stripes — R, G, B, gray — each a 0..max ramp -// along x. Per-channel and luma quantization in one pattern. -struct Hramp { - using Pixel = RGB16; - - explicit Hramp(const Params&) noexcept { - } - - RGB16 sample(size_t x, size_t y, size_t W, size_t H) const noexcept - { - const size_t stripe = (H == 0) ? 0 : (y * 4) / H; - return detail::rgb_gray_stripe(stripe, detail::ramp16(x, W)); - } -}; - -// Four vertical columns — R, G, B, gray — each a 0..max ramp -// along y. Same coverage as hramp, rotated 90°. -struct Vramp { - using Pixel = RGB16; - - explicit Vramp(const Params&) noexcept { - } - - RGB16 sample(size_t x, size_t y, size_t W, size_t H) const noexcept - { - const size_t col = (W == 0) ? 0 : (x * 4) / W; - return detail::rgb_gray_stripe(col, detail::ramp16(y, H)); - } -}; - -// Diagonal RGB ramp: R sweeps with x, G with y, B with x+y. -struct Dramp { - using Pixel = RGB16; - - explicit Dramp(const Params&) noexcept { - } - - RGB16 sample(size_t x, size_t y, size_t W, size_t H) const noexcept - { - const uint16_t r = detail::ramp16(x, W); - const uint16_t g = detail::ramp16(y, H); - const size_t span = (W + H >= 2) ? (W + H - 1) : 1; - const uint16_t b = detail::ramp16(x + y, span); - return RGB16{ r, g, b, kNormMax }; - } -}; - -namespace detail -{ -// Seven-region color sequence used by hbar/vbar: -// white, red, white, green, white, blue, white. The white separators -// between R/G/B make per-channel offsets at the band boundaries -// visible. -constexpr RGB16 bar_color7(size_t band) noexcept -{ - switch (band) { - case 1: return rgb8(255, 0, 0); - case 3: return rgb8( 0, 255, 0); - case 5: return rgb8( 0, 0, 255); - default: return rgb8(255, 255, 255); - } -} -} // namespace detail - -// Vertical bar (full image height, narrow along x) over a black -// background. `pos` is the left edge in pixels (signed; negative -// values clip at the left edge); `width` is the bar thickness in -// pixels (default 32). The bar is split into 7 equal-height regions -// colored white/red/white/green/white/blue/white. -struct VBarRGB { - using Pixel = RGB16; - - explicit VBarRGB(const Params& p) noexcept - { - auto pp = p.get_int("pos"); - if (!pp) { - ready_ = false; - return; - } - pos_ = *pp; - if (p.get("width")) { - auto w = p.get_int("width"); - if (!w || *w <= 0) { - ready_ = false; - return; - } - width_ = size_t(*w); - } - } - - bool ready() const noexcept { - return ready_; - } - - RGB16 sample(size_t x, size_t y, size_t, size_t H) const noexcept - { - const long long sx = static_cast<long long>(x); - const long long lo = pos_; - const long long hi = lo + static_cast<long long>(width_); - if (sx < lo || sx >= hi) - return detail::rgb8(0, 0, 0); - const size_t band = (H == 0) ? 0 : (y * 7) / H; - return detail::bar_color7(band); - } - -private: - int pos_{}; - size_t width_{ 32 }; - bool ready_{ true }; -}; - -// Horizontal bar: vbar rotated 90°. `pos` is the top edge in pixels; -// `width` is the bar thickness in pixels (default 32). The bar spans -// the full image width and is split into 7 equal-width regions -// colored white/red/white/green/white/blue/white. -struct HBarRGB { - using Pixel = RGB16; - - explicit HBarRGB(const Params& p) noexcept - { - auto pp = p.get_int("pos"); - if (!pp) { - ready_ = false; - return; - } - pos_ = *pp; - if (p.get("width")) { - auto w = p.get_int("width"); - if (!w || *w <= 0) { - ready_ = false; - return; - } - width_ = size_t(*w); - } - } - - bool ready() const noexcept { - return ready_; - } - - RGB16 sample(size_t x, size_t y, size_t W, size_t) const noexcept - { - const long long sy = static_cast<long long>(y); - const long long lo = pos_; - const long long hi = lo + static_cast<long long>(width_); - if (sy < lo || sy >= hi) - return detail::rgb8(0, 0, 0); - const size_t band = (W == 0) ? 0 : (x * 7) / W; - return detail::bar_color7(band); - } - -private: - int pos_{}; - size_t width_{ 32 }; - bool ready_{ true }; -}; - -// Same shape as VBarRGB but emits YUV16 directly. The five unique colors -// (black bg + white/red/green/blue bar regions) are precomputed from -// `spec` at construction so the cross-kind pass is a no-op when the -// sink is YUV. Use the RGB-native `VBarRGB` for RGB sinks instead — it -// avoids the YUV→RGB pass that this variant would incur there. -struct VBarYUV { - using Pixel = YUV16; - - explicit VBarYUV(const Params& p, ColorSpec spec) noexcept - { - auto pp = p.get_int("pos"); - if (!pp) { - ready_ = false; - return; - } - pos_ = *pp; - if (p.get("width")) { - auto w = p.get_int("width"); - if (!w || *w <= 0) { - ready_ = false; - return; - } - width_ = size_t(*w); - } - const ColorCoeffs c = coeffs_for(spec); - using X = ColorXfm<RGB16, YUV16>; - bg_ = X::apply(detail::rgb8( 0, 0, 0), c); - bands_[0] = X::apply(detail::rgb8(255, 255, 255), c); - bands_[1] = X::apply(detail::rgb8(255, 0, 0), c); - bands_[2] = bands_[0]; - bands_[3] = X::apply(detail::rgb8( 0, 255, 0), c); - bands_[4] = bands_[0]; - bands_[5] = X::apply(detail::rgb8( 0, 0, 255), c); - bands_[6] = bands_[0]; - } - - bool ready() const noexcept { - return ready_; - } - - YUV16 sample(size_t x, size_t y, size_t, size_t H) const noexcept - { - const long long sx = static_cast<long long>(x); - const long long lo = pos_; - const long long hi = lo + static_cast<long long>(width_); - if (sx < lo || sx >= hi) - return bg_; - const size_t band = (H == 0) ? 0 : (y * 7) / H; - return bands_[band]; - } - -private: - YUV16 bg_{}; - YUV16 bands_[7]{}; - int pos_{}; - size_t width_{ 32 }; - bool ready_{ true }; -}; - -// YUV-native counterpart to HBarRGB. See VBarYUV. -struct HBarYUV { - using Pixel = YUV16; - - explicit HBarYUV(const Params& p, ColorSpec spec) noexcept - { - auto pp = p.get_int("pos"); - if (!pp) { - ready_ = false; - return; - } - pos_ = *pp; - if (p.get("width")) { - auto w = p.get_int("width"); - if (!w || *w <= 0) { - ready_ = false; - return; - } - width_ = size_t(*w); - } - const ColorCoeffs c = coeffs_for(spec); - using X = ColorXfm<RGB16, YUV16>; - bg_ = X::apply(detail::rgb8( 0, 0, 0), c); - bands_[0] = X::apply(detail::rgb8(255, 255, 255), c); - bands_[1] = X::apply(detail::rgb8(255, 0, 0), c); - bands_[2] = bands_[0]; - bands_[3] = X::apply(detail::rgb8( 0, 255, 0), c); - bands_[4] = bands_[0]; - bands_[5] = X::apply(detail::rgb8( 0, 0, 255), c); - bands_[6] = bands_[0]; - } - - bool ready() const noexcept { - return ready_; - } - - YUV16 sample(size_t x, size_t y, size_t W, size_t) const noexcept - { - const long long sy = static_cast<long long>(y); - const long long lo = pos_; - const long long hi = lo + static_cast<long long>(width_); - if (sy < lo || sy >= hi) - return bg_; - const size_t band = (W == 0) ? 0 : (x * 7) / W; - return bands_[band]; - } - -private: - YUV16 bg_{}; - YUV16 bands_[7]{}; - int pos_{}; - size_t width_{ 32 }; - bool ready_{ true }; -}; - -// Centered radial cosine zone plate: 0.5 + 0.5 * cos(k * (cx² + cy²)) -// with cx, cy measured from the image center and k chosen so the -// local frequency hits Nyquist at the longer edge — i.e. the pattern -// uses every spatial frequency the grid can resolve. -struct Zoneplate { - using Pixel = RGB16; - - explicit Zoneplate(const Params&) noexcept { - } - - RGB16 sample(size_t x, size_t y, size_t W, size_t H) const noexcept - { - const double max_dim = double(W > H ? W : H); - // Local frequency d(k r²)/dr = 2 k r. At r = max_dim/2 the - // frequency reaches π/pixel (Nyquist), giving k = π / max_dim. - const double k = 3.14159265358979323846 / (max_dim > 0 ? max_dim : 1.0); - const double cx = double(x) - 0.5 * double(W); - const double cy = double(y) - 0.5 * double(H); - const double phase = k * (cx * cx + cy * cy); - const double v = 0.5 + 0.5 * std::cos(phase); - const double scaled = v * 65535.0; - const uint16_t g = (scaled < 0.0) ? uint16_t(0) - : (scaled > 65535.0) ? kNormMax - : uint16_t(scaled + 0.5); - return RGB16{ g, g, g, kNormMax }; - } -}; - -} // namespace pixpat::patterns |
