using System.IO; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; using SharepointToolbox.Core.Models; using SharepointToolbox.Infrastructure.Persistence; namespace SharepointToolbox.Services; public class BrandingService : IBrandingService { private const int MaxSizeBytes = 512 * 1024; // 512 KB // PNG signature: first 4 bytes private static readonly byte[] PngMagic = { 0x89, 0x50, 0x4E, 0x47 }; // JPEG signature: first 3 bytes private static readonly byte[] JpegMagic = { 0xFF, 0xD8, 0xFF }; private readonly BrandingRepository _repository; public BrandingService(BrandingRepository repository) { _repository = repository; } /// /// Reads a file, validates that it is PNG or JPEG via magic bytes, auto-compresses if over 512 KB, /// and returns a LogoData record. Does NOT persist anything — the caller decides where to store it. /// public async Task ImportLogoAsync(string filePath) { var bytes = await File.ReadAllBytesAsync(filePath); return await ImportLogoFromBytesAsync(bytes); } /// /// Validates raw bytes as PNG or JPEG via magic bytes, auto-compresses if over 512 KB, /// and returns a LogoData record. Used when bytes are obtained from a stream (e.g. Entra branding API). /// public Task ImportLogoFromBytesAsync(byte[] bytes) { var mimeType = DetectMimeType(bytes); if (bytes.Length > MaxSizeBytes) { bytes = CompressToLimit(bytes, mimeType, MaxSizeBytes); } return Task.FromResult(new LogoData { Base64 = Convert.ToBase64String(bytes), MimeType = mimeType }); } public async Task SaveMspLogoAsync(LogoData logo) { var settings = await _repository.LoadAsync(); settings.MspLogo = logo; await _repository.SaveAsync(settings); } public async Task ClearMspLogoAsync() { var settings = await _repository.LoadAsync(); settings.MspLogo = null; await _repository.SaveAsync(settings); } public async Task GetMspLogoAsync() { var settings = await _repository.LoadAsync(); return settings.MspLogo; } // ------------------------------------------------------------------------- // Private helpers // ------------------------------------------------------------------------- private static string DetectMimeType(byte[] bytes) { if (bytes.Length == 0) throw new InvalidDataException("File is empty. Only PNG and JPG files are accepted."); if (bytes.Length >= 4 && bytes[0] == PngMagic[0] && bytes[1] == PngMagic[1] && bytes[2] == PngMagic[2] && bytes[3] == PngMagic[3]) return "image/png"; if (bytes.Length >= 3 && bytes[0] == JpegMagic[0] && bytes[1] == JpegMagic[1] && bytes[2] == JpegMagic[2]) return "image/jpeg"; throw new InvalidDataException( "File format is not PNG or JPG. Only PNG and JPG are accepted."); } /// /// Compresses image bytes using WPF imaging (PresentationCore) to fit within . /// Resizes proportionally to max 300x300 at quality 75 first pass; if still too large, 200x200 at quality 50. /// private static byte[] CompressToLimit(byte[] bytes, string mimeType, int maxBytes) { // First pass: resize to 300x300 max, quality 75 var compressed = ResizeAndEncode(bytes, mimeType, 300, 75); if (compressed.Length <= maxBytes) return compressed; // Second pass: resize to 200x200 max, quality 50 compressed = ResizeAndEncode(bytes, mimeType, 200, 50); return compressed; } private static byte[] ResizeAndEncode(byte[] originalBytes, string mimeType, int maxDimension, int quality) { // Decode source image using WPF BitmapDecoder using var inputStream = new MemoryStream(originalBytes); var decoder = BitmapDecoder.Create( inputStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); var frame = decoder.Frames[0]; // Calculate target dimensions (proportional scaling) double srcWidth = frame.PixelWidth; double srcHeight = frame.PixelHeight; double scale = Math.Min((double)maxDimension / srcWidth, (double)maxDimension / srcHeight); // Only scale down, never up if (scale >= 1.0) scale = 1.0; int targetWidth = Math.Max(1, (int)(srcWidth * scale)); int targetHeight = Math.Max(1, (int)(srcHeight * scale)); // Scale the bitmap using TransformedBitmap var scaledBitmap = new TransformedBitmap( frame, new ScaleTransform(scale, scale)); // Encode to target format using var outputStream = new MemoryStream(); BitmapEncoder encoder = mimeType == "image/png" ? new PngBitmapEncoder() : CreateJpegEncoder(quality); encoder.Frames.Add(BitmapFrame.Create(scaledBitmap)); encoder.Save(outputStream); return outputStream.ToArray(); } private static BitmapEncoder CreateJpegEncoder(int quality) { return new JpegBitmapEncoder { QualityLevel = quality }; } }