Resizing an image and adding black background using MagickImage and C# - c#

I have to cover four possible scenarios with Magick Image:
IF width or height of the image uploaded is less than 150 center the image and paint everything in the background in black
Width > 150 and aspect ratio different from 1:1 crop image from both sides to cover 150px
Height > 150 and aspect ratio different from 1:1 crop image from both sides to cover 150 px
in all other cases resize to 150x150
I struggle to find documentation or any info on it. I am using trial and error.
I draw a sketch of all the cases first then I wanted to implement this into C# code
public async Task<IActionResult> SetImageFroUser([FromRoute] string userId, [FromForm] byte[] avatar)
{
if (string.IsNullOrEmpty(userId) || avatar == null || avatar.Length == 0) return BadRequest();
var user = await userManagerWrapper.FindByIdAsync(userId);
if (user == null) return NotFound();
// if (User == null || !User.IsInRole("Administrator")) return Forbid();
if ((User == null || !User.IsInRole("Administrator")) && !user.Id.Equals(userId)) return Forbid();
try
{
avatarManager.SetAvatar(user, new MemoryStream(avatar));
}
catch (MagickException)
{
return BadRequest("Invalid image format");
}
return NoContent();
}
public void SetAvatar(IntentUser user, Stream avatar)
{
using (var ms = new MemoryStream())
{
avatar.CopyTo(ms);
ms.Seek(0, SeekOrigin.Begin);
using MagickImage image = new MagickImage(ms);
ms.Seek(0, SeekOrigin.Begin);
ProcessImage(image.Width, image.Height, image);
image.Write(ms, MagickFormat.Jpeg);
var dbUser = database.Users.FirstOrDefault(u => u.Id == user.Id);
if (dbUser == null)
{
throw new NotFoundException();
}
dbUser.Avatar = ms.ToArray();
user.Avatar = dbUser.Avatar;
database.SaveChanges();
}
}
private void ProcessImage(int width, int height, MagickImage image)
{
double startPosition, endPosition;
if (width < 150 && height < 150)
{
startPosition = 150 - width;
endPosition = 150 - height;
//center image
image.Draw(new DrawableRectangle(startPosition / 2, endPosition / 2, startPosition / 2 + width, endPosition / 2 + height));
image.Draw(new DrawableFillColor(MagickColors.Black));
//rest of background is black
}
else if (width < 150 && height > 150)
{
startPosition = 150 - width;
endPosition = height - 150;
image.Draw(new DrawableRectangle(startPosition / 2, endPosition / 2, startPosition / 2 + width, endPosition / 2 + height));
image.Draw(new DrawableFillColor(MagickColors.Black));
}
else if (width > 150 && height > 150 && width != height)
{
startPosition = width - 150;
endPosition = height - 150;
image.Draw(new DrawableRectangle(startPosition / 2, endPosition / 2, startPosition / 2 + width, endPosition / 2 + height));
image.Draw(new DrawableFillColor(MagickColors.Black));
}
else if (width > 150 && height < 150)
{
startPosition = width - 150;
endPosition = 150 - height;
image.Draw(new DrawableRectangle(startPosition / 2, endPosition / 2, startPosition / 2 + width, endPosition / 2 + height));
image.Draw(new DrawableFillColor(MagickColors.Black));
}
else
{
image.Resize(150, 150);
}
image.Strip();
image.Quality = 75;
}
And here is the original image:

