您的位置:首页 > 其它

.NET生成漂亮桌面背景

2019-09-30 11:46 2106 查看

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

可以看到作者和文本,可以使用

author
body
两个字段来表示。

这部分使用

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

如代码所示,我将

body
author
两个字段拼接成了一个字符串,可以直接使用,像这样:

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.JpegBitmapDeco
576
der(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)]
s
1c4f
tatic 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骚操作】

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: