0%

macOS Mojave 动态壁纸

How Apple built dynamic wallpapers? And is it possible to create your own dynamic wallpaper for macOS? I spent some time because I would like to answer to the both above questions.
Apple 如何构建动态壁纸?是否可以为 macOS 创建自己的动态壁纸?我花了一些时间,因为我想回答上述两个问题。


In Mojave we can choose new type of wallpaper: dynamic.
在 Mojave,我们可以选择新型壁纸:动态。

Depending on day time, system displays different wallpaper. At night it is picture of Mojave at night, during day it's picture of Mojave taken during day. So at night we have dark wallpaper at day we have light wallpaper.
根据时间,系统会显示不同的壁纸。晚上显示 Mojave 晚上的照片,白天则是在白天拍摄的 Mojave 的照片。所以晚上我们有暗色的壁纸,白天则是明亮的壁纸。

All built in wallpapers in macOS we can find in folder: /Library/Desktop Pictures. Also here we can find dynamic wallpaper.
我们可以在文件夹 /Library/Desktop Pictures 中找到所有 macOS 内置的壁纸。在这里我们也可以找到动态壁纸。

Dynamic wallpaper is saved as a HEIC file (Mojave (Dynamic).heic). More information about this type of file you can find on Wikipedia. Generally this is type of file which Apple uses in iOS devices. Photos in iOS (also live photos) are stored as a HEIC file. That kind of file can contains multiple images/thumbnails and metadata in a single file.
动态壁纸保存为 HEIC 文件(Mojave (Dynamic).heic)。有关此类文件的更多信息,请访问 Wikipedia。通常,这是 Apple 在 iOS 设备中使用的文件类型。iOS 中的照片(也包括实时照片)会存储为 HEIC 文件。这种文件可以在单个文件中包含多个图像 / 缩略图和元数据。

There is really good online tool where we can check content of Mojave wallpaper file: https://strukturag.github.io/libheif/. You can upload Mojave (Dynamic).heic file and you will see that in this single file there is 16 separate pictures taken in different day phase.
有一个很好的在线工具,让我们可以检查 Mojave 壁纸文件的内容:https://strukturag.github.io/libheif/ 。你可以上传 Mojave (Dynamic).heic 文件,然后你会发现在这个单独的文件中有 16 张不同时间拍摄的单独照片。

So maybe it's enough to create new HEIC file with 16 separate images and macOS will show them properly. Let's try it!
所以也许它足以创建具有 16 个单独图像的新 HEIC 文件,macOS 将正确显示它们。我们来试试吧!

First I had to prepare 16 different images.
首先,我必须准备 16 张不同的图像。

I know, I know - they are not impressive :-). However for our experiment they will be enough.
我知道,我知道 - 它们非常 trivial :-)。然而,对于我们的实验,它们就足够了。

Now I have to prepare application which convert set of images to one HEIC file. I prepared simple console application in Swift.
现在我必须准备一个能将图像集转换为 HEIC 文件的应用程序。我用 Swift 写了一个简单的控制台应用程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import Foundation
import AppKit
import AVFoundation

extension NSImage {
@objc var CGImage: CGImage? {
get {
guard let imageData = self.tiffRepresentation else { return nil }
guard let sourceData = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
return CGImageSourceCreateImageAtIndex(sourceData, 0, nil)
}
}
}

let output = "wallpapers-new/output.heic"
let quality = 0.9
var imageData: Data? = nil

if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {

let destinationData = NSMutableData()
if let destination = CGImageDestinationCreateWithData(destinationData, AVFileType.heic as CFString, 16, nil) {
let options = [kCGImageDestinationLossyCompressionQuality: quality]

for index in 1...16 {
let fileURL = dir.appendingPathComponent("wallpapers-new/\(index).png")
let orginalImage = NSImage(contentsOf: fileURL)

if let cgImage = orginalImage?.CGImage {
CGImageDestinationAddImage(destination, cgImage, options as CFDictionary)
}
}

CGImageDestinationFinalize(destination)
imageData = destinationData as Data

let outputURL = dir.appendingPathComponent(output)
try! imageData?.write(to: outputURL)
}
}

