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.
Manual crop
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().
The dimming used to swallow the toolbar
Dragging a crop edge over the overlay's own Apply and Cancel controls turned them too dark to read — precisely when you were reaching for them. Fixed in 1.9, and it was one word:
ImDrawList* dl = ImGui::GetBackgroundDrawList(); // was GetForegroundDrawList
ImGui assembles its draw data as background lists, then windows, then foreground lists. Both extra lists sit over the video regardless — the picture does not come from ImGui at all, it is already in the back buffer before any of this is drawn — but only the background list sits under the toolbar, which is an ordinary window.
On the foreground list, the four dimming rectangles were painted over the
toolbar. The idle screen in overlay.cpp had the ordering right all along; the
crop overlay simply asked for the wrong list.
A crop is dropped when the source changes size
UpdateCropForFormat()
The four numbers are counts of source pixels — cropLeft = 20 means twenty
of 720 — so they stop meaning anything the moment the source is a different size.
A crop measured on a 720×576 PAL picture, left in place when the console
switches to 60 Hz and the capture becomes 720×480, is no longer describing a
border; it is cutting into the picture at coordinates that belong to a different
frame.
So when the source size changes, the crop is set to zero. The old values go to the log, and a notice says what happened:
Video format changed (720×480) — crop reset
This happens on its own during an automatic standard change, which is exactly when a line count changes without anyone touching a setting.
It is deliberately not re-measured automatically. The moment a standard changes is the worst possible moment to measure a border: the graph has just been rebuilt, the console is often mid-boot, and a logo on black is precisely the case the refusal above exists for. Sending the measurement straight into its own guard would achieve nothing but a refusal in the log. F8 is there for when there is a real picture to measure.
Unless it is remembered per size
Remember per picture size, cropPerFormat, off by default.
One console can deliver two sizes. A PAL GameCube gives 720×576 at 50 Hz and 720×480 in 60 Hz mode, and each has its own border. Resetting is right when the crop has stopped meaning anything, but it is the wrong answer to a source that keeps coming back to a size it has been set up for once already — and so is keeping two profiles for one console.
Ticked, each size keeps its own four numbers:
struct CropForFormat {
int width, height;
int left, right, top, bottom;
};
On a format change the current crop is written to the entry for the size being left, and if there is an entry for the size arriving it is put in place, with a notice saying so instead of the reset one. If there is none, the reset happens as before — an unknown size has no border on record, and a crop from a different one is exactly what must not be carried over.
Two details are worth stating, because both are places where a simpler version misbehaves:
- A crop of zero is stored as no entry at all, and an existing entry is removed when its crop is cleared. Otherwise "reset it and it stays reset" and "reset it and it comes back" would be two different things depending on which order the sizes had been seen in.
- The entry for the current size is kept up to date every frame, not at the moment of editing. There are six ways to change a crop — four drag fields, the frame on the picture, Detect, the reset button, the menu item — and hooking each one means a seventh can be forgotten. Four integer comparisons per frame cannot be.
At the first format of a session the stored entry wins, because the plain crop fields belong to whatever size qBlank was last showing, which is not necessarily the one it starts on.
The settings show what is on record, under the checkbox:
Stored for: 720x480, 720x576
The list is per profile and goes in the config file with everything else. Unticking the box leaves it there, so turning the feature off and on again does not cost what was measured.
Line doubling
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
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.