.NET生成漂亮桌面背景
前言
一天,我朋友指著某某付費(fèi)軟件對(duì)我說(shuō),這個(gè)東西不錯(cuò),每天生成一張桌面背景,還能學(xué)英語(yǔ)(放置名人名言和翻譯)!我說(shuō),這東西搞不好我也能做,然后朋友說(shuō),“如果你搞出來(lái)了,我愿意給你付費(fèi)$$$$元”,然后就有了今天的故事?。
該桌面背景效果如下:
該桌面背景有4個(gè)特點(diǎn):
背景為一張從?必應(yīng)下載的壁紙
英文為隨機(jī)的名人名言,從?API獲取
注意文件下文有陰影,使用?Direct2D
英文被翻譯成了中文,使用了?AzureCognitiveService
當(dāng)然還有重要的,需要將這張圖片設(shè)為桌面背景,這通過(guò) WindowsAPI完成。下面我將對(duì)里面的功能點(diǎn)一一講解。
第一步 下載必應(yīng)壁紙
bing.com每天提供了一張壁紙,下載 bing壁紙是最簡(jiǎn)單的方式。根據(jù)用戶協(xié)議,必應(yīng)每日?qǐng)D片允許(也只允許)用戶將其設(shè)置為桌面背景,因此可以放心使用。
bing壁紙的 API如下:
https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=5&mkt=zh-cn
使用瀏覽器訪問(wèn),格式如下:
由圖可見(jiàn), API返回了一個(gè) JSON,里面一個(gè) images的數(shù)組,里面元素中的 url屬性即是 bing壁紙。可以通過(guò)拼接 https://www.bing.com來(lái)下載今天的 bing壁紙:
因此,本段也分為三小步,用 C#代碼可以這樣寫:
1. 下載?bing.com壁紙查詢?API
下載使用 HttpClient,注意 HttpClient在單個(gè)應(yīng)用中應(yīng)該定義為靜態(tài)的。代碼如下:
var http = new HttpClient(); string url = @"https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=5&mkt=zh-cn"; string content = await http.GetStringAsync(url);注意其中的 &n=5中的 5,指的是最新的 5張照片,如果想獲取 10張,可以將 5改成 10。
2. 解析返回的壁紙?JSON信息
解析 JSON有很多方式,本文使用傳統(tǒng)的 Json.NET/ Newtonsoft.Json來(lái)做:
string json = JToken.Parse(content); string images = json["images"] .Select(x => x["url"].ToString()) .Select(x => "https://cn.bing.com" + x); string pictureUrl = images.First();注意第二行代碼,其實(shí)可以直接獲取所有的`bing
3. 下載完成的壁紙圖片
這一步也通過(guò) HttpClient完成:
var fileName = Path.GetTempFileName(); File.WriteAllBytes(fileName, await http.GetByteArrayAsync(url)); return fileName;然后下載的圖片就保存在 fileName這個(gè)變量所表達(dá)的路徑中了。
注意:不一定非要下載到文件中,下載到內(nèi)存中亦可,但下文中的代碼需要少許調(diào)整,這里就不深入了。
第二步 獲取名人名言
我在網(wǎng)上找了一下,有不少網(wǎng)站都提供了英語(yǔ)名人名言服務(wù),其中還不乏免費(fèi)服務(wù)。本文使用的是 favqs.com提供的 API(隨便找的),該 API每次調(diào)用都會(huì)返回不同的“名人名言”,我試了一下,可堪一用,免費(fèi) API調(diào)用地址如下:
https://favqs.com/api/qotd
返回的 json格式如下:
{ "qotd_date": "2019-09-30T00:00:00.000+00:00", "quote": { "id": 61060, "dialogue": false, "private": false, "tags": [ "work" ], "url": "https://favqs.com/quotes/voltaire/61060-let-us-work-w-", "favorites_count": 0, "upvotes_count": 1, "downvotes_count": 0, "author": "Voltaire", "author_permalink": "voltaire", "body": "Let us work without theorizing, tis the only way to make life endurable." } }可以看到作者和文本,可以使用 author和 body兩個(gè)字段來(lái)表示。
這部分使用 C#代碼下載和解析過(guò)程如下:
async Task<string> GetQuote() { var url = @"https://favqs.com/api/qotd"; var content = await http.GetStringAsync(url); var json = JToken.Parse(content); return json["quote"]["body"] + "\r\n\t\t\t\t——" + json["quote"]["author"]; }如代碼所示,我將 body和 author兩個(gè)字段拼接成了一個(gè)字符串,可以直接使用,像這樣:
Let us work without theorizing, tis the only way to make life endurable. ——Voltaire第三步 生成圖片(加陰影)
這步使用 Direct2D,比較復(fù)雜,要注意的點(diǎn)很多,各位可以選擇跳過(guò)這一步(直接拿代碼?),或者稍微看看。
string GenerateWallpaper(string pictureFileName, string english, string chinese) { var wic = new WIC.ImagingFactory2(); var d2d = new D2D.Factory(); float dpi = d2d.DesktopDpi.Width; Size2 size = new Size2(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); WIC.FormatConverter image = CreateWicImage(wic, pictureFileName); using (var wicBitmap = new WIC.Bitmap(wic, size.Width, size.Height, WIC.PixelFormat.Format32bppPBGRA, WIC.BitmapCreateCacheOption.CacheOnDemand)) using (var target = new D2D.WicRenderTarget(d2d, wicBitmap, new D2D.RenderTargetProperties())) using (var dc = target.QueryInterface<D2D.DeviceContext>()) using (var bmpPicture = D2D.Bitmap.FromWicBitmap(target, image)) using (var dwriteFactory = new SharpDX.DirectWrite.Factory()) using (var brush = new SolidColorBrush(target, SharpDX.Color.LightGoldenrodYellow)) using (var bmpLayer = new D2D.Bitmap1(dc, target.PixelSize, new D2D.BitmapProperties1(new D2D.PixelFormat(SharpDX.DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Premultiplied), dpi, dpi, D2D.BitmapOptions.Target))) { var oldTarget = dc.Target; dc.Target = bmpLayer; target.BeginDraw(); { var textFormat = new DWrite.TextFormat(dwriteFactory, "Tahoma", size.Height / 27); // draw English { var textLayout = new DWrite.TextLayout(dwriteFactory, english, textFormat, target.Size.Width * 0.75f, float.MaxValue); var center = new Vector2((target.Size.Width - textLayout.Metrics.Width) / 2, (target.Size.Height - textLayout.Metrics.Height) / 2); target.DrawTextLayout(new Vector2(center.X, center.Y), textLayout, brush); } { // draw Chinese var textLayout = new DWrite.TextLayout(dwriteFactory, chinese, textFormat, target.Size.Width * 0.75f, float.MaxValue); var center = new Vector2((target.Size.Width - textLayout.Metrics.Width) / 2, target.Size.Height - textLayout.Metrics.Height - size.Height / 18); target.DrawTextLayout(new Vector2(center.X, center.Y), textLayout, brush); } } target.EndDraw(); // shadow var shadow = new D2D.Effects.Shadow(dc); shadow.SetInput(0, bmpLayer, new RawBool(false)); dc.Target = oldTarget; target.BeginDraw(); { target.DrawBitmap(bmpPicture, new RectangleF(0, 0, target.Size.Width, target.Size.Height), 1.0f, BitmapInterpolationMode.Linear); dc.DrawImage(shadow, new Vector2(size.Height / 150.0f, size.Height / 150.0f)); dc.UnitMode = UnitMode.Pixels; target.DrawBitmap(bmpLayer, 1.0f, BitmapInterpolationMode.Linear); } target.EndDraw(); string wallpaperFileName = Path.GetTempPath() + "wallpaper.png"; using (var wallpaperStream = File.OpenWrite(wallpaperFileName)) { SaveD2DBitmap(wic, wicBitmap, wallpaperStream); wallpaperStream.Close(); return wallpaperFileName; } } } WIC.FormatConverter CreateWicImage(WIC.ImagingFactory wicFactory, string filename) { using (var decoder = new WIC.JpegBitmapDecoder(wicFactory)) using (var decodeStream = new WIC.WICStream(wicFactory, filename, NativeFileAccess.Read)) { decoder.Initialize(decodeStream, WIC.DecodeOptions.CacheOnLoad); using (var decodeFrame = decoder.GetFrame(0)) { var converter = new WIC.FormatConverter(wicFactory); converter.Initialize(decodeFrame, WIC.PixelFormat.Format32bppPBGRA); return converter; } } } void SaveD2DBitmap(WIC.ImagingFactory wicFactory, WIC.Bitmap wicBitmap, Stream outputStream) { using (var encoder = new WIC.BitmapEncoder(wicFactory, WIC.ContainerFormatGuids.Png)) { encoder.Initialize(outputStream); using (var frame = new WIC.BitmapFrameEncode(encoder)) { frame.Initialize(); frame.SetSize(wicBitmap.Size.Width, wicBitmap.Size.Height); var pixelFormat = wicBitmap.PixelFormat; frame.SetPixelFormat(ref pixelFormat); frame.WriteSource(wicBitmap); frame.Commit(); encoder.Commit(); } } }要看的話,要點(diǎn)如下:
圖片大小是由主顯示器分辨率決定的,可以使用?Screen.PrimaryScreen.Bounds.Width/?Screen.PrimaryScreen.Bounds.Height獲取;
一定要注意不同電腦的?DPI設(shè)置,這樣可以保證高?DPI和低?DPI的顯示器都能有完美的效果,這部分是使用?d2d.DesktopDpi.Width獲取的,請(qǐng)注意里面的使用方式(最重要的是用不同客戶的電腦親自運(yùn)行看看);
字體大小是根據(jù)圖片的高度決定的,如代碼所示,字體大小為?size.Height/27;
雖然代碼前后順序是先畫(huà)文字、再畫(huà)陰影,但實(shí)際生成代碼部分,是先畫(huà)陰影、再畫(huà)文字,這樣確保文字在陰影之上;
可以使用?textLayout.Metrics獲取生成文字的寬度和高度,這樣可以確保文件顯示在中心位置;
注意下文中?dc.UnitMode=UnitMode.Pixels,這是確保?DPI顯示正常。說(shuō)來(lái)復(fù)雜,長(zhǎng)話短說(shuō)就是這其實(shí)很合理,前面設(shè)置了?DPI,該?DPI不僅影響文字,也會(huì)影響圖片,但實(shí)際上圖片不應(yīng)該被?DPI影響。
以后有機(jī)會(huì)我會(huì)多聊聊 Direct2D,這簡(jiǎn)直是一個(gè)寶庫(kù)。
第四步 將文字翻譯成中文
翻譯服務(wù) API提供商就更多了,選擇很多。我用的是 AzureCognitiveService,它也是免費(fèi)的(也有付費(fèi)版本)。創(chuàng)建這個(gè)服務(wù)后,它會(huì)提供兩個(gè)單獨(dú)的 key,使用這個(gè) key即可調(diào)用翻譯服務(wù)了:
不像是阿里云、AWS那種, Azure會(huì)為不同的服務(wù)提供不同的 AccessKey,這樣做可能更容易控制信息安全一些。
Azure提供了 SDK,因此調(diào)用起來(lái)非常簡(jiǎn)單:
async Task<string> EnglishToChinese(string english) { var client = new TranslateClient(new CognitiveServicesConfig { SubscriptionKey = Util.GetPassword("Translate_Free") }); var response = await client.TranslateAsync(new RequestContent { Text = english }, new RequestParameter { To = new string[] { "zh-Hans" } }); return response[0].Translations[0].Text; }其實(shí)它的功能非常強(qiáng)大,甚至還能多國(guó)語(yǔ)言同步翻譯等等。
最后一步 設(shè)置桌面背景
這一步調(diào)用 WindowsAPI,直接使用“祖?zhèn)鞔a”即可:
public sealed class Wallpaper { const int SPI_SETDESKWALLPAPER = 20; const int SPIF_UPDATEINIFILE = 0x01; const int SPIF_SENDWININICHANGE = 0x02; [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); public enum Style : int { Tiled, Centered, Stretched } public static void Set(string pictureFileName, Style style) { RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true); if (style == Style.Stretched) { key.SetValue(@"WallpaperStyle", 2.ToString()); key.SetValue(@"TileWallpaper", 0.ToString()); } if (style == Style.Centered) { key.SetValue(@"WallpaperStyle", 1.ToString()); key.SetValue(@"TileWallpaper", 0.ToString()); } if (style == Style.Tiled) { key.SetValue(@"WallpaperStyle", 1.ToString()); key.SetValue(@"TileWallpaper", 1.ToString()); } SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, pictureFileName, SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE); } }使用時(shí),直接這樣調(diào)用:
Wallpaper.Set(wallpaperFileName, Wallpaper.Style.Centered);注意:由于第三步中確保了分辨率一樣,因此也不用關(guān)心第二個(gè)參數(shù)。
總結(jié)
最后看一下執(zhí)行效果:
然而最后,我那個(gè)朋友說(shuō),你這東西要是支持 Linux就好咯。我不用 Linux,所以我也不打算支持(我看他其實(shí)也根本不用 Linux),因此最后說(shuō)好的 $$$$我一分錢也沒(méi)拿到?。
所以這部分代碼我托盤而出,向各位免費(fèi)相送,分為帶翻譯版本和不帶翻譯版本,不帶翻譯版本可直接使用,帶翻譯版本需要注冊(cè)一個(gè)免費(fèi) Azure帳號(hào)(其實(shí)也能運(yùn)行,只是翻譯中文會(huì)顯示翻譯錯(cuò)誤)。愿博君一笑:
無(wú)翻譯:https://github.com/sdcb/blog-data/blob/master/2019/20190930-wallpaper-by-dotnet/PictureAndQuote-Wallpaper.linq 帶翻譯:https://github.com/sdcb/blog-data/blob/master/2019/20190930-wallpaper-by-dotnet/PictureAndQuote-Wallpaper-T.linq總結(jié)
以上是生活随笔為你收集整理的.NET生成漂亮桌面背景的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 在树莓派4上安装 .NET Core 3
- 下一篇: Autofac的AOP面向切面编程研究