C
C#•4d ago
eli

ImageSharp slow asf for some reason

a
14 Replies
eli
eliOP•4d ago
async Task<Stream> CompressImageSharp(Stream imageStream, SemaphoreSlim semaphore, int maxWidth=1000, int maxHeight=1000)
{
try
{
await semaphore.WaitAsync();
imageStream.Position = 0; // Reset the stream position in case the stream has already been ready before.

var outputStream = new MemoryStream();
using var image = await Image.LoadAsync(imageStream);

var currentWidth = image.Width;
var currentHeight = image.Height;

var newMaxWidth = Math.Min(currentWidth, maxWidth);
var newMaxHeight = Math.Min(currentHeight, maxHeight);

var resizeOptions = new ResizeOptions
{
Size = new Size(newMaxWidth, newMaxHeight),
};

image.Mutate(x => x.Resize(resizeOptions));

var encoder = new WebpEncoder
{
Quality = 100,
FileFormat = WebpFileFormatType.Lossy
};

await image.SaveAsync(outputStream, encoder);

outputStream.Position = 0;

return outputStream;
}
finally
{
semaphore.Release();
}
}
async Task<Stream> CompressImageSharp(Stream imageStream, SemaphoreSlim semaphore, int maxWidth=1000, int maxHeight=1000)
{
try
{
await semaphore.WaitAsync();
imageStream.Position = 0; // Reset the stream position in case the stream has already been ready before.

var outputStream = new MemoryStream();
using var image = await Image.LoadAsync(imageStream);

var currentWidth = image.Width;
var currentHeight = image.Height;

var newMaxWidth = Math.Min(currentWidth, maxWidth);
var newMaxHeight = Math.Min(currentHeight, maxHeight);

var resizeOptions = new ResizeOptions
{
Size = new Size(newMaxWidth, newMaxHeight),
};

image.Mutate(x => x.Resize(resizeOptions));

var encoder = new WebpEncoder
{
Quality = 100,
FileFormat = WebpFileFormatType.Lossy
};

await image.SaveAsync(outputStream, encoder);

outputStream.Position = 0;

return outputStream;
}
finally
{
semaphore.Release();
}
}
async Task<Stream> CompressSkiaSharp(Stream imageStream, SemaphoreSlim semaphore, int maxWidth = 1000, int maxHeight = 1000)
{
try
{
await semaphore.WaitAsync();
imageStream.Position = 0;

var outputStream = new MemoryStream();

// Load the image data
using var original = SKBitmap.Decode(imageStream);
if (original == null) throw new InvalidOperationException("Failed to load image");

var currentWidth = original.Width;
var currentHeight = original.Height;

// Calculate new dimensions while maintaining aspect ratio
var newMaxWidth = Math.Min(currentWidth, maxWidth);
var newMaxHeight = Math.Min(currentHeight, maxHeight);

var ratioX = (double)newMaxWidth / currentWidth;
var ratioY = (double)newMaxHeight / currentHeight;
var ratio = Math.Min(ratioX, ratioY);

var newWidth = (int)(currentWidth * ratio);
var newHeight = (int)(currentHeight * ratio);

// Create resized bitmap
using var resized = original.Resize(new SKImageInfo(newWidth, newHeight), new SKSamplingOptions());
if (resized == null) throw new InvalidOperationException("Failed to resize image");

// Convert bitmap to image for encoding
using var image = SKImage.FromBitmap(resized);

// Encode as WebP
using var data = image.Encode(SKEncodedImageFormat.Webp, 100);

// Write to output stream
data.SaveTo(outputStream);
outputStream.Position = 0;

return outputStream;
}
finally
{
semaphore.Release();
}
}
async Task<Stream> CompressSkiaSharp(Stream imageStream, SemaphoreSlim semaphore, int maxWidth = 1000, int maxHeight = 1000)
{
try
{
await semaphore.WaitAsync();
imageStream.Position = 0;

var outputStream = new MemoryStream();

// Load the image data
using var original = SKBitmap.Decode(imageStream);
if (original == null) throw new InvalidOperationException("Failed to load image");

var currentWidth = original.Width;
var currentHeight = original.Height;

// Calculate new dimensions while maintaining aspect ratio
var newMaxWidth = Math.Min(currentWidth, maxWidth);
var newMaxHeight = Math.Min(currentHeight, maxHeight);

var ratioX = (double)newMaxWidth / currentWidth;
var ratioY = (double)newMaxHeight / currentHeight;
var ratio = Math.Min(ratioX, ratioY);

var newWidth = (int)(currentWidth * ratio);
var newHeight = (int)(currentHeight * ratio);

// Create resized bitmap
using var resized = original.Resize(new SKImageInfo(newWidth, newHeight), new SKSamplingOptions());
if (resized == null) throw new InvalidOperationException("Failed to resize image");

// Convert bitmap to image for encoding
using var image = SKImage.FromBitmap(resized);

// Encode as WebP
using var data = image.Encode(SKEncodedImageFormat.Webp, 100);

// Write to output stream
data.SaveTo(outputStream);
outputStream.Position = 0;

return outputStream;
}
finally
{
semaphore.Release();
}
}
async Task Worker(string imagePath, SemaphoreSlim semaphore)
{
while (true)
{
var sw = Stopwatch.StartNew();
var stream = new FileStream(imagePath, FileMode.Open);
await CompressSkiaSharp(stream, semaphore);
sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds}ms");
}

}

