<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Christian Specht</title>
    <description>The latest articles on DEV Community by Christian Specht (@christianspecht).</description>
    <link>https://dev.to/christianspecht</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1093291%2F7ca2dc0b-919b-4d88-bd90-46e80eef5871.jpeg</url>
      <title>DEV Community: Christian Specht</title>
      <link>https://dev.to/christianspecht</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/christianspecht"/>
    <language>en</language>
    <item>
      <title>Tinkerforge Weather Station, part 3 - Continuing the project after a decade</title>
      <dc:creator>Christian Specht</dc:creator>
      <pubDate>Tue, 03 Sep 2024 21:00:00 +0000</pubDate>
      <link>https://dev.to/christianspecht/tinkerforge-weather-station-part-3-continuing-the-project-after-a-decade-1oho</link>
      <guid>https://dev.to/christianspecht/tinkerforge-weather-station-part-3-continuing-the-project-after-a-decade-1oho</guid>
      <description>&lt;p&gt;Once upon a time…I bought a &lt;a href="https://www.tinkerforge.com/en/doc/Kits/WeatherStation/WeatherStation.html" rel="noopener noreferrer"&gt;Tinkerforge Weather Station&lt;/a&gt;, wrote some C# code to interact with it, and managed to get it to run via Mono on a Raspberry PI with Linux.&lt;br&gt;&lt;br&gt;
My end goal was to measure temperature etc. in my garden, and somehow show it on my website.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(There are already two &lt;a href="https://christianspecht.de/2013/06/17/tinkerforge-weather-station-part-1-intro-and-construction/" rel="noopener noreferrer"&gt;old&lt;/a&gt; &lt;a href="https://christianspecht.de/2013/08/15/tinkerforge-weather-station-part-2-connecting-to-a-raspberry-pi/" rel="noopener noreferrer"&gt;posts&lt;/a&gt; about this on my website but not here, because they are from before I started mirroring my posts to here)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then I put the weather station and the Raspberry PI in a drawer…and completely forgot about them. That was about eleven years ago!&lt;/p&gt;

&lt;p&gt;Now I found both items again and decided to continue the project.&lt;br&gt;&lt;br&gt;
But of course, a decade is quite a long time for software - a lot of my original 2013 plan is not relevant anymore today:&lt;/p&gt;




&lt;h2&gt;
  
  
  The new plan
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;I don’t need Mono anymore. Ten years ago, the “new” .NET (a.k.a. .NET Core) didn’t even exist, but today I can just create a project in the current .NET Version (on Windows, because this is what I use) and it will just run on any Raspberry PI (very likely &lt;em&gt;not&lt;/em&gt; on Windows).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Back then, I didn’t want to put the PI &lt;em&gt;inside&lt;/em&gt; the weather station because I wanted to keep it in the house and also use it for other things (I didn’t explicitly write it, but I remember that I actually had home automation in mind). So I bought a &lt;a href="https://www.tinkerforge.com/en/doc/Hardware/Master_Extensions/WIFI_Extension.html" rel="noopener noreferrer"&gt;Tinkerforge WIFI Master Extension&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
But:&lt;/p&gt;

&lt;p&gt;a) now there's the Raspberry PI Zero W, which is ridiculously cheap AND has Wi-Fi&lt;/p&gt;

&lt;p&gt;b) &lt;a href="https://www.home-assistant.io/" rel="noopener noreferrer"&gt;Home Assistant&lt;/a&gt;, the home automation solution I evaluated so far, seems to "prefer" running on a dedicated machine anyway.&lt;br&gt;&lt;br&gt;
I have zero Home Assistant experience so far, but just from reading the documentation, it looks like that: the &lt;a href="https://www.home-assistant.io/installation/#diy-with-raspberry-pi" rel="noopener noreferrer"&gt;easiest installation method&lt;/a&gt; comes with its own operating system, and manually installing HA on an existing OS seems to be the &lt;a href="https://www.home-assistant.io/installation/#advanced-installation-methods" rel="noopener noreferrer"&gt;hardest installation method&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;⇒ it doesn't make sense to have a Raspberry PI running in the house 24/7 that does nothing but power the weather station. So the obvious choice is removing the Wi-Fi module and putting a Raspberry PI Zero W directly into the weather station.&lt;br&gt;&lt;br&gt;
&lt;em&gt;(I still have the old Raspberry PI from 2013, but it has no Wi-Fi)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I wanted (and still want) to periodically save the current temperature, humidity etc. &lt;em&gt;somewhere&lt;/em&gt; and do something cool with it.&lt;br&gt;&lt;br&gt;
In 2013, the Tinkerforge docs recommended Xively for this, but today their website doesn’t even load anymore, and &lt;a href="https://en.wikipedia.org/wiki/Xively" rel="noopener noreferrer"&gt;according to Wikipedia they were bought by Google years ago&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In 2024, there are &lt;em&gt;waaay&lt;/em&gt; more cloud services, and there's probably one that makes perfect sense for a requirement like this &lt;em&gt;(and has a free tier that's enough for my needs)&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
I don't have any experience building stuff for/in the cloud yet, so I will use this project to get my feet wet.&lt;/p&gt;

&lt;p&gt;So this is a but vague, but I'm planning to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;periodically send temperature etc. somewhere to &lt;em&gt;The Cloud™&lt;/em&gt; and store it there (not for all eternity, just the last few days?)&lt;/li&gt;
&lt;li&gt;eventually use this data to create an auto-refreshing "this is the current weather in my garden" site with some nice graphs, &lt;code&gt;weather.christianspecht.de&lt;/code&gt; or something like that.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The application so far
&lt;/h2&gt;

&lt;p&gt;The code is here on GitHub: &lt;a href="https://github.com/christianspecht/weather-station" rel="noopener noreferrer"&gt;https://github.com/christianspecht/weather-station&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s .NET 8 (the current LTS version). At the time of writing this post, it consists of nothing but &lt;a href="https://github.com/christianspecht/weather-station/tree/290f067f48b71d0730a9fde7a808ed2bc64728aa" rel="noopener noreferrer"&gt;a simple “Hello World” example&lt;/a&gt; (similar to the one in the 2013 blog posts), which shows the current temperature on the display.&lt;/p&gt;

&lt;p&gt;Next (before I write the next post), I will add more functionality to the application, similar to the &lt;a href="https://www.tinkerforge.com/en/doc/Kits/WeatherStation/CSharpToLCD.html" rel="noopener noreferrer"&gt;two&lt;/a&gt; &lt;a href="https://www.tinkerforge.com/en/doc/Kits/WeatherStation/CSharpToButtonControl.html" rel="noopener noreferrer"&gt;tutorials&lt;/a&gt; in the Tinkerforge docs: read the values of all sensors and make use of the buttons to show different things on the display. And after that, I will start with the cloud stuff.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Migrating my blog from Jekyll to Hugo</title>
      <dc:creator>Christian Specht</dc:creator>
      <pubDate>Thu, 30 May 2024 22:00:00 +0000</pubDate>
      <link>https://dev.to/christianspecht/migrating-my-blog-from-jekyll-to-hugo-4l9m</link>
      <guid>https://dev.to/christianspecht/migrating-my-blog-from-jekyll-to-hugo-4l9m</guid>
      <description>&lt;p&gt;A few weeks ago, I migrated my blog for the third time…after WordPress, &lt;a href="https://christianspecht.de/2013/01/29/switched-from-wordpress-to-blogofile/" rel="noopener noreferrer"&gt;Blogofile&lt;/a&gt; and &lt;a href="https://christianspecht.de/2013/12/31/hello-jekyll/" rel="noopener noreferrer"&gt;Jekyll&lt;/a&gt; it’s now powered by &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There’s nothing wrong with Jekyll. I just became more proficient with Hugo in the meantime &lt;em&gt;(I hope I’m behind the steepest part of Hugo’s learning curve now…)&lt;/em&gt; and besides its insane build speed, I like the simplicity of installing/updating Hugo (compared to Ruby/Jekyll) on Windows machines.&lt;/p&gt;