Application requires that in my Documents folder I have wallpaper-new folder with 16 images (from 1.png to 16.png). I can run application and after that I have new output.heic file in the same directory.
应用程序要求在我的 Documents 文件夹中存在有 wallpaper-new 文件夹,里面有 16 张图片(从 1.png16.png)。我可以运行应用程序,之后我在同一目录中有了新的 output.heic 文件。

After setting this file as a wallpaper I had only one image displayed during whole day. So it seems that this file have to contains something extra. I decided to check what metadata is inside original Mojave wallpaper. I downloaded libheif library (I modified a little bit heif-info application) and I've got below result:
将此文件设置为壁纸后,我在一整天中只显示了一个图像。所以似乎这个文件必须包含额外的东西。我决定检查原始 Mojave 壁纸中的元数据。我下载了 libheif 库(修改了一下 heif-info 应用程序),然后得到了以下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
Number of top level images: 16
image #0
id: 61
width: 5120
height: 2880
is primary: yes
number of metadata: 1
metadata type: mime
content type: application/rdf+xml
metadata size: 3302
metadata content:

<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:apple_desktop="http://ns.apple.com/namespace/1.0/" apple_desktop:solar="YnBsaXN0MDDRAQJSc2mvEBADDBAUGBwgJCgsMDQ4PEFF1AQFBgcICQoLUWlRelFhUW8QACNAcO7vOubr3yO/1e+pmkOtXBAB1AQFBgcNDg8LEAEjQFRxqCKOFiAjwCR6waUkDgHUBAUGBxESEwsQAiNAVZV4BI4c+CPAEP2uFrMcrdQEBQYHFRYXCxADI0BWtALKmrjwIz/2ObLnx6l21AQFBgcZGhsLEAQjQFfTrJlEjnwjQByrLle1Q0rUBAUGBx0eHwsQBSNAWPrrmI0ISCNAKiwhpSRpc9QEBQYHISIjCxAGI0BgJff9KDpyI0BENTOsilht1AQFBgclJicLEAcjQGbHdYIVQKojQEq3fAg86lXUBAUGBykqKwsQCCNAbTGmpC2YRiNAQ2WFOZGjntQEBQYHLS4vCxAJI0BwXfII2B+SI0AmLcjfuC7g1AQFBgcxMjMLEAojQHCnF6YrsxcjQBS9AVBLTq3UBAUGBzU2NwsQCyNAcTcSnimmjCPAGP5E0ASXJtQEBQYHOTo7CxAMI0BxgSADjxK2I8AoalieOTyE1AQFBgc9Pj9AEA0jQHNWsnnMcWIjwEO+oq1pXr8QANQEBQYHQkNEQBAOI0ABZpkFpAcAI8BKYGg/VvMf1AQFBgdGR0hAEA8jQErBKblRzPgjwEMGElBIUO0ACAALAA4AIQAqACwALgAwADIANAA9AEYASABRAFMAXABlAG4AcAB5AIIAiwCNAJYAnwCoAKoAswC8AMUAxwDQANkA4gDkAO0A9gD/AQEBCgETARwBHgEnATABOQE7AUQBTQFWAVgBYQFqAXMBdQF+AYcBkAGSAZsBpAGtAa8BuAHBAcMBzAHOAdcB4AHpAesB9AAAAAAAAAIBAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAH9"/> </rdf:RDF> </x:xmpmeta><?xpacket end="w"?>

image #1
id: 123
width: 5120
height: 2880
image #2
id: 184
width: 5120
height: 2880
image #3
id: 245
width: 5120
height: 2880
image #4
id: 306
width: 5120
height: 2880
image #5
id: 367
width: 5120
height: 2880
image #6
id: 428
width: 5120
height: 2880
image #7
id: 489
width: 5120
height: 2880
image #8
id: 550
width: 5120
height: 2880
image #9
id: 611
width: 5120
height: 2880
image #10
id: 672
width: 5120
height: 2880
image #11
id: 733
width: 5120
height: 2880
image #12
id: 794
width: 5120
height: 2880
image #13
id: 855
width: 5120
height: 2880
image #14
id: 916
width: 5120
height: 2880
image #15
id: 977
width: 5120
height: 2880

Thus we can see that in the first image we have additional XMP metadata. Now I know that I have to modify a little bit my Swift application. It should also add metadata for the first image in the sequence.
因此,我们可以看到在第一张图片中我们有额外的 XMP 元数据。现在我知道我必须修改一下我的 Swift 应用程序。它还应该为序列中的第一个图像添加元数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import Foundation
import AppKit
import AVFoundation

extension NSImage {
@objc var CGImage: CGImage? {
get {
guard let imageData = self.tiffRepresentation else { return nil }
guard let sourceData = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
return CGImageSourceCreateImageAtIndex(sourceData, 0, nil)
}
}
}

let output = "wallpapers-new/output.heic"
let quality = 0.9
var imageData: Data? = nil

if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {

let destinationData = NSMutableData()
if let destination = CGImageDestinationCreateWithData(destinationData, AVFileType.heic as CFString, 16, nil) {
let options = [kCGImageDestinationLossyCompressionQuality: quality]

for index in 1...16 {
let fileURL = dir.appendingPathComponent("wallpapers-new/\(index).png")
let orginalImage = NSImage(contentsOf: fileURL)

if let cgImage = orginalImage?.CGImage {

if index == 1 {
let imageMetadata = CGImageMetadataCreateMutable()

let imageMetadataTag = CGImageMetadataTagCreate("http://ns.apple.com/namespace/1.0/" as CFString,
"apple_desktop" as CFString,
"solar" as CFString,
CGImageMetadataType.string,
"YnBsaXN0MDDRAQJSc2mvEBADDBAUGBwgJCgsMDQ4PEFF1AQFB" +
"gcICQoLUWlRelFhUW8QACNAcO7vOubr3yO/1e+pmkOtXBAB1A" +
"QFBgcNDg8LEAEjQFRxqCKOFiAjwCR6waUkDgHUBAUGBxESEws" +
"QAiNAVZV4BI4c+CPAEP2uFrMcrdQEBQYHFRYXCxADI0BWtALK" +
"mrjwIz/2ObLnx6l21AQFBgcZGhsLEAQjQFfTrJlEjnwjQByrL" +
"le1Q0rUBAUGBx0eHwsQBSNAWPrrmI0ISCNAKiwhpSRpc9QEBQ" +
"YHISIjCxAGI0BgJff9KDpyI0BENTOsilht1AQFBgclJicLEAc" +
"jQGbHdYIVQKojQEq3fAg86lXUBAUGBykqKwsQCCNAbTGmpC2Y" +
"RiNAQ2WFOZGjntQEBQYHLS4vCxAJI0BwXfII2B+SI0AmLcjfu" +
"C7g1AQFBgcxMjMLEAojQHCnF6YrsxcjQBS9AVBLTq3UBAUGBz" +
"U2NwsQCyNAcTcSnimmjCPAGP5E0ASXJtQEBQYHOTo7CxAMI0B" +
"xgSADjxK2I8AoalieOTyE1AQFBgc9Pj9AEA0jQHNWsnnMcWIj" +
"wEO+oq1pXr8QANQEBQYHQkNEQBAOI0ABZpkFpAcAI8BKYGg/V" +
"vMf1AQFBgdGR0hAEA8jQErBKblRzPgjwEMGElBIUO0ACAALAA" +
"4AIQAqACwALgAwADIANAA9AEYASABRAFMAXABlAG4AcAB5AII" +
"AiwCNAJYAnwCoAKoAswC8AMUAxwDQANkA4gDkAO0A9gD/AQEB" +
"CgETARwBHgEnATABOQE7AUQBTQFWAVgBYQFqAXMBdQF+AYcBk" +
"AGSAZsBpAGtAa8BuAHBAcMBzAHOAdcB4AHpAesB9AAAAAAAAA" +
"IBAAAAAAAAAEkAAAAAAAAAAAAAAAAAAAH9" as CFTypeRef)

let success = CGImageMetadataSetTagWithPath(imageMetadata, nil, "xmp:solar" as CFString, imageMetadataTag!)
if !success {
print("Error!!!")
}

CGImageDestinationAddImageAndMetadata(destination, cgImage, imageMetadata, options as CFDictionary)

} else {
CGImageDestinationAddImage(destination, cgImage, options as CFDictionary)
}
}
}

CGImageDestinationFinalize(destination)
imageData = destinationData as Data

let outputURL = dir.appendingPathComponent(output)
try! imageData?.write(to: outputURL)
}
}

