Skip to main content
ImgForge package icon

ImgForge

v0.2.0Latest Stable
Any Framework

Generate blog and YouTube thumbnail images from HTML templates.

Published
Oct 8, 2026
24d ago
Total Downloads
912
912

Install

dotnet add package ImgForge --version 0.2.0

No framework information available.

README

ImgForge

A .NET CLI tool for generating blog and YouTube thumbnail images from HTML templates.

Read the full docs

Getting Started

Install as a global tool:

dotnet tool install --global ImgForge

Or run directly from NuGet via DNX (requires -- before the arguments that should be sent to imgforge):

dnx -y imgforge -- generate --help

Quick Examples

Generate a blog post Open Graph image with a random background:

dnx -y imgforge -- generate --template blog --title "Getting Started with ImgForge" --bg random --format blog

Create a YouTube thumbnail:

dnx -y imgforge -- generate --template youtube --title "How to Build Better Software" --bg random --format youtube

Generate a blog card with subtitle and headshot:

dnx -y imgforge -- generate --template blog-subtitle \
  --title "Advanced .NET Patterns" \
  --subtitle "A practical guide" \
  --bg random \
  --headshot ./photo.jpg \
  --format blog

All three built-in templates (blog, youtube, blog-subtitle) work immediately without any additional setup. Chromium is downloaded automatically on first run.

How It Works

ImgForge follows a three-stage pipeline:

  1. Template rendering β€” An HTML template (built-in or custom) is loaded and variable placeholders are replaced with the supplied values (title, background, dimensions, overlays) using Scriban.
  2. Browser rendering β€” The resulting HTML is loaded into a headless Chromium browser via PuppeteerSharp, which applies all CSS layout and styling exactly as a real browser would.
  3. Screenshot capture β€” PuppeteerSharp screenshots the rendered page at the specified viewport size and writes a PNG to the output path.
GenerateOptions
      β”‚
      β–Ό
TemplateRenderer  (Scriban: injects title, bg, width, height, overlays into HTML)
      β”‚
      β–Ό
    HTML string
      β”‚
      β–Ό
ImageGenerator    (PuppeteerSharp: loads HTML in headless Chromium, screenshots to PNG)
      β”‚
      β–Ό
    output.png

Dependencies

Package Role
Scriban Liquid-syntax template engine β€” substitutes {{ title }}, {{ bg }}, {{ width }}, {{ height }}, and {% for img in overlays %} in HTML templates
PuppeteerSharp Headless Chromium driver β€” renders the HTML with full CSS support and captures a pixel-perfect screenshot. Chromium is downloaded automatically on first run.
System.CommandLine Parses CLI arguments and subcommands
xunit Test framework

Usage

imgforge generate \
  --template blog \             # built-in name ("blog", "youtube") or path to a .html file or folder (with a template.html file in it)
  --title "Modular Monoliths Done Right" \
  --subtitle "A practical guide" \  # optional subtitle rendered below the title in templates that support it
  --bg ./images/cover.jpg \     # local path, HTTP(S) URL, or "random" (fetches a random image from picsum.photos); optional
  --overlay ./logo.png \        # optional; repeat for multiple overlays
  --headshot ./guest.jpg \      # optional guest headshot; placed in a styled circle
  --headshot-filter blue-mono \ # built-in: blue-mono (default), mono, none β€” or a raw CSS filter string
  --var season=3 \              # arbitrary key=value pairs injected as {{ vars.season }} in templates; repeat for multiple
  --format podcast-episode \    # sets width/height from a named preset (see formats below)
  --out og.png \                # output file path; defaults to title slug in current directory
  --out-dir ./output \          # output directory; filename derived from --title; ignored if --out is provided
  --width 1200 \                # explicit width β€” overrides --format; prompted if neither is given
  --height 630                  # explicit height β€” overrides --format; prompted if neither is given

All flags

Flag Required Default Description
--template Yes β€” Built-in template name (blog, youtube, blog-subtitle) or path to a .html file or a directory containing template.html
--title Yes β€” Main heading text injected into the template
--subtitle No β€” Optional subtitle rendered below the title in templates that support it (e.g. blog-subtitle)
--bg No β€” Background image: local file path, HTTP(S) URL, or random (fetches a random image from picsum.photos)
--overlay No β€” Overlay image path. Repeatable for multiple overlays
--headshot No β€” Guest headshot image path placed in the template's headshot slot
--headshot-filter No blue-mono Filter applied to the headshot. Built-in: blue-mono, mono, none. Or supply a raw CSS filter string
--var No β€” Arbitrary template variable as key=value (e.g. --var episode=42). Accessible in templates as {{ vars.episode }}. Repeatable
--format No β€” Output format preset that sets width and height. Choices: youtube, blog, github, podcast-show, podcast-episode. Explicit --width/--height override the preset
--out No title slug .png Output PNG file path
--out-dir No . Output directory. Filename is derived from --title. Ignored if --out is provided
--width No 1200 Viewport width in pixels. Overrides --format
--height No 630 Viewport height in pixels. Overrides --format