&lt;p&gt;And not to forget &lt;a href="https://christianspecht.de/2020/08/10/creating-an-image-gallery-with-hugo-and-lightbox2/" rel="noopener noreferrer"&gt;all the things I learned about processing images with Hugo&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
My blog has a few old posts which directly load multiple larger images (e.g. &lt;a href="https://christianspecht.de/2013/06/17/tinkerforge-weather-station-part-1-intro-and-construction/" rel="noopener noreferrer"&gt;this one&lt;/a&gt;). The images are not &lt;em&gt;that&lt;/em&gt; large…but still, it would make more sense to auto-generate thumbnails and load just the thumbnails directly with the post. Not sure if it’s worth the effort, I don’t have that many posts with large images yet, but who knows…&lt;/p&gt;

&lt;p&gt;Even though this is not the first Hugo site I built, I still learned a few new things about Hugo…and noticed some differences between Hugo and Jekyll:&lt;/p&gt;


&lt;h2&gt;
  
  
  Some things are easier in Hugo
&lt;/h2&gt;

&lt;p&gt;…like getting the number of posts per year, for example.&lt;br&gt;&lt;br&gt;
Compare &lt;a href="https://github.com/christianspecht/blog/commit/9816be021377604ef83555c2b0e1012338698316" rel="noopener noreferrer"&gt;the Jekyll and Hugo versions of the “Archives” box in the sidebar&lt;/a&gt;. In Jekyll, I had to create logic for this myself, whereas Hugo supports it out of the box.&lt;/p&gt;


&lt;h2&gt;
  
  
  Hugo template code works ONLY in templates
&lt;/h2&gt;

&lt;p&gt;In Jekyll, I could just create a page (the &lt;a href="https://christianspecht.de/archive/" rel="noopener noreferrer"&gt;archive&lt;/a&gt;, for example) and write code like this directly in the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% for post in site.posts %}
  &amp;lt;a href="{{ post.url }}"&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;
{% endfor %}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hugo just ignores all template code inside regular pages. To build something like the archive page, you need &lt;a href="https://github.com/christianspecht/blog/blob/6f2767c191c92bab234fb771b1cb393d2891a42a/src/content/archive.html" rel="noopener noreferrer"&gt;the actual (empty) page&lt;/a&gt;, and a &lt;a href="https://github.com/christianspecht/blog/blob/6f2767c191c92bab234fb771b1cb393d2891a42a/src/layouts/page/archive.html" rel="noopener noreferrer"&gt;special template&lt;/a&gt; which contains all the logic and is used only by that single page.&lt;/p&gt;




&lt;h2&gt;
  
  
  Render hooks
&lt;/h2&gt;

&lt;p&gt;Hugo has so-called render hooks, which allow things like auto-prefixing ALL image URLs with the complete site URL.&lt;br&gt;&lt;br&gt;
In my old Jekyll site, I created all hyperlinks (in all templates and in all Markdown pages) without domain, e.g. &lt;code&gt;/page.html&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When I switched to Hugo, I decided to use “proper” hyperlinks with full URLs (&lt;code&gt;https://christianspecht.de/page.html&lt;/code&gt;). Pasting &lt;code&gt;{{ "/page.html" | absURL }}&lt;/code&gt; all over the main template (for the menus etc.) was one thing, but all images in all posts are also linked without domain (&lt;a href="https://github.com/christianspecht/blog/blob/c81ed4255e043ec371fefc01fc452e5a8641725c/src/content/posts/2021-10-06-deleting-ssh-key-from-git-gui-the-easy-windows-solution/index.md?plain=1#L23" rel="noopener noreferrer"&gt;example&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;But - as noted in the last paragraph - in Hugo it’s not even possible to put template code into regular pages, so I couldn’t just do something like &lt;code&gt;{{ "/image.png" | absURL }}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Hugo’s solution for this is called &lt;a href="https://gohugo.io/render-hooks/images/" rel="noopener noreferrer"&gt;Render Hooks&lt;/a&gt; &lt;em&gt;(there are more types of them, but I needed the ones for images)&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
You just need to create &lt;a href="https://github.com/christianspecht/blog/commit/716f5341d30ae3bafcef13aaaf8375c6ee080b28" rel="noopener noreferrer"&gt;one file with what looks similar to a shortcode&lt;/a&gt;, and this causes Hugo to render &lt;strong&gt;all&lt;/strong&gt; images with full URLs.&lt;br&gt;&lt;br&gt;
For example, the example Markdown image code linked above looks like this when rendered with this hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;img src="https://christianspecht.de/img/git-ssh-2.png" alt="Git Gui screen" /&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  No HTML comments
&lt;/h2&gt;

&lt;p&gt;Hugo also removes all HTML comments when generating the output, including the ASCII art I have at the top of my blog’s HTML code.&lt;br&gt;&lt;br&gt;
Apparently the only way to get a &lt;code&gt;&amp;lt;!-- --&amp;gt;&lt;/code&gt; comment block into a Hugo site is &lt;a href="https://github.com/christianspecht/blog/commit/b6762142d4ca24406ceb581cd63a1809af33a350" rel="noopener noreferrer"&gt;converting it to Unicode, storing it in Hugo’s config file and loading it in the template with &lt;code&gt;safeHTML&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating PHP pages is more complicated
&lt;/h2&gt;

&lt;p&gt;My blog contains some PHP pages (for &lt;a href="https://christianspecht.de/2014/11/09/how-to-display-markdown-files-from-other-sites-now-with-caching/" rel="noopener noreferrer"&gt;the project pages&lt;/a&gt;), and apparently it’s not that easy to generate PHP pages in Hugo.&lt;/p&gt;

&lt;p&gt;Jekyll treats all files (as long as they contain YAML front-matter) equally and doesn’t really care about the file extension.&lt;br&gt;&lt;br&gt;
Hugo treats files with different extensions &lt;strong&gt;completely&lt;/strong&gt; different. To create PHP pages with Hugo, I would have to &lt;a href="https://discourse.gohugo.io/t/how-can-i-include-php-code-in-hugo/28589" rel="noopener noreferrer"&gt;create a copy of all layout files&lt;/a&gt;, just with .php instead of .html.&lt;br&gt;&lt;br&gt;
In the end, I decided to “cheat”, &lt;a href="https://github.com/christianspecht/blog/commit/2ccac56d91984782b4ed18809c03ecd979784f0d" rel="noopener noreferrer"&gt;generate the project pages as &lt;code&gt;.html&lt;/code&gt; files&lt;/a&gt; &lt;em&gt;(so they use the same layout files as all other pages)&lt;/em&gt;, and &lt;a href="https://github.com/christianspecht/blog/commit/d8f638ae9ef9c4e3c999d8d7f24af6387a25d3fb" rel="noopener noreferrer"&gt;rename them to &lt;code&gt;.php&lt;/code&gt; in the build script&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jekyll</category>
      <category>hugo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Implementing post series in Jekyll</title>
      <dc:creator>Christian Specht</dc:creator>
      <pubDate>Fri, 29 Dec 2023 22:00:00 +0000</pubDate>
      <link>https://dev.to/christianspecht/implementing-post-series-in-jekyll-38lc</link>
      <guid>https://dev.to/christianspecht/implementing-post-series-in-jekyll-38lc</guid>
      <description>&lt;p&gt;There were several occasions in the past where I wrote a “post series”, i.e. multiple posts about the same topic that belong together.&lt;/p&gt;

&lt;p&gt;Until now, there was only one way to visually indicate this: including the name of the series in the post title, like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cool Series, part 1: Intro&lt;/li&gt;
&lt;li&gt;Cool Series, part 2: Implementation&lt;/li&gt;
&lt;li&gt;etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sometimes, I actually remembered to name the posts like this…sometimes I didn’t.&lt;/p&gt;

&lt;p&gt;Recently, I noticed a few examples how others show post series on their blogs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ayende (&lt;a href="https://ayende.com/blog/200289-B/production-postmortem-the-spawn-of-denial-of-service" rel="noopener noreferrer"&gt;example post&lt;/a&gt;) 

&lt;ul&gt;
&lt;li&gt;at the top of the post, there are separate “previous/next post” and “previous/next post &lt;em&gt;in this series&lt;/em&gt;” links&lt;/li&gt;
&lt;li&gt;at the bottom of a post is a “More posts in this series” box&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Steven van Deursen (&lt;a href="https://blogs.cuttingedge.it/steven/posts/2019/di-composition-models-primer/" rel="noopener noreferrer"&gt;example post&lt;/a&gt;) 

&lt;ul&gt;
&lt;li&gt;at the top of the post, there is a box with links to all posts in the series&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;“Previous/next post in this series” links sound tempting, but my blog is built with Jekyll, and it’s not that easy to determine the previous/next post that belongs to the same series.&lt;/p&gt;

&lt;p&gt;But a box with links to all posts in the series, that’s doable. Here’s how I built it:&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Defining the series in the YAML front-matter:
&lt;/h2&gt;

&lt;p&gt;I just added a new variable called &lt;code&gt;series&lt;/code&gt; to all relevant posts.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;series: "Hugo/Lightbox image gallery"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This string is used to determine which posts belong together, so it must be 100% identical for all posts in the same series.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Adding the “series box” to the layout file:
&lt;/h2&gt;

&lt;p&gt;I’m using a separate layout file (which &lt;a href="http://jekyllrb.com/docs/layouts/#inheritance" rel="noopener noreferrer"&gt;inherits from the default layout&lt;/a&gt;) for posts.&lt;/p&gt;

&lt;p&gt;The additional code for the “series box” goes into that file &lt;em&gt;(so the box can only appear in posts)&lt;/em&gt;, and here’s the complete code for it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% if page.series %}
&amp;lt;div&amp;gt;
  &amp;lt;h4&amp;gt;This post is part of a series: {{ page.series }}&amp;lt;/h4&amp;gt;
  &amp;lt;ol&amp;gt;
    {% assign posts = site.posts | where: "series", page.series | sort: "date" %}
    {% for post in posts %}
    &amp;lt;li&amp;gt;{% if post.url == page.url %}{{ post.title }} &amp;lt;i&amp;gt;(this post)&amp;lt;/i&amp;gt;{% else %}&amp;lt;a href="{{ post.url }}"&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;{% endif %}&amp;lt;/li&amp;gt;
    {% endfor %}
  &amp;lt;/ol&amp;gt;
