Skip to content

Commit f5f5e9d

Browse files
authored
Merge pull request #356 from python-adaptive/transparent-logo
WIP: Add transparent logo (for dark mode)
2 parents 353b31e + 79df0ce commit f5f5e9d

File tree

3 files changed

+114
-23
lines changed

3 files changed

+114
-23
lines changed

docs/environment.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ dependencies:
1717
- myst-nb=0.16.0
1818
- sphinx_fontawesome=0.0.6
1919
- sphinx=4.2.0
20-
- ffmpeg=4.3.2
20+
- ffmpeg=5.1.1
2121
- cloudpickle
2222
- loky
2323
- furo

docs/source/algorithms_and_examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def get_hm(loss_per_interval, N=101):
111111
plots = {n: plot(learner, n) for n in range(N)}
112112
return hv.HoloMap(plots, kdims=["npoints"])
113113
114-
plot_homo = get_hm(uniform_loss).relabel("homogeneous samping")
114+
plot_homo = get_hm(uniform_loss).relabel("homogeneous sampling")
115115
plot_adaptive = get_hm(default_loss).relabel("with adaptive")
116116
layout = plot_homo + plot_adaptive
117117
layout.opts(plot=dict(toolbar=None))

docs/source/logo.md

Lines changed: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
---
2-
kernelspec:
3-
name: python3
4-
display_name: python3
52
jupytext:
63
text_representation:
74
extension: .md
85
format_name: myst
9-
format_version: '0.13'
10-
jupytext_version: 1.13.8
6+
format_version: 0.13
7+
jupytext_version: 1.14.1
8+
kernelspec:
9+
display_name: Python 3 (ipykernel)
10+
language: python
11+
name: python3
1112
---
1213

1314
```{code-cell} ipython3
1415
:tags: [remove-input]
1516
1617
import os
18+
import functools
19+
import subprocess
20+
import tempfile
21+
from pathlib import Path
1722
1823
import matplotlib.tri as mtri
1924
import numpy as np
@@ -26,7 +31,8 @@ from tqdm.auto import tqdm
2631
import adaptive
2732
2833
29-
def add_rounded_corners(size, rad):
34+
@functools.lru_cache
35+
def make_cut(size, rad):
3036
# Make new images
3137
circle = Image.new("L", (rad * 2, rad * 2), color=1)
3238
draw = ImageDraw.Draw(circle)
@@ -41,7 +47,12 @@ def add_rounded_corners(size, rad):
4147
alpha.paste(circle.crop((rad, rad, rad * 2, rad * 2)), (w - rad, h - rad))
4248
4349
# To array
44-
cut = np.array(alpha)
50+
return np.array(alpha)
51+
52+
53+
@functools.lru_cache
54+
def add_rounded_corners(size=(1000, 1000), rad=300):
55+
cut = make_cut(size, rad)
4556
cut = cut.reshape((*cut.shape, 1)).repeat(4, axis=2)
4657
4758
# Set the corners to (252, 252, 252, 255) to match the RTD background #FCFCFC
@@ -50,6 +61,16 @@ def add_rounded_corners(size, rad):
5061
return cut
5162
5263
64+
def remove_rounded_corners(fname):
65+
im = Image.open(fname)
66+
ar = np.array(im)
67+
cut = make_cut(size=ar.shape[:-1], rad=round(ar.shape[0] * 0.3)).astype(bool)
68+
ar[:, :, -1] = np.where(~cut, ar[:, :, -1], 0)
69+
im_new = Image.fromarray(ar)
70+
im_new.save(fname)
71+
return im_new
72+
73+
5374
def learner_till(till, learner, data):
5475
new_learner = adaptive.Learner2D(None, bounds=learner.bounds)
5576
new_learner.data = {k: v for k, v in data[:till]}
@@ -61,7 +82,7 @@ def learner_till(till, learner, data):
6182
6283
def plot_tri(learner, ax):
6384
tri = learner.ip().tri
64-
triang = mtri.Triangulation(*tri.points.T, triangles=tri.vertices)
85+
triang = mtri.Triangulation(*tri.points.T, triangles=tri.simplices)
6586
return ax.triplot(triang, c="k", lw=0.8, alpha=0.8)
6687
6788
@@ -70,10 +91,14 @@ def get_new_artists(npoints, learner, data, rounded_corners, ax):
7091
line1, line2 = plot_tri(new_learner, ax)
7192
data = np.rot90(new_learner.interpolated_on_grid()[-1])
7293
im = ax.imshow(data, extent=(-0.5, 0.5, -0.5, 0.5), cmap="viridis")
73-
im2 = ax.imshow(rounded_corners, extent=(-0.5, 0.5, -0.5, 0.5), zorder=10)
74-
return im, line1, line2, im2
94+
if rounded_corners is None:
95+
return im, line1, line2
96+
else:
97+
im2 = ax.imshow(rounded_corners, extent=(-0.5, 0.5, -0.5, 0.5), zorder=10)
98+
return im, line1, line2, im2
7599
76100
101+
@functools.lru_cache
77102
def create_and_run_learner():
78103
def ring(xy):
79104
import numpy as np
@@ -87,11 +112,7 @@ def create_and_run_learner():
87112
return learner
88113
89114
90-
def main(fname="source/_static/logo_docs.mp4"):
91-
learner = create_and_run_learner()
92-
93-
data = list(learner.data.items())
94-
115+
def get_figure():
95116
fig, ax = plt.subplots(figsize=(5, 5))
96117
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None)
97118
ax.set_xticks([])
@@ -100,29 +121,99 @@ def main(fname="source/_static/logo_docs.mp4"):
100121
ax.spines["right"].set_visible(False)
101122
ax.spines["bottom"].set_visible(False)
102123
ax.spines["left"].set_visible(False)
124+
return fig, ax
125+
126+
127+
def setup(nseconds=15):
128+
learner = create_and_run_learner()
129+
130+
data = list(learner.data.items())
131+
132+
fig, ax = get_figure()
103133
104-
nseconds = 15
105134
npoints = (len(data) * np.linspace(0, 1, 24 * nseconds) ** 2).astype(int)
106135
rounded_corners = add_rounded_corners(size=(1000, 1000), rad=300)
136+
return npoints, learner, data, rounded_corners, fig, ax
137+
138+
139+
def animate_mp4(fname="source/_static/logo_docs.mp4", nseconds=15):
140+
npoints, learner, data, rounded_corners, fig, ax = setup()
107141
artists = [
108142
get_new_artists(n, learner, data, rounded_corners, ax) for n in tqdm(npoints)
109143
]
110-
111144
ani = animation.ArtistAnimation(fig, artists, blit=True)
112145
ani.save(fname, writer=FFMpegWriter(fps=24))
113146
114147
148+
def animate_png(folder=None, nseconds=15):
149+
npoints, learner, data, rounded_corners, fig, ax = setup(nseconds)
150+
if folder is None:
151+
folder = Path(tempfile.gettempdir()) / next(tempfile._get_candidate_names())
152+
folder = Path(folder)
153+
folder.mkdir(parents=True, exist_ok=True)
154+
fnames = []
155+
ims = []
156+
for i, n in tqdm(enumerate(npoints), total=len(npoints)):
157+
fname = folder / f"logo_docs_{i:07d}.png"
158+
fnames.append(fname)
159+
npoints, learner, data, _, fig, ax = setup(nseconds)
160+
get_new_artists(n, learner, data, None, ax)
161+
fig.savefig(fname, transparent=True)
162+
ax.cla()
163+
plt.close(fig)
164+
im = remove_rounded_corners(fname)
165+
ims.append(im)
166+
return fnames, ims
167+
168+
169+
def save_webp(fname_webp, ims):
170+
(im, *_ims) = ims
171+
im.save(
172+
fname_webp,
173+
save_all=True,
174+
append_images=_ims,
175+
opimize=False,
176+
durarion=2,
177+
quality=70,
178+
)
179+
180+
181+
def save_webm(fname, fnames):
182+
args = [
183+
"ffmpeg",
184+
"-framerate",
185+
"24",
186+
"-f",
187+
"image2",
188+
"-i",
189+
str(fnames[0]).replace("0000000", "%07d"),
190+
"-c:v",
191+
"libvpx-vp9",
192+
"-pix_fmt",
193+
"yuva420p",
194+
"-crf",
195+
"23", # 0 is lossless 51 is worst
196+
"-y",
197+
fname,
198+
]
199+
return subprocess.run(args, capture_output=True)
200+
201+
115202
if __name__ == "__main__":
116-
fname = "_static/logo_docs.mp4"
117-
if not os.path.exists(fname):
118-
main(fname)
203+
fname_mp4 = Path("_static/logo_docs.mp4")
204+
# if not fname_mp4.exists():
205+
# animate_mp4(fname_mp4)
206+
fname_webm = fname_mp4.with_suffix(".webm")
207+
if not fname_webm.exists():
208+
fnames, ims = animate_png()
209+
save_webm(fname_webm, fnames)
119210
```
120211

121212
```{eval-rst}
122213
.. raw:: html
123214
124215
<video autoplay loop muted playsinline webkit-playsinline
125216
style="width: 400px; max-width: 100%; margin: 0 auto; display:block;">
126-
<source src="_static/logo_docs.mp4" type="video/mp4">
217+
<source src="_static/logo_docs.webm" type="video/mp4">
127218
</video><br>
128219
```

0 commit comments

Comments
 (0)