Very simple solution.
Also I added a check to preserve the aspect of the image so it is not squeezed:
var aspect_ratio = (decimal)image.Width / (decimal)image.Height;
if (aspect_ratio > 1)
{
var newWidth = (int) Math.Round((decimal) (aspect_ratio * 150));
image.Resize(newWidth, 150);
// cut width and new width is multiply 150 by aspect ratio
}
else if (aspect_ratio < 1)
{
//cut height and new height is multiply 150 by aspect ratio
var newHeight = (int)Math.Round((decimal)((decimal)image.Height / (decimal)image.Width * 150));
image.Resize(150, newHeight);
}
private void ProcessImage(MagickImage image, MemoryStream ms)
{
using var finalImage = new MagickImage(new MagickColor("#000000"), 150, 150);
finalImage.Composite(image, Gravity.Center, CompositeOperator.Over);
finalImage.Write(ms, MagickFormat.Jpg);
image.Strip();
image.Quality = 75;
}
For ImageMagick doc refer to their github:
https://github.com/dlemstra/Magick.NET
If you download the project and open docs you can see a documentation - comments that describe the methods and then the logic in the methods.

Related

Why when resizing image/s the result is not the size I input?

The file type in this case is jpg
private void beginOperationToolStripMenuItem_Click(object sender, EventArgs e)
{
if (files.Length > 0)
{
backgroundWorker1.RunWorkerAsync();
}
}
private static Bitmap ResizeImage(String filename, int maxWidth, int maxHeight)
{
using (Image originalImage = Image.FromFile(filename))
{
//Caluate new Size
int newWidth = originalImage.Width;
int newHeight = originalImage.Height;
double aspectRatio = (double)originalImage.Width / (double)originalImage.Height;
if (aspectRatio <= 1 && originalImage.Width > maxWidth)
{
newWidth = maxWidth;
newHeight = (int)Math.Round(newWidth / aspectRatio);
}
else if (aspectRatio > 1 && originalImage.Height > maxHeight)
{
newHeight = maxHeight;
newWidth = (int)Math.Round(newHeight * aspectRatio);
}
if (newWidth >= 0 && newHeight >= 0)
{
Bitmap newImage = new Bitmap(newWidth, newHeight);
using (Graphics g = Graphics.FromImage(newImage))
{
//--Quality Settings Adjust to fit your application
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.DrawImage(originalImage, 0, 0, newImage.Width, newImage.Height);
return newImage;
}
}
return null;
}
}
Then
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
BackgroundWorker worker = sender as BackgroundWorker;
int counter = 0;
int percentage = 0;
foreach (string file in files)
{
Bitmap bmp1 = new Bitmap(ResizeImage(file, 512, 512));
string resizedfilename = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(file) + "_resized.jpg");
bmp1.Save(resizedfilename);
bmp1.Dispose();
counter++;
percentage = counter * 100 / files.Length;
worker.ReportProgress(percentage);
}
}
catch(Exception err)
{
}
}
The original image jpg size on the hard disk is Width 536 and Height 589
The resize image on the hard disk is Width 512 Height 536
Why the resized image Height is 536 ?
and why the size on the hard disk of the resized image is 600 KB while the original image size is only 130 KB ?
I think your aspect ratio less than and greater than are backwards. If the image is 'landscape' (width > height), then the aspect ration will be positive (because you are doing width / height). If it's 'portrait', it will be negative. But you have them the other way around.

Pad or Crop Image to achieve square size in C#