&amp;lt;/div&amp;gt;
{% endif %}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;if page.series&lt;/code&gt; ⇒ only show the whole block if the current page actually has a &lt;code&gt;series&lt;/code&gt; variable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;assign posts = site.posts | where: "series", page.series | sort: "date"&lt;/code&gt; ⇒ load all posts that have the same series as the current post &lt;em&gt;(including the current post)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;loop them 

&lt;ul&gt;
&lt;li&gt;if it’s the current post, show the title only as text with “&lt;em&gt;(this post)&lt;/em&gt;” behind it&lt;/li&gt;
&lt;li&gt;for all other posts, show them as links&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;And finally, here’s &lt;a href="https://christianspecht.de/2020/08/10/creating-an-image-gallery-with-hugo-and-lightbox2/" rel="noopener noreferrer"&gt;an example how the finished “series box” looks like&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>jekyll</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hugo/Lightbox image gallery: Renaming processed images</title>
      <dc:creator>Christian Specht</dc:creator>
      <pubDate>Sat, 18 Mar 2023 22:00:00 +0000</pubDate>
      <link>https://dev.to/christianspecht/hugolightbox-image-gallery-renaming-processed-images-1gm7</link>
      <guid>https://dev.to/christianspecht/hugolightbox-image-gallery-renaming-processed-images-1gm7</guid>
      <description>&lt;p&gt;After the last post, I thought I was finished with &lt;a href="https://christianspecht.de/2020/08/10/creating-an-image-gallery-with-hugo-and-lightbox2/" rel="noopener noreferrer"&gt;my Hugo/Lightbox image gallery&lt;/a&gt;, but there was one more thing I didn’t like: the filenames generated by Hugo’s image processing.&lt;/p&gt;

&lt;p&gt;As a reminder, my current image gallery shortcode &lt;em&gt;(including &lt;a href="https://christianspecht.de/2022/06/29/hugo-lightbox-image-gallery-loading-image-captions-from-exif-data/" rel="noopener noreferrer"&gt;image captions from EXIF data&lt;/a&gt; and &lt;a href="https://christianspecht.de/2022/09/15/hugo-lightbox-image-gallery-overlay-images-with-logo/" rel="noopener noreferrer"&gt;overlaying images with a logo&lt;/a&gt;)&lt;/em&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $logo := resources.Get "img/overlay-logo.png" }}
{{ $image := (.Page.Resources.ByType "image") }}
{{ with $image }}
    {{ range . }}
    {{ $resized := .Fill "150x115 q20" }}
    {{ $x := sub .Width (add $logo.Width 10) }}
    {{ $y := sub .Height (add $logo.Height 10) }}
    {{ $withlogo := .Filter (images.Overlay $logo $x $y)}}
    &amp;lt;a href="{{ $withlogo.Permalink }}" data-lightbox="x" data-title="{{ with .Exif }}{{ .Tags.ImageDescription }}{{ end }}"&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;
    {{ end }}
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both the thumbnail (&lt;code&gt;$resized&lt;/code&gt;) and the main image (&lt;code&gt;$withlogo&lt;/code&gt;) are created with Hugo’s image processing features, which means that their final filenames are assigned at build time by Hugo, and they are &lt;em&gt;ugly&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For example one of the &lt;a href="https://github.com/christianspecht/code-examples/tree/master/hugo-gallery-example/content/galleries/gallery1/img" rel="noopener noreferrer"&gt;images from my demo project&lt;/a&gt;: the original filename is &lt;code&gt;notebook.jpg&lt;/code&gt;, but the the shortcode shown above produces this HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="http://localhost:1313/galleries/gallery1/img/notebook_hu3d03a01dcc18bc5be0e67db3d8d209a6_1586565_filter_6003090614434618376.jpg" data-lightbox="x" data-title=""&amp;gt;
    &amp;lt;img src="http://localhost:1313/galleries/gallery1/img/notebook_hu3d03a01dcc18bc5be0e67db3d8d209a6_1586565_150x115_fill_q20_box_smart1.jpg" /&amp;gt;
&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  1. The easy part: renaming the main image
&lt;/h2&gt;

&lt;p&gt;Starting with version 0.100.0, Hugo supports &lt;a href="https://gohugo.io/hugo-pipes/introduction/#copy-a-resource" rel="noopener noreferrer"&gt;renaming files after processing them, via &lt;code&gt;resources.Copy&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, there aren’t many examples out there yet. The only one in the docs (&lt;code&gt;resources.Copy "images/mynewname.jpg"&lt;/code&gt;) doesn’t help me, because it uses a fixed filename, and I need dynamic filenames because I’m dealing with lists of images.&lt;/p&gt;

&lt;p&gt;But this one case is easy: I want to give the processed image its original filename back, the one that it had before processing.&lt;/p&gt;

&lt;p&gt;As I’m looping all images anyway and processing each one in the loop, the original filename with its complete path, which &lt;code&gt;resources.Copy&lt;/code&gt; needs, is still accessible via &lt;code&gt;.RelPermalink&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
&lt;em&gt;(&lt;code&gt;.&lt;/code&gt; is the current image)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So it’s just a matter of changing this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $withlogo := .Filter (images.Overlay $logo $x $y)}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $withlogo := .Filter (images.Overlay $logo $x $y) | resources.Copy .RelPermalink }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As always, &lt;a href="https://github.com/christianspecht/code-examples/commit/3414df43314f7555f3efad991a53e0d6cf1b5843" rel="noopener noreferrer"&gt;here’s the commit with the changes in the demo project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The resulting HTML now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="http://localhost:1313/galleries/gallery1/img/notebook.jpg" data-lightbox="x" data-title=""&amp;gt;
    &amp;lt;img src="http://localhost:1313/galleries/gallery1/img/notebook_hu3d03a01dcc18bc5be0e67db3d8d209a6_1586565_150x115_fill_q20_box_smart1.jpg" /&amp;gt;
&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Already better than before, but the thumbnail still has an ugly filename.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The not-so-easy part: renaming the thumbnail
&lt;/h2&gt;

