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);
}
}
Related
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.
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
I have a method that takes in a bitmap, width, height, and a list of strings, overlays it on top of a black background, and adds y-axis text onto it (as demonstrated in the picture below).
In my attempt to align the text so they occupy the exact vertical space of my graph, I took the height and divided it by the number of values in the list:
float verticalDistance = height / times.Count;
However, perhaps of alignment issues, the space between each time value on the y-axis doesn't fix exactly into the height of the original bitmap, which is the variable height.
I thought it was an issue with int and it rounding numbers up or down, but changing it verticalDistance to a float did not ameliorate the issue.
private Bitmap overlayBitmap(Bitmap sourceBMP, int width, int height, List<String> times) {
int newWidth = width + (width / 3);
int newHeight = height + (height / 3);
Bitmap result = new Bitmap(newWidth, newHeight);
using (Graphics g = Graphics.FromImage(result)) {
g.FillRectangle(Brushes.Black, 0, 0, newWidth, newHeight);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
StringFormat stringFormatX = new StringFormat();
stringFormatX.LineAlignment = StringAlignment.Center;
Font drawFontX = new Font("Whitney", 10);
float verticalDistance = height / times.Count;
for (int i = 0; i < times.Count; i++) {
if (i % 5 == 0) {
g.DrawString(times[i], drawFontX, Brushes.White, 5, ((newHeight - height)/2) + (verticalDistance * i), stringFormatX);
}
}
g.DrawImage(sourceBMP, width / 6, height / 6, width, height);
}
return result;
}
What could be the issue here?
Reduce your problem to the simplest case: What if you had just two values to display on your Y axis? Your vertical distance would be the length of the axis. Your vertical distance formula would not work in that case. Therefore
float verticalDistance = height / (times.Count - 1.0);
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);
I am writing a small 2d game engine in C# for my own purposes, and it works fine except for the sprite collision detection. I've decided to make it a per-pixel detection (easiest for me to implement), but it is not working the way it's supposed to. The code detects a collision long before it happens. I've examined every component of the detection, but I can't find the problem.
The collision detection method:
public static bool CheckForCollision(Sprite s1, Sprite s2, bool perpixel) {
if(!perpixel) {
return s1.CollisionBox.IntersectsWith(s2.CollisionBox);
}
else {
Rectangle rect;
Image img1 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s1.Image, s1.Position, s1.Origin, s1.Rotation, out rect), s1.Scale);
int posx1 = rect.X;
int posy1 = rect.Y;
Image img2 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s2.Image, s2.Position, s2.Origin, s2.Rotation, out rect), s2.Scale);
int posx2 = rect.X;
int posy2 = rect.Y;
Rectangle abounds = new Rectangle(posx1, posy1, (int)img1.Width, (int)img1.Height);
Rectangle bbounds = new Rectangle(posx2, posy2, (int)img2.Width, (int)img2.Height);
if(Utilities.RectangleIntersects(abounds, bbounds)) {
uint[] bitsA = s1.GetPixelData(false);
uint[] bitsB = s2.GetPixelData(false);
int x1 = Math.Max(abounds.X, bbounds.X);
int x2 = Math.Min(abounds.X + abounds.Width, bbounds.X + bbounds.Width);
int y1 = Math.Max(abounds.Y, bbounds.Y);
int y2 = Math.Min(abounds.Y + abounds.Height, bbounds.Y + bbounds.Height);
for(int y = y1; y < y2; ++y) {
for(int x = x1; x < x2; ++x) {
if(((bitsA[(x - abounds.X) + (y - abounds.Y) * abounds.Width] & 0xFF000000) >> 24) > 20 &&
((bitsB[(x - bbounds.X) + (y - bbounds.Y) * bbounds.Width] & 0xFF000000) >> 24) > 20)
return true;
}
}
}
return false;
}
}
The image rotation method:
internal static Image RotateImagePoint(Image img, Vector pos, Vector orig, double rotation, out Rectangle sz) {
if(!(new Rectangle(new Point(0), img.Size).Contains(new Point((int)orig.X, (int)orig.Y))))
Console.WriteLine("Origin point is not present in image bound; unwanted cropping might occur");
rotation = (double)ra_de((double)rotation);
sz = GetRotateDimensions((int)pos.X, (int)pos.Y, img.Width, img.Height, rotation, false);
Bitmap bmp = new Bitmap(sz.Width, sz.Height);
Graphics g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.RotateTransform((float)rotation);
g.TranslateTransform(sz.Width / 2, sz.Height / 2, MatrixOrder.Append);
g.DrawImage(img, (float)-orig.X, (float)-orig.Y);
g.Dispose();
return bmp;
}
internal static Rectangle GetRotateDimensions(int imgx, int imgy, int imgwidth, int imgheight, double rotation, bool Crop) {
Rectangle sz = new Rectangle();
if (Crop == true) {
// absolute trig values goes for all angles
double dera = de_ra(rotation);
double sin = Math.Abs(Math.Sin(dera));
double cos = Math.Abs(Math.Cos(dera));
// general trig rules:
// length(adjacent) = cos(theta) * length(hypotenuse)
// length(opposite) = sin(theta) * length(hypotenuse)
// applied width = lo(img height) + la(img width)
sz.Width = (int)(sin * imgheight + cos * imgwidth);
// applied height = lo(img width) + la(img height)
sz.Height = (int)(sin * imgwidth + cos * imgheight);
}
else {
// get image diagonal to fit any rotation (w & h =diagonal)
sz.X = imgx - (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0));
sz.Y = imgy - (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0));
sz.Width = (int)Math.Sqrt(Math.Pow(imgwidth, 2.0) + Math.Pow(imgheight, 2.0)) * 2;
sz.Height = sz.Width;
}
return sz;
}
Pixel getting method:
public uint[] GetPixelData(bool useBaseImage) {
Rectangle rect;
Image image;
if (useBaseImage)
image = Image;
else
image = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(Image, Position, Origin, Rotation, out rect), Scale);
BitmapData data;
try {
data = ((Bitmap)image).LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
}
catch (ArgumentException) {
data = ((Bitmap)image).LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat);
}
byte[] rawdata = new byte[data.Stride * image.Height];
Marshal.Copy(data.Scan0, rawdata, 0, data.Stride * image.Height);
((Bitmap)image).UnlockBits(data);
int pixelsize = 4;
if (data.PixelFormat == PixelFormat.Format24bppRgb)
pixelsize = 3;
else if (data.PixelFormat == PixelFormat.Format32bppArgb || data.PixelFormat == PixelFormat.Format32bppRgb)
pixelsize = 4;
double intdatasize = Math.Ceiling((double)rawdata.Length / pixelsize);
uint[] intdata = new uint[(int)intdatasize];
Buffer.BlockCopy(rawdata, 0, intdata, 0, rawdata.Length);
return intdata;
}
The pixel retrieval method works, and the rotation method works as well, so the only place that the code might be wrong is the collision detection code, but I really have no idea where the problem might be.
I don't think many people here will bother to scrutinize your code to figure out what exactly is wrong. But I can come with some hints to how you can find the problem.
If collision happens long before it is supposed to I suggest your bounding box check isn't working properly.
I would change the code to dump out all the data about rectangles at collision. So you can create some code that will display the situation at collision. That might be easier than looking over the numbers.
Apart from that I doubt that per pixel collision detection easier for you to implement. When you allow for rotation and scaling that quickly becomes difficult to get right. I would do polygon based collision detection instead.
I have made my own 2D engine like you but I used polygon based collision detection and that worked fine.
I think I've found your problem.
internal static Rectangle GetRotateDimensions(int imgx, int imgy, int imgwidth, int imgheight, double rotation, bool Crop) {
Rectangle sz = new Rectangle(); // <-- Default constructed rect here.
if (Crop == true) {
// absolute trig values goes for all angles
double dera = de_ra(rotation);
double sin = Math.Abs(Math.Sin(dera));
double cos = Math.Abs(Math.Cos(dera));
// general trig rules:
// length(adjacent) = cos(theta) * length(hypotenuse)
// length(opposite) = sin(theta) * length(hypotenuse)
// applied width = lo(img height) + la(img width)
sz.Width = (int)(sin * imgheight + cos * imgwidth);
// applied height = lo(img width) + la(img height)
sz.Height = (int)(sin * imgwidth + cos * imgheight);
// <-- Never gets the X & Y assigned !!!
}
Since you never assigned imgx and imgy to the X and Y coordinates of the Rectangle, every call of GetRotateDimensions will produce a Rectangle with the same location. They may be of differing sizes, but they will always be in the default X,Y position. This would cause the really early collisions that you are seeing because any time you tried to detect collisions on two sprites, GetRotateDimensions would put their bounds in the same position regardless of where they actually are.
Once you have corrected that problem, you may run into another error:
Rectangle rect;
Image img1 = GraphicsHandler.ResizeImage(GraphicsHandler.RotateImagePoint(s1.Image, s1.Position, s1.Origin, s1.Rotation, out rect), s1.Scale);
// <-- Should resize rect here.
int posx1 = rect.X;
int posy1 = rect.Y;
You get your boundary rect from the RotateImagePoint function, but you then resize the image. The X and Y from the rect are probably not exactly the same as that of the resized boundaries of the image. I'm guessing that you mean for the center of the image to remain in place while all points contract toward or expand from the center in the resize. If this is the case, then you need to resize rect as well as the image in order to get the correct position.
I doubt this is the actual problem, but LockBits doesn't guarantee that the bits data is aligned to the image's Width.
I.e., there may be some padding. You need to access the image using data[x + y * stride] and not data[x + y * width]. The Stride is also part of the BitmapData.