Format presets

Use --format <name> instead of --width/--height. Explicit dimensions always override the preset. If neither --format nor explicit dimensions are provided, the tool prompts you interactively.

--format value Width Height Use case
youtube 1280 720 YouTube video thumbnail
blog / og 1200 630 Blog post / Open Graph social card
github 1280 640 GitHub repository social preview
podcast-show 3000 3000 Podcast show art
podcast-episode 3000 3000 Podcast episode art

Image Dimension Reference

Ideal dimensions vary by platform and use case. Use --width and --height to match:

Use Case Width Height Aspect Ratio Built-in Template Repository Template Notes
YouTube Video Thumbnail 1280 720 16:9 youtube youtube-bold.html Minimum 640Γ—360; displayed at up to 1280Γ—720
Blog / Open Graph (og:image) 1200 630 ~1.91:1 blog, blog-subtitle blog-gradient.html Recommended by Facebook, LinkedIn, Slack, and most social crawlers. Twitter also accepts this size.
GitHub Repository Social Preview 1280 640 2:1 β€” github-social.html Displayed at 640Γ—320 on repository pages
Podcast Show Art 3000 3000 1:1 β€” podcast-show.html Apple Podcasts minimum 1400Γ—1400; Spotify and most platforms prefer 3000Γ—3000, max 512 KB
Podcast Episode Art 3000 3000 1:1 β€” podcast-episode.html Same requirements as show art; some hosts fall back to show art if omitted

Quick reference commands

# YouTube thumbnail (using --format preset)
imgforge generate --template youtube --title "My Video Title" --bg ./bg.jpg --out thumb.png --format youtube

# YouTube thumbnail with a random background
imgforge generate --template youtube --title "My Video Title" --bg random --out thumb.png --format youtube

# YouTube thumbnail with guest headshot (blue monochrome filter)
imgforge generate --template youtube --title "Building Better APIs" \
  --headshot ./guest.jpg --out thumb.png --format youtube

# Blog / Open Graph card
imgforge generate --template blog --title "My Post Title" --bg ./cover.jpg --out og.png --format blog

# NimblePros blog card with subtitle and headshot
imgforge generate --template blog-subtitle --title "Next Level AI Agents" \
  --subtitle "MCP Servers" --bg ./cover.jpg \
  --headshot ./logo.png --format blog --out og.png

# GitHub social preview
imgforge generate --template templates/github-social.html --title "my-repo" --bg ./cover.jpg --out social-preview.png --format github

# Podcast show art
imgforge generate --template templates/podcast-show.html --title "My Podcast" --out podcast-show.png --format podcast-show

# Podcast episode art with guest headshot, season/episode labels, and named show in footer
imgforge generate --template templates/podcast-episode.html \
  --title "Build vs Buy with guest Ardalis" \
  --headshot ./guest.jpg --headshot-filter blue-mono \
  --var season=3 --var episode=42 --var show="It Depends by NimblePros" \
  --out podcast-episode.png --format podcast-episode

# Podcast episode art β€” greyscale headshot instead of blue tint
imgforge generate --template templates/podcast-episode.html --title "My Episode Title" \
  --headshot ./guest.jpg --headshot-filter mono --out podcast-episode.png --format podcast-episode

# Write output to a specific directory (filename derived from title)
imgforge generate --template blog --title "My Post Title" --bg ./cover.jpg --out-dir ./output --format blog

# Omit --format and --width/--height to be prompted interactively
imgforge generate --template templates/podcast-episode.html --title "My Episode Title" --out out.png

# Omit --out and --out-dir to use title as the output filename in the current directory
imgforge generate --template youtube --title "This is the title of the show" --format youtube

Built-in Templates

ImgForge includes 3 embedded templates that are always available when you install the tool:

Template Name Default Dimensions Description Usage
blog 1200Γ—630 Open Graph / social preview card with centered title --template blog
youtube 1280Γ—720 YouTube thumbnail with bold title styling --template youtube
blog-subtitle 1200Γ—630 Open Graph card with faded background, optional subtitle, and headshot support --template blog-subtitle

These templates are embedded in the tool's DLL and work immediately after installationβ€”no need to download template files.

Example Usage

# Simple blog Open Graph image
imgforge generate --template blog --title "My Blog Post" --bg random --format blog

# YouTube thumbnail
imgforge generate --template youtube --title "My Video Title" --bg random --format youtube

# Blog card with subtitle and headshot
imgforge generate --template blog-subtitle \
  --title "Building Modular Monoliths" \
  --subtitle "A Practical Guide" \
  --bg ./cover.jpg \
  --headshot ./author.jpg \
  --format blog

Additional Templates in Repository

The repository's templates/ folder contains additional example templates (like youtube-bold.html, podcast-episode.html, github-social.html) that are not embedded in the tool. To use these, either:

  1. Copy them from the repository and reference by file path:

    imgforge generate --template ./templates/youtube-bold.html --title "My Title" --format youtube
    
  2. Clone the repo and run from source to access all example templates

Custom Templates

