diff --git a/RadioDJViewer/Main.cs b/RadioDJViewer/Main.cs index 8029630..d3599ef 100644 --- a/RadioDJViewer/Main.cs +++ b/RadioDJViewer/Main.cs @@ -40,6 +40,7 @@ namespace RadioDJViewer private string currentSongImagePath = string.Empty; // Path to the current song image private string defaultImagePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "default.jpg"); private string mainImagesFolderPath = string.Empty; // New field for main images folder path + private string _lastToastImagePath = string.Empty; private Profile loadedProfile = null; // Field to store the loaded profile private WidgetServer widgetServer = null; // Local widget HTTP server private bool widgetRunning = false; @@ -283,18 +284,16 @@ namespace RadioDJViewer } if (useFallback) { - // Use fallback PNG from resources var fallbackImg = Properties.Resources.fallback; if (fallbackImg != null) { var resized = new Bitmap(fallbackImg, pictureBox1.Size); pictureBox1.Image = resized; currentSongImagePath = defaultImagePath; - // Save fallback image to output folder as PNG if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName)) { string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png")); - resized.Save(destPath, System.Drawing.Imaging.ImageFormat.Png); + SaveImageAsPng(resized, destPath); } } else @@ -311,17 +310,15 @@ namespace RadioDJViewer var resized = new Bitmap(img, pictureBox1.Size); pictureBox1.Image = resized; currentSongImagePath = imagePath; - // Save to output folder as PNG if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName)) { string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png")); - resized.Save(destPath, System.Drawing.Imaging.ImageFormat.Png); + SaveImageAsPng(resized, destPath); } } } catch { - // If loading image fails, fallback var fallbackImg = Properties.Resources.fallback; if (fallbackImg != null) { @@ -331,7 +328,7 @@ namespace RadioDJViewer if (!string.IsNullOrEmpty(outputFolderPath) && !string.IsNullOrEmpty(outputImageName)) { string destPath = Path.Combine(outputFolderPath, Path.ChangeExtension(outputImageName, ".png")); - resized.Save(destPath, System.Drawing.Imaging.ImageFormat.Png); + SaveImageAsPng(resized, destPath); } } else @@ -342,6 +339,131 @@ namespace RadioDJViewer } } + private void SaveImageAsPng(Image image, string destinationPath) + { + if (image == null || string.IsNullOrWhiteSpace(destinationPath)) + return; + + string directory = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrWhiteSpace(directory)) + Directory.CreateDirectory(directory); + + string tempPath = destinationPath + ".tmp"; + using (var fileStream = new FileStream(tempPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + image.Save(fileStream, System.Drawing.Imaging.ImageFormat.Png); + fileStream.Flush(true); + } + + if (File.Exists(destinationPath)) + File.Delete(destinationPath); + + File.Move(tempPath, destinationPath); + } + + private string BuildToastImagePath() + { + try + { + string sourcePath = currentSongImagePath; + if (string.IsNullOrWhiteSpace(sourcePath) || !File.Exists(sourcePath)) + sourcePath = GetProfileOutputImagePath(); + + if (string.IsNullOrWhiteSpace(sourcePath) || !File.Exists(sourcePath)) + return string.Empty; + + string toastDirectory = Path.Combine(Path.GetTempPath(), "RadioDJViewer", "ToastImages"); + Directory.CreateDirectory(toastDirectory); + string toastPath = Path.Combine(toastDirectory, $"Album-Art-Hero-{DateTime.UtcNow.Ticks}.png"); + + using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (var memoryStream = new MemoryStream()) + { + sourceStream.CopyTo(memoryStream); + memoryStream.Position = 0; + + using (var sourceImage = Image.FromStream(memoryStream)) + using (var heroImage = CreateToastHeroCanvas(sourceImage)) + using (var fileStream = new FileStream(toastPath, FileMode.Create, FileAccess.Write, FileShare.Read)) + { + heroImage.Save(fileStream, System.Drawing.Imaging.ImageFormat.Png); + fileStream.Flush(true); + } + } + + if (!string.IsNullOrWhiteSpace(_lastToastImagePath) && !string.Equals(_lastToastImagePath, toastPath, StringComparison.OrdinalIgnoreCase)) + { + try + { + if (File.Exists(_lastToastImagePath)) + File.Delete(_lastToastImagePath); + } + catch { } + } + + _lastToastImagePath = toastPath; +#if DEBUG + System.Diagnostics.Debug.WriteLine($"[Toast] Prepared 16:9 hero image '{toastPath}' from '{sourcePath}'"); +#endif + return toastPath; + } + catch (Exception ex) + { +#if DEBUG + System.Diagnostics.Debug.WriteLine($"[Toast] Failed to prepare toast image: {ex}"); +#endif + return string.Empty; + } + } + + private Bitmap CreateToastHeroCanvas(Image sourceImage) + { + const double targetAspectRatio = 16d / 9d; + + int sourceWidth = Math.Max(sourceImage.Width, 1); + int sourceHeight = Math.Max(sourceImage.Height, 1); + double sourceAspectRatio = (double)sourceWidth / sourceHeight; + + if (sourceWidth >= sourceHeight && Math.Abs(sourceAspectRatio - targetAspectRatio) < 0.01d) + return new Bitmap(sourceImage); + + int canvasWidth; + int canvasHeight; + + if (sourceAspectRatio < targetAspectRatio) + { + canvasHeight = sourceHeight; + canvasWidth = (int)Math.Ceiling(canvasHeight * targetAspectRatio); + } + else + { + canvasWidth = sourceWidth; + canvasHeight = (int)Math.Ceiling(canvasWidth / targetAspectRatio); + } + + canvasWidth = Math.Max(canvasWidth, sourceWidth); + canvasHeight = Math.Max(canvasHeight, sourceHeight); + + var heroCanvas = new Bitmap(canvasWidth, canvasHeight, PixelFormat.Format32bppArgb); + using (var graphics = Graphics.FromImage(heroCanvas)) + { + graphics.Clear(Color.Black); + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.SmoothingMode = SmoothingMode.HighQuality; + + int x = (canvasWidth - sourceWidth) / 2; + int y = (canvasHeight - sourceHeight) / 2; + graphics.DrawImage(sourceImage, new Rectangle(x, y, sourceWidth, sourceHeight)); + } + +#if DEBUG + System.Diagnostics.Debug.WriteLine($"[Toast] Hero canvas created: source={sourceWidth}x{sourceHeight}, canvas={canvasWidth}x{canvasHeight}"); +#endif + return heroCanvas; + } + private void PlaybackTimer_Tick(object sender, EventArgs e) { UpdatePlaybackDisplay(); @@ -838,7 +960,8 @@ namespace RadioDJViewer string safeArtist = string.IsNullOrWhiteSpace(artist) ? "No artist" : artist; DateTime now = DateTime.Now; string attribution = $"Date: {now:MM/dd/yyyy} | Time: {now:hh:mm tt}"; - ToastNotificationService.ShowTrackChangeToast(safeTitle, safeArtist, attribution, outputImagePath); + string toastImagePath = BuildToastImagePath(); + ToastNotificationService.ShowTrackChangeToast(safeTitle, safeArtist, attribution, string.IsNullOrWhiteSpace(toastImagePath) ? outputImagePath : toastImagePath); } private void HandleToastForTrackChange(string title, string artist) @@ -1014,7 +1137,7 @@ namespace RadioDJViewer string safeImage = SecurityElement.Escape(imageUri); string imageNode = string.IsNullOrWhiteSpace(safeImage) ? string.Empty - : $""; + : $""; #if DEBUG System.Diagnostics.Debug.WriteLine($"[Toast] Image URI used: '{imageUri}'"); diff --git a/RadioDJViewer/Properties/AssemblyInfo.cs b/RadioDJViewer/Properties/AssemblyInfo.cs index cdee07a..3342d4d 100644 --- a/RadioDJViewer/Properties/AssemblyInfo.cs +++ b/RadioDJViewer/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ using System.Runtime.InteropServices; // Build Number // Revision // -[assembly: AssemblyVersion("1.14.3")] -[assembly: AssemblyFileVersion("1.14.3")] +[assembly: AssemblyVersion("1.14.5")] +[assembly: AssemblyFileVersion("1.14.5")]