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

🍭支持一根棒棒糖!
0%