&lt;p&gt;In past image galleries, I had used a suffix like &lt;code&gt;_t&lt;/code&gt; for the thumbnails’ filenames, e.g. &lt;code&gt;notebook.jpg&lt;/code&gt; would have a thumbnail named &lt;code&gt;notebook_t.jpg&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This means that I would have to build the new file path from scratch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;path + original filename + "_t" + original file extension

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So how do you do this in Hugo’s templating language?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;You build strings &lt;a href="https://gohugo.io/functions/printf/" rel="noopener noreferrer"&gt;via &lt;code&gt;printf&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;{{ printf "%s%s" "foo" "bar" }}&lt;/code&gt; ⇒  &lt;code&gt;foobar&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There are multiple &lt;code&gt;path.xyz&lt;/code&gt; methods which expect the complete path to a file and return parts of it (what I need: &lt;a href="https://gohugo.io/functions/path.dir/" rel="noopener noreferrer"&gt;directory&lt;/a&gt;, &lt;a href="https://gohugo.io/functions/path.basename/" rel="noopener noreferrer"&gt;filename&lt;/a&gt;, &lt;a href="https://gohugo.io/functions/path.ext/" rel="noopener noreferrer"&gt;extension&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;{{ path.BaseName .RelPermalink }}&lt;/code&gt; ⇒ for &lt;code&gt;notebook.jpg&lt;/code&gt; as the current image in a loop, like in the example above, this returns &lt;code&gt;notebook&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Syntax: Similar to a lot of other programming languages, nested function calls in Hugo’s template language must be in parentheses.&lt;/p&gt;

&lt;p&gt;I.e. if I want to use the &lt;code&gt;printf&lt;/code&gt; example from above, but instead of &lt;code&gt;"foo"&lt;/code&gt; I want to pass the &lt;code&gt;path.BaseName&lt;/code&gt; example (also from above), the correct syntax is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ printf "%s%s" (path.BaseName .RelPermalink) "bar" }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this is the line of the shortcode that creates the thumbnails, which must be replaced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $resized := .Fill "150x115 q20" }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the new version which creates &lt;code&gt;path + original filename + "_t" + original file extension&lt;/code&gt; filenames as intended:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $thumbname := printf "%s/%s%s%s" (path.Dir .RelPermalink) (path.BaseName .RelPermalink) "_t" (path.Ext .RelPermalink) }}
{{ $resized := .Fill "150x115 q20" | resources.Copy $thumbname }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commit with this change &lt;a href="https://github.com/christianspecht/code-examples/commit/55f6bfbbc5f5dd2e09639161f295e7c8ae17390d" rel="noopener noreferrer"&gt;is here&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Conclusion
&lt;/h2&gt;

&lt;p&gt;Here is the final shortcode one last time - with EXIF image captions, logo overlays &lt;strong&gt;and&lt;/strong&gt; pretty filenames:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $logo := resources.Get "img/overlay-logo.png" }}
{{ $image := (.Page.Resources.ByType "image") }}
{{ with $image }}
    {{ range . }}
    {{ $thumbname := printf "%s/%s%s%s" (path.Dir .RelPermalink) (path.BaseName .RelPermalink) "_t" (path.Ext .RelPermalink) }}
    {{ $resized := .Fill "150x115 q20" | resources.Copy $thumbname }}
    {{ $x := sub .Width (add $logo.Width 10) }}
    {{ $y := sub .Height (add $logo.Height 10) }}
    {{ $withlogo := .Filter (images.Overlay $logo $x $y) | resources.Copy .RelPermalink }}
    &amp;lt;a href="{{ $withlogo.Permalink }}" data-lightbox="x" data-title="{{ with .Exif }}{{ .Tags.ImageDescription }}{{ end }}"&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;
    {{ end }}
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the same demo image as above (&lt;code&gt;notebook.jpg&lt;/code&gt;), the HTML produced by this shortcode looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="http://localhost:1313/galleries/gallery1/img/notebook.jpg" data-lightbox="x" data-title=""&amp;gt;
    &amp;lt;img src="http://localhost:1313/galleries/gallery1/img/notebook_t.jpg" /&amp;gt;
&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>hugo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hugo/Lightbox image gallery: Overlay images with logo</title>
      <dc:creator>Christian Specht</dc:creator>
      <pubDate>Thu, 15 Sep 2022 20:00:00 +0000</pubDate>
      <link>https://dev.to/christianspecht/hugolightbox-image-gallery-overlay-images-with-logo-217</link>
      <guid>https://dev.to/christianspecht/hugolightbox-image-gallery-overlay-images-with-logo-217</guid>
      <description>&lt;p&gt;Another nice addition to the &lt;a href="https://christianspecht.de/2020/08/10/creating-an-image-gallery-with-hugo-and-lightbox2/" rel="noopener noreferrer"&gt;Hugo/Lightbox2 image gallery&lt;/a&gt;: one of Hugo’s available image filters &lt;a href="https://gohugo.io/functions/images/#overlay" rel="noopener noreferrer"&gt;is able to overlay the gallery images with a logo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Before we start, here’s the previous version of the gallery shortcode &lt;em&gt;(including &lt;a href="https://christianspecht.de/2022/06/29/hugo-lightbox-image-gallery-loading-image-captions-from-exif-data/" rel="noopener noreferrer"&gt;the EXIF caption from the last post&lt;/a&gt;)&lt;/em&gt; for reference:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/layouts/shortcodes/gallery.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $image := (.Page.Resources.ByType "image") }}
{{ with $image }}
    {{ range . }}
    {{ $resized := .Fill "150x115 q20" }}
    &amp;lt;a href="{{ .Permalink }}" data-lightbox="x" data-title="{{ with .Exif }}{{ .Tags.ImageDescription }}{{ end }}"&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;
    {{ end }}
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like in the previous post, I will add the changes shown here as new commits in the &lt;a href="https://github.com/christianspecht/code-examples/tree/master/hugo-gallery-example" rel="noopener noreferrer"&gt;&lt;code&gt;hugo-gallery-example&lt;/code&gt; project in my &lt;code&gt;code-examples&lt;/code&gt; repository&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: show logo
&lt;/h2&gt;

&lt;p&gt;In order to be processed by Hugo, images must be &lt;a href="https://gohugo.io/content-management/image-processing/#image-resources" rel="noopener noreferrer"&gt;either page resources or global resources&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
The gallery images are page resources anyway, but the logo that will overlay all gallery images doesn’t belong to one single page. So it has to be a global resource.&lt;/p&gt;

&lt;p&gt;The simplest way to make it a global resource is &lt;a href="https://gohugo.io/content-management/image-processing/#global-resources" rel="noopener noreferrer"&gt;putting it into the &lt;code&gt;assets&lt;/code&gt; folder&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once it’s there, it can be applied to all gallery images by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loading the logo with &lt;code&gt;resources.Get&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;applying the filter, which overlays the gallery image with the logo&lt;/li&gt;
&lt;li&gt;showing the changed image in the gallery instead of the original one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/christianspecht/code-examples/commit/cbc2686c0e1d6dad603eaa09f4ebddf268fcc9dc" rel="noopener noreferrer"&gt;Here’s the commit&lt;/a&gt; with the changes.&lt;/p&gt;

&lt;p&gt;The complete changed shortcode now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $logo := resources.Get "img/overlay-logo.png" }}
{{ $image := (.Page.Resources.ByType "image") }}
{{ with $image }}
    {{ range . }}
    {{ $resized := .Fill "150x115 q20" }}
    {{ $withlogo := .Filter (images.Overlay $logo 10 10)}}
    &amp;lt;a href="{{ $withlogo.Permalink }}" data-lightbox="x" data-title="{{ with .Exif }}{{ .Tags.ImageDescription }}{{ end }}"&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;
    {{ end }}
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2: move to bottom right
&lt;/h2&gt;

&lt;p&gt;The coordinates are from the top left, so &lt;code&gt;images.Overlay $logo 10 10&lt;/code&gt; means 10 pixels from the top and 10 pixels from the left.&lt;/p&gt;

&lt;p&gt;But I wanted to have the logo at the bottom right, so I needed to calculate the correct pixel values depending on the sizes of the gallery image and the logo.&lt;/p&gt;

&lt;p&gt;The calculation itself is not difficult. All images in Hugo have properties for width and height, so I can access &lt;code&gt;$logo.Width&lt;/code&gt; and &lt;code&gt;$logo.Height&lt;/code&gt; &lt;em&gt;(for the logo)&lt;/em&gt; and &lt;code&gt;.Width&lt;/code&gt; and &lt;code&gt;.Height&lt;/code&gt; &lt;em&gt;(for the current gallery image)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For example, the correct &lt;code&gt;x&lt;/code&gt; value for the logo’s top left corner &lt;em&gt;(so that its bottom right corner is 10 pixels away from the right and from the bottom)&lt;/em&gt; is &lt;code&gt;.Width - $logo.Width - 10&lt;/code&gt;,&lt;/p&gt;

