PROWAREtech

articles » current » asp-net-core » image-utility-for-sixlabors-imagesharp

ASP.NET Core: Image Utility Class for SixLabors.ImageSharp v1.0

Manipulate images on Windows and Linux web servers including creating a scatter plot and histogram, and rotating the images as needed to display properly with asp.net core web application example written in C#.

This small utility library requires adding the NuGet SixLabors.ImageSharp package (version 1.0.4) to the .NET Core 3.1/.NET 6/.NET 8 Project. This is compatible with Windows, Linux and MacOS.

This has been re-worked for ImageSharp v3.0.1.

It can resize an image based on the number of megapixels or length by width and preserve the aspect ratio if so desired.

It rotates/flips the image based on EXIF data. This is to accommodate mobile devices.

It also creates scatter plots and histograms.

See also: Bitmap Image Creation and Download for MVC Application


using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Formats.Png;
using System.IO;
using System;
using SixLabors.ImageSharp.Formats.Jpeg;

namespace ImageUtil
{
	public class GetSize
	{
		public GetSize(Stream stream)
		{
			using (Image iOriginal = Image.Load(stream))
			{
				stream.Position = 0;
				Width = iOriginal.Width;
				Height = iOriginal.Height;
			}
		}

		/// <summary>
		/// The width of the image specified in the class constructor's Stream parameter
		/// </summary>
		public int Width { get; }

		/// <summary>
		/// The height of the image specified in the class constructor's Stream parameter
		/// </summary>
		public int Height { get; }
	}

	public static class Resize
	{
		/// <summary>
		/// Resize and save an image to a Stream specifying its new width and height
		/// </summary>
		public static void SaveImage(Stream imageStream, int newWidth, int newHeight, bool preserveImageRatio, Stream saveToStream, int jpegQuality = 100)
		{
			using (Image iOriginal = Image.Load(imageStream))
			{
				imageStream.Position = 0;
				if (preserveImageRatio)
				{
					float percentWidth = newWidth / (float)iOriginal.Width;
					float percentHeight = newHeight / (float)iOriginal.Height;
					float percent = percentHeight < percentWidth ? percentHeight : percentWidth;
					newWidth = (int)Math.Round(iOriginal.Width * percent, 0);
					newHeight = (int)Math.Round(iOriginal.Height * percent, 0);
				}
				resize(imageStream, iOriginal, newWidth, newHeight, saveToStream, jpegQuality);
			}
		}

		/// <summary>
		/// Resize and save an image to a Stream specifying the number of pixels to resize to
		/// </summary>
		public static void SaveImage(Stream imageStream, int newNumberOfPixels, Stream saveToStream, int jpegQuality = 100)
		{
			using (Image iOriginal = Image.Load(imageStream))
			{
				imageStream.Position = 0;
				double ratio = Math.Sqrt(newNumberOfPixels / (double)(iOriginal.Width * iOriginal.Height));
				resize(imageStream, iOriginal, (int)Math.Round(iOriginal.Width * ratio, 0), (int)Math.Round(iOriginal.Height * ratio, 0), saveToStream, jpegQuality);
			}
		}