I have Bitmap Images that are usually smaller than 500x500 pixels.
But sometimes one or both dimensions can exceed 500 pixels.
If the height is >500 I want to crop the image at the bottom and if the width is >500 I want to crop it on both sides equally.
If the Image is <500 in any dimension I want to pad it with white pixels on each side equally to make it 500x500.
I'm not familliar with .NET but I understand there's a lot that'S already been done for you (I'm a C++ developer).
I appreciate any help! Thanks!
This is what I have so far, which puts the image in the center of a white 500x500 rectangular image. It's just that I cant wrap my head around the cases where one dimension of the original image exceeds 500 pixels. (See the two lines with ??)
public static System.Drawing.Bitmap PadImage(System.Drawing.Bitmap originalImage)
{
if (originalImage.Height > 500)
??
if (originalImage.Width > 500)
??
Size squareSize = new Size(500, 500);
System.Drawing.Bitmap squareImage = new System.Drawing.Bitmap(squareSize.Width, squareSize.Height);
using (Graphics graphics = Graphics.FromImage(squareImage))
{
graphics.FillRectangle(System.Drawing.Brushes.White, 0, 0, squareSize.Width, squareSize.Height);
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphics.DrawImage(originalImage, (squareSize.Width / 2) - (originalImage.Width / 2), (squareSize.Height / 2) - (originalImage.Height / 2), originalImage.Width, originalImage.Height);
}
return squareImage;
}
Bitmap PadCropImage(Bitmap original)
{
if (original.Width == 500 && original.Height == 500)
return original;
if (original.Width > 500 && original.Height > 500)
{
int x = (original.Width - 500) / 2;
int y = (original.Height - 500) / 2;
return original.Clone(new Rectangle(x, y, 500, 500), original.PixelFormat);
}
Bitmap square = new Bitmap(500, 500);
var g = Graphics.FromImage(square);
if (original.Width > 500)
{
int x = (original.Width - 500) / 2;
int y = (500 - original.Height) / 2;
g.DrawImageUnscaled(original, -x, y);
}
else if (original.Height > 500)
{
int x = (500 - original.Width) / 2;
int y = (original.Height - 500) / 2;
g.DrawImageUnscaled(original, x, -y);
}
else
{
int x = (500 - original.Width) / 2;
int y = (500 - original.Height) / 2;
g.DrawImageUnscaled(original, x, y);
}
return square;
}
Here is an old method I have used many times
public static Image ThumbnailImage(Image sourceImage, int imageSize, bool maintainAspectRatio, bool maintainImageSize, Color backgroundColor)
{
try
{
int thumbnailWidth = imageSize;
int thumbnailHeight = imageSize;
if (maintainAspectRatio)
{
float aspectRatio = (float) sourceImage.Width/sourceImage.Height;
float targetAspectRatio = (float) thumbnailWidth/thumbnailHeight;
if (aspectRatio < targetAspectRatio)
{
thumbnailWidth = (int) (thumbnailHeight*aspectRatio);
}
else if (aspectRatio > targetAspectRatio)
{
thumbnailHeight = (int) (thumbnailWidth/aspectRatio);
}
}
Image thumbnail = sourceImage.GetThumbnailImage(thumbnailWidth, thumbnailHeight, null, new IntPtr());
if (maintainImageSize)
{
var offset = new Point(0, 0);
if (thumbnailWidth != imageSize)
{
offset.X = ((imageSize - thumbnailWidth)/2);
}
if (thumbnailHeight != imageSize)
{
offset.Y = ((imageSize - thumbnailHeight)/2);
}
var bmpImage = new Bitmap(imageSize, imageSize, PixelFormat.Format32bppArgb);
using (Graphics graphics = Graphics.FromImage(bmpImage))
{
graphics.Clear(backgroundColor);
graphics.DrawImage(thumbnail, new Rectangle(offset.X, offset.Y, thumbnailWidth, thumbnailHeight), new Rectangle(0, 0, thumbnailWidth, thumbnailHeight), GraphicsUnit.Pixel);
}
thumbnail.Dispose();
return Image.FromHbitmap(bmpImage.GetHbitmap());
}
return thumbnail;
}
catch (Exception exception)
{
const string strExMsg = "Error Creating Thumbnail";
throw new Exception(Assembly.GetExecutingAssembly().GetName().Name + " - " + strExMsg + " Msg : " + exception.Message);
}
}

actual dimensions of background image with "zoom" layout

