.NET生成漂亮桌面背景


.NET生成漂亮桌面背景

一天,我朋友指着某某付費軟件對我說,這個東西不錯,每天生成一張桌面背景,還能學英語(放置名人名言和翻譯)!我說,這東西搞不好我也能做,然后朋友說,“如果你搞出來了,我願意給你付費$$$$元”,然后就有了今天的故事😎。

該桌面背景效果如下:

該桌面背景有4個特點:

  1. 背景為一張從必應下載的壁紙
  2. 英文為隨機的名人名言,從API獲取
  3. 注意文件下文有陰影,使用Direct2D
  4. 英文被翻譯成了中文,使用了Azure Cognitive Service

當然還有重要的,需要將這張圖片設為桌面背景,這通過Windows API完成。下面我將對里面的功能點一一講解。

第一步 下載必應壁紙

bing.com每天提供了一張壁紙,下載bing壁紙是最簡單的方式。根據用戶協議,必應每日圖片允許(也只允許)用戶將其設置為桌面背景,因此可以放心使用。

bing壁紙的API如下:

https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=5&mkt=zh-cn

使用瀏覽器訪問,格式如下:

由圖可見,API返回了一個JSON,里面一個images的數組,里面元素中的url屬性即是bing壁紙。可以通過拼接https://www.bing.com來下載今天的bing壁紙:

因此,本段也分為三小步,用C#代碼可以這樣寫:

1. 下載bing.com壁紙查詢API

下載使用HttpClient,注意HttpClient在單個應用中應該定義為靜態的。代碼如下:

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有很多方式,本文使用傳統的Json.NET/Newtonsoft.Json來做:

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();

注意第二行代碼,其實可以直接獲取所有的bing壁紙。

3. 下載完成的壁紙圖片

這一步也通過HttpClient完成:

var fileName = Path.GetTempFileName();
File.WriteAllBytes(fileName, await http.GetByteArrayAsync(url));
return fileName;

然后下載的圖片就保存在fileName這個變量所表達的路徑中了。

注意:不一定非要下載到文件中,下載到內存中亦可,但下文中的代碼需要少許調整,這里就不深入了。

第二步 獲取名人名言

我在網上找了一下,有不少網站都提供了英語名人名言服務,其中還不乏免費服務。本文使用的是favqs.com提供的API(隨便找的),該API每次調用都會返回不同的“名人名言”,我試了一下,可堪一用,免費API調用地址如下:

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."
    }
}

可以看到作者和文本,可以使用authorbody兩個字段來表示。

這部分使用C#代碼下載和解析過程如下:

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"];
}

如代碼所示,我將bodyauthor兩個字段拼接成了一個字符串,可以直接使用,像這樣:

Let us work without theorizing, tis the only way to make life endurable.
                ——Voltaire

第三步 生成圖片(加陰影)

這步使用Direct2D,比較復雜,要注意的點很多,各位可以選擇跳過這一步(直接拿代碼😂),或者稍微看看。

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();
		}
	}
}

要看的話,要點如下:

  1. 圖片大小是由主顯示器分辨率決定的,可以使用Screen.PrimaryScreen.Bounds.Width/Screen.PrimaryScreen.Bounds.Height獲取;
  2. 一定要注意不同電腦的DPI設置,這樣可以保證高DPI和低DPI的顯示器都能有完美的效果,這部分是使用d2d.DesktopDpi.Width獲取的,請注意里面的使用方式(最重要的是用不同客戶的電腦親自運行看看);
  3. 字體大小是根據圖片的高度決定的,如代碼所示,字體大小為size.Height / 27
  4. 雖然代碼前后順序是先畫文字、再畫陰影,但實際生成代碼部分,是先畫陰影、再畫文字,這樣確保文字在陰影之上;
  5. 可以使用textLayout.Metrics獲取生成文字的寬度和高度,這樣可以確保文件顯示在中心位置;
  6. 注意下文中dc.UnitMode = UnitMode.Pixels,這是確保DPI顯示正常。說來復雜,長話短說就是這其實很合理,前面設置了DPI,該DPI不僅影響文字,也會影響圖片,但實際上圖片不應該被DPI影響。

以后有機會我會多聊聊Direct2D,這簡直是一個寶庫。

第四步 將文字翻譯成中文

翻譯服務API提供商就更多了,選擇很多。我用的是Azure Cognitive Service,它也是免費的(也有付費版本)。創建這個服務后,它會提供兩個單獨的key,使用這個key即可調用翻譯服務了:

不像是阿里雲、AWS那種,Azure會為不同的服務提供不同的Access Key,這樣做可能更容易控制信息安全一些。

Azure提供了SDK,因此調用起來非常簡單:

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;
}

其實它的功能非常強大,甚至還能多國語言同步翻譯等等。

最后一步 設置桌面背景

這一步調用Windows API,直接使用“祖傳代碼”即可:

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);
	}
}

使用時,直接這樣調用:

Wallpaper.Set(wallpaperFileName, Wallpaper.Style.Centered);

注意:由於第三步中確保了分辨率一樣,因此也不用關心第二個參數。

總結

最后看一下執行效果:

然而最后,我那個朋友說,你這東西要是支持Linux就好咯。我不用Linux,所以我也不打算支持(我看他其實也根本不用Linux),因此最后說好的$$$$我一分錢也沒拿到😂。

所以這部分代碼我托盤而出,向各位免費相送,分為帶翻譯版本和不帶翻譯版本,不帶翻譯版本可直接使用,帶翻譯版本需要注冊一個免費Azure帳號(其實也能運行,只是翻譯中文會顯示翻譯錯誤)。願博君一笑:

無翻譯: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

喜歡的朋友請關注我的微信公眾號:【DotNet騷操作】

DotNet騷操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM