<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

<title>ConfuSomu / Twilight Sparkle</title>
<subtitle xml:lang="en">A world of wonders has yet to be discovered…</subtitle>
<link href="https://twilightsparkle.space/"/>
<link rel="self" href="https://twilightsparkle.space/feed.xml"/>
<generator version="1.0">Custom PHP script</generator>
<icon>/favicon.ico</icon>
<rights xml:lang="en">© 2026 by ConfuSomu is licensed under CC BY-NC 4.0</rights>
<author>
  <name>ConfuSomu</name>
  <uri>https://twilightsparkle.space/about/</uri>
</author>
<id>tag:twilightsparkle.space,2022-05-05:/ConfuSomu</id> <updated>2025-10-16T10:00:00-04:00</updated>
<entry xml:lang='fr'>
<title>Ce blog a désormais un flux Web</title><link href="https://twilightsparkle.space/blog/?p=newlysyndicated&amp;l=fr" rel="alternate" /><id>tag:twilightsparkle.space,2025-10-07:/blog/newlysyndicated/ConfuSomu/fr</id><published>2025-10-07T15:30:00-04:00</published><updated>2025-10-07T15:30:00-04:00</updated><summary>Vous pouvez facilement rester à jour avec mon site web</summary>
<author><name>ConfuSomu</name><uri>https://twilightsparkle.space/about/</uri></author><content type="html">&lt;article class=&quot;h-entry&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;&lt;p&gt;La fin de semaine dernière, j&apos;ai implanté un flux Atom sur mon site web. &lt;a href=&quot;https://fr.wikipedia.org/wiki/Atom_Syndication_Format&quot;&gt;Atom&lt;/a&gt; est une norme similaire à &lt;a href=&quot;https://fr.wikipedia.org/wiki/RSS&quot;&gt;RSS&lt;/a&gt; qui permet la &lt;a href=&quot;https://fr.wikipedia.org/wiki/Syndication_de_contenu&quot;&gt;syndication de contenu&lt;/a&gt;. En d&apos;autre mots, elle permet de vérifier automatiquement les mises à jour présentes sur un site web sans devoir s&apos;y rendre manuellement. Pour le moment, mon flux Atom contient seulement les billets de blogue, mais il devrait être facilement extensible pour prendre en charge d&apos;autre parties de mon site qui vont utiliser mon moteur de rendu de blog.&lt;/p&gt;&lt;p&gt;&#xA;&lt;/p&gt;&lt;p&gt;&#xA;Si vous voulez vous abonner à mon site, n&apos;hésitez pas à ajouter &lt;a href=&quot;https://twilightsparkle.space/feed.xml&quot;&gt;ce lien&lt;/a&gt; à votre &lt;a href=&quot;https://fr.wikipedia.org/wiki/Agr%C3%A9gateur&quot;&gt;agr&lt;/a&gt;&lt;a href=&quot;https://fr.wikipedia.org/wiki/Agr%C3%A9gateur&quot;&gt;é&lt;/a&gt;&lt;a href=&quot;https://fr.wikipedia.org/wiki/Agr%C3%A9gateur&quot;&gt;gateur&lt;/a&gt; de flux préféré. Le flux est aussi présenté dans les endroits attendus par ce genre de logiciel, donc vous pouvez aussi coller le nom de domaine et le logiciel va trouver le flux automatiquement. Si vous n&apos;avez pas de lecteur de flux, voici une petite liste pour débuter vos recherches : &lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt;, &lt;a href=&quot;https://miniflux.app/&quot;&gt;miniflux&lt;/a&gt;, votre logiciel de messagerie (&lt;a href=&quot;https://www.thunderbird.net&quot;&gt;Thunderbird&lt;/a&gt;, GNOME &lt;a href=&quot;https://gitlab.gnome.org/GNOME/evolution/-/wikis/home&quot;&gt;Evolution&lt;/a&gt;), &lt;a href=&quot;https://apps.kde.org/akregator/&quot;&gt;Akregator&lt;/a&gt;, &lt;a href=&quot;https://apps.gnome.org/NewsFlash/&quot;&gt;NewsFlash&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&#xA;&lt;/p&gt;&lt;p&gt;&#xA;Le flux Atom est généré par du PHP écrit à la main, comme le reste du site. Ce n&apos;est pas le meilleur PHP, mais ça fonctionne et le flux Atom 1.0 est &lt;a href=&quot;https://validator.w3.org/feed/check.cgi?url=https%3A//twilightsparkle.space/feed.xml&quot;&gt;certifi&lt;/a&gt;&lt;a href=&quot;https://validator.w3.org/feed/check.cgi?url=https%3A//twilightsparkle.space/feed.xml&quot;&gt;é&lt;/a&gt;&lt;a href=&quot;https://validator.w3.org/feed/check.cgi?url=https%3A//twilightsparkle.space/feed.xml&quot;&gt; valide&lt;/a&gt; ! Le flux est construit manuellement dans un panneau d&apos;administration qui restera secret. Il me permet aussi de vérifier que tout a été fait correctement et qu&apos;il n&apos;y a pas d&apos;erreurs (même si j&apos;échappe tout le texte correctement pour avoir un fichier XML valide). À moment donné, je vais mettre une &lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/257/systemd.timer.html&quot;&gt;minuterie systemd&lt;/a&gt; en place pour mettre le flux à jour automatiquement. Cette mise à jour sera faite plusieurs fois par jour parce que mon moteur de blogue (fait à la main) prend en charge la programmation de billets.&lt;/p&gt;&lt;p&gt;&#xA;&lt;/p&gt;&lt;p&gt;&#xA;En tout cas, attendez-vous à un billet de blogue plus long cette semaine. À plus !&lt;/p&gt;&#xA;&lt;/div&gt;&lt;/article&gt;</content></entry>
<entry xml:lang='en'>
<title>This blog now has a web feed</title><link href="https://twilightsparkle.space/blog/?p=newlysyndicated&amp;l=en" rel="alternate" /><id>tag:twilightsparkle.space,2025-10-07:/blog/newlysyndicated/ConfuSomu/en</id><published>2025-10-07T15:30:00-04:00</published><updated>2025-10-07T15:30:00-04:00</updated><summary>You can now easily stay up to date with my website</summary>
<author><name>ConfuSomu</name><uri>https://twilightsparkle.space/about/</uri></author><content type="html">&lt;article class=&quot;h-entry&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;&lt;p&gt;Last weekend I have implemented an Atom feed for my website. &lt;a href=&quot;https://en.wikipedia.org/wiki/Atom_(web_standard)&quot;&gt;Atom&lt;/a&gt; is a standard similar to &lt;a href=&quot;https://en.wikipedia.org/wiki/RSS&quot;&gt;RSS&lt;/a&gt; that allows users to check for updates on a website without having to manually visit it, which is called &lt;a href=&quot;https://en.wikipedia.org/wiki/Web_syndication&quot;&gt;web syndication&lt;/a&gt;. For the moment, it only processes blog posts, but it should be easily extensible to work with other parts of the website that will use my custom blogging engine/renderer.&lt;/p&gt;&lt;p&gt;&#xA;&lt;/p&gt;&lt;p&gt;&#xA;If you want to subscribe to the Atom feed, add &lt;a href=&quot;https://twilightsparkle.space/feed.xml&quot;&gt;this link&lt;/a&gt; to your favourite feed reader. The feed is also exposed in the expected locations, so you can alternatively just paste the domain name into your feed reader and it will find the feed and know what to do with it. If you don&apos;t have any feed reader, here is a small list of ones to start your search: &lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt;, &lt;a href=&quot;https://miniflux.app/&quot;&gt;miniflux&lt;/a&gt;, your e-mail client (&lt;a href=&quot;https://www.thunderbird.net&quot;&gt;Thunderbird&lt;/a&gt;, GNOME &lt;a href=&quot;https://gitlab.gnome.org/GNOME/evolution/-/wikis/home&quot;&gt;Evolution&lt;/a&gt;, etc.), &lt;a href=&quot;https://apps.kde.org/akregator/&quot;&gt;Akregator&lt;/a&gt;, &lt;a href=&quot;https://apps.gnome.org/NewsFlash/&quot;&gt;NewsFlash&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&#xA;&lt;/p&gt;&lt;p&gt;&#xA;The Atom feed generator is hand-written PHP like the rest of this website. It&apos;s not the best PHP, but it works and gives a &lt;a href=&quot;https://validator.w3.org/feed/check.cgi?url=https%3A//twilightsparkle.space/feed.xml&quot;&gt;certified valid&lt;/a&gt; Atom 1.0 feed! The feed is manually generated in an admin panel that shall stay secret, which allows me to manually verify that everything was generated properly (not that it shouldn&apos;t be, as I correctly escape all text to ensure well-formed XML). I will eventually set up a &lt;a href=&quot;https://www.freedesktop.org/software/systemd/man/257/systemd.timer.html&quot;&gt;systemd timer&lt;/a&gt; to automatically generate the feed multiple times per day to work with programmed posts (yes, my blog engine supports that).&lt;/p&gt;&lt;p&gt;&#xA;&lt;/p&gt;&lt;p&gt;&#xA;Anyways, stay tuned for a bigger blog post later this week. See you then!&lt;/p&gt;&#xA;&lt;/div&gt;&lt;/article&gt;</content></entry>
<entry xml:lang='fr'>
<title>Article de test</title><link href="https://twilightsparkle.space/blog/?p=test1&amp;l=fr" rel="alternate" /><id>tag:twilightsparkle.space,2024-09-14:/blog/test1/ConfuSomu/fr</id><published>2024-09-14T00:00:00-04:00</published><updated>2024-09-14T00:00:00-04:00</updated><summary>Ceci est un article pour voir si ma platforme de blogue fonctionne!</summary>
<author><name>ConfuSomu</name><uri>https://twilightsparkle.space/about/</uri></author><content type="html">&lt;article class=&quot;h-entry&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;&lt;h2&gt;Bienvenue&lt;/h2&gt;&#xA;&lt;p&gt;Ceci est un petit article qui présente mon blogue à tout le monde. Le blogue que vous lisez est écrit à la main en PHP! Certes, il est peut-être un peu basique, surtout en cas d&apos;erreur, mais comme Applejack dit : ça fait un travail honnête. J&apos;ai entièrement implementé ce blogue en me connectant à mon serveur par SSH et en modifiant les fichiers PHP dans Vim.&lt;/p&gt;&#xA;&lt;p&gt;Comme vous pouvez voir, ce blogue est &lt;i lang=en&gt;bilingual&lt;/i&gt; (ou bilingue). Cela dépend de l&apos;en-tête &lt;code&gt;Accept-Language&lt;/code&gt; envoyé par votre furteur web. Vous pouvez aussi écraser la langue choisie avec le paramètre &lt;code&gt;l=[langue ici]&lt;/code&gt; dans l&apos;URL, où &quot;[langue ici]&quot; est soit &quot;fr&quot; ou &quot;en&quot;.&lt;/p&gt;&#xA;&lt;p lang=oc&gt;Amusatz ! 😉 &lt;span lang=fr class=small&gt;(c&apos;est de l&apos;occitan)&lt;/span&gt;&lt;/p&gt;&#xA;&lt;/div&gt;&lt;/article&gt;</content></entry>
<entry xml:lang='en'>
<title>Test Article</title><link href="https://twilightsparkle.space/blog/?p=test1&amp;l=en" rel="alternate" /><id>tag:twilightsparkle.space,2024-09-14:/blog/test1/ConfuSomu/en</id><published>2024-09-14T00:00:00-04:00</published><updated>2024-09-14T00:00:00-04:00</updated><summary>This is an article for testing if my blog platform works.</summary>
<author><name>ConfuSomu</name><uri>https://twilightsparkle.space/about/</uri></author><content type="html">&lt;article class=&quot;h-entry&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;&lt;h2&gt;The Welcome&lt;/h2&gt;&#xA;&lt;p&gt;This is a little article that introduces my blog to everypony. The blog you are currently looking at is written in hoof-made PHP, locally grown in Equestria! It might be a little bare-bones, perhaps on the error checking side of things, but it does—as Applejack nicely puts it—&lt;i&gt;hard, honest work&lt;/i&gt;. I wrote all of it by SSHing into my server and editing the PHP files there.&lt;/p&gt;&#xA;&lt;p&gt;Also, this blog is &lt;i lang=fr&gt;bilingue&lt;/i&gt;, depending on the &lt;code&gt;Accept-Language&lt;/code&gt; header that is sent by your browser. You can also overwrite the language with the &lt;code&gt;l=[some language]&lt;/code&gt; query parameter, where &quot;[some language]&quot; is either &quot;fr&quot; or &quot;en&quot;. &lt;span lang=fr&gt;Amusez-vous !&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p&gt;Check out the &lt;a href=&quot;?p=test1&amp;l=fr&quot;&gt;french version&lt;/a&gt;! I will eventually add more languages to the mix, at least for the UI…&lt;/p&gt;&#xA;&lt;/div&gt;&lt;/article&gt;</content></entry>
<entry xml:lang='fr'>
<title>Génération d'un bouton 88×31</title><link href="https://twilightsparkle.space/blog/?p=web-button&amp;l=fr" rel="alternate" /><id>tag:twilightsparkle.space,2025-10-16:/blog/web-button/ConfuSomu/fr</id><published>2025-10-16T10:00:00-04:00</published><updated>2025-10-16T10:00:00-04:00</updated><summary>Création d'un bouton 88×31 à l'aide d'ffmpeg et de fichiers Make</summary>
<author><name>ConfuSomu</name><uri>https://twilightsparkle.space/about/</uri></author><contributor><name>Izzy (relecteur·ice)</name><uri>https://izzy.horse</uri></contributor><contributor><name>fox (relecteur·ice)</name><uri>https://cadnomori.neocities.org</uri></contributor><content type="html">&lt;article class=&quot;h-entry&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;&lt;!-- TODO: add alt text everywhere --&gt;&#xA;&lt;p&gt;Depuis la création de mon site web, il y a quelques années, j&apos;ai été inspiré·e par les boutons 88×31 faits par les autres. J&apos;avais toujours envie d&apos;en créer un. Finalement, il y a quelques semaines&lt;a href=&quot;#fn1&quot; id=&quot;fn1-back&quot; class=&quot;footnote&quot;&gt;¹&lt;/a&gt;, après mainte procrastination, j&apos;ai trouvé que trop c&apos;est trop et je me suis mis·e à l&apos;ouvrage.&lt;/p&gt;&#xA;&lt;p&gt;Pour créer un bouton 88×31, on se sert généralement d&apos;un logiciel d&apos;édition de &lt;i lang=en&gt;pixel art&lt;/i&gt; &lt;span class=small&gt;(ou «&amp;#8201;art du pixel&amp;#8201;», selon l&apos;&lt;a href=&quot;https://vitrinelinguistique.oqlf.gouv.qc.ca/fiche-gdt/fiche/26543994/art-du-pixel&quot;&gt;OQLF&lt;/a&gt;&lt;span class=&quot;emoticon&quot; aria-label=&quot;Smiley d&apos;un visage qui tire sa langue&quot;&gt;:P&lt;/span&gt;)&lt;/span&gt;. Dans le passé, j&apos;en ai utilisé quelques uns, mais aucun d&apos;entre eux n&apos;a vraiment captivé mon attention, sûrement parce que je ne suis pas vraiment dans la création d&apos;art du pixel (même si j&apos;aime le style). À cause de cela, je remettais mon bouton à plus tard pendant bien trop longtemps. Mais il fallait le faire un jour, donc, j&apos;ai choisi un logiciel avec lequel j&apos;avais plus d&apos;expérience et qui peux faire du &lt;i lang=en&gt;pixel art&lt;/i&gt;. Je me suis déjà servi·e de &lt;a href=&quot;https://gimp.org/&quot;&gt;&lt;abbr title=&quot;GNU Image Manipulation Program&quot;&gt;GIMP&lt;/abbr&gt;&lt;/a&gt; dans le passé pour créer &lt;a href=&quot;//git.twilightsparkle.space/ActorViewer/tree/res/icons&quot;&gt;des icônes&lt;/a&gt; et des &lt;a href=&quot;//wiki.twilightsparkle.space/doku.php?id=ideas:visual_novel&quot;&gt;exemples&lt;/a&gt; d&apos;interfaces utilisateurs au pixel près, donc je savais à quoi m&apos;attendre avec cet outil. Avec un logiciel pour faire du &lt;span lang=en&gt;pixel art&lt;/span&gt;, nous pouvons finalement débuter. Place à la création artistique&amp;#8201;!&lt;/p&gt;&#xA;&lt;p&gt;J&apos;avais en tête l&apos;idée de créer un bouton 88×31 statique, sans aucune animation. Cela m&apos;aurais permis de le finir rapidement. Mais, tout au long de mes premières esquisses, je commençais à être inspiré·e et je voyais l&apos;intérêt de le rendre animé, pour y mettre plus de variété. Le programme &lt;abbr title=&quot;GNU Image Manipulation Program&quot;&gt;GIMP&lt;/abbr&gt; a une fonction pour exporter l&apos;image en tant qu&apos;animation GIF, en utilisant chaque calque du projet comme image de l&apos;animation, donc je pensais me servir de cette fonction. Par contre, si un utilisateur se sert de la fonction groupement de calques, il faut les combiner manuellement, ce qui devient fastidieux rapidement, donc je n&apos;étais pas sûr·e à 100% si l&apos;animation serait le meilleur choix. Il y a des scripts pour rendre le processus plus rapide mais c&apos;est plus compliqué à installer. En explorant les fonctions artistiques du logiciel, je suis tombé·e sur le filtre «&amp;#8201;Onduler&amp;#8201;» qui m&apos;a entièrement converti·e. Là, j&apos;étais sûr·e de vouloir faire une animation avec cet effet trop cool. Je pouvais plus y résister&amp;#8201;!&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&quot;articles/web-button/ondulate.png&quot; alt=&quot;Capture d&apos;écran de GIMP montrant une fenêtre avec l&apos;effet Onduler qui a des paramères pour l&apos;amplitude, la période, le décalage de phase et l&apos;angle.&quot;&gt;&lt;figcaption&gt;La fenêtre du filtre Onduler dans &lt;abbr&gt;GIMP&lt;/abbr&gt; 3&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;p&gt;Cependant, il fallait que je change l&apos;organisation de mon projet. Les calques dans &lt;abbr&gt;GIMP&lt;/abbr&gt; ne me permettent pas d&apos;animer un filtre parce qu&apos;il modifie l&apos;image originale. De plus, &lt;abbr&gt;GIMP&lt;/abbr&gt; est un éditeur d&apos;image, pas de vidéo, donc il n&apos;a pas de ligne de temps qui permettraient de transformer l&apos;image sur une période donnée et de faire une animation. Ce n&apos;est pas le bon outil pour la &lt;span lang=en&gt;job&lt;/span&gt;. Pendant un moment, j&apos;ai pensé utiliser &lt;a href=&quot;https://kdenlive.org/&quot;&gt;Kdenlive&lt;/a&gt;, mais même en détournant l&apos;outil, je pense qu&apos;il n&apos;aurait pas fait un super travail avec du &lt;span lang=en&gt;pixel art&lt;/span&gt;. C&apos;est ainsi qu&apos;il faut passer du côté artistique au côté informatique. On n&apos;a pas le choix.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;div&gt;&lt;img src=&quot;articles/web-button/layers1.png&quot; alt=&quot;Capture d&apos;écran de GIMP montrant le panneau avec les calques. On voit des calques nommées &apos;frame 1&apos;, &apos;frame 2&apos;, etc. qui ont des sous calques avec du texte ou une partie de l&apos;image.&quot;&gt;&lt;img src=&quot;articles/web-button/layers2.png&quot; alt=&quot;Capture d&apos;écran avec d&apos;autres calques: on voit les calques composant chaque groupement de calque.&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;Quelques calques composant mon bouton. Notez l&apos;usage de groupes de calques.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;h2 id=&quot;gegl&quot;&gt;Un GEGL peu connu&lt;/h2&gt;&#xA;&lt;p&gt;Il devait avoir un moyen d&apos;appeler ces filtres en dehors de &lt;abbr&gt;GIMP&lt;/abbr&gt;, n&apos;est-ce pas&amp;#8201;? C&apos;est un programme GNU et c&apos;est du logiciel libre&amp;#8201;; ça doit être compatible avec d&apos;autres logiciels. J&apos;ai découvert que &lt;abbr&gt;GIMP&lt;/abbr&gt; a un mode batch, qui permet d&apos;exécuter des commandes sans interaction, et prend en charge le &lt;a href=&quot;https://docs.gimp.org/3.0/fr_CA/gimp-using-script-fu-tutorial.html&quot;&gt;scriptage&lt;/a&gt; avec des scripts écrits en Scheme (un dialecte du langage Lisp). J&apos;ai voulu faire un petit script Scheme qui prend une image d&apos;entrée, la transforme avec l&apos;effet Onduler et ressort une image finale. Malheureusement, je ne suis pas arrivé·e très loin… je n&apos;ai pas réussi à trouver l&apos;effet ni dans le navigateur de greffons ni dans le navigateur de &lt;a href=&quot;https://docs.gimp.org/3.0/fr_CA/plug-in-dbbrowser.html&quot;&gt;procédures&lt;/a&gt;. Mais il devait bien être quelque part. Où était cet effet&amp;#8201;? Est-ce qu&apos;on peut l&apos;appeler en dehors de &lt;abbr&gt;GIMP&lt;/abbr&gt;&amp;#8201;? Sinon, quel est le code qui compose son implémentation&amp;#8201;?&lt;/p&gt;&#xA;&lt;p&gt;Il fallait élucider ce mystère… naturellement, en regardant dans &lt;a href=&quot;https://developer.gimp.org/core/setup/git/&quot;&gt;le code source&lt;/a&gt; de &lt;abbr&gt;GIMP&lt;/abbr&gt;&amp;#8201;! Bon, téléchargeons le &lt;span class=en&gt;tarball&lt;/span&gt; et utilisons &lt;tt lang=en&gt;grep&lt;/tt&gt; pour en apprendre plus&amp;#8201;! La fonction &lt;span lang=en&gt;quickfixes&lt;/span&gt; de l&apos;éditeur texte Vim est bien pratique pour trouver des correspondances. En tout cas, le plus grand indice que j&apos;ai trouvé était que &quot;Onduler&quot; s&apos;accorde avec &quot;gegl:ripple&quot;. Il n&apos;y avait rien d&apos;autre dans l&apos;archive &quot;gimp&quot;. Par contre, que signifie &quot;gegl&quot;&amp;#8201;? En fait, les filtres de &lt;abbr&gt;GIMP&lt;/abbr&gt; sont implémentés ailleurs&amp;#8201;: dans la librairie &lt;span lang=en&gt;GEGL&lt;/span&gt; ou &lt;i lang=en&gt;Generic Graphics Library&lt;/i&gt;. Il fallait regarder là-bas&amp;#8201;! Après un peu plus de recherche, cette fois dans le dépôt de &quot;gegl&quot;, j&apos;ai trouvé la clef&amp;#8201;: l&apos;implémentation de notre effet se trouve dans le fichier &lt;a href=&quot;https://gitlab.gnome.org/GNOME/gegl/-/blob/2e1e4112d671ef60533be0a97aeaed67ad83badb/operations/common-gpl3+/ripple.c&quot;&gt;&lt;tt&gt;operations/common-gpl3+/ripple.c&lt;/tt&gt;&lt;/a&gt; Maintenant, qu&apos;allons-nous faire avec ça&amp;#8201;?&lt;/p&gt;&#xA;&lt;figure class=right&gt;&lt;figcaption&gt;Voici des horreurs sortant droit de mon terminal:&lt;/figcaption&gt;&lt;code style=&quot;font-size: small;&quot;&gt;$ ffmpeg -loop 1 -i To\ ondulate/username.png -t 5 -f lavfi -i nullsrc=s=88x31,lutrgb=128:128:128 -f lavfi -i nullsrc=s=88x31,geq=&apos;r=r(X+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*sin(0),Y+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*cos(0)):g=g(X+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*sin(0),Y+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*cos(0)):b=b(X+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*sin(0),Y+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*cos(0))&apos; -lavfi &apos;[0][1][2]displace&apos; test.gif&lt;/code&gt;&lt;p&gt;&lt;i&gt;(prenez en note que &lt;tt&gt;lavfi&lt;/tt&gt; est équivalant à &lt;tt&gt;filter_complex&lt;/tt&gt;)&lt;/i&gt;&lt;/p&gt;&lt;/figure&gt;&#xA;&lt;p&gt;Ne sachant rien de mieux, j&apos;ai décidé de recréer le filtre Onduler comme filtre &lt;tt lang=en&gt;geq&lt;/tt&gt; (équation graphique) dans l&apos;utilitaire ffmpeg. Pas de bol&amp;#8201;: le travail était bien plus important qu&apos;attendu et je n&apos;avançais pas. C&apos;était un cul-de-sac et &lt;tt lang=en&gt;geq&lt;/tt&gt; n&apos;est pas du tout approprié pour faire ça.&lt;/p&gt;&#xA;&lt;p&gt;Pourtant, j&apos;allais pas abandonner aussi rapidement&amp;#8201;: après plus de recherche, j&apos;ai appris que les effets gegl peuvent être exécutés &lt;a href=&quot;https://old.reddit.com/r/GIMP/comments/avp4kq/here_are_some_gegl_command_line_examples_for/&quot;&gt;directement dans le terminal&lt;/a&gt;. Finalement, nous nous approchions de quelque chose. Tout ce qu&apos;il fallait faire était de créer une petite boucle dans bash pour faire varier la période en fonction du temps&amp;#8201;: &lt;tt&gt;gegl -i To\ ondulate/username.png -o test.png -- gegl:ripple amplitude=6 period=1000 phi=0 angle=0 sampler-type=nearest wave-type=triangle abyss-policy=none tileable=no&lt;/tt&gt;&lt;/p&gt;&#xA;&lt;p&gt;Il restait toujours la question de comment combiner la séquence d&apos;images pour faire une animation.&lt;/p&gt;&#xA;&lt;h2 id=&quot;ffmpeg&quot;&gt;Dîtes bonjour à ffmpeg&lt;/h2&gt;&#xA;&lt;p&gt;Concaténer des images pour faire des vidéos, c&apos;est vraiment &lt;em&gt;le&lt;/em&gt; travail pour ffmpeg. Par contre, il faut bien que toutes les images d&apos;entrée aient les mêmes dimensions, ce qui n&apos;est pas le cas avec les images de sortie de gegl pour une raison qui me reste inconnue. Ainsi, je les centre avec &lt;a href=&quot;https://imagemagick.org/&quot; lang=en&gt;ImageMagick&lt;/a&gt; pour être sûr·e qu&apos;elles soient 88×31 pixels comme voulu. Aussi, il faut que la vidéo de sortie soit sans perte et elle doit conserver la transparence des fichiers d&apos;entrée. Donc, on copie les images en tant que PNG, en étant sûr·e que le ratio des pixels reste le même (parce qu&apos;aussi certaines images avaient un ratio différent, pour une raison que je n&apos;ai pas élucidé): &lt;tt&gt;ffmpeg -hide_banner -framerate &quot;$fps&quot; -i frame%04d.png -filter_complex &quot;[0:v:0]scale=88:31:force_original_aspect_ratio=disable,setsar=sar=1,setdar=dar=88/31[v]&quot; -map &quot;[v]&quot; -c:v png -pix_fmt rgba64be -y out.mkv&lt;/tt&gt; &lt;i&gt;(oui, réenconder un PNG en tant que PNG nécessite plus de temps système, mais ça reste tout de même une goutte d&apos;eau lors de la génération de l&apos;image finale)&lt;/i&gt;. Le script complet est disponible &lt;a href=&quot;//git.twilightsparkle.space/web-button/tree/ripple?h=v1&quot;&gt;ici&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;div&gt;&lt;img src=&quot;articles/web-button/ponies_frame.png&quot; style=&quot;width: 50%; image-rendering: pixelated;&quot; alt=&quot;Une image de mon bouton avec Twilight Sparkle et Pinkie Pie entouré par un drapeau non-binaire et un drapeau transgenre. L&apos;image est ondulée.&quot;&gt;&lt;video loop autoplay style=&quot;width: 50%; border-radius: unset; image-rendering: pixelated;&quot; alt=&quot;Cette même image mais l&apos;ondulation est animée.&quot;&gt;&lt;source src=&quot;articles/web-button/ponies-h264.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;source src=&quot;articles/web-button/ponies-av1.mkv&quot; type=&quot;video/av1&quot;&gt;&lt;source src=&quot;articles/web-button/ponies-png.mkv&quot; type=&quot;video/mkv&quot;&gt;&lt;/video&gt;&lt;/div&gt;&lt;figcaption&gt;Une des images de l&apos;effet Onduler et l&apos;animation finale. Notez la transparence présente sur l&apos;image (mais la vidéo n&apos;est pas transparente à cause du réencodage).&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;p&gt;La procédure ci-dessus applique l&apos;effet Onduler à une image et génère une vidéo, mais ce n&apos;est pas assez pour faire un bouton 88×31. Il faut enchaîner des images pour faire quelque chose de substantiel. Parce que l&apos;effet est seulement appliqué à l&apos;avant-plan, il nous faut aussi une image d&apos;arrière-plan. Pour faire plus simple et garder le tout lisible, j&apos;ai préféré un arrière-plan statique. ffmpeg nous donne les outils pour combiner des fichiers vidéos et arriver au résultat escompté. Il nous faut seulement un moyen d&apos;orchestrer le tout &lt;span class=small&gt;(mais pas avec Kubernetes!)&lt;/span&gt; et de faire plusieurs appels à ffmpeg avec différentes images pour générer programmatiquement un bouton. Il est trop tard maintenant, car sans nous apercevoir, nous sommes rendus trop profondément dans le gouffre de l&apos;ordinateur. On ne va pas en ressortir immédiatement…&lt;/p&gt;&#xA;&lt;h2 id=&quot;make&quot;&gt;Les fichiers Make&lt;/h2&gt;&#xA;&lt;p&gt;Les fichiers Make (ou &lt;span lang=en&gt;Makefiles&lt;/span&gt; en anglais) peuvent sembler compliqués et opaques, mais ils ne sont pas si complexes et difficiles à apprendre. Avant ce projet, j&apos;étais presque entièrement ignorant·e&amp;#8201;! Je ne vais pas trop aller en détails sur leur fonctionnement, mais je vous suggère fortement de lire ce superbe &lt;a href=&quot;https://makefiletutorial.com/&quot;&gt;tutoriel&lt;/a&gt; &lt;span class=small&gt;(en anglais)&lt;/span&gt;. En somme, ils fonctionne en suivant le principe de dépendances qui sont construites après leurs dépendants et ce suivant certaines règles prédéfinies. Cela rend le processus de compilation très efficace car seul un sous-ensemble de fichiers sont reconstruits, soit ceux qui ont été modifiés depuis la dernière compilation.&lt;/p&gt;&#xA;&lt;p&gt;J&apos;ai commencé avec une règle pour générer des ondulations&amp;#8201;:&lt;/p&gt;&#xA;&lt;pre&gt;%.ond.mkv: ondulate/%.png ripple&#xA;	./ripple &quot;$&amp;lt;&quot; 500 10 $(duree_ondulation) $(ips) $(duree_statique) &quot;build/$@&quot; 6&lt;/pre&gt;&#xA;&lt;p&gt;Cette règle fonctionne grâce aux &lt;span lang=en&gt;wildcards&lt;/span&gt; qui nous permettent d&apos;éviter de devoir coder en dur les noms de fichiers d&apos;entrée et de sortie. Le nom à gauche des deux-points est le nom de la cible (sortie) et ceux à droite sont les dépendances de la cible. Ces dépendances peuvent être des noms de fichiers ou d&apos;autres cibles (parce que les cibles génèrent en général des fichiers portant le même nom que la cible). Dans ce cas, nous cherchons des fichiers PNG définis par une autre cible présents dans le répertoire &lt;tt lang=en&gt;ondulate&lt;/tt&gt; et le script &lt;tt lang=en&gt;ripple&lt;/tt&gt; qui génère notre animation, expliqué dans la session précédente. Suivant les règles de &lt;span lang=en&gt;Make&lt;/span&gt;, la cible &lt;tt&gt;%.ond.mkv&lt;/tt&gt; sera seulement reconstruite si ses dépendances sont plus récentes que le fichier généré par la cible. En somme, toute modification aux fichiers sources ou du script &lt;tt lang=en&gt;ripple&lt;/tt&gt; entraînera la reconstruction de la vidéo d&apos;ondulation. Notez que &lt;tt&gt;$&amp;lt;&lt;/tt&gt; et &lt;tt&gt;$@&lt;/tt&gt; sont des &lt;a href=&quot;https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html&quot;&gt;variables automatiques&lt;/a&gt; qui sont remplacées par le nom de la première dépendance et par le nom de la cible. Les autres variables, du type &lt;tt&gt;$(nom_variable)&lt;/tt&gt;, ont étés manuellement déclarées.&lt;/p&gt;&#xA;&lt;p&gt;De la même manière, j&apos;ai crée d&apos;autres cibles &lt;span lang=en&gt;Make&lt;/span&gt; qui génèrent d&apos;autres vidéos d&apos;avant-plan&amp;#8201;: &lt;tt&gt;%.sta.mkv&lt;/tt&gt; pour des images statiques (sans ondulation), &lt;tt lang=en&gt;wallpaper.mkv&lt;/tt&gt; pour l&apos;image d&apos;arrière-plan et &lt;tt&gt;%.qr.mkv&lt;/tt&gt; pour des images statiques contenant un code QR (aussi généré dans le fichier &lt;span lang=en&gt;Make&lt;/span&gt;. Les définitions complètes sont disponibles &lt;a href=&quot;//git.twilightsparkle.space/web-button/tree/Makefile?h=v1&quot;&gt;ici&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&quot;articles/web-button/wallpaper.png&quot; style=&quot;image-rendering: pixelated; width: 60%;&quot; alt=&quot;L&apos;arrière-plan du bouton avec une plante aux feuilles jaunes et vertes (clair et foncé) sur un mur couleur crème.&quot;&gt;&lt;figcaption&gt;L&apos;image d&apos;arrière-plan, une photo que j&apos;ai prise d&apos;un &lt;i lang=la&gt;Euonymus fortunei&lt;/i&gt;.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;p&gt;Nous pouvons ensuite utiliser ces règles pour concaténer les animations&amp;#8201;:&lt;/p&gt;&#xA;&lt;pre&gt;concat.mkv: username.ond.mkv ponies.ond.mkv themes_en.sta.mkv themes_fr.sta.mkv multilingue.sta.mkv scannez.qr.mkv&#xA;	$(ffmpeg) $(addprefix -i ,$+) -filter_complex &quot;concat=n=$(nombre_banieres):v=1:a=0 [outv]&quot; -map &quot;[outv]&quot; -c:v png -pix_fmt rgba64be build/concat.mkv&lt;/pre&gt;&#xA;&lt;p&gt;La cible &lt;tt lang=en&gt;concat.mkv&lt;/tt&gt; dépend de plusieurs animations avant d&apos;appeler ffmpeg. &lt;tt&gt;$(addprefix -i ,$+)&lt;/tt&gt; ajoute «&amp;#8201;-i &amp;#8201;» comme préfixe au nom de chaque dépendance pour qu&apos;elles deviennent des fichiers d&apos;entrée. Le filtre &lt;tt lang=en&gt;concat&lt;/tt&gt; travaille implicitement sur les fichiers d&apos;entrée, dans le même ordre que ce qui a été spécifié sur la ligne de commande. Par contre, il faut quand même spécifier le nombre de flux vidéos à traiter.&lt;/p&gt;&#xA;&lt;p&gt;Finalement, on peux combiner le fichier &lt;tt lang=en&gt;concat.mkv&lt;/tt&gt; et l&apos;arrière-plan pour créer le bouton 88×31 final&amp;#8201;:&lt;/p&gt;&#xA;&lt;pre&gt;layers.mkv: concat.mkv wallpaper.mkv&#xA;	$(ffmpeg) -i build/wallpaper.mkv -i build/concat.mkv -filter_complex &quot;[0:v:0][1:v:0] overlay=format=rgb:eof_action=repeat&quot; -c:v png -framerate $(ips) build/layers.mkv&#xA;&#xA;build/button.apng: layers.mkv&#xA;	$(ffmpeg) -i build/layers.mkv -framerate $(ips) -plays 0 -final_delay 0.0 build/button.apng&lt;/pre&gt;&#xA;&lt;p&gt;Il faut être sûr que les vidéos soient superposées dans l&apos;espace couleur &lt;abbr title=&quot;couleur à trois valeurs des couleurs primaires : rouge, vert et bleu&quot;&gt;RVB&lt;/abbr&gt; à la place de l&apos;espace couleur &lt;abbr title=&quot;couleur à trois composantes : Y est la luma, et U et V sont la chrominance&quot;&gt;YUV&lt;/abbr&gt; pour éviter des dégradations dans les couleurs. Par exemple, les pixels violets ou rouge qui sont superposés sur les parties claires de l&apos;image perdent leur couleur comme s&apos;ils étaient «&amp;#8201;mangés&amp;#8201;» par l&apos;arrière-plan couleur crème. Aussi, nous ne voulons pas que l&apos;animation finisse prématurément lorsque l&apos;arrière-plan fini, donc on utilise l&apos;option &lt;tt&gt;eof_action=repeat&lt;/tt&gt;. En tout cas, après ces dernières difficultés, notre fichier &lt;span lang=en&gt;Make&lt;/span&gt; crée le bouton ci-dessous&amp;#8201;!&lt;/p&gt;&#xA;&lt;div class=&quot;project card&quot;&gt;&lt;div class=&quot;inline&quot;&gt;&#xA;  &lt;div class=&quot;buttons emphasis&quot;&gt;&#xA;    &lt;picture&gt;&#xA;      &lt;source srcset=&quot;/btn/button1.webp&quot; type=&quot;image/webp&quot;&gt;&#xA;      &lt;source srcset=&quot;/btn/button1.apng&quot; type=&quot;image/apng&quot;&gt;&#xA;      &lt;img alt=&quot;Website button showcasing what kinds of things you can find on my website. We first see &apos;ConfuSomu/Twilight Sparkle&apos;, followed by Pinkie Pie hugging Twilight Sparkle with a non-binary flag and a transgender flag in the background, followed by an image with the text &apos;Computers, nature, arts and way more!&apos; and then by a showcase of the multiligualism of the author and website through the use of country flags relating to different languages (French, English, Polish, German and Toki Pona). Finally, there is a QR Code that links to the website with the text &apos;Scan me&apos; next to it.&quot; src=&quot;/btn/button1.gif&quot;&gt;&#xA;    &lt;/picture&gt;&#xA;  &lt;/div&gt;&#xA;  &lt;p&gt;Feel free to put my 88x31 button on your website!&lt;br&gt;If you want to trade buttons, just send me a message and I&apos;ll add yours to my website.&lt;/p&gt;&#xA;  &lt;div class=&quot;links&quot;&gt;&#xA;    &lt;a href=&quot;https://git.twilightsparkle.space/web-button&quot;&gt;Source code&lt;/a&gt;&#xA;    &lt;details&gt;&#xA;      &lt;summary&gt;View HTML embed&lt;/summary&gt;&#xA;      &lt;div&gt;&#xA;        &lt;code style=&quot;line-break: anywhere; user-select: all;&quot;&gt;&#xA;        &amp;lt;a href=&quot;https://twilightsparkle.space/&quot;&amp;gt;&#xA;          &amp;lt;img alt=&quot;88x31 button presenting ConfuSomu&apos;s website&quot; style=&quot;image-rendering: pixelated;&quot; src=&quot;https://twilightsparkle.space/btn/button1.apng&quot;&amp;gt;&#xA;        &amp;lt;/a&amp;gt;&#xA;        &lt;/code&gt;&#xA;        &lt;p&gt;N.B: Note that you may copy the image onto your website. The image displayed above has longer &lt;code&gt;alt&lt;/code&gt; text than the one in this example HTML code.&lt;/p&gt;&#xA;      &lt;/div&gt;&#xA;    &lt;/details&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;Et c&apos;est tout&amp;#8201;! Avec ffmpeg et les fichiers &lt;span lang=en&gt;Make&lt;/span&gt;, nous pouvons générer des boutons 88×31. N&apos;hésitez pas à générer les vôtres avec le fichier &lt;span lang=en&gt;Make&lt;/span&gt; et le script partagé. C&apos;est pas si compliqué après tout et c&apos;est du &lt;span lang=en&gt;fun&lt;/span&gt;&amp;#8201;!&lt;/p&gt;&#xA;&lt;p&gt;Néanmoins, je n&apos;ai pas fini. Je vois des choses que je pourrais faire dans le futur&amp;#8201;: il faudrait pouvoir changer d&apos;image d&apos;arrière-plan et appliquer un effet de transition, comme l&apos;effet Onduler, un simple basculement ou un effet de fondu, à toute la surface du bouton, pas seulement l&apos;avant-plan. Il faudrait aussi ajouter plus d&apos;effets et peut-être aussi pouvoir inclure des vidéos dans le bouton. Lorsque je ferait une second version, j&apos;implanterai certains de ces effets.&lt;/p&gt;&#xA;&lt;p class=&quot;small&quot;&gt;PS: Et oui, je devrais traduire mon site web en plus de langues, pour respecter ce qui est écrit sur mon bouton &lt;span class=&quot;emoticon&quot; aria-label=&quot;Smiley d&apos;un visage qui tire sa langue&quot;&gt;:P&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p id=&quot;fn1&quot; class=&quot;footnote content&quot;&gt;¹: Malheureusement, j&apos;étais malade, donc ça fait plutôt un mois maintenant &lt;a href=&quot;#fn1-back&quot; class=&quot;footnote home&quot;&gt;↸&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;/div&gt;&lt;/article&gt;&lt;aside class=contributors&gt;&lt;h2&gt;Relecteur·ices&lt;/h2&gt;&lt;p&gt;Ces personnes ont relu mon article. Merci beaucoup à elles&amp;#8201;!&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&apos;https://izzy.horse&apos;&gt;Izzy&lt;/a&gt; (&lt;span lang=&apos;en&apos;&gt;English&lt;/span&gt;, &lt;span lang=&apos;fr&apos;&gt;Français&lt;/span&gt;)&lt;/li&gt;&lt;li&gt;&lt;a href=&apos;https://cadnomori.neocities.org&apos;&gt;fox&lt;/a&gt; (&lt;span lang=&apos;en&apos;&gt;English&lt;/span&gt;)&lt;/li&gt;&lt;/ol&gt;&lt;/aside&gt;</content></entry>
<entry xml:lang='en'>
<title>Generating a 88×31 button</title><link href="https://twilightsparkle.space/blog/?p=web-button&amp;l=en" rel="alternate" /><id>tag:twilightsparkle.space,2025-10-16:/blog/web-button/ConfuSomu/en</id><published>2025-10-16T10:00:00-04:00</published><updated>2025-10-16T10:00:00-04:00</updated><summary>Creation of a 88×31 button using ffmpeg and Makefiles</summary>
<author><name>ConfuSomu</name><uri>https://twilightsparkle.space/about/</uri></author><contributor><name>Izzy (proofreader)</name><uri>https://izzy.horse</uri></contributor><contributor><name>fox (proofreader)</name><uri>https://cadnomori.neocities.org</uri></contributor><content type="html">&lt;article class=&quot;h-entry&quot;&gt;&lt;div class=&quot;e-content&quot;&gt;&lt;!-- TODO: add alt text everywhere, make cgit repo public, validate button widget to make sure it&apos;s 100% fine --&gt;&#xA;&lt;p&gt;Since creating my website, about two years ago, I have been inspired by 88×31 buttons made by other people and always wanted to make one. Finally, a few weeks ago&lt;a href=&quot;#fn1&quot; id=&quot;fn1-back&quot; class=&quot;footnote&quot;&gt;¹&lt;/a&gt;, I decided that i should finally stop procrastinating and simply create one.&lt;/p&gt;&#xA;&lt;p&gt;To make a tiny 88×31 button, you generally make use of a pixel art editor. I had dabbled with few of them in the past, but I never spent enough time with them to find one that fits me, maybe because I am not very much into pixel art creation (even though i like the style). Due to this, I put off making a button to later, for a long time. But I had to create it someday, thus, I turned to tools with which I already had some experience which may help me create pixel art. I already used &lt;a href=&quot;https://gimp.org/&quot;&gt;&lt;abbr title=&quot;GNU Image Manipulation Program&quot;&gt;GIMP&lt;/abbr&gt;&lt;/a&gt; to &lt;a href=&quot;//git.twilightsparkle.space/ActorViewer/tree/res/icons&quot;&gt;make icons&lt;/a&gt; and pixel precise &lt;a href=&quot;//wiki.twilightsparkle.space/doku.php?id=ideas:visual_novel&quot;&gt;mock-ups&lt;/a&gt;, so i knew what to expect with that tool. Thus, we can finally start! yay!&lt;/p&gt;&#xA;&lt;p&gt;I was first thinking of creating a static 88×31 button… then slowly felt like adding more frames, and make it animated, to allow more variety to sweep in. &lt;abbr title=&quot;GNU Image Manipulation Program&quot;&gt;GIMP&lt;/abbr&gt; has a GIF exporter that allows you to export each layer as a separate frame, so it was only natural to consider it. But, if you have layer groups, they need to be manually combined which quickly becomes tedious (or you can use a script to do that, but it&apos;s more involved), so I was still on the fence. As I explored &lt;abbr&gt;GIMP&lt;/abbr&gt;&apos;s artistic options, I quickly became fully sold by the idea of making it animated after finding the ripple effect in &lt;abbr&gt;GIMP&lt;/abbr&gt;&apos;s filter library. Adding a ripple effect between pages of my button was too hard to resist!&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&quot;articles/web-button/ondulate.png&quot; alt=&quot;Screenshot of GIMP showing a dialog window with the ripple effect open which has options for amplitude, period, phase offset and angle.&quot;&gt;&lt;figcaption&gt;The ripple effect dialog in &lt;abbr&gt;GIMP&lt;/abbr&gt; 3 &lt;i&gt;(i prefer to have my computers in non-english)&lt;/i&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;p&gt;This brought up questions on how to organize my project: i couldn&apos;t continue with layer groups, as the animated ripple effect works on an existing image layer and modifies it. Furthermore, &lt;abbr&gt;GIMP&lt;/abbr&gt; is an image editor, it doesn&apos;t have a timeline, so I cannot transform an image over a period of time and have any kind of animation. It is not the right tool for the job for that kind of stuff. For a second, I had even considered using &lt;a href=&quot;https://kdenlive.org/&quot;&gt;Kdenlive&lt;/a&gt;, but, even by abusing the tool, I don&apos;t think that it would do a good job with pixel art. So, we necessarily have to go from the artsy side to the computer side.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;div&gt;&lt;img src=&quot;articles/web-button/layers1.png&quot; alt=&quot;Screenshot of GIMP showing the pane/window with layers. We see layer groups named &apos;frame 1&apos;, &apos;frame 2&apos;, etc. which have child layers containing text or parts of the images.&quot;&gt;&lt;img src=&quot;articles/web-button/layers2.png&quot; alt=&quot;Screenshot with other layers: we see the different layers that make up each layer group.&quot;&gt;&lt;/div&gt;&lt;figcaption&gt;Some of the layers present on my button, notice the use of layer groups&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;h2 id=&quot;gegl&quot;&gt;Little known GEGL&lt;/h2&gt;&#xA;&lt;p&gt;There had to be a way to call these filters from outside of &lt;abbr&gt;GIMP&lt;/abbr&gt;, right? It&apos;s GNU and open-source software; it has to be composable with other tools. I read about &lt;abbr&gt;GIMP&lt;/abbr&gt; having a batch mode for executing commands headless and &lt;a href=&quot;https://docs.gimp.org/3.0/en_CA/gimp-using-script-fu-tutorial.html&quot;&gt;scripting support&lt;/a&gt;, with scripts written in Scheme (a dialect of Lisp), and wanted to make a small script that would take an input image, transform it with the ripple effect and spit out an output image. Sadly, I didn&apos;t get very far… I wasn&apos;t able to find the ripple effect anywhere in the procedure and the plug-in &lt;a href=&quot;https://docs.gimp.org/3.0/en_CA/plug-in-dbbrowser.html&quot;&gt;browsers&lt;/a&gt;, but it must still be somewhere. Where was that effect? Can we call it outside of &lt;abbr&gt;GIMP&lt;/abbr&gt;? If not, what function does it apply on the pixels to generate these effects?&lt;/p&gt;&#xA;&lt;p&gt;I had to elucidate that mystery… naturally, by looking around in &lt;a href=&quot;https://developer.gimp.org/core/setup/git/&quot;&gt;the source code&lt;/a&gt; of &lt;abbr&gt;GIMP&lt;/abbr&gt;! Well, downloading the tarball and &lt;tt&gt;grep&lt;/tt&gt; it is then! Vim&apos;s quickfixes feature was handy in following found matches. Anyways, after figuring out to which string &quot;Onduler&quot; maps to, the most I found was &quot;gegl:ripple&quot;. There was nothing else of interest in the &quot;gimp&quot; tarball. What is this &quot;gegl&quot; thing? Apparently, filters in &lt;abbr&gt;GIMP&lt;/abbr&gt; are split into another library: the Generic Graphics Library, or GEGL. We had to look there! After doing a little more &lt;tt&gt;grep&lt;/tt&gt;ping, this time, in the &quot;gegl&quot; repository, I finally found it: the implementation of our effect is in the &lt;a href=&quot;https://gitlab.gnome.org/GNOME/gegl/-/blob/2e1e4112d671ef60533be0a97aeaed67ad83badb/operations/common-gpl3+/ripple.c&quot;&gt;&lt;tt&gt;operations/common-gpl3+/ripple.c&lt;/tt&gt;&lt;/a&gt; file! Now, what do we do with this?&lt;/p&gt;&#xA;&lt;figure class=right&gt;&lt;figcaption&gt;Look at these horrors from my terminal history:&lt;/figcaption&gt;&lt;code style=&quot;font-size: small;&quot;&gt;$ ffmpeg -loop 1 -i To\ ondulate/username.png -t 5 -f lavfi -i nullsrc=s=88x31,lutrgb=128:128:128 -f lavfi -i nullsrc=s=88x31,geq=&apos;r=r(X+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*sin(0),Y+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*cos(0)):g=g(X+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*sin(0),Y+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*cos(0)):b=b(X+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*sin(0),Y+(6*(abs((((((X*cos(0)-Y*sin(0))+(1000-T*2)*3/4)//(1000-T*2))/(1000-T*2))*4)-2)-1))*cos(0))&apos; -lavfi &apos;[0][1][2]displace&apos; test.gif&lt;/code&gt;&lt;p&gt;&lt;i&gt;(note that &lt;tt&gt;lavfi&lt;/tt&gt; is equivalent to &lt;tt&gt;filter_complex&lt;/tt&gt;)&lt;/i&gt;&lt;/p&gt;&lt;/figure&gt;&#xA;&lt;p&gt;Not knowing any better, I was remaking the ripple effect as a &lt;tt&gt;geq&lt;/tt&gt; filter in ffmpeg, but didn&apos;t have any luck: the labour was too unwieldy and i wasn&apos;t making much progress. It was a dead end and clearly not suited to my application.&lt;/p&gt;&#xA;&lt;p&gt;Nevertheless, i did not give up: after some more searching, I &lt;a href=&quot;https://old.reddit.com/r/GIMP/comments/avp4kq/here_are_some_gegl_command_line_examples_for/&quot;&gt;learned&lt;/a&gt; that gegl effects can be simply ran directly from the command line. Finally, we were starting to go somewhere, all what&apos;s needed is to make a small bash loop to vary the period over time: &lt;tt&gt;gegl -i To\ ondulate/username.png -o test.png -- gegl:ripple amplitude=6 period=1000 phi=0 angle=0 sampler-type=nearest wave-type=triangle abyss-policy=none tileable=no&lt;/tt&gt;&lt;/p&gt;&#xA;&lt;p&gt;We then have to figure out a way to collate the images together and make an animation that has multiple pages with different information. One wavy effect won&apos;t cut it…&lt;/p&gt;&#xA;&lt;h2 id=&quot;ffmpeg&quot;&gt;Behold ffmpeg&lt;/h2&gt;&#xA;&lt;p&gt;Collating images to make a video, that&apos;s &lt;em&gt;the&lt;/em&gt; work for ffmpeg! Though, when passing a sequence of image files (with &lt;tt&gt;-i frame%04d.png&lt;/tt&gt;), they all need to have the same dimensions and I noticed that this isn&apos;t the case with the output files of gegl for some reason. Thus, I centre them with &lt;a href=&quot;https://imagemagick.org/&quot;&gt;ImageMagick&lt;/a&gt;, making sure that they are all 88×31 as expected. Also, the output video has to be lossless and preserve transparency (i had some trouble with ImageMagick for a moment), so we copy the frames as PNG, making sure that the pixel aspect ratio and display aspect ratios have expected values (as some of the images managed to have different aspect ratios): &lt;tt&gt;ffmpeg -hide_banner -framerate &quot;$fps&quot; -i frame%04d.png -filter_complex &quot;[0:v:0]scale=88:31:force_original_aspect_ratio=disable,setsar=sar=1,setdar=dar=88/31[v]&quot; -map &quot;[v]&quot; -c:v png -pix_fmt rgba64be -y out.mkv&lt;/tt&gt; &lt;i&gt;(yes, re-encoding a PNG as a PNG has overhead, but it&apos;s completely negligible in the whole build process)&lt;/i&gt;. The complete script is available &lt;a href=&quot;//git.twilightsparkle.space/web-button/tree/ripple?h=v1&quot;&gt;right here&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;div&gt;&lt;img src=&quot;articles/web-button/ponies_frame.png&quot; style=&quot;width: 50%; image-rendering: pixelated;&quot; alt=&quot;A frame of my button with Twilight Sparkle and Pinkie Pie surrounded by the non-binary flag and the transgender flag. The image is in ripple.&quot;&gt;&lt;video loop autoplay style=&quot;width: 50%; border-radius: unset; image-rendering: pixelated;&quot; alt=&quot;The same image but the ripple is animated.&quot;&gt;&lt;source src=&quot;articles/web-button/ponies-h264.mp4&quot; type=&quot;video/mp4&quot;&gt;&lt;source src=&quot;articles/web-button/ponies-av1.mkv&quot; type=&quot;video/av1&quot;&gt;&lt;source src=&quot;articles/web-button/ponies-png.mkv&quot; type=&quot;video/mkv&quot;&gt;&lt;/video&gt;&lt;/div&gt;&lt;figcaption&gt;One of the frames of the ripple and the resulting animation. Notice the transparency (the video may not be transparent due to re-encoding)&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;p&gt;The procedure above applies a ripple effect to an image and gives us a video file, but this is not enough to make a button. We need more images and we need to chain them together to make something complete. As I only apply the effect to the foreground layer, we must not forget a backdrop to wrap everything up. To simplify the work and make sure that everything stays readable, I opted for a static background image. ffmpeg gives us the tools to combine video files and make what we want. We just need to orchestrate &lt;span class=small&gt;(not with Kubernetes!)&lt;/span&gt; multiple calls to ffmpeg with different image files to programatically generate a 88×31 button. It&apos;s too late now, we are too deep in the depths of the computer, we cannot step back anymore…&lt;/p&gt;&#xA;&lt;h2 id=&quot;make&quot;&gt;Makefiles&lt;/h2&gt;&#xA;&lt;p&gt;Makefiles may sound daunting, opaque and scary at first, but once your start understanding how they work, they aren&apos;t that bad. Before this project, I knew nearly nothing about them! I won&apos;t go much into details here and strongly recommend reading &lt;a href=&quot;https://makefiletutorial.com/&quot;&gt;this great tutorial&lt;/a&gt;. I will just explain that they work with the concept of dependencies that have to be built following certain rules before building their dependents. This makes the build process very efficient as only a subset of files — those that have been modified since the last build — are rebuilt, instead of starting from scratch every time.&lt;/p&gt;&#xA;&lt;p&gt;I started by writing a rule to generate ripple animations:&lt;/p&gt;&#xA;&lt;pre&gt;%.ond.mkv: ondulate/%.png ripple&#xA;	./ripple &quot;$&amp;lt;&quot; 500 10 $(ripple_time) $(fps) $(still_time) &quot;build/$@&quot; 6&lt;/pre&gt;&#xA;&lt;p&gt;This rule works with wildcards which avoids us from having to hard code input and output file names. The name on the left hand side of the colon is the target name (output) and on the right we have the dependencies of that target, which may be files or the names of other targets (in that case, these other targets will generate output files that have the same name). In our case, we look for the PNG specified by another target under the &lt;tt&gt;ondulate&lt;/tt&gt; directory and at the &lt;tt&gt;ripple&lt;/tt&gt; script, which generates our ripple, as seen in the previous section. Following the rules of Make, the &lt;tt&gt;%.ond.mkv&lt;/tt&gt; target would only be rebuilt if its dependencies are newer than the output file of the target. This makes sure that any modifications of the original files or of the &lt;tt&gt;ripple&lt;/tt&gt; script results in rebuilding of the ripple video. Note that &lt;tt&gt;$&amp;lt;&lt;/tt&gt; and &lt;tt&gt;$@&lt;/tt&gt; are &lt;a href=&quot;https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html&quot;&gt;automatic variables&lt;/a&gt; that are replaced with the first dependency and the target name respectively. The other variables, of the form &lt;tt&gt;$(var_name)&lt;/tt&gt;, have been manually declared.&lt;/p&gt;&#xA;&lt;p&gt;In a similar fashion, i created other Make targets that generate &quot;animations&quot;: &lt;tt&gt;%.sta.mkv&lt;/tt&gt; for static images (with no ripple), &lt;tt&gt;wallpaper.mkv&lt;/tt&gt; for the background image and &lt;tt&gt;%.qr.mkv&lt;/tt&gt; for static images that embed a QR Code (also generated in the Makefile). The complete definitions are available &lt;a href=&quot;//git.twilightsparkle.space/web-button/tree/Makefile?h=v1&quot;&gt;here&lt;/a&gt;, but note that the latter one should probably be divided into multiple smaller rules to avoid repetition and be more efficient.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img src=&quot;articles/web-button/wallpaper.png&quot; style=&quot;image-rendering: pixelated; width: 60%;&quot; alt=&quot;The wallpaper of my button with a plant that has light green, dark green and yellow leaves on a wall that has a cream colour.&quot;&gt;&lt;figcaption&gt;The background image, a picture I took of an &lt;i lang=la&gt;Euonymus fortunei&lt;/i&gt; on a muted wall&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;p&gt;We can then build on these rules to concatenate, or string together, these animations:&lt;/p&gt;&#xA;&lt;pre&gt;concat.mkv: username.ond.mkv ponies.ond.mkv themes_en.sta.mkv themes_fr.sta.mkv multilingue.sta.mkv scannez.qr.mkv&#xA;	$(ffmpeg) $(addprefix -i ,$+) -filter_complex &quot;concat=n=$(num_banners):v=1:a=0 [outv]&quot; -map &quot;[outv]&quot; -c:v png -pix_fmt rgba64be build/concat.mkv&lt;/pre&gt;&#xA;&lt;p&gt;This &quot;concat.mkv&quot; target depends on multiple animation targets before calling ffmpeg. The &lt;tt&gt;$(addprefix -i ,$+)&lt;/tt&gt; prefixes each dependency with &quot;-i &quot; to make it an input file to ffmpeg. The concat filter implicitly operates on the input video streams, in order, but we still have to specify the number of input streams.&lt;/p&gt;&#xA;&lt;p&gt;Finally, we can combine the concat.mkv file and the wallpaper to compose the final 88×31 button:&lt;/p&gt;&#xA;&lt;pre&gt;layers.mkv: concat.mkv wallpaper.mkv&#xA;	$(ffmpeg) -i build/wallpaper.mkv -i build/concat.mkv -filter_complex &quot;[0:v:0][1:v:0] overlay=format=rgb:eof_action=repeat&quot; -c:v png -framerate $(fps) build/layers.mkv&#xA;&#xA;build/button.apng: layers.mkv&#xA;	$(ffmpeg) -i build/layers.mkv -framerate $(fps) -plays 0 -final_delay 0.0 build/button.apng&lt;/pre&gt;&#xA;&lt;p&gt;We have to make sure that the videos are overlayed in the RGB colourspace instead of using the YUV colourspace, as this creates perceptual degradations. For instance, purple or red pixels that overlay the light parts of the wallpaper lose their colour as if it were partially &quot;eaten&quot; by the cream colour in the background. We also don&apos;t want the button animation to end prematurely if the video ends, so we use the &lt;tt&gt;eof_action=repeat&lt;/tt&gt; option. Anyways, after these hurdles, our Makefile creates the final button below!!&lt;/p&gt;&#xA;&lt;div class=&quot;project card&quot;&gt;&lt;div class=&quot;inline&quot;&gt;&#xA;  &lt;div class=&quot;buttons emphasis&quot;&gt;&#xA;    &lt;picture&gt;&#xA;      &lt;source srcset=&quot;/btn/button1.webp&quot; type=&quot;image/webp&quot;&gt;&#xA;      &lt;source srcset=&quot;/btn/button1.apng&quot; type=&quot;image/apng&quot;&gt;&#xA;      &lt;img alt=&quot;Website button showcasing what kinds of things you can find on my website. We first see &apos;ConfuSomu/Twilight Sparkle&apos;, followed by Pinkie Pie hugging Twilight Sparkle with a non-binary flag and a transgender flag in the background, followed by an image with the text &apos;Computers, nature, arts and way more!&apos; and then by a showcase of the multiligualism of the author and website through the use of country flags relating to different languages (French, English, Polish, German and Toki Pona). Finally, there is a QR Code that links to the website with the text &apos;Scan me&apos; next to it.&quot; src=&quot;/btn/button1.gif&quot;&gt;&#xA;    &lt;/picture&gt;&#xA;  &lt;/div&gt;&#xA;  &lt;p&gt;Feel free to put my 88x31 button on your website!&lt;br&gt;If you want to trade buttons, just send me a message and I&apos;ll add yours to my website.&lt;/p&gt;&#xA;  &lt;div class=&quot;links&quot;&gt;&#xA;    &lt;a href=&quot;https://git.twilightsparkle.space/web-button&quot;&gt;Source code&lt;/a&gt;&#xA;    &lt;details&gt;&#xA;      &lt;summary&gt;View HTML embed&lt;/summary&gt;&#xA;      &lt;div&gt;&#xA;        &lt;code style=&quot;line-break: anywhere; user-select: all;&quot;&gt;&#xA;        &amp;lt;a href=&quot;https://twilightsparkle.space/&quot;&amp;gt;&#xA;          &amp;lt;img alt=&quot;88x31 button presenting ConfuSomu&apos;s website&quot; style=&quot;image-rendering: pixelated;&quot; src=&quot;https://twilightsparkle.space/btn/button1.apng&quot;&amp;gt;&#xA;        &amp;lt;/a&amp;gt;&#xA;        &lt;/code&gt;&#xA;        &lt;p&gt;N.B: Note that you may copy the image onto your website. The image displayed above has longer &lt;code&gt;alt&lt;/code&gt; text than the one in this example HTML code.&lt;/p&gt;&#xA;      &lt;/div&gt;&#xA;    &lt;/details&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;p&gt;And we are done! Using ffmpeg and Makefiles, we can &lt;em&gt;generate&lt;/em&gt; a 88×31 button. Feel free to make your own buttons using the Makefile and the script shared, it&apos;s not too difficult once we got going, and it&apos;s fun!&lt;/p&gt;&#xA;&lt;p&gt;Nevertheless, I already have ideas for a few improvements for the future: it should be possible to change the background image over time and apply a transition effect — like the ripple effect, a basic change of frame or a fade effect — to the whole button instead of just the foreground. More effects should also be added, maybe even being able to include downscaled videos in the button. When I will make a second version, I will implement some of these effects.&lt;/p&gt;&#xA;&lt;p class=&quot;small&quot;&gt;PS: And yes, I shall translate my website into more languages, as to be faithful to my button! &lt;span class=&quot;emoticon&quot; aria-label=&quot;A face with tongue out emoticon.&quot;&gt;:P&lt;/span&gt;&lt;/p&gt;&#xA;&lt;p id=&quot;fn1&quot; class=&quot;footnote content&quot;&gt;¹: Sadly, I was sick while writing this, so it&apos;s more like a month now &lt;a href=&quot;#fn1-back&quot; class=&quot;footnote home&quot;&gt;↸&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;/div&gt;&lt;/article&gt;&lt;aside class=contributors&gt;&lt;h2&gt;Proofreaders&lt;/h2&gt;&lt;p&gt;These people have proofread my article. Thanks a lot to them!&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&apos;https://izzy.horse&apos;&gt;Izzy&lt;/a&gt; (&lt;span lang=&apos;en&apos;&gt;English&lt;/span&gt;, &lt;span lang=&apos;fr&apos;&gt;Français&lt;/span&gt;)&lt;/li&gt;&lt;li&gt;&lt;a href=&apos;https://cadnomori.neocities.org&apos;&gt;fox&lt;/a&gt; (&lt;span lang=&apos;en&apos;&gt;English&lt;/span&gt;)&lt;/li&gt;&lt;/ol&gt;&lt;/aside&gt;</content></entry>
</feed>