With "Zoom" layout background image actual width and height do not always match the width and height of the containing control, as opposed to "Stretch" layout. I am wondering if there is a property or something in winforms to retrieve current image rendered dimensions without doing any math?
This returns the Rectangle pixels from a PictureBox for any of its SizeModes.
But yes, it does take some math for Zoom mode.
It can be adapted easily to the corresponding BackgroudImageLayout values:
Rectangle ImageArea(PictureBox pbox)
{
Size si = pbox.Image.Size;
Size sp = pbox.ClientSize;
if (pbox.SizeMode == PictureBoxSizeMode.StretchImage) return pbox.ClientRectangle;
if (pbox.SizeMode == PictureBoxSizeMode.Normal ||
pbox.SizeMode == PictureBoxSizeMode.AutoSize) return new Rectangle(Point.Empty, si);
if (pbox.SizeMode == PictureBoxSizeMode.CenterImage)
return new Rectangle(new Point( (sp.Width - si.Width) / 2,
(sp.Height - si.Height) / 2), si);
// PictureBoxSizeMode.Zoom
float ri = 1f * si.Width / si.Height;
float rp = 1f * sp.Width / sp.Height;
if (rp > ri)
{
int width = si.Width * sp.Height / si.Height;
int left = (sp.Width - width) / 2;
return new Rectangle(left, 0, width, sp.Height);
}
else
{
int height = si.Height * sp.Width / si.Width;
int top = (sp.Height - height) / 2;
return new Rectangle(0, top, sp.Width, height);
}
}

How to resize an image maintaining the aspect ratio in C#

I need to know of a way to resize an image to fit in a box without the image stretching too much. The box has set width and height and I want the image to fill as much of the box as possible but maintain the aspect ratio it originally had.
//calculate the ratio
double dbl = (double)image.Width / (double)image.Height;
//set height of image to boxHeight and check if resulting width is less than boxWidth,
//else set width of image to boxWidth and calculate new height
if( (int)((double)boxHeight * dbl) <= boxWidth )
{
resizedImage = new Bitmap(original, (int)((double)boxHeight * dbl), boxHeight);
}
else
{
resizedImage = new Bitmap(original, boxWidth, (int)((double)boxWidth / dbl) );
}
The formula for scaling with the same ratio is:
newWidth = (int)((double)boxHeight * dbl)
or
newHeight = (int)((double)boxWidth / dbl)
A Math.Max could simplify the problem:
double ratio = Math.Max((double)image.width / (double)box.width , (double)image.height / (double)box.height);
image.width = (int)(image.width / ratio);
image.height = (int)(image.height / ratio);
Bitmap original,resizedImage;
try
{
using (FileStream fs = new System.IO.FileStream(imageLabel.Text, System.IO.FileMode.Open))
{
original = new Bitmap(fs);
}
int rectHeight = BOXWIDTH;
int rectWidth = BOXWIDTH;
//if the image is squared set it's height and width to the smallest of the desired dimensions (our box). In the current example rectHeight<rectWidth
if (original.Height == original.Width)
{
resizedImage = new Bitmap(original, rectHeight, rectHeight);
}
else
{
//calculate aspect ratio
float aspect = original.Width / (float)original.Height;
int newWidth, newHeight;
//calculate new dimensions based on aspect ratio
newWidth = (int)(rectWidth * aspect);
newHeight = (int)(newWidth / aspect);
//if one of the two dimensions exceed the box dimensions
if (newWidth > rectWidth || newHeight > rectHeight)
{
//depending on which of the two exceeds the box dimensions set it as the box dimension and calculate the other one based on the aspect ratio
if (newWidth > newHeight)
{
newWidth = rectWidth;
newHeight = (int)(newWidth / aspect);
}
else
{
newHeight = rectHeight;
newWidth = (int)(newHeight * aspect);
}
}
resizedImage = new Bitmap(original, newWidth, newHeight);
}
}
catch (Exception ex)
{
MessageBox.Show( ex.Message);
}
}
The accepted answer probably works but I stared at it for a long time and could not understand exactly how, so I thought I would share what I came up with: shrink height first, then check width and shrink again if needed, always preserving the aspect ratio.
I used SixLabors.ImageSharp because it's compatible with Linux, but you can easily swap out the resizing function once you have the newHeight and newWidth.
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
public class ImageSharpService : IImageService
{
public async Task ShrinkAndSaveAsync(Stream stream, string savePath, int maxHeight, int maxWidth)
{
using Image image = Image.Load(stream);
// check if resize is needed
if (ResizeNeeded(image.Height, image.Width, maxHeight, maxWidth, out int newHeight, out int newWidth))
// swap this part out if not using ImageSharp
image.Mutate(x => x.Resize(new ResizeOptions
{
Size = new Size(newWidth, newHeight)
}));
await image.SaveAsync(savePath);
}
private bool ResizeNeeded(int height, int width, int maxHeight, int maxWidth, out int newHeight, out int newWidth)
{
// first use existing dimensions
newHeight = height;
newWidth = width;
// if below max on both then do nothing
if (height <= maxHeight && width <= maxWidth) return false;
// naively check height first
if (height > maxHeight)
{
// set down to max height
newHeight = maxHeight;
// calculate what new width would be
var heightReductionRatio = maxHeight / height; // ratio of maxHeight:image.Height
newWidth = width * heightReductionRatio; // apply ratio to image.Width
}
// does width need to be reduced?
// (this will also re-check width after shrinking by height dimension)
if (newWidth > maxWidth)
{
// if so, re-calculate height to fit for maxWidth
var widthReductionRatio = maxWidth / newWidth; // ratio of maxWidth:newWidth (height reduction ratio may have been applied)
newHeight = maxHeight * widthReductionRatio; // apply new ratio to maxHeight to get final height
newWidth = maxWidth;
}
// if we got here, resize needed and out vars have been set
return true;
}
}
I believe that if you only change the height or only the width it will remain in better ratios and the width/height will change with it. So you can try that

