Resizing and cropping images using ImageResizer - c#

I'm trying to resize and then square-crop incoming images. I have my image in a ReadOnlyStream and would like to output to MemoryStream.
I'm using ImageResizer library to do this.
I'd like my images to first reduce in size and then center-square-crop them. I'm using this code, but it doesn't produce what I require. It produces nothing...
var resultStream = new MemoryStream();
ImageJob job = new ImageJob(imageStream, resultStream, new Instructions {
Width = 100,
Height = 100,
Mode = FitMode.Crop
});
job.Build();
This code should downsample large images and crop them based on library defaults (center cropping).
I didn't provide any specific configuration in web.config because as I understand things it's not required.
What am I doing wrong?

ImageResizer does not reset the output stream position to 0 after writing to it, as this would break non-seekable write streams like HttpResponseStream.
You need to call resultStream.Seek(0, SeekOrigin.Begin); before reading from it.

Related

Imagesharp resizes my image larger than the original

I have a program in .NETCoreApp 3.1 and I use the SixLabors.ImageSharp version 2.1.3 to handle the processing of images in our CDN for all our sites.
The application run in docker container linux and for testing locally, I use WSL2 with ubuntu.
We are planning to review all our image and provide better format with a compressed version et a webp version.
For now the objective was to use TunyPNG (https://tinypng.com/developers/reference/dotnet) to provide a compressed and webp version from the original image and use Imagesharp to resize these versions in the different dimensions that exists.
The first tests were successfull but I found a problem. I had an original transparent PNG (587x444) with filesize of 244k. The compressed version is 70k and webp converted is 16k using TinyPNG API.
The process after is to resize these versions and then i have a problem. The webp version resized to 581x439 using Imagesharp library has a filesize of 266k.
Here is the part of the code :
...
using var image = Image.Load(absoluteFilePath, out var format);
image.Mutate(x => x.Resize(width, height));
using var fileStream = new FileStream(convertedAbsolutePath, FileMode.Create);
await image.SaveAsync(fileStream, format);
...
I tried to force webp encoder and decoder but no amelioration.
With an image without transparent background, I don't have this problem.
If I use TinyPNG to do the resize, i don't have this problem and the filesize is 14k.
Is there some configurations to change or is it a problem with the Imagesharp library ?
You can compress the image more when you use a IImageEncoder in the SaveAsyncinstead of the format. For Webp the encoder would be WebpEncoder. That has a few properties that you can change for example the NearLosslessQuality or Quality, which you can decrease to get a smaller file size. If you want to use the same method to process multiple formats you can save them as IImageEncoder and put that in SaveAsync. An example how you could do that is the following:
using var image = Image.Load(absoluteFilePath, out var format);
image.Mutate(x => x.Resize(width, height));
var encoder = GetImageEncoder(format);
using var fileStream = new FileStream(convertedAbsolutePath, FileMode.Create);
await image.SaveAsync(fileStream, encoder);
Where GetImageEncoder(IImageFormat format) is defined the following:
static IImageEncoder GetImageEncoder(IImageFormat format) {
if(format is WebpFormat) {
return new WebpEncoder() {
Quality = 20,
NearLosslessQuality = 50,
};
} else if(format is PngFormat) {
return new PngEncoder();
}
throw new NotSupportedException();
}
The Quality and NearLosslessQuality have to be in the range between 0 and 100, where a 100 is highest Quality and 0 is the worst.

Raspberry Pi and framebuffer input with mono

I'm trying to render a bitmap in Memory using mono. This image should be displayed on Adafruits 2.8" touch TFT (320*240). The Programm is developed with Visual Studio 2013 Community Edition. I want to host a ASP.NET Web Api and Show
some data on the Display. The ASP.NET part is working fine and the image is rendered. My idea was to write the Image to the framebuffer Input, but doing this I get an Exception saying that file is to large. I'm just writing raw data without BMP Header. Has someone managed doing this? Maybe creation of image is
wrong.
It seems as something is happening because the display changes and I can see white areas which might be from my image.
I don't want to use any extra libraries to keep it simple. So my idea is to use FBI directly. Does anyone know this problem and the solution?
Here is some of my code:
using (Bitmap bmp = new Bitmap(240, 320, PixelFormat.Format16bppRgb555))
{
[...]
Byte[] image = null;
using(MemoryStream memoryStream = new MemoryStream())
{
bitmap.Save(memoryStream, ImageFormat.Bmp);
Byte[] imageTemp = memoryStream.GetBuffer();
//Remove BMP header
image = new Byte[imageTemp.Length - 54];
Buffer.BlockCopy(imageTemp, 54, image, 0, image.Length);
//153600 byte
using (FileStream fb1 = new FileStream("/dev/fb1", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
fb1.Write(image, 0, image.Length);
fb1.Close();
}
}
}
Take a look at http://computerstruggles.blogspot.de/2013/02/how-to-program-directfb-in-c-on.html - the idea is to install the directfb library and use it from C# with PInvoke. The blog's author uses a mini wrapper in C to make using it even easier. BTW why don't you like to install additional libraries and to profit from the work others have done for you?
You may be running out of memory when the MemoryStream reallocates memory. When it needs to grow, it doubles in size. With this large of a write, the internal buffer is probably exceeding available memory. See Why does C# memory stream reserve so much memory? for more information.

Reduce the file size (quality) of a BitmapImage (without resizing it)

I can't find a way to reduce the file size of an image.
I started from a StorageFile I got using the FilePicker, then I resized it using the WriteableBitmap class, and now I want to reduce the file of my image.
This is the part of my code that isn't working:
BitmapDecoder compressDecoder = await BitmapDecoder.CreateAsync(imageStream);
PixelDataProvider compressionData = await compressDecoder.GetPixelDataAsync();
byte[] compressionBytes = compressionData.DetachPixelData();
//Set target image quality
BitmapPropertySet propertySet = new BitmapPropertySet();
BitmapTypedValue qualityValue = new BitmapTypedValue(0.5, PropertyType.Single);
propertySet.Add("ImageQuality", qualityValue);
imageStream.Seek(0);
BitmapEncoder compressionEncoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, imageStream, propertySet);
compressionEncoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, compressDecoder.PixelWidth, compressDecoder.PixelHeight, compressDecoder.DpiX, compressDecoder.DpiY, compressionBytes);
await compressionEncoder.FlushAsync();
The problem is that even if I try to set the qualityValue to 0.5 or less, I can see the image loosing quality (of course), but the file size remains exactly the same.
How's that even possible?
Actually, if I set the quality to 1.0, the file size almost doubles, it I turn it down to like 0.5 the file size remains the same (even if the image has a worse quality) and then if I keep turning it down the file size doesn't change.
If this makes sense, I can't see how :/
Am I missing something here? Is there another way to compress an image?
I mean, I just want my stream not to exceed a fixed maximum lenght, so I want to keep decreasing the quality until I reach the right balance.
Thank you for your help!
Sergio

