Setting up a blog with jekyll is super easy. It’s also easy to use Azure Pipelines to build Jekyll sites in a continuous integration pipeline. Most Jekyll blog templates are designed to use a large hero image as the feature image at the top of the post. Indeed, the template used for this blog has such a design. Typical of the same templates is to also generate a home page with the posts and their thumnail images in a grid or column layout.
There’s jekyll plugins that generate images with different dimensions, including jekyll-minimagick. I found the documentation is a little lacking for jekyll-minimagick, however the examples in the project’s README gave me enough to work from, to generate images for my homepage thumbnails.
Social media cards
I want to go one step further, and design the site’s posts to be optimised for social media. To start, I want to focus on the Facebook and Twitter cards. For my test case, we have a jekyll site for a podcast I’m working on. I want to ensure whenever a post from the podcast is shared on either of the social media site, the podcast’s name, colour scheme, and logo, is present.
The jekyll-minimagick plugin uses the minimagick ruby library to perform its image manipulation. And the minimagick library in turn uses the ImageMagick command-line tool. Unfortunately, jekyll-minimagick only exposes a sub-set of minimagick’s capability, and some of the image manipulation tasks I want to perform, needs something more.
Optimised image dimensions
I specifically want my social media images to be sized to the optimal dimensions for Facebook and Twitter. While I couldn’t find a definitive guide, trial and error with social media site’s respective card validation tools show the following dimensions are suitable:
Width | Height | |
---|---|---|
1080px | 570px | |
800px | 420px |
Overlayed site branding
The social media images should also be branded with the site’s name, logo, and colour scheme. This can be acheived by a super-imposing a strap spanning the full width of the image.
The end result needs to look something like this:
Manipulating images with ImageMagick
The challenge with front-matter images, for most jekyll templates, is their source must be wide enough to span the width of most wide-screen monitors. When sourcing these images, an author typically doesn’t have an extensive image library available. If they’re anything like me, they’ll use DuckDuckGo’s or Google’s image search function, and select the option to search for images that are explicitly marked for free re-use.
Given an image of any width and any height, the simple operation of scaling the image down to the optimised size won’t be enough. The width-to-height ratio of any arbitrary image will not match the required image dimensions of either social media card.
To get to the desired dimensions, three ImageMagick operations need to be completed:
- Resize - to meet the minimum dimensions (1080x570px or larger for Facebook, and 800x420px or larger for Twitter)
- Composite - take an image with a transparency (a PNG formatted image), overlay it on top of the post’s image
- Crop - trim the edges, so the final image is the correct dimensions without any unnecessarily compromised quality
Unfortunately, the jekyll-minimagick plugin does not support all these operations, so instead we have to create our own plugin.
Creating a custom jekyll plugin
There are six different types of Jekyll plugins. The plugin we want to create is of generator type, so it inherits from the Generator class. Like the jekyll-minimagick plugin, it should take the source images from a configured folder, and output the generated images to a configured destination folder.
The new plugin will use functionality from the mini_magick
library, so we’ll require that at the top.
To add the new plugin to the jekyll site, we’ll create it as a Ruby (.rb) file called magick-composite.rb
, and place it in the jekyll site’s _plugins
folder.
require 'mini_magick'
module Jekyll
module MagickComposite
class MagickComposite < Jekyll::StaticFile
# Initialize a new GeneratedImage.
# +site+ is the Site
# +base+ is the String path to the <source>
# +dir+ is the String path between <source> and the file
# +name+ is the String filename of the file
# +preset+ is the Preset hash from the config.
#
# Returns <MagickComposite>
def initialize(site, base, dir, name, preset)
@site = site
@base = base
@dir = dir
@name = name
@crop = preset.delete('crop')
@composite = preset.delete('composite')
@dst_dir = preset.delete('destination')
@src_dir = preset.delete('source')
@commands = preset
end
# Obtains source file path by substituting the preset's source directory
# for the destination directory.
#
# Returns source file path.
def path
File.join(@base, @dir.sub(@dst_dir, @src_dir), @name)
end
# Obtains source file path by substituting the preset's source directory
# for the destination directory.
#
# Returns source file path.
def overlaypath
File.join(@base, @composite)
end
# Use MiniMagick to create a derivative image at the destination
# specified (if the original is modified).
# +dest+ is the String path to the destination dir
#
# Returns false if the file was not modified since last time (no-op).
def write(dest)
dest_path = destination(dest)
# return false if File.exist? dest_path and !modified?
self.class.mtimes[path] = mtime
FileUtils.mkdir_p(File.dirname(dest_path))
image = ::MiniMagick::Image.open(path)
compositeimage = ::MiniMagick::Image.open(overlaypath)
result = image.composite(compositeimage) do |c|
@commands.each_pair do |command, arg|
c.send command, arg
end
end
@commands.each_pair do |command, arg|
result.send command, arg
end
result.write dest_path
if @crop
cropimage = ::MiniMagick::Image.open(dest_path)
cropimage.combine_options do |c|
@commands.each_pair do |command, arg|
c.send command, arg
end
c.crop @crop
end
cropimage.write dest_path
end
true
end
end
class ImageOverlayGenerator < Generator
safe true
# Find all image files in the source directories of the presets specified
# in the site config. Add a MagickComposite to the static_files stack
# for later processing.
def generate(site)
return unless site.config['magick_composite']
site.config['magick_composite'].each_pair do |name, preset|
Dir.glob(File.join(site.source, preset['source'], "*.{png,jpg,jpeg,gif}")) do |source|
site.static_files << MagickComposite.new(site, site.source, preset['destination'], File.basename(source), preset.clone)
end
end
end
end
end
end
Next we’ll create a PNG image file with a transparent background, to use as the overlay. This image file will be created at a size of 2400x2400px, and only the bottom 400px has the strap we’re looking to overlay across the bottom. It doesn’t matter where it’s located but for the purposes of this example, it is named 365daysofcloud-socials_overlay.png
and located in the jekyll site’s assets/img/2400x2400/
folder.
Finally, we’ll configure the new plugin, by including the following in to the jekyll site’s _config.yml
file.
This can be added to the bottom of the site’s existing _config.yml
file:
magick_composite:
social800:
source: assets/img/source
destination: assets/img/socials-800x420
composite: assets/img/2400x2400/365daysofcloud-socials_overlay.png
resize: "800x420^"
crop: "800x420+0+0"
gravity: south
social1080-square:
source: assets/img/source
destination: assets/img/socials-1080x1080
composite: assets/img/2400x2400/365daysofcloud-socials_overlay.png
resize: "1080x1080^"
crop: "1080x1080+0+0"
gravity: south
social1080-wide:
source: assets/img/source
destination: assets/img/socials-1080x570
composite: assets/img/2400x2400/365daysofcloud-socials_overlay.png
resize: "1080x570^"
crop: "1080x570+0+0"
gravity: south
Values of the _config.yml
file’s resize
, crop
, and gravity
parameters are passed all the way through to the ImageMagick command-line, so the ImageMagick help documentation covers their usage:
- Resize and Crop - parameters conform to the Image Geometry command-line option
- Gravity - parameters conform to the Gravity command-line option
In a later post, I’ll describe how to use the images generated by the new plugin in the site’s <meta>
tags.