Now we can set up our new file as a wallpaper. And this is the result:
现在我们可以将新文件设置为壁纸。这就是结果:
https://www.youtube.com/watch?v=_r0Qxblyz8U

It works! We can prepare our custom dynamic wallpaper! And it's pretty easy.
它是有效的!我们可以准备我们的自定义动态壁纸!这很容易。


Now the question is: what is in the apple_desktop:solar attribute? It seems that this is something encoded in base64. We can save value of that attribute as a text file (e.g. text.base64) and run following command:
现在的问题是:apple_desktop:solar 属性是什么?似乎这是 base64 编码的东西。我们可以将该属性的值保存为文本文件(例如 text.base64)并运行以下命令:

1
base64 -D text.base64 -o decoded.txt

After opening decoded.txt file we can notice that it starts from: bplist00. So it seems that it's binary plist file. We can run next command:
打开 decoding.txt 文件后,我们可以注意到它从 bplist00 开始。所以它似乎是二进制 plist 文件。我们可以运行下一个命令:

1
plutil -convert xml1 decoded.txt

And now in decoded.txt file we have XML with data which was prepared by Apple.
现在在 decoding.txt 文件中,我们拥有由 Apple 准备的 XML 数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>si</key>
<array>
<dict>
<key>a</key>
<real>-0.34275283875350282</real>
<key>i</key>
<integer>0</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>270.9334057827345</real>
</dict>
<dict>
<key>a</key>
<real>-10.239758644725045</real>
<key>i</key>
<integer>1</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>81.775887144809985</real>
</dict>
<dict>
<key>a</key>
<real>-4.2477344080754564</real>
<key>i</key>
<integer>2</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>86.33545030477751</real>
</dict>
<dict>
<key>a</key>
<real>1.3890866331008431</real>
<key>i</key>
<integer>3</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>90.812670374961954</real>
</dict>
<dict>
<key>a</key>
<real>7.167168970526129</real>
<key>i</key>
<integer>4</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>95.307409588765893</real>
</dict>
<dict>
<key>a</key>
<real>13.08619419164163</real>
<key>i</key>
<integer>5</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>99.920629632689383</real>
</dict>
<dict>
<key>a</key>
<real>40.415639464904281</real>
<key>i</key>
<integer>6</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>129.18652208191958</real>
</dict>
<dict>
<key>a</key>
<real>53.433472661727741</real>
<key>i</key>
<integer>7</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>182.23309425497911</real>
</dict>
<dict>
<key>a</key>
<real>38.793128200638634</real>
<key>i</key>
<integer>8</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>233.55159195809591</real>
</dict>
<dict>
<key>a</key>
<real>11.089423171265878</real>
<key>i</key>
<integer>9</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>261.87159046576664</real>
</dict>
<dict>
<key>a</key>
<real>5.1845753236736245</real>
<key>i</key>
<integer>10</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>266.44327370710511</real>
</dict>
<dict>
<key>a</key>
<real>-6.2483093741227886</real>
<key>i</key>
<integer>11</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>275.44204536695247</real>
</dict>
<dict>
<key>a</key>
<real>-12.20770735214888</real>
<key>i</key>
<integer>12</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>280.07031589401174</real>
</dict>
<dict>
<key>a</key>
<real>-39.48933951993012</real>
<key>i</key>
<integer>13</integer>
<key>o</key>
<integer>0</integer>
<key>z</key>
<real>309.41857318745144</real>
</dict>
<dict>
<key>a</key>
<real>-52.753181378799347</real>
<key>i</key>
<integer>14</integer>
<key>o</key>
<integer>0</integer>
<key>z</key>
<real>2.1750965538675473</real>
</dict>
<dict>
<key>a</key>
<real>-38.04743388682423</real>
<key>i</key>
<integer>15</integer>
<key>o</key>
<integer>0</integer>
<key>z</key>
<real>53.509085812513092</real>
</dict>
</array>
</dict>
</plist>

