299 lines
12 KiB
C++
299 lines
12 KiB
C++
#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 0–1
|
||
Color p23, uint16_t y2, uint16_t y3, // pair 2–3
|
||
Color p45, uint16_t y4, uint16_t y5) // pair 4–5
|
||
{
|
||
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
|