		private static void resize(Stream origSource, Image image, int newWidth, int newHeight, Stream saveTo, int jpegQuality)
		{
			image.Mutate(x => x.Resize(newWidth, newHeight));
			transformImage(image); // NOTE: transform image AFTER resizing it!!!
			var format = Image.DetectFormat(origSource);
			if (format.Name.ToLower() == "jpeg")
			{
				var encoder = new JpegEncoder();
				encoder.Quality = jpegQuality;
				image.SaveAsJpeg(saveTo, encoder);
			}
			else
				image.Save(saveTo, format);
		}
		private static void transformImage(Image image)
		{
			IExifValue exifOrientation = image.Metadata?.ExifProfile?.GetValue(ExifTag.Orientation);

			if (exifOrientation == null)
				return;

			RotateMode rotateMode;
			FlipMode flipMode;
			setRotateFlipMode(exifOrientation, out rotateMode, out flipMode);

			image.Mutate(x => x.RotateFlip(rotateMode, flipMode));
			image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)1);
		}
		private static void setRotateFlipMode(IExifValue exifOrientation, out RotateMode rotateMode, out FlipMode flipMode)
		{
			var orientation = (ushort)exifOrientation.GetValue();

			switch (orientation)
			{
				case 2:
					rotateMode = RotateMode.None;
					flipMode = FlipMode.Horizontal;
					break;
				case 3:
					rotateMode = RotateMode.Rotate180;
					flipMode = FlipMode.None;
					break;
				case 4:
					rotateMode = RotateMode.Rotate180;
					flipMode = FlipMode.Horizontal;
					break;
				case 5:
					rotateMode = RotateMode.Rotate90;
					flipMode = FlipMode.Horizontal;
					break;
				case 6:
					rotateMode = RotateMode.Rotate90;
					flipMode = FlipMode.None;
					break;
				case 7:
					rotateMode = RotateMode.Rotate90;
					flipMode = FlipMode.Vertical;
					break;
				case 8:
					rotateMode = RotateMode.Rotate270;
					flipMode = FlipMode.None;
					break;
				default:
					rotateMode = RotateMode.None;
					flipMode = FlipMode.None;
					break;
			}
		}
	}

	/* CREATE A SCATTER PLOT JPEG/PNG IMAGE */
	public static class ScatterPlot
	{
		static readonly Rgba32 WHITE = new Rgba32(255, 255, 255);
		static readonly Rgba32 BLACK = new Rgba32(0, 0, 0);
		static readonly Rgba32 RED = new Rgba32(255, 0, 0);
		static readonly Rgba32 BLUE = new Rgba32(0, 0, 255);
		static readonly Rgba32 GREEN = new Rgba32(0, 192, 0);
		static readonly Rgba32 PURPLE = new Rgba32(128, 0, 128);
		static readonly Rgba32 ORANGE = new Rgba32(255, 164, 0);
		static readonly Rgba32 YELLOW = new Rgba32(255, 225, 0);
		static readonly Rgba32 MAGENTA = new Rgba32(255, 0, 255);
		static readonly Rgba32 AQUA = new Rgba32(0, 225, 255);
		static readonly Rgba32 BLUEJEAN = new Rgba32(0, 128, 255);
		static readonly Rgba32 BROWN = new Rgba32(150, 75, 50);
		static readonly Rgba32 CHARTREUSE = new Rgba32(223, 255, 0);
		static readonly Rgba32 DODGERBLUE = new Rgba32(30, 144, 255);
		static readonly Rgba32 NAVY = new Rgba32(0, 0, 128);
		static readonly Rgba32 DARKRED = new Rgba32(139, 0, 0);
		static readonly Rgba32[] colors = new Rgba32[] { WHITE, BLACK, RED, BLUE, GREEN, PURPLE, ORANGE, YELLOW, MAGENTA, AQUA, BLUEJEAN, BROWN, DODGERBLUE, CHARTREUSE, NAVY, DARKRED };
		public static void CreateJPEG(Stream stream, ScatterPlotColors backgroundColor, IEnumerable<PlotPoint> points, int width, int height, int pointSize, int jpegQuality = 100)
		{
			using (var bmp = new Image<Rgba32>(width / pointSize + 6, height / pointSize + 6, colors[(int)backgroundColor]))
				create(stream, width, height, bmp, points, pointSize, new JpegEncoder() { Quality = jpegQuality });
		}
		public static void CreateJPEG(string filename, ScatterPlotColors backgroundColor, IEnumerable<PlotPoint> points, int width, int height, int pointSize, int jpegQuality = 100)
		{
			using (var stream = File.Create(filename))
				CreateJPEG(stream, backgroundColor, points, width, height, pointSize, jpegQuality);
		}

		public static void CreatePNG(Stream stream, IEnumerable<PlotPoint> points, int width, int height, int pointSize)
		{
			using (var bmp = new Image<Rgba32>(width / pointSize + 6, height / pointSize + 6))
				create(stream, width, height, bmp, points, pointSize, new PngEncoder());
		}
		public static void CreatePNG(string filename, IEnumerable<PlotPoint> points, int width, int height, int pointSize)
		{
			using (var stream = File.Create(filename))
				CreatePNG(stream, points, width, height, pointSize);
		}

		private static double normalize(float min, float max, float value)
		{
			double x = max - min;
			if(x == 0)
				return 0;
			return (value - min) / x;
		}

		private static void create(Stream stream, int width, int height, Image<Rgba32> bmp, IEnumerable<PlotPoint> points, int pointSize, SixLabors.ImageSharp.Formats.IImageEncoder imageEncoder)
		{
			float xMax = float.MinValue, xMin = float.MaxValue, yMax = float.MinValue, yMin = float.MaxValue;
			foreach (var pt in points)
			{
				xMax = Math.Max(pt.x, xMax);
				xMin = Math.Min(pt.x, xMin);
				yMax = Math.Max(pt.y, yMax);
				yMin = Math.Min(pt.y, yMin);
			}
			float xDelta = Math.Max(1, xMax - xMin), yDelta = Math.Max(1, yMax - yMin);
			int w = width / pointSize;
			int h = height / pointSize;
			foreach (var pt in points)
			{
				int x = (int)(w * normalize(xMin, xMax, pt.x));
				int y = (int)(h * normalize(yMin, yMax, pt.y));
				bmp[x + 3, y + 3] = colors[((int)pt.color) % colors.Length];
			}
			bmp.Mutate(x => x.Flip(FlipMode.Vertical));
			bmp.Mutate(x => x.Resize(width, height));
			bmp.Save(stream, imageEncoder);
		}
	}
	public class PlotPoint
	{
		public ScatterPlotColors color { get; }
		public float x { get; }
		public float y { get; }
		public PlotPoint(float x, float y, ScatterPlotColors color)
		{
			this.x = x;
			this.y = y;
			this.color = color;
		}
	}
	public enum ScatterPlotColors
	{
		WHITE,
		BLACK,
		RED,
		BLUE,
		GREEN,
		PURPLE,
		ORANGE,
		YELLOW,
		MAGENTA,
		AQUA,
		BLUEJEAN,
		BROWN,
		CHARTREUSE,
		DODGERBLUE,
		NAVY,
		DARKRED
	}

	/* CREATE A PNG HISTOGRAM OF AN IMAGE */
	public static class Histogram
	{
		/// <summary>
		/// Create a histogram from the data in a stream
		/// </summary>
		public static MemoryStream CreatePNG(Stream stream, int width, int height, LRGB lrgb, byte alphaChannel = 128, bool clipBlackAndWhite = true, byte luminanceShade = 255)
		{
			using (var bmp = Image<Rgb24>.Load(stream))
			{
				return create(bmp, width, height, lrgb, alphaChannel, clipBlackAndWhite, luminanceShade);
			}
		}

		/// <summary>
		/// Create a histogram from the data in a file
		/// </summary>
		public static MemoryStream CreatePNG(string filename, int width, int height, LRGB lrgb, byte alphaChannel = 128, bool clipBlackAndWhite = true, byte luminanceShade = 255)
		{
			using (var bmp = Image<Rgb24>.Load(filename))
			{
				return create(bmp, width, height, lrgb, alphaChannel, clipBlackAndWhite, luminanceShade);
			}
		}

		private static MemoryStream create(Image bmp, int width, int height, LRGB lrgb, byte alpha, bool clip, byte shade)
		{
			ulong[] lumin = new ulong[256];
			ulong[] red = new ulong[256];
			ulong[] green = new ulong[256];
			ulong[] blue = new ulong[256];
			var bred = (lrgb & LRGB.RED) != 0;
			var bgreen = (lrgb & LRGB.GREEN) != 0;
			var bblue = (lrgb & LRGB.BLUE) != 0;
			var blumin = (lrgb == LRGB.LUMINANCE);
			int w = bmp.Width;
			int h = bmp.Height;
			var bmp2 = bmp.CloneAs<Rgb24>();
			for (int y = 0; y < h; y++)
			{
				Span<Rgb24> pixelRow = bmp2.GetPixelRowSpan(y);
				for (int x = 0; x < w; x++)
				{
					var c = pixelRow[x];
					lumin[(int)Math.Round((c.R + c.G + c.B) / 3.0)]++;
					red[c.R]++;
					green[c.G]++;
					blue[c.B]++;
				}
			}
			ulong max = 0;
			int a = (clip ? 1 : 0), b = (clip ? 255 : 256);
			for (int i = a; i < b; i++)
			{
				if (!blumin)
				{
					if (bred)
						if (max < red[i])
							max = red[i];
					if (bgreen)
						if (max < green[i])
							max = green[i];
					if (bblue)
						if (max < blue[i])
							max = blue[i];
				}
				else if (max < lumin[i])
					max = lumin[i];
			}
			double HEIGHTFACTOR = 256.0 / max;
			if (blumin)
			{
				using (var bmplum = new Image<Rgba32>(256, 256))
				{
					var penlum = new VerticalPen(new Rgba32(shade, shade, shade, alpha));
					for (int i = 0; i < 256; i++)
						penlum.Draw(bmplum, i, (int)(lumin[i] * HEIGHTFACTOR));
					bmplum.Mutate(x => x.Resize(width, height));
					MemoryStream ms = new MemoryStream();
					bmplum.Save(ms, new PngEncoder());
					return ms;
				}
			}
			else
			{
				using (var bmppre = new Image<Rgba32>(256, 256))
				{
					Image<Rgba32>? bmpred = null, bmpgreen = null, bmpblue = null;
					VerticalPen? penred = null, pengreen = null, penblue = null;
					if (bred)
					{
						bmpred = new Image<Rgba32>(256, 256);
						penred = new VerticalPen(new Rgba32(255, 0, 0, alpha));
					}
					if (bgreen)
					{
						bmpgreen = new Image<Rgba32>(256, 256);
						pengreen = new VerticalPen(new Rgba32(0, 255, 0, alpha));
					}
					if (bblue)
					{
						bmpblue = new Image<Rgba32>(256, 256);
						penblue = new VerticalPen(new Rgba32(0, 0, 255, alpha));
					}

					for (int i = 0; i < 256; i++)
					{
						if (bred)
							penred.Draw(bmpred, i, (int)(red[i] * HEIGHTFACTOR));
						if (bgreen)
							pengreen.Draw(bmpgreen, i, (int)(green[i] * HEIGHTFACTOR));
						if (bblue)
							penblue.Draw(bmpblue, i, (int)(blue[i] * HEIGHTFACTOR));
					}

					if (bred)
					{
						bmppre.Mutate(x => x.DrawImage(bmpred, 1));
						bmpred.Dispose();
					}
					if (bgreen)
					{
						bmppre.Mutate(x => x.DrawImage(bmpgreen, 1));
						bmpgreen.Dispose();
					}
					if (bblue)
					{
						bmppre.Mutate(x => x.DrawImage(bmpblue, 1));
						bmpblue.Dispose();
					}
					bmppre.Mutate(x => x.Resize(width, height));
					MemoryStream ms = new MemoryStream();
					bmppre.Save(ms, new PngEncoder());
					return ms;
				}
			}
		}
		internal class VerticalPen
		{
			private readonly Rgba32 color;
			public VerticalPen(Rgba32 color)
			{
				this.color = color;
			}
			public void Draw(Image<Rgba32> bmp, int row, int height)
			{
				if (height <= bmp.Height)
					for (int y = height - 1; y >= 0; y--)
						bmp[row, bmp.Height - 1 - y] = color;
			}
		}
		public enum LRGB
		{
			LUMINANCE = 0,
			RED = 1,
			GREEN = 2,
			BLUE = 4,
			REDBLUE = 1 | 4,
			REDGREEN = 1 | 2,
			BLUEGREEN = 2 | 4,
			REDGREENBLUE = 1 | 2 | 4
		}
	}
}
Example Usage