&lt;p&gt;Here’s some fancy ASCII art to visualize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌───────────────────────────────────────────────┐
│                                               │
│                                               │
│                                               │
│                                               │
│                          ┌─────────────┐      │
│                          │     LOGO    │      │
│                          └─────────────┘      │
│&amp;lt;---------- x -----------&amp;gt;&amp;lt;-$logo.Width-&amp;gt;&amp;lt;-10-&amp;gt;│
│                                               │
└───────────────────────────────────────────────┘

&amp;lt;-------------------- .Width -------------------&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same for the height: &lt;code&gt;.Height - $logo.Height - 10&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But I spent an insane amount of time getting the syntax right. The problem was the (for me) unusual syntax of &lt;a href="https://gohugo.io/templates/introduction/#functions" rel="noopener noreferrer"&gt;Hugo’s template functions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Getting used to the &lt;code&gt;{{ name parameter1 parameter2 }}&lt;/code&gt; format and finding &lt;a href="https://gohugo.io/functions/math/" rel="noopener noreferrer"&gt;the correct math functions&lt;/a&gt; was one thing, but nesting two of these calls with all the correct brackets made my brain hurt.&lt;/p&gt;

&lt;p&gt;But actually I just had to change this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $withlogo := .Filter (images.Overlay $logo 10 10)}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $x := sub .Width (add $logo.Width 10) }}
{{ $y := sub .Height (add $logo.Height 10) }}
{{ $withlogo := .Filter (images.Overlay $logo $x $y)}}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here’s &lt;a href="https://github.com/christianspecht/code-examples/commit/53554ac1c7d728ae5c09574e310a732d6023fa1e" rel="noopener noreferrer"&gt;the commit&lt;/a&gt;, and finally here’s the complete changed shortcode again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $logo := resources.Get "img/overlay-logo.png" }}
{{ $image := (.Page.Resources.ByType "image") }}
{{ with $image }}
    {{ range . }}
    {{ $resized := .Fill "150x115 q20" }}
    {{ $x := sub .Width (add $logo.Width 10) }}
    {{ $y := sub .Height (add $logo.Height 10) }}
    {{ $withlogo := .Filter (images.Overlay $logo $x $y)}}
    &amp;lt;a href="{{ $withlogo.Permalink }}" data-lightbox="x" data-title="{{ with .Exif }}{{ .Tags.ImageDescription }}{{ end }}"&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;
    {{ end }}
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, I am amazed by Hugo’s image manipulation features.&lt;/p&gt;

&lt;p&gt;With these few lines of template code, I can now auto-create a thumbnail, pull the image title from the image’s EXIF data &lt;em&gt;and&lt;/em&gt; overlay the image with a logo.&lt;/p&gt;

</description>
      <category>hugo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Hugo/Lightbox image gallery: Loading image captions from EXIF data</title>
      <dc:creator>Christian Specht</dc:creator>
      <pubDate>Wed, 29 Jun 2022 21:00:00 +0000</pubDate>
      <link>https://dev.to/christianspecht/hugolightbox-image-gallery-loading-image-captions-from-exif-data-3dd2</link>
      <guid>https://dev.to/christianspecht/hugolightbox-image-gallery-loading-image-captions-from-exif-data-3dd2</guid>
      <description>&lt;p&gt;When I figured out &lt;a href="https://christianspecht.de/2020/08/10/creating-an-image-gallery-with-hugo-and-lightbox2/" rel="noopener noreferrer"&gt;how to create an image gallery with Hugo and Lightbox2&lt;/a&gt;, there’s one thing I left out: image captions.&lt;/p&gt;

&lt;p&gt;In Lightbox2 itself, this is straightforward. Here’s the generated HTML from the current version of my Hugo/Lightbox2 gallery, for one single image &lt;em&gt;(image URLs shortened for brevity)&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="path/to/image.jpg" data-lightbox="x"&amp;gt;&amp;lt;img src="path/to/thumbnail.jpg" /&amp;gt;&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To add a caption, you just add a &lt;code&gt;data-title&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="path/to/image.jpg" data-lightbox="x" data-title="My caption"&amp;gt;&amp;lt;img src="path/to/thumbnail.jpg" /&amp;gt;&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my previous image gallery attempts with Jekyll, the list of images always came from a YAML list &lt;em&gt;(either &lt;a href="https://christianspecht.de/2014/03/08/generating-an-image-gallery-with-jekyll-and-lightbox2/" rel="noopener noreferrer"&gt;from a data file&lt;/a&gt; or &lt;a href="https://christianspecht.de/2014/08/22/jekyll-lightbox2-image-gallery-another-approach/" rel="noopener noreferrer"&gt;directly from the respective page’s frontmatter&lt;/a&gt;)&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
In both cases, the list just had an additional property for the caption, which I used to fill the caption in the HTML attribute.&lt;/p&gt;

&lt;p&gt;But in my Hugo gallery, the images are &lt;a href="https://gohugo.io/content-management/page-resources/" rel="noopener noreferrer"&gt;page resources&lt;/a&gt;, i.e. there is no actual “list of images” defined anywhere. The image files are stored in a subdirectory of the post, and Hugo reads them directly from the file system.&lt;br&gt;&lt;br&gt;
So it’s not obvious how/where to store captions per image - that’s why I omitted them in the first version of my Hugo gallery.&lt;/p&gt;



&lt;p&gt;As a reminder/comparison, here’s the current version of the line from the shortcode that generates the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="{{ .Permalink }}" data-lightbox="x"&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(for the full shortcode, see the &lt;a href="https://christianspecht.de/2020/08/10/creating-an-image-gallery-with-hugo-and-lightbox2/" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Solution: Hugo and EXIF data
&lt;/h3&gt;

&lt;p&gt;I wasn’t even aware that Hugo is able to access the EXIF data of images. I found it out by accident when reading &lt;a href="https://discourse.gohugo.io/t/exif-iptc/25995" rel="noopener noreferrer"&gt;this discussion in the Hugo forum&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First of all, how to edit an image’s EXIF data? There are probably many tools that can do this, but on Windows 10 &lt;em&gt;(where I’m writing this text right now)&lt;/em&gt; you don’t even need a tool, Windows Explorer is enough:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Right-click on an image in the Explorer ⇒ Properties&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Go to the &lt;code&gt;Details&lt;/code&gt; tab and write the caption into the &lt;code&gt;Title&lt;/code&gt; field:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkrzviqsdz9jde47kqmsi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkrzviqsdz9jde47kqmsi.png" alt="Edit EXIF in Windows Explorer" width="484" height="289"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To show this caption in the Hugo image gallery, the line in the shortcode must look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="{{ .Permalink }}" data-lightbox="x" data-title="{{ with .Exif }}{{ .Tags.ImageDescription }}{{ end }}"&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/christianspecht/code-examples/commit/425418fef8e2ef27c1b9b89ea691eb19cfceff1a" rel="noopener noreferrer"&gt;New commit with the changes&lt;/a&gt; &lt;em&gt;(shortcode and one image)&lt;/em&gt;, in the existing example project in the &lt;code&gt;code-examples&lt;/code&gt; repo.&lt;/p&gt;

&lt;p&gt;That’s all - &lt;em&gt;one&lt;/em&gt; changed line of code.&lt;br&gt;&lt;br&gt;
The more I learn about Hugo, the more I like it. As I said in the previous post: IMO the learning curve is insanely steep in the beginning, but apparently I have the hardest part behind me now. Now I’m at the stage where I’m amazed what Hugo can do with very little code.&lt;/p&gt;


&lt;h3&gt;
  
  
  Alternative solution: load captions from page’s front matter
&lt;/h3&gt;

&lt;p&gt;This is the first solution I had, before finding out about Hugo’s EXIF feature. It works, but I like the EXIF solution better.&lt;br&gt;&lt;br&gt;
But for the sake of completeness, I wanted to show this solution as well.&lt;/p&gt;

&lt;p&gt;Similar to my &lt;a href="https://christianspecht.de/2014/08/22/jekyll-lightbox2-image-gallery-another-approach/" rel="noopener noreferrer"&gt;second Jekyll gallery&lt;/a&gt;, the list of captions is coming from the page’s front matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources:
- src: "img/Pope-Edouard-de-Beaumont-1844.jpg"
  title: "Example caption 1"
- src: "img/esmeralda.jpg"
  title: "Example caption 2"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the list must also contain the image’s name/path, so Hugo knows which text belongs to which image.&lt;/p&gt;

&lt;p&gt;Then, the line in the shortcode must be changed like this to show the caption:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;a href="{{ .Permalink }}" data-lightbox="x" {{ if ne .Title .Name }}data-title="{{ .Title }}"{{ end }}&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but I didn’t like it because a) the data for the image gallery is now spread across two places &lt;em&gt;(file system and front matter)&lt;/em&gt; and b) I have to duplicate the file name of each image in the front-matter again.&lt;/p&gt;