Any .html file (or a folder with a template.html file in it) can be used as a template. If you omit --template, ImgForge looks for a default template starting in the current directory, following the .tools/ directory spec:

  1. .tools/imgforge/template.html (recommended)
  2. .imgforge/template.html (legacy location, still supported)

If neither exists in the current directory and you are inside a git repository, ImgForge checks each parent directory in turn, up to and including the repository root (the nearest directory containing .git), so you can run it from any subdirectory. It never searches above the repository root, and outside a repository only the current directory is checked. The nearest match wins; within a single directory, .tools/imgforge/template.html takes precedence over .imgforge/template.html.

Scriban Liquid syntax is supported for variable injection:

<html>
<body style="width:{{ width }}px; height:{{ height }}px; margin:0;
             background-image:url('{{ bg }}'); background-size:cover;
             display:flex; align-items:center; justify-content:center;
             font-family:sans-serif; color:white;">
  <h1 style="font-size:64px; text-align:center;">{{ title }}</h1>
  {% for img in overlays %}
  <img src="{{ img.src }}" style="position:absolute; {{ img.style }}" />
  {% endfor %}
  {% if headshot %}
  <img src="{{ headshot.src }}" style="filter:{{ headshot.filter_css }};" />
  {% endif %}
  <p>Season {{ vars.season }}, Episode {{ vars.episode }}</p>
</body>
</html>

Template variables

Variable Type Description
title string Main heading text
subtitle string Optional subtitle text β€” empty string when --subtitle is not supplied; guard with {% if subtitle %} in templates
bg string Background image URI β€” local file:/// paths, HTTP(S) URLs, and random picsum URLs are all resolved before injection
width int Viewport width in pixels
height int Viewport height in pixels
overlays array List of { src, style } overlay image objects
headshot object or null Guest headshot β€” exposes headshot.src (file URI) and headshot.filter_css (CSS filter string). null when --headshot is not supplied
vars object Arbitrary key/value pairs supplied via --var key=value. Access as {{ vars.key }}

Headshot filters

Pass a name to --headshot-filter or supply any raw CSS filter string.

Name CSS applied Effect
blue-mono (default) grayscale(100%) sepia(100%) hue-rotate(190deg) saturate(300%) brightness(1.15) Light-blue monochrome β€” "black and white but in shades of blue"
mono grayscale(100%) True greyscale
none none No filter; original colours kept
(custom) (your string) Any valid CSS filter string, e.g. sepia(80%) hue-rotate(270deg)

Custom template directories

When you pass a directory path to --template, ImgForge looks for template.html inside it and injects a <base> tag so that relative image references (e.g. a watermark sitting next to template.html) resolve correctly.

my-template/
  template.html
  watermark.png   ← referenced as just "watermark.png" inside the template
imgforge generate --template ./my-template --title "Hello" --format blog

Build and Test

dotnet build

# Unit tests (no browser required)
dotnet test --filter "Category!=Integration"

# Full test suite including integration tests (Chromium is downloaded automatically on first run)
dotnet test

Publishing a Release

Releases are published to NuGet.org automatically by the Publish to NuGet workflow when a GitHub release is published. The package version comes from the release tag; the <Version> in src/ImgForge/ImgForge.csproj is a 0.0.0-local placeholder for local builds and does not need to be bumped.

  1. Make sure main contains everything for the release and passes dotnet BuildTestFormat.cs (build, tests, and format check).

  2. Create a GitHub release with a new tag in the form vX.Y.Z (or vX.Y.Z-suffix for a prerelease, e.g. v1.0.0-beta.1), targeting main. The version must be higher than the latest one on NuGet.org. Write the release notes on the release β€” the NuGet package's release notes link to it.

    gh release create v0.1.7 --target main --generate-notes
    
  3. Publishing the release triggers the workflow, which validates the tag, builds and tests with that version, packs, and pushes to NuGet.org. Tags that aren't valid SemVer (e.g. v0.1.5a, v1.2) fail before anything is pushed.

Running the workflow manually (Actions β†’ Publish to NuGet β†’ Run workflow) performs a dry run: it builds, tests, and packs a 0.0.0-ci.N package but never pushes it.

Install as a Global Tool

dotnet tool install --global ImgForge
imgforge generate --template blog --title "Hello!" --bg random --format blog

Chromium is downloaded automatically on the first run β€” no separate install step needed.

Run Directly from NuGet via DNX

You can run ImgForge without installing it globally:

# Simple example
dnx -y imgforge -- generate --template blog --title "Hello World" --bg random --format blog

# YouTube thumbnail
dnx -y imgforge -- generate --template youtube --title "My Video" --bg random --format youtube

# Blog post with subtitle
dnx -y imgforge -- generate --template blog-subtitle \
  --title "Advanced Techniques" \
  --subtitle "Learn the secrets" \
  --bg random \
  --format blog

The -y flag automatically accepts any prompts, making it perfect for quick one-off image generation.

Changes from Previous Version

No structural changes between v0.1.6 and v0.2.0.

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

No target framework metadata is available. Confirm compatibility in a representative project before standardising on this package.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.