The Magickal Way to Distort Images

This post is part of the 2021 C# Advent Series. Be sure to check out the awesome blog posts!

Intro

I have a weird fascination with making goofy images and seeing how others react to it. My fascination grew once my friends introduced me to this amazing Telegram Bot that distorts media. I've used the bot so many times and wondered, "Hmm. How do I create my own way of distorting images?".

Well now I'm going to show you how to create those screwed up and distorted images in C#, using Magick.NET!

Prerequisites

  • .NET 6
  • (Optional) An IDE like Visual Studio or JetBrains Rider
  • The ability to run x64 or x86 applications (since the Magick.NET library doesn't have Arm64 support yet)

Getting Started

To get started, we'll need to create a new .NET project. For this, we can use a console project to keep it simple.

dotnet new console -o DistortImage

Then add a reference to the Magick.NET and System.CommandLine libraries.

The System.Commandline library provides a nice way of presenting command line arguments and defining actions.

dotnet add package Magick.NET-Q8-AnyCPU

dotnet add package System.Commandline --prerelease

Magick.NET

Magick.NET is a .NET implementation of the popular ImageMagick library used for manipulating images. The core piece of this post will involve Magick.NET and Liquid Rescale to distort images.

Liquid Rescale

What is Liquid Rescale? It's a library that uses Seam Carving for content-aware image resizing. Normally this would be used as a way to resize images without grainy artifacts, but what happens if you keep squishing it down?

Let's find out!

Distort Away!

In the Program.cs of our project, first add usings for ImageMagick, System.CommandLine, and System.CommandLine.Invocation.

First, we need to choose an image to work with.

For this distortion exercise, why not use myself as the example?

To work with the image in Magick.NET, create an instance of MagickImage and point it to the image path. MagickImage implements IDisposable which means we can wrap it in a using statement.

using var img = new MagickImage(source);

In this snippet, source is pointing to the path of my sample image. This could either be a string, FileInfo object, and even a Stream.

Next let's use the LiquidRescale method on the source image.

The LiquidRescale method requires width and height parameters, at the minimum. Luckily, we can also use a Percentage object that Magick.NET provides to set the width and height. The Percentage object in this instance is used to define the percentage of the image size that we want to scale. Let's try 30%.

img.LiquidRescale(new Percentage(30), new Percentage(30));

Now all that's left is to write the image.

img.Write(output);

output is the destination path.

Let's run this using dotnet run, then open the image we created and see how it looks.

Not bad! I'm sure we can make it even more awkward. Let's try changing the Percentage to 40 and running it again.

Oooo, this is getting weird - but can we make it even more weird??

Back in the LiquidRescale method, there's a few more parameters we can modify to control the seam carving traversal step. By default, the traversal step is set to 0 which means it will use straight seams. Let's update the method to add a deltaX parameter and set it to 1, and a rigidity to 0.

img.LiquidRescale(new Percentage(30), new Percentage(30), 1, 0);

Goodness! This is the distortion I'm looking for!

If you've noticed by now, the images are smaller in size - but we can use the Scale method and scale the image back to its original size before writing the destination image, by using the BaseWidth and BaseHeight properties of the MagickImage object.

img.Scale(img.BaseWidth, img.BaseHeight);

Adding Support for Command Line arguments

Now that we have our distortion going, let's make some command line arguments to control the values we are feeding.

Create a new RootCommand object and add options for source, percentage, and seamtraversal.

var rootCommand = new RootCommand()
{
    new Option<FileInfo>("--source","The location of the source image"),
    new Option<int>("--percentage",getDefaultValue: () => 30),
    new Option<int>("--seamtraversal", getDefaultValue: () => 0)
};

With the snippet above, we have 3 options that can be set when running our program. At the minimum, you have to define a source path.

To define what what will run when the command line arguments are parsed, we will create a CommandHandler and assign it to the rootCommand.Handler property using CommandHandler.Create<>.

rootCommand.Handler = CommandHandler.Create<FileInfo, int, int>((source, percentage, seamTraversal) =>
{
// Code to be processed
});

Paste the Magick.NET code we first created and modify the variables to match.

To fire off the actual command line processing, we need to Invoke the RootCommand and pass the args in.

await rootCommand.InvokeAsync(args);

Now you should have a full fledged command line application that you can call!

dotnet run -- --source "C:\somefolder\image.png --percentage 40 --seamtraversal 3

If you build or publish the console app, you can call it directly and pass in the arguments without using dotnet run -- above.

Completed Code

If you want to skip the explanation, here's the completed code:

using ImageMagick;
using System.CommandLine;
using System.CommandLine.Invocation;

var rootCommand = new RootCommand()
{
    new Option<FileInfo>("--source","The location of the source image"),
    new Option<int>("--percentage", getDefaultValue: () => 30),
    new Option<int>("--seamtraversal", getDefaultValue: () => 0)
};

rootCommand.Handler = CommandHandler.Create<FileInfo, int, int>((source, percentage, seamTraversal) =>
{
    // Create a MagickImage object from the source image
    using var img = new MagickImage(source);
    
    // Create a Percentage object from the value set in the commandline args. 
    var magickPercentage = new Percentage(percentage);
    
    // The resulting distorted image will be the original image base file name with _distorted appended
    var output = $"{source.Directory?.FullName}{Path.DirectorySeparatorChar}{source.Name.TrimEnd(source.Extension.ToCharArray())}_distorted{source.Extension}";
    
    // Liquid Rescale the image to a percentage of the image size. The lower the percentage, the more screwed up the image will be
    img.LiquidRescale(magickPercentage, magickPercentage, seamTraversal, 0);
    
    // Scale the image back to the original size
    img.Scale(img.BaseWidth, img.BaseHeight);
    
    // Write the distorted image
    img.Write(output);
});

// Parse the commandline arguments and execute the handler
await rootCommand.InvokeAsync(args);

Conclusion

Using the Magick.NET library and Liquid Rescale opens up a lot of fun opportunities to do weird things with images, and it's easy to get started. Once Magick.NET has Arm64 support, my vision will be complete.

I wonder if I should do something even further, like creating a full fledged desktop application?

References

ImageMagick

Resizing or Scaling -- IM v6 Examples

Magick.NET

System.Commandline