</description>
      <category>hugo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Creating an image gallery with Hugo and Lightbox2</title>
      <dc:creator>Christian Specht</dc:creator>
      <pubDate>Mon, 10 Aug 2020 21:00:00 +0000</pubDate>
      <link>https://dev.to/christianspecht/creating-an-image-gallery-with-hugo-and-lightbox2-5h98</link>
      <guid>https://dev.to/christianspecht/creating-an-image-gallery-with-hugo-and-lightbox2-5h98</guid>
      <description>&lt;p&gt;In the last few years, I built multiple static sites with Jekyll. Altogether, I’m happy with Jekyll, but there are a few things that could be better, with the build duration being on top of the list.&lt;/p&gt;

&lt;p&gt;I wanted to evaluate another static site generator as an alternative, and I picked &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; mainly because of its speed (I read multiple sources who said it’s &lt;strong&gt;really&lt;/strong&gt; fast).&lt;/p&gt;

&lt;p&gt;Compared to Jekyll, I find Hugo’s learning curve insanely steep, though…and it took me quite some time to grasp enough concepts until I was able to create a basic site.&lt;/p&gt;

&lt;p&gt;At that point, I decided to create an image gallery first. That was one of my first experiments with Jekyll - after getting the basics to run, I built an image gallery with &lt;a href="https://lokeshdhakar.com/projects/lightbox2/" rel="noopener noreferrer"&gt;Lightbox2&lt;/a&gt; in two versions (&lt;a href="https://christianspecht.de/2014/03/08/generating-an-image-gallery-with-jekyll-and-lightbox2/" rel="noopener noreferrer"&gt;1&lt;/a&gt;, &lt;a href="https://christianspecht.de/2014/08/22/jekyll-lightbox2-image-gallery-another-approach/" rel="noopener noreferrer"&gt;2&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Here’s how to create the same result with Hugo:&lt;/p&gt;




&lt;h2&gt;
  
  
  Directory structure
&lt;/h2&gt;

&lt;p&gt;Example code is &lt;a href="https://github.com/christianspecht/code-examples/tree/master/hugo-gallery-example" rel="noopener noreferrer"&gt;here in the &lt;code&gt;code-examples&lt;/code&gt; repo&lt;/a&gt;. While explaining the steps, I will link to the specific commit where I actually did the respective step.&lt;/p&gt;

&lt;p&gt;This is Hugo’s basic structure for the &lt;code&gt;content&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|       
+---content
| \---galleries
| | _index.md
| |   
| \---gallery1
| | index.md
| |   
| \---img
| esmeralda.jpg
| notebook.jpg
| Pope-Edouard-de-Beaumont-1844.jpg
| Victor_Hugo-Hunchback.jpg

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the different &lt;code&gt;_index.md&lt;/code&gt; and &lt;code&gt;index.md&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;_index.md&lt;/code&gt; is an &lt;a href="https://gohugo.io/content-management/organization/#index-pages-_indexmd" rel="noopener noreferrer"&gt;index page&lt;/a&gt;, which just list its sub-items.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index.md&lt;/code&gt; (without underscore) is a “single page”, which means an actual content page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/christianspecht/code-examples/commit/bbf88f739271456089e2bbacd88d6dfaa9321257" rel="noopener noreferrer"&gt;Here’s the commit where I create the project with the &lt;code&gt;.md&lt;/code&gt; files&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
Plus, I needed some sample images for the gallery, so I &lt;a href="https://github.com/christianspecht/code-examples/commit/b82d5f5455e9edda8c493acad0da29a808f25716" rel="noopener noreferrer"&gt;just copied some&lt;/a&gt; from the &lt;a href="https://github.com/theNewDynamic/gohugo-theme-ananke/tree/v2.6.2/exampleSite/static/images" rel="noopener noreferrer"&gt;example site provided with the Ananke theme&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Note the destination directory: the images are in a subdirectory of &lt;code&gt;/content/galleries/gallery1&lt;/code&gt;, so they are part of &lt;code&gt;/content/galleries/gallery1/index.md&lt;/code&gt;’s &lt;a href="https://gohugo.io/content-management/organization/#page-bundles" rel="noopener noreferrer"&gt;page bundle&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
In Hugo terminology, the images are &lt;a href="https://gohugo.io/content-management/page-resources/" rel="noopener noreferrer"&gt;page resources&lt;/a&gt;, which means that we can list the current page’s images like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ with .Page.Resources.ByType "image" }}
    &amp;lt;ul&amp;gt;
    {{ range . }}
    &amp;lt;li&amp;gt;{{ .Permalink }}&amp;lt;/li&amp;gt;
    {{ end }}
    &amp;lt;/ul&amp;gt;
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But apparently, it’s not possible in Hugo to paste that code directly into the page for testing…so we need to create a shortcode first:&lt;/p&gt;




&lt;h2&gt;
  
  
  Shortcodes
&lt;/h2&gt;

&lt;p&gt;For those who know Jekyll, Jekyll’s includes are called &lt;a href="https://gohugo.io/templates/shortcode-templates/" rel="noopener noreferrer"&gt;shortcodes&lt;/a&gt; in Hugo.&lt;/p&gt;

&lt;p&gt;Shortcodes allow us to create things we need multiple times (like the code to show an image gallery :-) in a central HTML file - in this case, it’s the “list all images” code from the last paragraph:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/layouts/shortcodes/gallery.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ with .Page.Resources.ByType "image" }}
    &amp;lt;ul&amp;gt;
    {{ range . }}
    &amp;lt;li&amp;gt;{{ .Permalink }}&amp;lt;/li&amp;gt;
    {{ end }}
    &amp;lt;/ul&amp;gt;
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…and we can “include” it into other pages like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{&amp;lt; gallery &amp;gt;}} 

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/christianspecht/code-examples/commit/a8945355940c45c3feb2039b5a8d525c8eda0e02" rel="noopener noreferrer"&gt;Here’s the commit with the changes&lt;/a&gt; - this will eventually become my “show image gallery for the current page” include.&lt;/p&gt;

&lt;p&gt;This will output the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;http://localhost:1313/galleries/gallery1/img/Pope-Edouard-de-Beaumont-1844.jpg&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;http://localhost:1313/galleries/gallery1/img/Victor_Hugo-Hunchback.jpg&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;http://localhost:1313/galleries/gallery1/img/esmeralda.jpg&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;http://localhost:1313/galleries/gallery1/img/notebook.jpg&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Auto-generating thumbnails
&lt;/h2&gt;

&lt;p&gt;This is, besides the insane build speed, one of Hugo’s killer features for me: it’s able to &lt;a href="https://gohugo.io/content-management/image-processing/" rel="noopener noreferrer"&gt;batch-manipulate images at build time&lt;/a&gt;, which means that unlike my Jekyll image galleries, I just need to give Hugo the actual images, and it will auto-generate the thumbnails when building the site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/christianspecht/code-examples/commit/ddeda4828f1af949935871fccd76139f27d5dcf3" rel="noopener noreferrer"&gt;Here’s a simple example&lt;/a&gt; where I create a thumbnail for each image in the page bundle (150x115 pixels, JPEG quality 20%), and show it together with the original (larger) image:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/layouts/shortcodes/gallery.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{{ $image := (.Page.Resources.ByType "image") }}
{{ with $image }}
    &amp;lt;ul&amp;gt;
    {{ range . }}
    {{ $resized := .Fill "150x115 q20" }}
    &amp;lt;li&amp;gt;{{ .Permalink }}
        {{ $resized.Permalink }}&amp;lt;/li&amp;gt;
    {{ end }}
    &amp;lt;/ul&amp;gt;
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate the following HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;http://localhost:1313/galleries/gallery1/img/Pope-Edouard-de-Beaumont-1844.jpg
    http://localhost:1313/galleries/gallery1/img/Pope-Edouard-de-Beaumont-1844_hu28e98c35df2fb9cf55bbe1469dad4e9d_67722_150x115_fill_q20_box_smart1.jpg&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;http://localhost:1313/galleries/gallery1/img/Victor_Hugo-Hunchback.jpg
    http://localhost:1313/galleries/gallery1/img/Victor_Hugo-Hunchback_hu0047bfedff79a029a47406b9671745f3_111947_150x115_fill_q20_box_smart1.jpg&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;http://localhost:1313/galleries/gallery1/img/esmeralda.jpg
    http://localhost:1313/galleries/gallery1/img/esmeralda_hueb36a26f61b343d8dba9f5eda0997ef5_54891_150x115_fill_q20_box_smart1.jpg&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;http://localhost:1313/galleries/gallery1/img/notebook.jpg
    http://localhost:1313/galleries/gallery1/img/notebook_hu3d03a01dcc18bc5be0e67db3d8d209a6_1586565_150x115_fill_q20_box_smart1.jpg&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in my real test project, I ignored the whole &lt;code&gt;resources/_gen&lt;/code&gt; directory in source control. The Hugo docs &lt;a href="https://gohugo.io/content-management/image-processing/#image-processing-performance-consideration" rel="noopener noreferrer"&gt;tell me to commit the manipulated images&lt;/a&gt;, but I chose to ignore them because IMO they’re some kind of build artifact (like an executable, which I wouldn’t commit either).&lt;/p&gt;