See also: console application example

This is a View in an MVC web application.


@{
	ViewData["Title"] = "/Home/UploadImages";
}

<div class="text-center">
	<h1 class="display-4">UploadImages</h1>
	<form enctype="multipart/form-data" method="post">
		<div>
			<input type="file" name="images" accept="image/jpeg, image/gif, image/png" multiple />
		</div>
		<div>
			<button type="submit" class="btn btn-primary">UPLOAD</button>
		</div>
	</form>
	@if(Model is int)
	{
		<p>@Model images saved.</p>
	}
</div>

And now the Controller code.


using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.TagHelpers;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using WebApplication1.Models;

namespace WebApplication1.Controllers
{
	public class HomeController : Controller
	{
		private readonly ILogger<HomeController> _logger;

		public HomeController(ILogger<HomeController> logger)
		{
			_logger = logger;
		}

		public IActionResult UploadImages()
		{
			return View();
		}

		[HttpPost]
		[DisableRequestSizeLimit] // NOTE: NOT RECOMMENDED - SET A LIMIT
		public IActionResult UploadImages(IFormCollection form)
		{
			int count = 0;
			foreach(var image in form.Files)
			{
				if(image.ContentType.StartsWith("image/"))
				{
					using (var openfs = image.OpenReadStream())
					{
						string filename = "thumbnail-" + image.FileName;
						if (System.IO.File.Exists(filename))
							System.IO.File.Delete(filename);
						using (var savefs = System.IO.File.OpenWrite(filename))
						{
							// NOTE: RESIZE IMAGE TO 10K PIXEL THUMBNAIL; THIS WILL ALSO FLIP AND ROTATE IMAGE AS NEEDED
							ImageUtil.Resize.SaveImage(openfs, 10000, savefs);
							count++;
						}
					}
				}	
			}
			return View(count);
		}

		public IActionResult Index()
		{
			return View();
		}

		public IActionResult Privacy()
		{
			return View();
		}

		[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
		public IActionResult Error()
		{
			return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
		}
	}
}

This site uses cookies. Cookies are simple text files stored on the user's computer. They are used for adding features and security to this site. Read the privacy policy.
CLOSE