Home

Awesome

ImageWizard

A ASP.NET Core service / middleware to resize your images on the fly as alternative for thumbor.

License: MIT NuGet Docker

Demo: imagewizard.net

Features

Example

https://localhost/image/cGiAwFYGYWx0SzO0YyCidWIfkdlUYrVgBwbm7bcTOjE/resize(200,200)/grayscale()/jpg(90)/fetch/https://upload.wikimedia.org/wikipedia/commons/b/b7/Europe_topography_map.png

DescriptionUrl segment
base path"image"
signature based on HMACSHA256"cGiAwFYGYWx0SzO0YyCidWIfkdlUYrVgBwbm7bcTOjE" or "unsafe" (if enabled)
any filters"resize(200,200)/grayscale()/jpg(90)"
loader type"fetch"
loader sourcehttps://upload.wikimedia.org/wikipedia/commons/b/b7/Europe_topography_map.png

Loader

NameLoader typeLoader sourceNuGet
Http loaderfetchabsolute or relative urlNuGet
File loaderfilerelative path to fileNuGet
YouTube loaderyoutubevideo idNuGet
Gravatar loadergravatarencoded email addressNuGet
OpenGraph loaderopengraphabsolute urlNuGet
Azure loaderazurerelative path to fileNuGet
PuppeteerSharp loaderscreenshotabsolute urlNuGet

Cache

NameDescriptionNuGet
File cacheMeta and blob file path based on cache id.NuGet
Distributed cacheMS SQL, RedisNuGet
MongoDB cacheUse GridFSNuGet

Pipeline

NameMime typeNuGet
ImageSharpimage/jpeg, image/png, image/gif, image/bmp, image/webp, image/tgaNuGet
SkiaSharpimage/jpeg, image/png, image/gif, image/bmp, image/webpNuGet
SvgNetimage/svg+xmlNuGet
DocNETapplication/pdfNuGet

How to use it

services.AddImageWizard();

//or