We have 16 dict elements (one for each image) with four keys:
我们有 16 个 dict 元素(每个图像一个),有四个键:

  • a - (probably) some time marker
    a - (可能)一些时间标记
  • i - image index
    i - 图像索引
  • o - ? (in current Mojave wallpaper it is always equal 0)
    o - ?(在当前的 Mojave 壁纸中它始终等于 0)
  • z - some time marker
    z - 一些时间标记

Unfortunately I don't know yet how to convert above float numbers to time markers. If you have some ideas please let me know. Then I will have all mandatory pieces to build macOS application which based on chosen files will generate dynamic wallpaper.
不幸的是我还不知道如何将浮点数转换为时间标记。如果您有任何想法,请告诉我。然后我将有所有强制性的部分来构建基于所选文件的 macOS 应用程序将生成动态壁纸。

For now I can build only wallpaper which will behave exactly the same way like Apple's wallpaper.
现在我只能制作壁纸,其行为与 Apple 的壁纸完全相同。


In my previous article I described how dynamic wallpapers works. I didn't know then what some of the properties in metadata means. I asked if somebody else knows what that properties means, and I've got a response really quickly. On Twitter @zwaldowski wrote to me explanation what all properties stands for.
在我的上一篇文章中,我描述了动态壁纸的工作原理。我不知道元数据中的某些属性意味着什么。我问其他人是否知道这些属性意味着什么,而且我很快得到了答复。在 Twitter 上 @zwaldowski 给我写了解释所有属性的含义。

Thus, as I wrote previously, in HEIC file we have metadata which looks like on below snippet.
因此,正如我之前所写,在 HEIC 文件中,我们有一些元数据,如下面的代码片段所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>si</key>
<array>
<dict>
<key>a</key>
<real>-0.34275283875350282</real>
<key>i</key>
<integer>0</integer>
<key>o</key>
<integer>1</integer>
<key>z</key>
<real>270.9334057827345</real>
</dict>

...

</array>
</dict>

We have 16 dict elements (one for each image) with four keys:
我们有 16 个 dict 元素(每个图像一个),有四个键:

  • a - altitude
    a - 海拔高度
  • i - image index
    i - 图像索引
  • o - indicates in which desktop theme image should be displayed. 0 - displays in both mode (light/dark). 1 - displays only in light mode.
    o - 表示应显示哪个桌面主题图像。0 - 以两种模式(亮 / 暗)显示。1 - 仅在灯光模式下显示。
  • z - azimuth
    z - 方位角

Thanks to altitude and azimuth w exactly know where the Sun was when image was taken.
感谢海拔高度和方位角,可以确切知道拍摄图像时太阳的位置。

This idea is brilliant, because thanks to this information macOS can change images differently during Summer and during Winter. System knows where the Sun is and it will choose image which was taken in similar conditions. Brilliant.
这个想法很棒,因为由于这些信息,macOS 可以在夏季和冬季期间以不同方式更改图像。系统知道太阳在哪里,它将选择在类似条件下拍摄的图像。太机智了


Based on that knowledge I prepared new dynamic wallpaper. Here is the video how it looks like:
基于这些知识,我准备了新的动态壁纸。以下是视频:
https://www.youtube.com/watch?v=TVqfPzdsbzY

You can download that wallpaper from my Dropbox:
你可以从我的 Dropbox 下载这个壁纸:
https://www.dropbox.com/s/kd2g59qswchsd0v/Earth%20View.heic?dl=0

Thank you for your help and feedback!
感谢您的帮助和反馈!


Update: I created simple console application for macOS which can help you with creating custom dynamic wallpapers: https://github.com/mczachurski/wallpapper
更新:我为 macOS 创建了简单的控制台应用程序,它可以帮助您创建自定义动态壁纸:https://github.com/mczachurski/wallpapper

本文翻译自:
https://itnext.io/macos-mojave-dynamic-wallpaper-fd26b0698223
https://itnext.io/macos-mojave-dynamic-wallpapers-ii-f8b1e55c82f

🍭支持一根棒棒糖!
张书樵 微信

微信

张书樵 支付宝

支付宝