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")]