using System.IO; using SkiaSharp; namespace Jellyfin.Plugin.SRFPlay.Utilities; /// /// Generates placeholder images for content without thumbnails. /// public static class PlaceholderImageGenerator { private const int Width = 640; private const int Height = 360; // 16:9 aspect ratio /// /// Generates a placeholder image with the given text centered. /// /// The text to display (typically channel/show name). /// A memory stream containing the PNG image. public static MemoryStream GeneratePlaceholder(string text) { using var surface = SKSurface.Create(new SKImageInfo(Width, Height)); var canvas = surface.Canvas; // Dark gradient background using var backgroundPaint = new SKPaint(); using var shader = SKShader.CreateLinearGradient( new SKPoint(0, 0), new SKPoint(Width, Height), new[] { new SKColor(45, 45, 48), new SKColor(28, 28, 30) }, null, SKShaderTileMode.Clamp); backgroundPaint.Shader = shader; canvas.DrawRect(0, 0, Width, Height, backgroundPaint); // Text using var textPaint = new SKPaint { Color = SKColors.White, IsAntialias = true, TextAlign = SKTextAlign.Center, Typeface = SKTypeface.FromFamilyName("Arial", SKFontStyle.Bold) }; // Calculate font size and wrap text if needed float fontSize = 48; textPaint.TextSize = fontSize; var maxWidth = Width * 0.85f; var maxHeight = Height * 0.8f; var lines = WrapText(text, textPaint, maxWidth, maxHeight, ref fontSize); // Draw each line centered var lineHeight = fontSize * 1.2f; var totalHeight = lines.Count * lineHeight; var startY = ((Height - totalHeight) / 2) + fontSize; foreach (var line in lines) { canvas.DrawText(line, Width / 2, startY, textPaint); startY += lineHeight; } // Encode to PNG using var image = surface.Snapshot(); using var data = image.Encode(SKEncodedImageFormat.Png, 100); var stream = new MemoryStream(); data.SaveTo(stream); stream.Position = 0; return stream; } /// /// Wraps text to fit within the specified width and height constraints. /// /// The text to wrap. /// The paint to use for measuring. /// Maximum width for each line. /// Maximum total height. /// Font size (will be adjusted if needed). /// List of text lines. private static System.Collections.Generic.List WrapText( string text, SKPaint paint, float maxWidth, float maxHeight, ref float fontSize) { const float minFontSize = 20; var words = text.Split(' '); var lines = new System.Collections.Generic.List(); while (fontSize >= minFontSize) { paint.TextSize = fontSize; lines.Clear(); var currentLine = string.Empty; foreach (var word in words) { var testLine = string.IsNullOrEmpty(currentLine) ? word : $"{currentLine} {word}"; var testWidth = paint.MeasureText(testLine); if (testWidth > maxWidth && !string.IsNullOrEmpty(currentLine)) { lines.Add(currentLine); currentLine = word; } else { currentLine = testLine; } } if (!string.IsNullOrEmpty(currentLine)) { lines.Add(currentLine); } // Check if total height fits var lineHeight = fontSize * 1.2f; var totalHeight = lines.Count * lineHeight; if (totalHeight <= maxHeight) { break; } // Reduce font size and try again fontSize -= 2; } // If still doesn't fit, just return what we have if (lines.Count == 0) { lines.Add(text); } return lines; } }