fix(web): stride-correct CropBounds Fill rendering in compositor (take-23)
This commit is contained in:
@@ -421,41 +421,37 @@ public sealed class SceneCompositor
|
|||||||
{
|
{
|
||||||
if (ew <= 0 || eh <= 0) return;
|
if (ew <= 0 || eh <= 0) return;
|
||||||
|
|
||||||
float scale, ox, oy;
|
float scale, ox, oy, invScale;
|
||||||
int srcCropX = 0, srcCropY = 0;
|
|
||||||
int w = src.Width, hgt = src.Height;
|
|
||||||
int sxMax, syMax;
|
|
||||||
float drawnW, drawnH;
|
float drawnW, drawnH;
|
||||||
|
var cbX = 0;
|
||||||
|
var cbY = 0;
|
||||||
|
var cbW = 0;
|
||||||
|
var cbH = 0;
|
||||||
|
|
||||||
if (src.CropBounds is (var cbX, var cbY, var cbW, var cbH))
|
if (src.CropBounds is { } cb)
|
||||||
{
|
{
|
||||||
// WebSource: Fill-style (stretch to cover, no aspect preservation) using
|
cbX = cb.X;
|
||||||
// the actual crop bounds. The transparent margins of the full canvas are
|
cbY = cb.Y;
|
||||||
// preserved so they reveal layers beneath; only the crop content fills
|
cbW = cb.W;
|
||||||
// the element rect — matching the preview's Stretch="Fill" on the crop.
|
cbH = cb.H;
|
||||||
scale = Math.Max(ew / cbW, eh / cbH);
|
scale = Math.Max(ew / cbW, eh / cbH);
|
||||||
|
invScale = 1f / scale;
|
||||||
drawnW = cbW * scale;
|
drawnW = cbW * scale;
|
||||||
drawnH = cbH * scale;
|
drawnH = cbH * scale;
|
||||||
ox = (ew - drawnW) / 2f;
|
ox = (ew - drawnW) / 2f;
|
||||||
oy = (eh - drawnH) / 2f;
|
oy = (eh - drawnH) / 2f;
|
||||||
srcCropX = cbX;
|
|
||||||
srcCropY = cbY;
|
|
||||||
w = cbW;
|
|
||||||
hgt = cbH;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var (uScale, uOx, uOy) = StretchMath.UniformToFill(ew, eh, src.Width, src.Height);
|
var (uScale, uOx, uOy) = StretchMath.UniformToFill(ew, eh, src.Width, src.Height);
|
||||||
scale = uScale;
|
scale = uScale;
|
||||||
|
invScale = 1f / uScale;
|
||||||
ox = uOx;
|
ox = uOx;
|
||||||
oy = uOy;
|
oy = uOy;
|
||||||
drawnW = src.Width * scale;
|
drawnW = src.Width * uScale;
|
||||||
drawnH = src.Height * scale;
|
drawnH = src.Height * uScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
sxMax = w - 1;
|
|
||||||
syMax = hgt - 1;
|
|
||||||
var invScale = 1f / scale;
|
|
||||||
var radius = Math.Min(ew, eh) / 2f;
|
var radius = Math.Min(ew, eh) / 2f;
|
||||||
var cx = ew / 2f;
|
var cx = ew / 2f;
|
||||||
var cy = eh / 2f;
|
var cy = eh / 2f;
|
||||||
@@ -469,16 +465,24 @@ public sealed class SceneCompositor
|
|||||||
var op = (int)(Math.Clamp(opacity, 0f, 1f) * 256);
|
var op = (int)(Math.Clamp(opacity, 0f, 1f) * 256);
|
||||||
if (op <= 0) return;
|
if (op <= 0) return;
|
||||||
var s = src.BgraPixels;
|
var s = src.BgraPixels;
|
||||||
|
var srcStride = src.Width; // canvas stride in pixels (not bytes)
|
||||||
|
|
||||||
for (var y = y0; y <= y1; y++)
|
for (var y = y0; y <= y1; y++)
|
||||||
{
|
{
|
||||||
var py = y - ey;
|
var py = y - ey;
|
||||||
if (py < oy || py > oy + drawnH) continue;
|
if (py < oy || py > oy + drawnH) continue;
|
||||||
var syRaw = Math.Clamp((py - oy) * invScale, 0f, syMax) + srcCropY;
|
|
||||||
var syi = (int)syRaw;
|
// syNorm: normalized [0,1] position within the drawn content (vertically)
|
||||||
var sy1i = Math.Min(syi + 1, syMax);
|
var syNorm = Math.Clamp((py - oy) / drawnH, 0f, 1f);
|
||||||
var fy8 = (int)((syRaw - syi) * 256);
|
// syCrop: pixel position within the crop region
|
||||||
|
var syCrop = syNorm * cbH;
|
||||||
|
// syCanvas: canvas pixel row (for buffer indexing)
|
||||||
|
var syCanvas = Math.Clamp(cbY + syCrop, 0f, src.Height - 1f);
|
||||||
|
var syi = (int)syCanvas;
|
||||||
|
var sy1i = Math.Min(syi + 1, src.Height - 1);
|
||||||
|
var fy8 = (int)((syCanvas - syi) * 256);
|
||||||
var fyInv = 256 - fy8;
|
var fyInv = 256 - fy8;
|
||||||
|
|
||||||
var di = (y * dstW + x0) * 4;
|
var di = (y * dstW + x0) * 4;
|
||||||
for (var x = x0; x <= x1; x++, di += 4)
|
for (var x = x0; x <= x1; x++, di += 4)
|
||||||
{
|
{
|
||||||
@@ -491,18 +495,23 @@ public sealed class SceneCompositor
|
|||||||
if (dx * dx + dy * dy > radius * radius) continue;
|
if (dx * dx + dy * dy > radius * radius) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sxRaw = Math.Clamp((px - ox) * invScale, 0f, sxMax) + srcCropX;
|
// sxNorm: normalized [0,1] position within the drawn content (horizontally)
|
||||||
if (isMirror) sxRaw = sxMax - sxRaw;
|
var sxNorm = Math.Clamp((px - ox) / drawnW, 0f, 1f);
|
||||||
sxRaw = Math.Clamp(sxRaw, 0f, sxMax);
|
// sxCrop: pixel position within the crop region
|
||||||
var sxi = (int)sxRaw;
|
var sxCrop = sxNorm * cbW;
|
||||||
var sx1i = Math.Min(sxi + 1, sxMax);
|
// sxCanvas: canvas pixel column (for buffer indexing)
|
||||||
var fx8 = (int)((sxRaw - sxi) * 256);
|
var sxCanvas = Math.Clamp(cbX + sxCrop, 0f, src.Width - 1f);
|
||||||
|
if (isMirror) sxCanvas = src.Width - 1 - sxCanvas;
|
||||||
|
var sxi = (int)sxCanvas;
|
||||||
|
var sx1i = Math.Min(sxi + 1, src.Width - 1);
|
||||||
|
var fx8 = (int)((sxCanvas - sxi) * 256);
|
||||||
var fxInv = 256 - fx8;
|
var fxInv = 256 - fx8;
|
||||||
|
|
||||||
var p00 = (syi * w + sxi) * 4;
|
// ALL pixel indexing uses srcStride (canvas stride), NOT crop width
|
||||||
var p10 = (syi * w + sx1i) * 4;
|
var p00 = (syi * srcStride + sxi) * 4;
|
||||||
var p01 = (sy1i * w + sxi) * 4;
|
var p10 = (syi * srcStride + sx1i) * 4;
|
||||||
var p11 = (sy1i * w + sx1i) * 4;
|
var p01 = (sy1i * srcStride + sxi) * 4;
|
||||||
|
var p11 = (sy1i * srcStride + sx1i) * 4;
|
||||||
var sb = Bilinear(s[p00], s[p10], s[p01], s[p11], fx8, fxInv, fy8, fyInv);
|
var sb = Bilinear(s[p00], s[p10], s[p01], s[p11], fx8, fxInv, fy8, fyInv);
|
||||||
var sg = Bilinear(s[p00 + 1], s[p10 + 1], s[p01 + 1], s[p11 + 1], fx8, fxInv, fy8, fyInv);
|
var sg = Bilinear(s[p00 + 1], s[p10 + 1], s[p01 + 1], s[p11 + 1], fx8, fxInv, fy8, fyInv);
|
||||||
var sr = Bilinear(s[p00 + 2], s[p10 + 2], s[p01 + 2], s[p11 + 2], fx8, fxInv, fy8, fyInv);
|
var sr = Bilinear(s[p00 + 2], s[p10 + 2], s[p01 + 2], s[p11 + 2], fx8, fxInv, fy8, fyInv);
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ public sealed class VideoFrame
|
|||||||
|
|
||||||
/// <summary>WebSource crop metadata: the alpha bounding box of the widget content
|
/// <summary>WebSource crop metadata: the alpha bounding box of the widget content
|
||||||
/// within the full canvas (X/Y/W/H in pixels). When set, the compositor uses these
|
/// within the full canvas (X/Y/W/H in pixels). When set, the compositor uses these
|
||||||
/// bounds for Fill-style scaling (stretch to cover, no aspect preservation) instead
|
/// bounds for Fill-style scaling (stretch to cover, no aspect preservation) instead of
|
||||||
/// of UniformToFill. The transparent margins of the full canvas are preserved so
|
/// UniformToFill. The transparent margins of the full canvas are preserved so they
|
||||||
/// they reveal layers beneath — the crop defines the actual content region.</summary>
|
/// reveal layers beneath — the crop defines the actual content region.</summary>
|
||||||
public (int X, int Y, int W, int H)? CropBounds { get; init; }
|
public (int X, int Y, int W, int H)? CropBounds { get; init; }
|
||||||
|
|
||||||
public VideoFrame(int width, int height, byte[] bgraPixels)
|
public VideoFrame(int width, int height, byte[] bgraPixels)
|
||||||
|
|||||||
Reference in New Issue
Block a user