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
{