Resize image width in c# but not the height

How do you resize the width of a image in C# without resizing the height using image.resize()
When I do it this way:
image.Resize(width: 800, preserveAspectRatio: true,preventEnlarge:true);
This is the full code:
var imagePath = "";
var newFileName = "";
var imageThumbPath = "";
WebImage image = null;
image = WebImage.GetImageFromRequest();
if (image != null)
{
newFileName = Path.GetFileName(image.FileName);
imagePath = #"pages/"+newFileName;
image.Resize(width:800, preserveAspectRatio:true, preventEnlarge:true);
image.Save(#"~/images/" + imagePath);
imageThumbPath = #"pages/thumbnail/"+newFileName;
image.Resize(width: 150, height:150, preserveAspectRatio:true, preventEnlarge:true);
image.Save(#"~/images/" + imageThumbPath);
}
I get this error message:
No overload for method 'Resize' takes 3 arguments
The documentation is garbage, so I peeked at the source code. The logic they are using is to look at the values passed for height and width and compute aspect ratios for each comparing the new value to the current value. Whichever value (height or width) has the greater aspect ratio gets its value computed from the other value. Here's the relevant snippet:
double hRatio = (height * 100.0) / image.Height;
double wRatio = (width * 100.0) / image.Width;
if (hRatio > wRatio)
{
height = (int)Math.Round((wRatio * image.Height) / 100);
}
else if (hRatio < wRatio)
{
width = (int)Math.Round((hRatio * image.Width) / 100);
}
So, what that means is that, if you don't want to compute the height value yourself, just pass in a height value that is very large.
image.Resize(800, 100000, true, true);
This will cause hRatio to be larger than wRatio and then height will be computed based on width.
Since you have preventEnlarge set to true, you could just pass image.Height in.
image.Resize(800, image.Height, true, true);
Of course, it's not difficult to just compute height yourself:
int width = 800;
int height = (int)Math.Round(((width * 1.0) / image.Width) * image.Height);
image.Resize(width, height, false, true);
Solution applicable for Winform
Using this function :
public static Image ScaleImage(Image image, int maxWidth)
{
var newImage = new Bitmap(newWidth, image.Height);
Graphics.FromImage(newImage).DrawImage(image, 0, 0, newWidth, image.Height);
return newImage;
}
Usage :
Image resized_image = ScaleImage(image, 800);

Categories

Resources