&lt;p&gt;For the local developer experience it doesn’t matter: on my local machine, Hugo only generates the images on the first build (because it doesn’t delete them before subsequent builds).&lt;br&gt;&lt;br&gt;
And the deploy will happen via CI, so I don’t care if the CI server builds a few seconds longer.&lt;/p&gt;

&lt;p&gt;However, in the demo project linked here, I &lt;strong&gt;did&lt;/strong&gt; commit the generated images.&lt;/p&gt;


&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;With all the “ingredients” prepared, we can now &lt;a href="https://github.com/christianspecht/code-examples/commit/9d50daeb28c3b46e58a4dd191ee7e38f633c5e95" rel="noopener noreferrer"&gt;show the actual image gallery&lt;/a&gt; with &lt;a href="https://lokeshdhakar.com/projects/lightbox2/" rel="noopener noreferrer"&gt;Lightbox2&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/layouts/shortcodes/gallery.html&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.1/css/lightbox.min.css" /&amp;gt;
&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.1/js/lightbox.min.js"&amp;gt;&amp;lt;/script&amp;gt;

{{ $image := (.Page.Resources.ByType "image") }}
{{ with $image }}
    {{ range . }}
    {{ $resized := .Fill "150x115 q20" }}
    &amp;lt;a href="{{ .Permalink }}" data-lightbox="x"&amp;gt;&amp;lt;img src="{{ $resized.Permalink }}" /&amp;gt;&amp;lt;/a&amp;gt;
    {{ end }}
{{ end }}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HTML output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.1/css/lightbox.min.css" /&amp;gt;
&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src="https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.11.1/js/lightbox.min.js"&amp;gt;&amp;lt;/script&amp;gt;

&amp;lt;a href="http://localhost:1313/galleries/gallery1/img/Pope-Edouard-de-Beaumont-1844.jpg" data-lightbox="x"&amp;gt;&amp;lt;img src="http://localhost:1313/galleries/gallery1/img/Pope-Edouard-de-Beaumont-1844_hu28e98c35df2fb9cf55bbe1469dad4e9d_67722_150x115_fill_q20_box_smart1.jpg" /&amp;gt;&amp;lt;/a&amp;gt;

&amp;lt;a href="http://localhost:1313/galleries/gallery1/img/Victor_Hugo-Hunchback.jpg" data-lightbox="x"&amp;gt;&amp;lt;img src="http://localhost:1313/galleries/gallery1/img/Victor_Hugo-Hunchback_hu0047bfedff79a029a47406b9671745f3_111947_150x115_fill_q20_box_smart1.jpg" /&amp;gt;&amp;lt;/a&amp;gt;

&amp;lt;a href="http://localhost:1313/galleries/gallery1/img/esmeralda.jpg" data-lightbox="x"&amp;gt;&amp;lt;img src="http://localhost:1313/galleries/gallery1/img/esmeralda_hueb36a26f61b343d8dba9f5eda0997ef5_54891_150x115_fill_q20_box_smart1.jpg" /&amp;gt;&amp;lt;/a&amp;gt;

&amp;lt;a href="http://localhost:1313/galleries/gallery1/img/notebook.jpg" data-lightbox="x"&amp;gt;&amp;lt;img src="http://localhost:1313/galleries/gallery1/img/notebook_hu3d03a01dcc18bc5be0e67db3d8d209a6_1586565_150x115_fill_q20_box_smart1.jpg" /&amp;gt;&amp;lt;/a&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is it - this HTML will display the thumbnails, and clicking on them will open the actual images with Lightbox2.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note: there is &lt;a href="https://github.com/christianspecht/code-examples/commit/bcbda057b832681e214d706b3a3c070798dfa6c7" rel="noopener noreferrer"&gt;one additional commit with a CSS file&lt;/a&gt; in the demo repo - this is because of the Ananke theme.&lt;br&gt;&lt;br&gt;
Without that tweak, Ananke’s default CSS would stretch all images to the page width and show each one in a new row…which doesn’t make any sense for thumbnails.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So far, I like what I’ve seen from Hugo.&lt;br&gt;&lt;br&gt;
As I said before, I find the learning curve insanely steep compared to Jekyll &lt;em&gt;(and the docs don’t explain the basic concepts enough, so my main problem was not knowing the exact terms for the things I was looking for)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;But once I got past that stage, working with Hugo is very nice because it comes with a lot of stuff built-in, which I needed to do manually in Jekyll, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;getting a list of images belonging to a specific page (although I &lt;em&gt;think&lt;/em&gt; Jekyll has improved in the meantime - I wrote the “Jekyll image gallery” posts in 2014)&lt;/li&gt;
&lt;li&gt;automatic &lt;a href="https://gohugo.io/content-management/organization/#index-pages-_indexmd" rel="noopener noreferrer"&gt;index pages&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;and, of course, auto-generating thumbnails!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hugo</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Creating custom OpenStreetMap tiles for my own tile server</title>
      <dc:creator>Christian Specht</dc:creator>
      <pubDate>Thu, 29 Aug 2019 21:00:00 +0000</pubDate>
      <link>https://dev.to/christianspecht/creating-custom-openstreetmap-tiles-for-my-own-tile-server-366p</link>
      <guid>https://dev.to/christianspecht/creating-custom-openstreetmap-tiles-for-my-own-tile-server-366p</guid>
      <description>&lt;p&gt;Once a year, I need to create a printable map with markers.&lt;/p&gt;

&lt;p&gt;The map is for &lt;a href="https://sindorf-troedelt.de/" rel="noopener noreferrer"&gt;Sindorf trödelt&lt;/a&gt;, a website which I built for an annual garage sale in my hometown.&lt;/p&gt;

&lt;p&gt;Users can register with their addresses, and I use Google Maps to show them on &lt;a href="https://sindorf-troedelt.de/karte/" rel="noopener noreferrer"&gt;this map&lt;/a&gt; on the website.&lt;/p&gt;

&lt;p&gt;The map is not active the whole year, so here’s a picture:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchristianspecht.de%2Fimg%2Fsindorf-map.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchristianspecht.de%2Fimg%2Fsindorf-map.png" title="map with markers" alt="map with markers" width="330" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The organizers of the garage sale also need to print flyers and posters with this map, but using the exact same Google Map for this doesn’t make sense, since there’s a limit how many copies you are allowed to print with the free version of Google Maps.&lt;/p&gt;

&lt;p&gt;Everybody involved in the garage sale is doing this in their spare time &lt;em&gt;(me included, and I’m sponsoring hosting as well)&lt;/em&gt; so there’s simply no budget to pay for a Google Maps license which would allow to print more copies.&lt;/p&gt;




&lt;h2&gt;
  
  
  First solution
&lt;/h2&gt;

&lt;p&gt;So I needed an alternative way to create a map for printing, and I managed to get something to work with &lt;a href="https://openlayers.org/" rel="noopener noreferrer"&gt;OpenLayers&lt;/a&gt; and &lt;a href="https://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OpenStreetMap&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;I based my code on some example code for a OpenLayers map with markers which I found online (I don’t have the link anymore) - it works well, &lt;a href="https://jsfiddle.net/yw1d3vjc/" rel="noopener noreferrer"&gt;here’s a simplified JSFiddle&lt;/a&gt; &lt;em&gt;(right-click on the map and save as image)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But after the first flyers were printed, one problem came up: the font size for the street names was too small, the names weren’t readable anymore.&lt;/p&gt;

