diff --git a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj index bf5237e99..bda4d13e4 100644 --- a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj +++ b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj @@ -17,6 +17,7 @@ + diff --git a/ErsatzTV.Infrastructure/Streaming/Graphics/Script/ScriptElement.cs b/ErsatzTV.Infrastructure/Streaming/Graphics/Script/ScriptElement.cs index a93009d71..78805643f 100644 --- a/ErsatzTV.Infrastructure/Streaming/Graphics/Script/ScriptElement.cs +++ b/ErsatzTV.Infrastructure/Streaming/Graphics/Script/ScriptElement.cs @@ -6,6 +6,7 @@ using CliWrap; using ErsatzTV.Core; using ErsatzTV.Core.Graphics; using ErsatzTV.Core.Interfaces.Streaming; +using Humanizer; using Microsoft.Extensions.Logging; using SkiaSharp; @@ -24,9 +25,15 @@ public class ScriptElement(ScriptGraphicsElement scriptElement, ILogger logger) private TimeSpan _startTime; private TimeSpan _endTime; private int _repeatCount; + private long _totalBytes; public void Dispose() { + logger.LogDebug( + "Script element produced {ByteSize} ({Bytes} bytes)", + ByteSize.FromBytes(_totalBytes), + _totalBytes); + GC.SuppressFinalize(this); _pipeReader?.Complete(); @@ -94,7 +101,7 @@ public class ScriptElement(ScriptGraphicsElement scriptElement, ILogger logger) .WithStandardOutputPipe(PipeTarget.ToStream(pipe.Writer.AsStream())); logger.LogDebug( - "script element command {Command} arguments {Arguments}", + "Script element command {Command} arguments {Arguments}", command.TargetFilePath, command.Arguments); @@ -163,6 +170,8 @@ public class ScriptElement(ScriptGraphicsElement scriptElement, ILogger logger) // mark this frame as consumed consumed = sequence.End; + _totalBytes += _frameSize; + // we are done, return the frame return new PreparedElementImage(_canvasBitmap, SKPointI.Empty, 1.0f, ZIndex, false); } @@ -226,6 +235,7 @@ public class ScriptElement(ScriptGraphicsElement scriptElement, ILogger logger) // consume header _pipeReader.AdvanceTo(buffer.GetPosition(11)); + _totalBytes += 11; var success = true; @@ -248,14 +258,64 @@ public class ScriptElement(ScriptGraphicsElement scriptElement, ILogger logger) _repeatCount = (int)BinaryPrimitives.ReadUInt32BigEndian(repeatBytes); break; case (byte)ScriptPayloadType.Rectangles: - // TODO: support rectangles - logger.LogWarning("Unsupported graphics packet type: {Type}", type); - success = false; + _canvasBitmap.Erase(SKColors.Transparent); + Span shortBytes = stackalloc byte[2]; + buffer.Slice(0, shortBytes.Length).CopyTo(shortBytes); + int rectangleCount = BinaryPrimitives.ReadUInt16BigEndian(shortBytes); + int offset = shortBytes.Length; + using (SKPixmap pixmap = _canvasBitmap.PeekPixels()) + { + int canvasWidth = pixmap.Width; + int canvasHeight = pixmap.Height; + const int BYTES_PER_PIXEL = 4; + int destRowBytes = pixmap.RowBytes; + Span canvasSpan = pixmap.GetPixelSpan(); + + for (int i = 0; i < rectangleCount; i++) + { + buffer.Slice(offset, 2).CopyTo(shortBytes); + offset += 2; + int x = BinaryPrimitives.ReadUInt16BigEndian(shortBytes); + + buffer.Slice(offset, 2).CopyTo(shortBytes); + offset += 2; + int y = BinaryPrimitives.ReadUInt16BigEndian(shortBytes); + + buffer.Slice(offset, 2).CopyTo(shortBytes); + offset += 2; + int w = BinaryPrimitives.ReadUInt16BigEndian(shortBytes); + + buffer.Slice(offset, 2).CopyTo(shortBytes); + offset += 2; + int h = BinaryPrimitives.ReadUInt16BigEndian(shortBytes); + + int effectiveW = Math.Max(0, Math.Min(w, canvasWidth - x)); + int effectiveH = Math.Max(0, Math.Min(h, canvasHeight - y)); + + if (effectiveW > 0 && effectiveH > 0) + { + int sourceRowBytes = w * BYTES_PER_PIXEL; + + for (int row = 0; row < effectiveH; row++) + { + int sourceIndex = offset + (row * sourceRowBytes); + int destIndex = ((y + row) * destRowBytes) + (x * BYTES_PER_PIXEL); + + buffer.Slice(sourceIndex, effectiveW * BYTES_PER_PIXEL) + .CopyTo(canvasSpan.Slice(destIndex, effectiveW * BYTES_PER_PIXEL)); + } + } + + offset += w * h * 4; + } + } + break; } // consume payload _pipeReader.AdvanceTo(buffer.GetPosition(payloadLen)); + _totalBytes += payloadLen; } else {