From 0cceac8b934924ea5eeeb56e2646178113474892 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 7 Sep 2019 23:01:25 +0200 Subject: [PATCH] Implement embedding picture title into image file The --embed-meta parameter will create a copy of the image with picture title embedded on top right corner of the file. The title size is adjusted to the current screen scaling factor. The image is also resized to match the screen resolution to make sure the title text will not get resized or cropped by Windows. --- README-En.txt | 5 +- README-Fr.txt | 5 +- Scripts/update-archive-and-lockscreen.bat | 2 +- Scripts/update-archive-and-wallpaper.bat | 2 +- Scripts/update-lockscreen.bat | 2 +- Scripts/update-wallpaper.bat | 2 +- SpotlightDownloader/Desktop.cs | 20 ++ SpotlightDownloader/Lockscreen.cs | 2 +- SpotlightDownloader/Program.cs | 23 +- .../SpotlightDownloader.csproj.user | 4 + SpotlightDownloader/SpotlightImage.cs | 210 +++++++++++++++++- 11 files changed, 261 insertions(+), 16 deletions(-) diff --git a/README-En.txt b/README-En.txt index 7dfe321..9f45b34 100644 --- a/README-En.txt +++ b/README-En.txt @@ -1,5 +1,5 @@ ===================================================== -==== SpotlightDL v1.3 - By ORelio - Microzoom.fr ==== +==== SpotlightDL v1.4 - By ORelio - Microzoom.fr ==== ===================================================== Thanks for dowloading SpotlightDL! @@ -118,6 +118,9 @@ R: Default mode downloads a list of images returned by a single API call: previo Q: Some images do not have a title or copyright in their metadata? R: Those fields are not provided for all images by the Spotlight API. +Q: I do not want to have the image title inside my wallpaper or lockscreen. How to remove it? +R: Edit the batch file you want to use and remove --embed-meta inside the corresponding command. + +--------------------+ | © 2018-2019 ORelio | +--------------------+ \ No newline at end of file diff --git a/README-Fr.txt b/README-Fr.txt index 7bc58c9..1b20a8b 100644 --- a/README-Fr.txt +++ b/README-Fr.txt @@ -1,5 +1,5 @@ ====================================================== -==== SpotlightDL v1.3 - Par ORelio - Microzoom.fr ==== +==== SpotlightDL v1.4 - Par ORelio - Microzoom.fr ==== ====================================================== Merci d'avoir téléchargé SpotlightDL! @@ -120,6 +120,9 @@ R: Par défaut, la liste d'images retournées par un seul appel API: précédemm Q: Certaines images n'ont pas de titre ou de copyright dans leur métadonnées? R: Ces informations ne sont pas fournies pour toutes les images au niveau de l'API Windows à la une. +Q: Je ne veux pas du titre de l'image sur mon fond d'écran ou écran de verrouillage. Comment l'enlever ? +R: Modifiez le fichier batch que vous utilisez pour enlever le paramètre --embed-meta dans la commande associée. + +--------------------+ | © 2018-2019 ORelio | +--------------------+ \ No newline at end of file diff --git a/Scripts/update-archive-and-lockscreen.bat b/Scripts/update-archive-and-lockscreen.bat index 4daea2b..847dfc0 100644 --- a/Scripts/update-archive-and-lockscreen.bat +++ b/Scripts/update-archive-and-lockscreen.bat @@ -14,4 +14,4 @@ if not "%errorlevel%" == "0" ( mkdir SpotlightArchive > nul 2>&1 SpotlightDownloader download --maxres --metadata --outdir SpotlightArchive -SpotlightDownloader lockscreen --from-dir SpotlightArchive \ No newline at end of file +SpotlightDownloader lockscreen --from-dir SpotlightArchive --embed-meta --outname lockscreen \ No newline at end of file diff --git a/Scripts/update-archive-and-wallpaper.bat b/Scripts/update-archive-and-wallpaper.bat index 95fc177..03b92c4 100644 --- a/Scripts/update-archive-and-wallpaper.bat +++ b/Scripts/update-archive-and-wallpaper.bat @@ -7,4 +7,4 @@ cd "%~dp0" mkdir SpotlightArchive > nul 2>&1 SpotlightDownloader download --maxres --metadata --outdir SpotlightArchive -SpotlightDownloader wallpaper --from-dir SpotlightArchive \ No newline at end of file +SpotlightDownloader wallpaper --from-dir SpotlightArchive --embed-meta --outname wallpaper \ No newline at end of file diff --git a/Scripts/update-lockscreen.bat b/Scripts/update-lockscreen.bat index 0195088..467c877 100644 --- a/Scripts/update-lockscreen.bat +++ b/Scripts/update-lockscreen.bat @@ -14,4 +14,4 @@ if not "%errorlevel%" == "0" ( mkdir SpotlightCache > nul 2>&1 SpotlightDownloader download --amount 10 --cache-size 10 --metadata --outdir SpotlightCache -SpotlightDownloader lockscreen --from-dir SpotlightCache \ No newline at end of file +SpotlightDownloader lockscreen --from-dir SpotlightCache --embed-meta --outname lockscreen \ No newline at end of file diff --git a/Scripts/update-wallpaper.bat b/Scripts/update-wallpaper.bat index ac8ab10..fb53f9c 100644 --- a/Scripts/update-wallpaper.bat +++ b/Scripts/update-wallpaper.bat @@ -7,4 +7,4 @@ cd "%~dp0" mkdir SpotlightCache > nul 2>&1 SpotlightDownloader download --amount 10 --cache-size 10 --metadata --outdir SpotlightCache -SpotlightDownloader wallpaper --from-dir SpotlightCache \ No newline at end of file +SpotlightDownloader wallpaper --from-dir SpotlightCache --embed-meta --outname wallpaper \ No newline at end of file diff --git a/SpotlightDownloader/Desktop.cs b/SpotlightDownloader/Desktop.cs index 045292c..a368352 100644 --- a/SpotlightDownloader/Desktop.cs +++ b/SpotlightDownloader/Desktop.cs @@ -121,5 +121,25 @@ public static void RefreshDesktop() System.Diagnostics.Process.Start("RUNDLL32.EXE", "USER32.DLL,UpdatePerUserSystemParameters 1, True"); UpdatePerUserSystemParameters(); } + + //stackoverflow.com/questions/5977445/ + + [DllImport("gdi32.dll")] + static extern int GetDeviceCaps(IntPtr hdc, int nIndex); + enum DeviceCap + { + VERTRES = 10, + DESKTOPVERTRES = 117, + } + + public static float GetScalingFactor() + { + System.Drawing.Graphics g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero); + IntPtr desktop = g.GetHdc(); + int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES); + int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES); + float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight; + return ScreenScalingFactor; // 1.25 = 125% + } } } diff --git a/SpotlightDownloader/Lockscreen.cs b/SpotlightDownloader/Lockscreen.cs index 2f4c353..f039727 100644 --- a/SpotlightDownloader/Lockscreen.cs +++ b/SpotlightDownloader/Lockscreen.cs @@ -80,7 +80,7 @@ public static void RestoreDefaultGlobalLockscreen() Win7_RegistryKey(false); } } - else if ((WindowsVersion.WinMajorVersion == 6 && WindowsVersion.WinMajorVersion >= 2) /* Windows 8 and 10 */ + else if ((WindowsVersion.WinMajorVersion == 6 && WindowsVersion.WinMinorVersion >= 2) /* Windows 8 and 10 */ || WindowsVersion.WinMajorVersion >= 10 /* Windows 10 and future Windows versions - might not work on newer versions */) { string lockscreenDir = Win10_InitDir(); diff --git a/SpotlightDownloader/Program.cs b/SpotlightDownloader/Program.cs index 75b1e87..6f6dda4 100644 --- a/SpotlightDownloader/Program.cs +++ b/SpotlightDownloader/Program.cs @@ -8,12 +8,12 @@ namespace SpotlightDownloader { /// - /// Download Microsoft Spotlight images - By ORelio (c) 2018 - CDDL 1.0 + /// Download Microsoft Spotlight images - By ORelio (c) 2018-2019 - CDDL 1.0 /// class Program { public const string Name = "SpotlightDL"; - public const string Version = "1.3"; + public const string Version = "1.4"; static void Main(string[] args) { @@ -30,6 +30,7 @@ static void Main(string[] args) int downloadAmount = int.MaxValue; int cacheSize = int.MaxValue; bool metadata = false; + bool embedMetadata = false; string fromFile = null; int apiTryCount = 3; @@ -149,6 +150,9 @@ static void Main(string[] args) case "--metadata": metadata = true; break; + case "--embed-meta": + embedMetadata = true; + break; case "--from-file": i++; if (i < args.Length) @@ -272,13 +276,15 @@ static void Main(string[] args) { if (singleImage || action == "wallpaper" || action == "lockscreen") { - string outputFile = fromFile ?? randomImage.DownloadToFile(outputDir, integrityCheck, metadata, outputName, apiTryCount); - Console.WriteLine(outputFile); + string imageFile = fromFile ?? randomImage.DownloadToFile(outputDir, integrityCheck, metadata, outputName, apiTryCount); + if (embedMetadata) + imageFile = SpotlightImage.EmbedMetadata(imageFile, outputDir, outputName); + Console.WriteLine(imageFile); if (action == "wallpaper") { try { - Desktop.SetWallpaper(fromFile ?? outputFile); + Desktop.SetWallpaper(imageFile); } catch (Exception e) { @@ -292,7 +298,7 @@ static void Main(string[] args) { try { - Lockscreen.SetGlobalLockscreen(fromFile ?? outputFile); + Lockscreen.SetGlobalLockscreen(imageFile); } catch (Exception e) { @@ -352,7 +358,7 @@ static void Main(string[] args) .OrderByDescending(fileInfo => fileInfo.CreationTime) .Skip(cacheSize)) { - string metadataFile = Path.Combine(imgToDelete.DirectoryName, Path.GetFileNameWithoutExtension(imgToDelete.Name) + ".txt"); + string metadataFile = SpotlightImage.GetMetaLocation(imgToDelete.FullName); if (File.Exists(metadataFile)) File.Delete(metadataFile); imgToDelete.Delete(); @@ -393,10 +399,11 @@ static void Main(string[] args) " --portrait Force portrait image instead of autodetecting from current screen res", " --landscape Force landscape image instead of autodetecting from current screen res", " --outdir Set output directory instead of defaulting to working directory", - " --outname Set output file name as .jpg in single image mode, ignored otherwise", + " --outname Set output file name as .ext for --single or --embed-meta", " --skip-integrity Skip integrity check of downloaded files: file size and sha256 hash", " --api-tries Amount of unsuccessful API calls before giving up. Default is 3.", " --metadata Also save image metadata such as title & copyright as .txt", + " --embed-meta When available, embed metadata into wallpaper or locksceeen image", " --from-file Set the specified file as wallpaper/lockscreen instead of downloading", " --from-dir Set a random image from the specified directory as wallpaper/lockscreen", " --restore Restore the default lockscreen image, has no effect with other actions", diff --git a/SpotlightDownloader/SpotlightDownloader.csproj.user b/SpotlightDownloader/SpotlightDownloader.csproj.user index 7eef911..cf95eef 100644 --- a/SpotlightDownloader/SpotlightDownloader.csproj.user +++ b/SpotlightDownloader/SpotlightDownloader.csproj.user @@ -4,4 +4,8 @@ + + + + \ No newline at end of file diff --git a/SpotlightDownloader/SpotlightImage.cs b/SpotlightDownloader/SpotlightImage.cs index 3407ee9..34100f1 100644 --- a/SpotlightDownloader/SpotlightImage.cs +++ b/SpotlightDownloader/SpotlightImage.cs @@ -4,6 +4,8 @@ using System.Text; using System.Security.Cryptography; using System.Threading; +using System.Drawing; +using SharpTools; namespace SpotlightDownloader { @@ -12,12 +14,73 @@ namespace SpotlightDownloader /// class SpotlightImage { + private const string MetaHeader = "[SpotlightImage]"; + public string Uri { get; set; } public string Sha256 { get; set; } public int FileSize { get; set; } public string Title { get; set; } public string Copyright { get; set; } + /// + /// Get Metadata file location from JPG file location + /// + /// Image file location + /// Thrown if an error occurs when accessing the specified file + /// Thrown if the specified file is not a valid metadata file + /// Metadata file location + public static string GetMetaLocation(string imagePath) + { + return Path.Combine(Path.GetDirectoryName(imagePath), Path.GetFileNameWithoutExtension(imagePath) + ".txt"); + } + + /// + /// Load a SpotlightImage object from a Metatada file + /// + /// File to load information from + /// SpotlightImage object + public static SpotlightImage LoadMeta(string metadataFile) + { + string[] lines = File.ReadAllLines(metadataFile); + if (lines.Length > 0 && lines[0] == MetaHeader) + { + SpotlightImage image = new SpotlightImage(); + foreach (string line in lines) + { + int equalIndex = line.IndexOf('='); + if (equalIndex > 0) + { + string value = ""; + string key = line.Substring(0, equalIndex); + if (line.Length > (equalIndex + 1)) + value = line.Substring(equalIndex + 1); + switch (key) + { + case "uri": + image.Uri = value; + break; + case "sha256": + image.Sha256 = value; + break; + case "filesize": + int fileSize; + if (int.TryParse(value, out fileSize)) + image.FileSize = fileSize; + break; + case "title": + image.Title = value; + break; + case "copyright": + image.Copyright = value; + break; + } + } + } + return image; + } + else throw new InvalidDataException("Not a SpotlightImage metadata file: " + metadataFile); + } + /// /// Get image path to which the image will be downloaded /// @@ -111,7 +174,7 @@ private string DownloadToFileSingleAttempt(string outputDir, bool integrityCheck { string outputMeta = GetFilePath(outputDir, outputName, ".txt"); File.WriteAllLines(outputMeta, new[]{ - "[SpotlightImage]", + MetaHeader, "uri=" + Uri, "sha256=" + Sha256, "filesize=" + FileSize, @@ -122,5 +185,150 @@ private string DownloadToFileSingleAttempt(string outputDir, bool integrityCheck return outputFile; } + + /// + /// Load an input image, adjust to screen res, embed metadata using current desktop scaling factor and save the result into a new image file + /// + /// Input image file + /// Output directory + /// Output file name + /// Auto adjust image to current screen resolution + /// Auto adjust text to current UI scaling factor + /// Output file path + public static string EmbedMetadata(string inputImageFile, string outputDir, string outputName, bool adjustToScreen = true, bool adjustToScaling = true) + { + //Windows 7 or lower cannot use PNG as wallpaper or will convert PNG to JPG, better use BMP for best quality + string fileExtension = ".bmp"; + var imageFormat = System.Drawing.Imaging.ImageFormat.Bmp; + + //Windows 8 and 10 will convert BMP to JPG, better use PNG for best quality + if ((WindowsVersion.WinMajorVersion == 6 && WindowsVersion.WinMinorVersion >= 2) + || WindowsVersion.WinMajorVersion >= 10) + { + fileExtension = ".png"; + imageFormat = System.Drawing.Imaging.ImageFormat.Png; + } + + if (String.IsNullOrEmpty(outputName)) + outputName = Path.GetFileNameWithoutExtension(inputImageFile); + string outputFile = Path.Combine(outputDir, outputName + fileExtension); + + Image img = Image.FromFile(inputImageFile); + + if (adjustToScreen) + { + Rectangle screen = System.Windows.Forms.Screen.PrimaryScreen.Bounds; + Image img2 = FixedImageResize(img, screen.Width, screen.Height, true); + img.Dispose(); + img = img2; + } + + string metadataFile = GetMetaLocation(inputImageFile); + if (File.Exists(metadataFile)) + { + SpotlightImage meta = LoadMeta(metadataFile); + Graphics gfx = Graphics.FromImage(img); + + gfx.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + gfx.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + gfx.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality; + + int fontSize = 11; + if (adjustToScaling) + fontSize = (int)((float)fontSize * Desktop.GetScalingFactor()); + Font font = new Font("Segoe UI Semilight", fontSize); + SizeF titleSize = gfx.MeasureString(meta.Title, font); + if (titleSize.Width < img.Size.Width - 20) + { + gfx.DrawString( + meta.Title, + font, + Brushes.White, + new Rectangle( + img.Size.Width - 10 - (int)Math.Ceiling(titleSize.Width), + 10, + (int)Math.Ceiling(titleSize.Width), + (int)Math.Ceiling(titleSize.Height) + ) + ); + gfx.Flush(); + } + } + + img.Save(outputFile, imageFormat); + img.Dispose(); + + return outputFile; + } + + /// + /// Resize image to a fixed destination size while preserving aspect ratio + /// + /// Source: stackoverflow.com/questions/10323633/ + /// Input image + /// Desized Width + /// Desized Height + /// True = crop to fill, False = fit inside + /// Output image + private static Image FixedImageResize(Image image, int Width, int Height, bool needToFill) + { + int sourceWidth = image.Width; + int sourceHeight = image.Height; + int sourceX = 0; + int sourceY = 0; + double destX = 0; + double destY = 0; + + double nScale = 0; + double nScaleW = 0; + double nScaleH = 0; + + nScaleW = ((double)Width / (double)sourceWidth); + nScaleH = ((double)Height / (double)sourceHeight); + + if (!needToFill) + { + nScale = Math.Min(nScaleH, nScaleW); + } + else + { + nScale = Math.Max(nScaleH, nScaleW); + destY = (Height - sourceHeight * nScale) / 2; + destX = (Width - sourceWidth * nScale) / 2; + } + + if (nScale > 1) + nScale = 1; + + int destWidth = (int)Math.Round(sourceWidth * nScale); + int destHeight = (int)Math.Round(sourceHeight * nScale); + + System.Drawing.Bitmap bmPhoto = null; + + try + { + bmPhoto = new System.Drawing.Bitmap(destWidth + (int)Math.Round(2 * destX), destHeight + (int)Math.Round(2 * destY)); + } + catch (Exception ex) + { + throw new ApplicationException(string.Format("destWidth:{0}, destX:{1}, destHeight:{2}, desxtY:{3}, Width:{4}, Height:{5}", + destWidth, destX, destHeight, destY, Width, Height), ex); + } + + using (System.Drawing.Graphics grPhoto = System.Drawing.Graphics.FromImage(bmPhoto)) + { + grPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + grPhoto.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; + grPhoto.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; + + Rectangle to = new System.Drawing.Rectangle((int)Math.Round(destX), (int)Math.Round(destY), destWidth, destHeight); + Rectangle from = new System.Drawing.Rectangle(sourceX, sourceY, sourceWidth, sourceHeight); + //Console.WriteLine("From: " + from.ToString()); + //Console.WriteLine("To: " + to.ToString()); + grPhoto.DrawImage(image, to, from, System.Drawing.GraphicsUnit.Pixel); + + return bmPhoto; + } + } } }