Resize image on the fly in .net and c#

I'm looking for a way to resize images without saving them on the server. The ways that i have found includes a controller file and such.
Is there a way to get the image from the stream, resize it and add it to the response?
Check out ImageResizer - it's a suite of NuGet packages designed for this exact purpose.
It runs eBay in Denmark, MSN Olympics, and a few other big sites.
Dynamic image processing can be done safely and efficiently, but not in a sane amount of code. It's trickier than it appears.
I wouldn't recommend this but you can do next thing:
using (Image img = Image.FromStream(originalImage))
{
using (Bitmap bitmap = new Bitmap(img, width, height))
{
bitmap.Save(outputStream, ImageFormat.Jpeg);
}
}
Be aware that this could cause OutOfMemoryException.

WinRT image handling

A friend and I spent the better part of last night nearly tearing our hair out trying to work with some images in a metro app. We got images into the app with the share charm, and then I wanted to do some other work with them, cropping the images and saving them back into the appdata folder. This proved extremely frustrating.
My question, at the end of all this, is going to be "What's the proper way of doing this, without feeling like I'm hammering together a bunch of mismatched jigsaw puzzle pieces?"
When sharing multiple images with the app, they come in as a list of Windows.Storage.StorageFiles. Here's some code used to handle that.
var storageItems = await _shareOperation.Data.GetStorageItemsAsync();
foreach (StorageFile item in storageItems)
{
var stream = await item.OpenReadAsync();
var properties = await item.Properties.GetImagePropertiesAsync();
var image = new WriteableBitmap((Int32)properties.Width, (Int32)properties.Height);
image.SetSource(stream);
images.Add(image);
}
Some searching online has indicated that currently, a Windows.UI.Xaml.Media.Imaging.WriteableBitmap is the only thing capable of letting you access the pixel data in the image. This question includes a helpful answer full of extension methods for saving images to a file, so we used those.
Our problems were the worst when I tried opening the files again later. I did something similar to before:
var files = await ApplicationData.Current.LocalFolder.GetFilesAsync();
foreach (var file in files)
{
var fileStream = await file.OpenReadAsync();
var properties = await file.Properties.GetImagePropertiesAsync();
var bitmap = new WriteableBitmap((Int32)properties.Width, (Int32)properties.Height);
bitmap.SetSource(fileStream);
System.IO.Stream stream = bitmap.PixelBuffer.AsStream();
Here comes a problem. How long is this stream, if I want the bytes out of it?
// CRASH! Length isn't supported on an IRandomAccessStream.
var pixels = new byte[fileStream.Length];
Ok try again.
var pixels = new byte[stream.Length];
This works, except... if the image is compressed, the stream is shorter than you would expect, so you will eventually get an out of bounds exception. For now pretend it's an uncompressed bitmap.
await _stream.ReadAsync(pixels, 0, pixels.Length);
Well guess what. Even though I said bitmap.SetSource(fileStream); in order to read in the data, my byte array is still full of zeroes. I have no idea why. If I pass this same bitmap into a my UI through the sample data group, the image shows up just fine. So it has clearly got the pixel data in that bitmap somewhere, but I can't read it out of bitmap.PixelBuffer? Why not?
Finally, here's what ended up actually working.
var decoder = await BitmapDecoder.CreateAsync(BitmapDecoder.PngDecoderId, fileStream);
var data = await decoder.GetPixelDataAsync();
var bytes = data.DetachPixelData();
/* process my data, finally */
} // end of that foreach I started a while ago
So now I have by image data, but I still have a big problem. In order to do anything with it, I have to make assumptions about its format. I have no idea whether it's rgba, rgb, abgr, bgra, whatever they can be. If I guess wrong my processing just fails. I've had dozens of test runs spit out zero byte and corrupted images, upside down images (???), wrong colors, etc. I would have expected to find some of this info in the properties that I got from calling await file.Properties.GetImagePropertiesAsync();, but no luck. That only contains the image width and height, plus some other useless things. Minimal documentation here.
So, why is this process so painful? Is this just reflecting the immaturity of the libraries right now, and can I expect it to get better? Or is there already some standard way of doing this? I wish it were as easy as in System.Drawing. That gave you all the data you ever needed, and happily loaded any image type correctly, without making you deal with streams yourself.
From what I have seen - when you are planning on loading the WriteableBitmap with a stream - you don't need to check the image dimensions - just do new WriteableBitmap(1,1), then call SetSource().
Not sure why you were thinking var pixels = new byte[fileStream.Length]; would work, since the fileStream has the compressed image bytes and not a pixel array.
You might need to seek to the beginning of the stream to get the pixels array:
var pixelStream = pixelBuffer.AsStream();
var bytes = new byte[this.pixelStream.Length];
this.pixelStream.Seek(0, SeekOrigin.Begin);
this.pixelStream.Read(bytes, 0, Bytes.Length);
I had started working on a WinRT port of WriteableBitmapEx - maybe it could help you: http://bit.ly/WriteableBitmapExWinRT. I have not tested it well and it is based on an older version of WBX, but it is fairly complete in terms of feature support. Might be a tad slower than it is possible too.

Categories