&lt;p&gt;That was two years ago. Last year I didn’t have time to find a better solution, so they just printed bigger flyers :-)&lt;/p&gt;

&lt;p&gt;This year, I finally had time to investigate this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running my own tile server
&lt;/h2&gt;

&lt;p&gt;Sounds impressive, doesn’t it?&lt;br&gt;&lt;br&gt;
When you search for things like “how to increase font size in &lt;em&gt;[random web mapping solution]&lt;/em&gt;”, you quickly find that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;most solutions use pre-made tiles by 3rd party providers, which are provided as images&lt;/li&gt;
&lt;li&gt;to change the appearance of those tiles, you either need to change the provider or run your own tile server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running my own tile server sounded scary, so at first I didn’t investigate this, but focused on creating the whole image with different tools.&lt;/p&gt;

&lt;p&gt;In the end, I found &lt;a href="http://maperitive.net/" rel="noopener noreferrer"&gt;Maperitive&lt;/a&gt;, which has its own scripting language, so it’s possible to &lt;a href="http://maperitive.net/docs/Command_Line.html" rel="noopener noreferrer"&gt;write a text file with commands and execute it in Maperitive to create a map image from scratch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While playing around with this, I found &lt;a href="https://wiki.openstreetmap.org/wiki/Wanderkarte_Steyregg/MaperitiveScript" rel="noopener noreferrer"&gt;example code&lt;/a&gt; which saves the &lt;a href="http://maperitive.net/docs/Commands/GenerateTiles.html" rel="noopener noreferrer"&gt;map as tiles &lt;em&gt;(=image files)&lt;/em&gt;&lt;/a&gt; and &lt;a href="http://maperitive.net/docs/Commands/FtpUpload.html" rel="noopener noreferrer"&gt;uploads them via FTP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That was my “aha” moment: a tile server is not a complex piece of software, but just a regular web server which serves pre-generated image files over HTTP.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I just needed some try-and-error, until the tiles looked the way I wanted them to.&lt;/p&gt;

&lt;p&gt;Here are the steps to do it with Maperitive (I used v2.4.3.0):&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Creating my own ruleset
&lt;/h3&gt;

&lt;p&gt;A ruleset is &lt;a href="http://maperitive.net/docs/Rulesets.html" rel="noopener noreferrer"&gt;a file with Maperitive rendering rules&lt;/a&gt;. The look of the map can be changed in Maperitive (colors, details, text appearance), just by &lt;a href="http://maperitive.net/docs/Rulesets.html#Switching%20Between%20Rulesets" rel="noopener noreferrer"&gt;providing a different ruleset&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I needed something "simple" (without many details which clutter the view), so I looked at all the rulesets and picked the &lt;a href="http://maperitive.net/docs/Rulesets.html#Google%20Maps%20Ruleset" rel="noopener noreferrer"&gt;Google Maps ruleset&lt;/a&gt;. This is just a file in the &lt;code&gt;Rules&lt;/code&gt; subfolder in the Maperitive application folder.&lt;/p&gt;

&lt;p&gt;I want bigger fonts for the streets...so I copied that file, searched for &lt;code&gt;font-size&lt;/code&gt; and increased some values for the street types that sounded right (some experimentation necessary). &lt;a href="https://gist.github.com/christianspecht/9826de05c0e58d46ef23c934269582aa/revisions?diff=split" rel="noopener noreferrer"&gt;Here's a gist with the changes&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Getting OpenStreetMap data as XML
&lt;/h3&gt;

&lt;p&gt;Quote from Maperitive's &lt;a href="http://maperitive.net/docs/TenMinutesIntro.html#Loading%20Map%20Data" rel="noopener noreferrer"&gt;Ten Minutes Intro ⇒ Loading Map Data&lt;/a&gt;: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using the browser, go to &lt;a href="http://www.openstreetmap.org/" rel="noopener noreferrer"&gt;OSM map&lt;/a&gt;, choose a smallish area (let's say the size of a few city blocks) and then click on the &lt;strong&gt;Export&lt;/strong&gt; tab. Select the &lt;strong&gt;OpenStreetMap XML Data&lt;/strong&gt; radio button value, click on the &lt;strong&gt;Export&lt;/strong&gt; button and you'll receive a file called &lt;em&gt;map.osm&lt;/em&gt; from the server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  3. Writing a Maperitive script
&lt;/h3&gt;

&lt;p&gt;This code goes into a text file with .mscript extension:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// this is the ruleset from step 1
use-ruleset location=sindorftiles.mrules

// this is the OpenStreetMap XML file from step 2
load-source sindorf.osm

// move and zoom the map, so that the whole town is visible on the screen
move-pos x=6.674573 y=50.904049 zoom=16

// now we could export the map to a file:
// export-bitmap file=map.png height=2000 width=1600

// ...but we want to generate tiles instead:
generate-tiles minzoom=16 maxzoom=16 subpixel=3 bounds=6.6554,50.8898,6.6924,50.9191

// upload via FTP:
ftp-upload host=tiles.sindorf-troedelt.de user=USERNAME pwd=PASSWORD remote-dir=/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;A few notes about the &lt;code&gt;generate-tiles&lt;/code&gt; command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;minzoom&lt;/code&gt; and &lt;code&gt;maxzoom&lt;/code&gt; should match the zoom level from the &lt;code&gt;move-pos&lt;/code&gt; command&lt;/li&gt;
&lt;li&gt;generating tiles for one single zoom level is enough in this case - we just need &lt;strong&gt;this view&lt;/strong&gt; of the map for printing, no more zooming necessary. &lt;a href="http://maperitive.net/docs/Commands/GenerateTiles.html#Performance%20And%20Storage%20Considerations" rel="noopener noreferrer"&gt;The more zoom levels, the more tiles are generated!&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For me, the FTP upload isn't really necessary because generating the tiles is a one-time thing. Uploading the tiles manually would be acceptable as well...but still, it's nice that Maperitive can do it.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Opening Maperitive and executing the script:
&lt;/h3&gt;

&lt;p&gt;This is just a two-liner, calling Maperitive and passing the script from step 3:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rd /s /q Tiles
C:\Maperitive\Maperitive.exe "%~dp0\sindorftiles.mscript" -exitafter 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;I put this into a batch file, and copied all the files mentioned before to the same directory where the batch file is.&lt;/p&gt;



&lt;p&gt;That’s it - running this will open Maperitive, which will generate the tiles, save them into the &lt;code&gt;\Tiles&lt;/code&gt; subfolder and upload them to the web server.&lt;/p&gt;

&lt;p&gt;Here is an example - on the left side an original OpenStreetMap tile, on the right side the same tile generated with Maperitive and my custom ruleset:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchristianspecht.de%2Fimg%2Fsindorf-tiles.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fchristianspecht.de%2Fimg%2Fsindorf-tiles.png" title="tiles before and after" alt="tiles before and after" width="519" height="256"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Loading my own tiles with OpenLayers
&lt;/h2&gt;

&lt;p&gt;Back to the &lt;a href="https://jsfiddle.net/yw1d3vjc/" rel="noopener noreferrer"&gt;JSFiddle from the beginning&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;At the bottom of the &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; block, there’s this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({
      source: new ol.source.OSM()
    })
    ,vectorLayer1
  ],
  view: new ol.View({
    center: ol.proj.fromLonLat([6.674573,50.904049]),
    zoom: 16
  })
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To change this in order to use my custom tiles, I just needed to use the &lt;a href="https://openlayers.org/en/latest/examples/xyz.html" rel="noopener noreferrer"&gt;XYZ source&lt;/a&gt; instead of the OpenStreetMap source which I’m using in the JSFiddle.&lt;/p&gt;

&lt;p&gt;So it’s simply changing this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source: new ol.source.OSM()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;…into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source: new ol.source.XYZ({url: 'https://tiles.sindorf-troedelt.de/{z}/{x}/{y}.png'})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/{z}/{x}/{y}.png&lt;/code&gt; matches the folder structure and file names generated by Maperitive’s &lt;code&gt;generate-tiles&lt;/code&gt; command.&lt;/p&gt;

</description>
      <category>commandline</category>
      <category>maps</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