async Task Main()
{
var semaphore = new SemaphoreSlim(50);
var tasks = new List<Task>();
var imagePath =
@"/Users/eli/RiderProjects/ImageSharpProfilingFr/ImageSharpProfilingFr/qa3w10439tyq47tt0w144te9fi247hiyfer56poy7r3ae134h19EwpQs625GEhO38AGUyrU54yEwfG5w6hE34OE5Q9DOQ0hO1EhD.jpg";

for (var i = 0; i < 50; i++)
{
tasks.Add(Worker(imagePath, semaphore));
}

await Task.WhenAll(tasks);
}

Main().Wait();
async Task Worker(string imagePath, SemaphoreSlim semaphore)
{
while (true)
{
var sw = Stopwatch.StartNew();
var stream = new FileStream(imagePath, FileMode.Open);
await CompressSkiaSharp(stream, semaphore);
sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds}ms");
}

}

async Task Main()
{
var semaphore = new SemaphoreSlim(50);
var tasks = new List<Task>();
var imagePath =
@"/Users/eli/RiderProjects/ImageSharpProfilingFr/ImageSharpProfilingFr/qa3w10439tyq47tt0w144te9fi247hiyfer56poy7r3ae134h19EwpQs625GEhO38AGUyrU54yEwfG5w6hE34OE5Q9DOQ0hO1EhD.jpg";

for (var i = 0; i < 50; i++)
{
tasks.Add(Worker(imagePath, semaphore));
}

await Task.WhenAll(tasks);
}

Main().Wait();
eli
eliOP•4d ago
Here is the image I am using
No description
eli
eliOP•4d ago
looks like this if you don't wanna download
No description
eli
eliOP•4d ago
With imageskia
No description
No description
eli
eliOP•4d ago
with imagesharp
No description
No description
eli
eliOP•4d ago
@Waffle Whisperer if you could take a look I would appreciate it does imagesharp just suck for this kind of thing? lol I ran the test on m2 macbook air with 16gb of ram btw, the node prod is running on has 4vcpus and 8gb ram
Cattywampus
Cattywampus•4d ago
I never used imagesharp, sadly, and I'm a bit bust atm 😅
eli
eliOP•4d ago
no worries, I'm just gonna drop in imageskia in prod and see if that fixes things lol
Cattywampus
Cattywampus•4d ago
you can ping the creator he's a member here too I forgot his name
eli
eliOP•4d ago
of skia or sharp?
Cattywampus
Cattywampus•4d ago
imagesharp @JBSP
eli
eliOP•4d ago
ty @JBSP Hi, I am wondering if imagesharp has a significantly worse performance than imageskia for resizing/converting images to webp or I am doing something wrong here? I just tried setting the Method to WebpEncodingMethod.Fastest and now it takes an average about 500ms but the CPU/ram usage is still high
JBSP
JBSP•4d ago
Webp encoding is currently slower since it’s currently lacking some intrinsics but that code you’ve written concerns me. The task thrashing and locking is going to yield poor benchmark accuracy. You need to compare like for like also. SkiaSharp will be doing a lower quality resize by default for example. Use BenchmarkDotNet and do proper benchmarks for each operation. Here’s a good example https://github.com/bleroy/core-imaging-playground p.s Webp is a terrible format. It’s massively slow to encode compared to jpeg and sacrifices size for quality.
GitHub
GitHub - bleroy/core-imaging-playground: This is just me playing ar...
This is just me playing around with .NET imaging libraries. - bleroy/core-imaging-playground
eli
eliOP•4d ago
Alright thanks very much for the info, and yes the semaphores were silly, in my production code I put those in as a bandaid cause I was running into resources limits due to high concurrency on compression, but obviously not needed in the benchmark 🤦

Did you find this page helpful?