Files
dmf-studio-rnd/shared/V210.hpp
2026-07-14 18:59:09 +03:00

299 lines
12 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <vector>
namespace dmf::v210 {
// SMPTE 75% color bars — 10-bit limited range
// Y: 64 (black) to 940 (white)
// Cb/Cr: 64 to 960, 512 = neutral grey
struct Color { uint16_t y, cb, cr; };
constexpr std::array<Color, 7> SMPTE_BARS = {{
{721, 512, 512}, // white
{674, 176, 543}, // yellow
{581, 589, 176}, // cyan
{534, 253, 207}, // green
{251, 771, 817}, // magenta
{204, 435, 848}, // red
{111, 848, 481}, // blue
}};
// IRE 11-step greyscale bars
// from total black to 100% white
// Y_10bit = 64 + (IRE / 100) × (940 64) Rec. 709/Rec. 2020
// Y_10bit = 64 + (IRE × 8.76)
constexpr std::array<Color, 11> IRE_BARS = {{
{64, 512, 512}, // IRE 0
{152, 512, 512}, // IRE 10
{239, 512, 512}, // IRE 20 and so on
{327, 512, 512},
{414, 512, 512},
{502, 512, 512},
{590, 512, 512},
{677, 512, 512},
{765, 512, 512},
{852, 512, 512},
{940, 512, 512},
}};
// Pack 6 pixels into 4 x 32-bit V210 words (16 bytes total).
//
// V210 is 4:2:2 — each pair of pixels shares one Cb and one Cr sample.
// The three pairs in a block map to words like this (bits [9:0],[19:10],[29:20]):
// word 0: Cb(pair0) | Y(px0) | Cr(pair0)
// word 1: Y(px1) | Cb(pair1) | Y(px2)
// word 2: Cr(pair1) | Y(px3) | Cb(pair2)
// word 3: Y(px4) | Cr(pair2) | Y(px5)
inline void pack_block(
uint8_t* out,
Color p01, uint16_t y0, uint16_t y1, // pair 01
Color p23, uint16_t y2, uint16_t y3, // pair 23
Color p45, uint16_t y4, uint16_t y5) // pair 45
{
auto* w = reinterpret_cast<uint32_t*>(out);
w[0] = (p01.cb & 0x3FFu) | ((y0 & 0x3FFu) << 10) | ((p01.cr & 0x3FFu) << 20);
w[1] = (y1 & 0x3FFu) | ((p23.cb & 0x3FFu) << 10) | ((y2 & 0x3FFu) << 20);
w[2] = (p23.cr & 0x3FFu) | ((y3 & 0x3FFu) << 10) | ((p45.cb & 0x3FFu) << 20);
w[3] = (y4 & 0x3FFu) | ((p45.cr & 0x3FFu) << 10) | ((y5 & 0x3FFu) << 20);
}
// Write one horizontal line of an arbitrary bar palette.
template<std::size_t N>
inline void write_palette_line(uint8_t* line, int width, const std::array<Color, N>& palette)
{
const int n = static_cast<int>(N);
const int blocks = width / 6;
for (int b = 0; b < blocks; b++) {
int x = b * 6;
auto color = [&](int px) -> const Color& {
return palette[static_cast<std::size_t>(px * n / width)];
};
const Color& c01 = color(x);
const Color& c23 = color(x + 2);
const Color& c45 = color(x + 4);
pack_block(line + b * 16,
c01, c01.y, color(x+1).y,
c23, c23.y, color(x+3).y,
c45, c45.y, color(x+5).y);
}
}
// Fill a frame with a solid color.
inline void fill_solid(uint8_t* buf, int width, int height, uint32_t stride, Color c)
{
const int blocks = width / 6;
for (int b = 0; b < blocks; b++)
pack_block(buf + b * 16, c, c.y, c.y, c, c.y, c.y, c, c.y, c.y);
for (int y = 1; y < height; y++)
std::memcpy(buf + static_cast<ptrdiff_t>(y) * stride, buf, blocks * 16);
}
// SMPTE 75% color bars.
inline void fill_colorbars(uint8_t* buf, int width, int height, uint32_t stride)
{
for (int y = 0; y < height; y++)
write_palette_line(buf + static_cast<ptrdiff_t>(y) * stride, width, SMPTE_BARS);
}
// IRE 11-step greyscale ramp.
inline void fill_ire_ramp(uint8_t* buf, int width, int height, uint32_t stride)
{
for (int y = 0; y < height; y++)
write_palette_line(buf + static_cast<ptrdiff_t>(y) * stride, width, IRE_BARS);
}
// 10-bit limited black (Y=64, Cb=Cr=512).
inline void fill_black(uint8_t* buf, int width, int height, uint32_t stride)
{
fill_solid(buf, width, height, stride, {64, 512, 512});
}
// 10-bit limited white (Y=940, Cb=Cr=512).
inline void fill_white(uint8_t* buf, int width, int height, uint32_t stride)
{
fill_solid(buf, width, height, stride, {940, 512, 512});
}
inline void UYVYtoV210(const uint8_t* src_buf, uint8_t* dst_buf, int width, int height, uint32_t src_stride, uint32_t dst_stride)
{
const uint8_t* src = src_buf;
uint8_t* dst = dst_buf;
const int blocks = width / 6;
for (int y = 0; y < height; y++) {
for (int b = 0; b < blocks; b++) {
const uint8_t* mp = src + b * 12; // 3 macropixels = 12 bytes
// mp[0]=U0, mp[1]=Y0, mp[2]=V0, mp[3]=Y1
// mp[4]=U1, mp[5]=Y2, mp[6]=V1, mp[7]=Y3
// mp[8]=U2, mp[9]=Y4, mp[10]=V2, mp[11]=Y5
dmf::v210::pack_block(dst + b * 16,
{0, static_cast<uint16_t>(mp[0]<<2), static_cast<uint16_t>(mp[2]<<2)},
static_cast<uint16_t>(mp[1]<<2), static_cast<uint16_t>(mp[3]<<2),
{0, static_cast<uint16_t>(mp[4]<<2), static_cast<uint16_t>(mp[6]<<2)},
static_cast<uint16_t>(mp[5]<<2), static_cast<uint16_t>(mp[7]<<2),
{0, static_cast<uint16_t>(mp[8]<<2), static_cast<uint16_t>(mp[10]<<2)},
static_cast<uint16_t>(mp[9]<<2), static_cast<uint16_t>(mp[11]<<2));
}
src += src_stride;
dst += dst_stride;
}
}
inline void YUV422P10toV210(
const uint16_t* y, const uint16_t* u, const uint16_t* v,
uint8_t* dst, int width, int height,
int y_stride, int u_stride, int v_stride,
uint32_t dst_stride)
{
for (int row = 0; row < height; row++) {
const uint16_t* y_row = reinterpret_cast<const uint16_t*>(
reinterpret_cast<const uint8_t*>(y) + row * y_stride);
const uint16_t* u_row = reinterpret_cast<const uint16_t*>(
reinterpret_cast<const uint8_t*>(u) + row * u_stride);
const uint16_t* v_row = reinterpret_cast<const uint16_t*>(
reinterpret_cast<const uint8_t*>(v) + row * v_stride);
uint8_t* dst_row = dst + static_cast<ptrdiff_t>(row) * dst_stride;
const int blocks = width / 6;
for (int b = 0; b < blocks; b++) {
const int x = b * 6;
const uint16_t cb0 = u_row[x/2], cb1 = u_row[x/2+1], cb2 = u_row[x/2+2];
const uint16_t cr0 = v_row[x/2], cr1 = v_row[x/2+1], cr2 = v_row[x/2+2];
const uint16_t y0 = y_row[x], y1 = y_row[x+1], y2 = y_row[x+2];
const uint16_t y3 = y_row[x+3], y4 = y_row[x+4], y5 = y_row[x+5];
auto* w = reinterpret_cast<uint32_t*>(dst_row + b * 16);
w[0] = (cb0 & 0x3FFu) | ((y0 & 0x3FFu) << 10) | ((cr0 & 0x3FFu) << 20);
w[1] = (y1 & 0x3FFu) | ((cb1 & 0x3FFu) << 10) | ((y2 & 0x3FFu) << 20);
w[2] = (cr1 & 0x3FFu) | ((y3 & 0x3FFu) << 10) | ((cb2 & 0x3FFu) << 20);
w[3] = (y4 & 0x3FFu) | ((cr2 & 0x3FFu) << 10) | ((y5 & 0x3FFu) << 20);
}
}
}
// Unpack one V210 row into planar uint16_t Y (width values),
// Cb and Cr (width/2 values each). Width must be a multiple of 6.
inline void unpack_row(const uint8_t* src, int width,
uint16_t* Y, uint16_t* Cb, uint16_t* Cr)
{
const auto* w = reinterpret_cast<const uint32_t*>(src);
const int blocks = width / 6;
for (int b = 0; b < blocks; ++b, w += 4) {
const int x = b * 6;
Cb[x/2] = (w[0] >> 0) & 0x3FF;
Y[x] = (w[0] >> 10) & 0x3FF;
Cr[x/2] = (w[0] >> 20) & 0x3FF;
Y[x+1] = (w[1] >> 0) & 0x3FF;
Cb[x/2+1] = (w[1] >> 10) & 0x3FF;
Y[x+2] = (w[1] >> 20) & 0x3FF;
Cr[x/2+1] = (w[2] >> 0) & 0x3FF;
Y[x+3] = (w[2] >> 10) & 0x3FF;
Cb[x/2+2] = (w[2] >> 20) & 0x3FF;
Y[x+4] = (w[3] >> 0) & 0x3FF;
Cr[x/2+2] = (w[3] >> 10) & 0x3FF;
Y[x+5] = (w[3] >> 20) & 0x3FF;
}
}
// Scale the inset V210 frame into a rectangular region of dst using bilinear
// interpolation. pip_x and pip_w must be multiples of 6 (V210 alignment).
// Workspace vectors are passed in to avoid per-call heap allocation.
inline void scale_and_overlay(
const uint8_t* inset, uint32_t inset_stride, int inset_w, int inset_h,
uint8_t* dst, uint32_t dst_stride,
int pip_x, int pip_y, int pip_w, int pip_h,
std::vector<uint16_t>& Y0_buf, std::vector<uint16_t>& Y1_buf,
std::vector<uint16_t>& Cb0_buf, std::vector<uint16_t>& Cb1_buf,
std::vector<uint16_t>& Cr0_buf, std::vector<uint16_t>& Cr1_buf)
{
Y0_buf.resize(inset_w); Y1_buf.resize(inset_w);
Cb0_buf.resize(inset_w / 2); Cb1_buf.resize(inset_w / 2);
Cr0_buf.resize(inset_w / 2); Cr1_buf.resize(inset_w / 2);
const int out_blocks = pip_w / 6;
const int dst_x_bytes = (pip_x / 6) * 16;
const float inv_pip_h = static_cast<float>(inset_h) / pip_h;
const float inv_pip_w = static_cast<float>(inset_w) / pip_w;
const float inv_pip_cw = static_cast<float>(inset_w / 2) / (pip_w / 2);
// Precompute horizontal source positions once — they are the same for every row.
// thread_local avoids heap allocation on repeated calls with the same dimensions.
struct XS { int x0, x1; float fx, ifx; };
static thread_local std::vector<XS> y_xs, c_xs;
static thread_local int cached_pip_w = 0, cached_inset_w = 0;
if (pip_w != cached_pip_w || inset_w != cached_inset_w) {
y_xs.resize(pip_w);
for (int dx = 0; dx < pip_w; ++dx) {
const float sx = (dx + 0.5f) * inv_pip_w - 0.5f;
const int x0 = std::max(0, static_cast<int>(sx));
const float fx = sx - static_cast<float>(x0);
y_xs[dx] = { x0, std::min(inset_w - 1, x0 + 1), fx, 1.0f - fx };
}
c_xs.resize(pip_w / 2);
for (int cx = 0; cx < pip_w / 2; ++cx) {
const float sx = (cx + 0.5f) * inv_pip_cw - 0.5f;
const int x0 = std::max(0, static_cast<int>(sx));
const float fx = sx - static_cast<float>(x0);
c_xs[cx] = { x0, std::min(inset_w / 2 - 1, x0 + 1), fx, 1.0f - fx };
}
cached_pip_w = pip_w;
cached_inset_w = inset_w;
}
int cur_row0 = -1, cur_row1 = -1;
for (int dy = 0; dy < pip_h; ++dy) {
const float sy = (dy + 0.5f) * inv_pip_h - 0.5f;
const int sy0 = std::max(0, static_cast<int>(sy));
const int sy1 = std::min(inset_h - 1, sy0 + 1);
const float fy = sy - static_cast<float>(sy0);
const float w0 = 1.0f - fy;
const float w1 = fy;
if (sy0 != cur_row0) {
unpack_row(inset + static_cast<size_t>(sy0) * inset_stride, inset_w,
Y0_buf.data(), Cb0_buf.data(), Cr0_buf.data());
cur_row0 = sy0;
}
if (sy1 != cur_row1) {
unpack_row(inset + static_cast<size_t>(sy1) * inset_stride, inset_w,
Y1_buf.data(), Cb1_buf.data(), Cr1_buf.data());
cur_row1 = sy1;
}
uint8_t* dst_row = dst + static_cast<size_t>(pip_y + dy) * dst_stride + dst_x_bytes;
for (int b = 0; b < out_blocks; ++b) {
const int bx = b * 6;
uint16_t Y[6], Cb[3], Cr[3];
for (int i = 0; i < 6; ++i) {
const XS& xs = y_xs[bx + i];
Y[i] = static_cast<uint16_t>(
(Y0_buf[xs.x0] * xs.ifx + Y0_buf[xs.x1] * xs.fx) * w0 +
(Y1_buf[xs.x0] * xs.ifx + Y1_buf[xs.x1] * xs.fx) * w1 + 0.5f);
}
for (int i = 0; i < 3; ++i) {
const XS& cs = c_xs[b * 3 + i];
Cb[i] = static_cast<uint16_t>(
(Cb0_buf[cs.x0] * cs.ifx + Cb0_buf[cs.x1] * cs.fx) * w0 +
(Cb1_buf[cs.x0] * cs.ifx + Cb1_buf[cs.x1] * cs.fx) * w1 + 0.5f);
Cr[i] = static_cast<uint16_t>(
(Cr0_buf[cs.x0] * cs.ifx + Cr0_buf[cs.x1] * cs.fx) * w0 +
(Cr1_buf[cs.x0] * cs.ifx + Cr1_buf[cs.x1] * cs.fx) * w1 + 0.5f);
}
pack_block(dst_row + b * 16,
{0, Cb[0], Cr[0]}, Y[0], Y[1],
{0, Cb[1], Cr[1]}, Y[2], Y[3],
{0, Cb[2], Cr[2]}, Y[4], Y[5]);
}
}
}
} // namespace dmf::v210