services.AddImageWizard(options => 
                       {
                           options.AllowUnsafeUrl = true;
                           options.AllowedDPR = new double[] { 1.0, 1.5, 2.0, 3.0, 4.0 };
                           options.Key = new byte[64] { .. };
                           options.UseETag = true;                                                
                           options.CacheControl.IsEnabled = true;
                           options.CacheControl.MaxAge = TimeSpan.FromDays(365);
                           options.CacheControl.MustRevalidate = false;
                           options.CacheControl.Public = true;
                           options.CacheControl.NoCache = false;
                           options.CacheControl.NoStore = false;
                           //select automatically the compatible mime type by request header
                           options.UseAcceptHeader = true;
			   options.RefreshLastAccessInterval = TimeSpan.FromMinutes(1);
			   options.FallbackHandler = (state, url, cachedData) =>
			    {
				//use the existing cached data if available?
				if (cachedData != null)
				{
				    return cachedData;
				}

				//load fallback image
				FileInfo fallbackImage = state switch
				{
				    LoaderResultState.NotFound => new FileInfo(@"notfound.jpg"),
				    LoaderResultState.Failed => new FileInfo(@"failed.jpg"),
				   _ => throw new Exception()
				};

				if (fallbackImage.Exists == false)
				{
				    return null;
				}

				//convert FileInfo to CachedData
				return fallbackImage.ToCachedData();
			    };
                       })
            //registers ImageSharp pipeline for specified mime types
           .AddImageSharp(c => c
                .WithMimeTypes(MimeTypes.WebP, MimeTypes.Jpeg, MimeTypes.Png, MimeTypes.Gif)
                .WithOptions(x =>
                            {
                                x.ImageMaxHeight = 4000;
                                x.ImageMaxWidth = 4000;
                            })
                //Adds your custom filters
                .WithFilter<BlurFilter>()
		//Executes custom action before the pipeline is started.
                .WithPreProcessing(x =>
                            {
                                x.Image.Mutate(m => m.AutoOrient());
                            })
		//Executes custom action after the pipeline is finished.
                .WithPostProcessing(x =>
                            {
			    	//blurs all images
                                x.Image.Mutate(m => m.Blur());
                              
                                //overrides target format (Jpeg to WebP)
				if (x.ImageFormat is JpegFormat)
				{
                                    x.ImageFormat = new WebPFormat() { Lossless = false };
				}
				//overrides target format (Png to WebP)
				else if (x.ImageFormat is PngFormat)
				{
                                    x.ImageFormat = new WebPFormat() { Lossless = true };
				}
				
				//overrides metadata
				x.Image.Metadata.ExifProfile = new ExifProfile();
                        	x.Image.Metadata.ExifProfile.SetValue(ExifTag.Copyright, "ImageWizard");
                            }))
           //.AddSkiaSharp()
           .AddSvgNet()
	   .AddDocNET()
           //uses file cache (relative or absolute path)
           .SetFileCache(options => options.Folder = "FileCache") 
           //or MongoDB cache
           .SetMongoDBCache(options => options.Hostname = "localhost")
           //or distributed cache
           .SetDistributedCache()
           //adds some loaders
           .AddFileLoader(options => options.Folder = "FileStorage")
           .AddHttpLoader(options => 
          	{
		   //checks every time for a new version of the original image.
		   options.RefreshMode = LoaderRefreshMode.EveryTime;

		   //sets base url for relative urls
		   options.DefaultBaseUrl = "https://mydomain";

		   //allows only relative urls 
		   //(use base url from request or DefaultBaseUrl from options)
		   options.AllowAbsoluteUrls = false;

		   //allows only specified hosts
		   options.AllowedHosts = new [] { "mydomain" };

		   //adds custom http header like apikey to prevent 
		   //that user can download the original image
		   options.SetHeader("ApiKey", "123456");
     		})
           .AddYoutubeLoader()
           .AddGravatarLoader()
	   .AddOpenGraphLoader()
           .AddAnalytics()
	    //Adds a background service which removes cached data based on defined CleanupReason.
            //The cache needs to implements ICleanupCache.
            .AddCleanupService(x =>
		    {
			//Duration between the cleanup actions. (Default: 1 day)
			x.Interval = TimeSpan.FromMinutes(1);

			//Removes cached data which are older than defined duration. 
			x.OlderThan(TimeSpan.FromMinutes(2));

			//Removes cached data which are last used since defined duration. 
			x.LastUsedSince(TimeSpan.FromMinutes(2));

			//Removes cached data which are expired (based on the loader result).
			x.Expired();
		    })
           ;
//default path ("/image")

//use middleware
app.UseImageWizard(x =>
		{
			//default path  ("/analytics")
			x.MapAnalytics();
		});

//or use endpoint
app.Endpoints(e => e.MapImageWizard("/image"));

Internal services

Create custom filter

 public class BackgroundColorFilter : ImageSharpFilter
    {
        //use dependency injection
        public BackgroundColorFilter(ILogger<BackgroundColorFilter> logger)
        {
          //...
        }
        
        [Filter]
        public void BackgroundColor(byte r, byte g, byte b)
        {
            Context.Image.Mutate(m => m.BackgroundColor(new Rgba32(r, g, b)));
        }

        [Filter]
        public void BackgroundColor(float r, float g, float b)
        {
            Context.Image.Mutate(m => m.BackgroundColor(new Rgba32(r, g, b)));
        }
    }

Register filter:

services.AddImageWizard()
	.AddImageSharp(c => c.WithFilter<BackgroundColorFilter>());

URL segments:

"/backgroundcolor(255,255,255)/"
"/backgroundcolor(1.0,1.0,1.0)/"

How to use the DPR (device pixel ratio) attribute

 public class ResizeFilter : ImageSharpFilter
 {
      [Filter]
      public void Resize([DPR]int width, [DPR]int height)
      {
          Context.Image.Mutate(m => m.Resize(width, height));
      }
 }

URL segment:

"/dpr(2.0)/resize(200,100)/"             //calls resize filter with the resolution 400 x 200
or 
"/resize(200,100)/" + client hints  

Response header:

Content-DPR: 2

How to use optional parameters

Example:

 public class TextFilter : ImageSharpFilter
 {
      [Filter]
      public void DrawText(int x = 0, int y = 0, string text = "", int size = 12, string font = "Arial")
      {
          Context.Image.Mutate(m =>
          {
              m.DrawText(
                  text,
                  new Font(SystemFonts.Find(font), size),
                  Rgba32.Black,
                  new PointF(x, y));
          });
      }
 }

URL segment:

"/drawtext(text='Hello',x=10,y=20)/"

ASP.NET Core UrlBuilder

https://www.nuget.org/packages/ImageWizard.Client/

Example:

Add settings to the appsettings.json

 "ImageWizard": {
    "BaseUrl": "https://<your-domain>/image",
    "Key": "DEMO-KEY---PLEASE-CHANGE-THIS-KEY---PLEASE-CHANGE-THIS-KEY---PLEASE-CHANGE-THIS-KEY---==",
    "Enabled": true
  }

Register settings to services

services.Configure<ImageWizardClientSettings>(Configuration.GetSection("ImageWizard"));

services.AddImageWizardClient();

//or

services.AddImageWizardClient(options => 
{
    options.BaseUrl = "https://<your-domain>/image";
    options.Key = "..";
    options.Enabled = true;
    options.UseUnsafeUrl = false;
});

Create url with fluent api

@Url
.ImageWizard()
//use HTTP loader
.Fetch("https://<your-domain>/test/picture.jpg")
//fetch local file from wwwroot folder (with fingerprint)
.FetchLocal("picture.jpg")
//or file loader
.File("test/picture.jpg")
//or azure
.Azure("image.jpg")
.AsImage()
.Resize(160,140,ResizeMode.Max)
.Blur()
.Grayscale()
.Jpg(90)
.BuildUrl()

Use dependency injection

@IImageWizardUrlBuilder UrlBuilder

<img src="@UrlBuilder.FetchLocalFile("picture.jpg").AsImage().Resize(400, 200, ResizeMode.Max).Grayscale().BuildUrl()" />

Use IUrlHelper

<img src="@Url.ImageWizard().FetchLocalFile("picture.jpg").AsImage().Resize(400, 200, ResizeMode.Max).Grayscale().BuildUrl()" />

Processing pipelines

ImageSharp

Image transformations

Output formats

Special options

SkiaSharp

Image transformations

Output formats

DocNET

DocNET transformations

SvgNet

SVG transformations

Plugin for Piranha CMS 8.0

ImageWizard.Piranha NuGet

Useful to resize imagefields.

<img src="@Url.ImageWizard().Fetch(Model.Body).Resize(900,900).Grayscale().Blur().BuildUrl()">

Docker

Docker

static:
    image: usercode/imagewizard
    container_name: imagewizard
    restart: always
    networks:
      - default      
    volumes:
      - file_cache:/data
    environment:
      - General__Key=DEMO+KEY+++PLEASE+CHANGE+THIS+KEY+++PLEASE+CHANGE+THIS+KEY+++PLEASE+CHANGE+THIS+KEY+++==
      - General__AllowUnsafeUrl=false
      - General__UseAcceptHeader=false
      - General__UseETag=true
      - General__AllowedDPR__0=1.0
      - General__AllowedDPR__1=1.5
      - General__AllowedDPR__2=2.0
      - General__AllowedDPR__3=3.0
      - General__CacheControl__IsEnabled=true
      - General__CacheControl__Public=true
      - General__CacheControl__MaxAge=60
      - General__CacheControl__MustRevalidate=false
      - General__CacheControl__NoCache=false
      - General__CacheControl__NoStore=false      
      - FileCache__Folder=/cache
      - FileLoader__Folder=/data
      - HttpLoader__DefaultBaseUrl=https://domain.tld
      - HttpLoader__AllowAbsoluteUrls=false
      - HttpLoader__AllowedHosts__0=domain.tld
      - HttpLoader__Headers__0__Name=ApiKey
      - HttpLoader__Headers__0__Value=123