18a21010bb
Software compositor that renders a scene into the encoder's master VideoFrame, mirroring the XAML preview minus editing chrome: backdrop -> background -> elements (UniformToFill cover-crop, round clip, mirror, opacity, border) -> branding flash. NOTE FOR USERS: this change shows NO difference in the app's UI — it is pure backend scaffolding laying the groundwork for live video capture/streaming. The preview you see is unchanged. - Services/Compositor/: SceneCompositor (Render(scene, frameFor resolver, flashFrame, CompositorOptions)), CompositorOptions (source rect + output size; 16:9 full master, vertical 607x1080 -> 1080x1920), StretchMath (pure UniformToFill + bilinear), StaticPixelCache (asset bytes -> BGRA8 frame) - Frame sources injected via Func<SceneElement, VideoFrame?> resolver, so the compositor is pure, WPF-free, and hermetic to test (D3D11 upgrade behind the same seam later) - SceneElement.TryGetBorderColor public (shared hex parse), stale MainViewModel comment fixed, pre-existing CS1998 in YouTubeAuthServiceTests cleaned up - Tests: SceneCompositorTests integration (full scene + vertical tier + flash) + StretchMath units, docs updated (72 tests passing, 0 warnings)
49 lines
1.7 KiB
C#
49 lines
1.7 KiB
C#
using System.IO;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
|
|
namespace ytLive.Services.Compositor;
|
|
|
|
/// <summary>
|
|
/// Decodes a layout asset's bytes into a tightly-packed BGRA8 <see cref="VideoFrame"/>
|
|
/// once per content-hash asset id. The preview uses the WPF <c>BitmapImage</c> in
|
|
/// ImageCache; the output path needs raw pixels, so assets decode here instead.
|
|
/// </summary>
|
|
public static class StaticPixelCache
|
|
{
|
|
private static readonly Dictionary<string, VideoFrame> Cache = new(StringComparer.Ordinal);
|
|
|
|
public static VideoFrame? Get(string assetId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(assetId)) return null;
|
|
if (Cache.TryGetValue(assetId, out var frame)) return frame;
|
|
|
|
var bytes = LayoutStore.Instance?.GetAssetBytes(assetId);
|
|
if (bytes == null || bytes.Length == 0) return null;
|
|
|
|
var decoded = Decode(bytes);
|
|
if (decoded != null) Cache[assetId] = decoded;
|
|
return decoded;
|
|
}
|
|
|
|
public static VideoFrame? Decode(byte[] bytes)
|
|
{
|
|
try
|
|
{
|
|
using var stream = new MemoryStream(bytes, writable: false);
|
|
var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
|
|
var source = decoder.Frames[0];
|
|
var bgra = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
|
|
var width = bgra.PixelWidth;
|
|
var height = bgra.PixelHeight;
|
|
var pixels = new byte[width * height * 4];
|
|
bgra.CopyPixels(pixels, width * 4, 0);
|
|
return new VideoFrame(width, height, pixels);
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
}
|