-
Notifications
You must be signed in to change notification settings - Fork 0
Cropping and geometry
src/render/video_renderer.cpp (AnalyzeContentBounds), src/render/shaders.h
(pass 2)
Crop, line doubling and rotation all happen in pass 2, before scaling. That
ordering is what makes them appear in recordings and screenshots as well as in
the window — outputWidth() / outputHeight() report the size after all
three, and that is the size everything outside the shader works with.
Dragged on the picture itself. The four edges are stored per profile in source
pixels, and the overlay converts between window coordinates and source
coordinates through videoRect().
Known issue: when a crop edge is dragged over the overlay's own Apply and Cancel controls, those controls become too dark to read. Not yet fixed.
AnalyzeContentBounds() runs continuously on the CPU over the raw capture
buffer, and Detect simply applies whatever it has currently measured.
static const int kContentLuma = 24;Anything above that is picture rather than border. Analogue black does not arrive at zero and it is not quiet, so the bar sits a little above where the blacker-than-black of a limited range signal would be.
const int minRun = max(w / (50 * kBoundsColumnStep), 2);A row counts as picture only when a decent stretch of it — about 2 % of the columns sampled — is above black. One bright speck of analogue noise in the letterbox must not widen the crop.
Rows are scanned every second line, columns every second pixel
(kBoundsColumnStep = 2). Each lit pixel also votes for its column in
columnHits_.
const int minCol = max(litRows / 20, 1);A column has to be lit in at least 5 % of the rows that carry picture at all. Same idea the other way round.
An entirely black frame returns early and says nothing.
if (left < accL_) accL_ = left; // and so on for all four edgesThe result is the union across five sampled frames, because a fade to black
is not evidence that the picture got smaller. Only after
kBoundsFramesWanted = 5 windows is the accumulated rectangle published, and
the accumulator resets.
The bounds are logged only when they move, not every window — the border is a fixed property of the signal, so a line that keeps reappearing in the log means something is wrong.
kBoundsSampleEvery = 9 — every ninth frame, and every second column.
This is the only one of the three analyses that never finishes. The range and interlace verdicts latch and stop; this one keeps running for the whole session, so it is the only one still costing anything once the picture has settled.
And it was costing enough to matter. A full-width scan of every third frame lands ten times a second, and measured against the deinterlacer, ten per cent of its field switches were arriving more than ten milliseconds late. That is a stutter you can see. The border is in no hurry, so the same answer arrives just as reliably from a sixth of the work.
RGB layouts arrive bottom-up. The scan reads buffer order, so the two vertical edges are swapped on the way out:
if (source_.bottomUp) { const int t = h - 1 - bottom; bottom = h - 1 - top; top = t; }Both scans step, so each far edge can fall one step short and is nudged outward by the step size before being stored.
For 240p and 288p sources that arrive half as tall as they should. gLineDouble
in the pass-2 constant buffer; the logical picture becomes twice as tall and the
row index is ly >> 1.
This is a different thing from the co-sited field case in Deinterlacing: line doubling is for a source that genuinely arrives at half height, where co-siting is for a half-height source the card has already packed into a full-height interlaced frame.
Rotation in src/config.h: None, Cw90, Half, Ccw90.
Rotation is undone first in pass 2, before anything else:
if (gRotation == 1) { lx = op.y; ly = logicalH - 1 - op.x; }
else if (gRotation == 2) { lx = logicalW - 1 - op.x; ly = logicalH - 1 - op.y; }
else if (gRotation == 3) { lx = logicalW - 1 - op.y; ly = op.x; }
else { lx = op.x; ly = op.y; }Everything below that line works in the picture's own orientation, which is the only one in which "the line above" means anything — a deinterlacer running in rotated coordinates would be reconstructing across columns.
Note that logicalH accounts for line doubling but a quarter turn swaps the axes
of the render target only, not of the logical picture.