How could I calculate the zoom level (graphics scale) to fit any image to any panel?
The image size and the picture size could be any size.
The method signature I need is the following:
public float CalculateZoomToFit(Image image, Panel targetPanel)
{
// I need to calculate the zoom level to make the picture fit into the panel
return ???
}
Thanks in advance.
The ratio of width over height for both the panel and the image is the key for the answer.
var panel_ratio = targetPanel.Width / targetPanel.Height;
var image_ratio = image.Width / image.Height;
return panel_ratio > image_ratio
? targetPanel.Height / image.Height
: targetPanel.Width / image.Width
;
Add checks for divide-by-zero errors, if you want.
In general, it'll be the width or height (depending on what you want to fit) of the container divided by the width or height of the object you're putting in it. This will give you the adjustment you need to make to the image for it to fit.
Related
Whilst working on a functionality for viewing embedded images that get fetched from one of our servers, one of the requirements is to make an overview page, where all images are displayed, in a fasion like the iOS Native Photos App (square tiles, in rows of 4/5 and when tapped, displays the image screen-wide.
This has been successfully implemented, except that the images were morphed (image aspects were never taken into account). Thus, they have asked me to devise a method to crop the image, so the original aspect stays unchanged.
In order to do this, I was thinking of first resizing the image until the width reaches the max width I want it to be ((screenwidth - 10) / 4 - 5).
If the height of the image is smaller than the width, I set the height to be equal to the width, and crop the width accordingly (with aspect unchanged).
If the height of the image is greater than the width, I just want to crop the height.
This is the method I am currently using:
//Images is an array/List with base64 strings
foreach (var image in Images)
{
UIImage imageToAdd = UIImage.LoadFromData(NSData.FromArray(Convert.FromBase64String(image.Content)));
var width = imageToAdd.Size.Width;
var height = imageToAdd.Size.Height;
//GlobalSupport.ScreenWidth is a static variable that contains the actual screensize
var newWidth = (GlobalSupport.ScreenWidth - 10) / 4 - 5;
var newHeight = height * newWidth / width;
var widthToCrop = 0.0f;
var heightToCrop = 0.0f;
//If the new height is smaller than the new width, make the new height equal to the new width, and modify the new width accordingly.
if (newHeight < newWidth)
{
newHeight = newWidth;
newWidth = width * newHeight / height;
widthToCrop = newWidth - newHeight;
}
//Or, if the new height is greater than the new width, just crop the height.
else if (newHeight > newWidth)
{
heightToCrop = newHeight - newWidth;
}
UIGraphics.BeginImageContext(new SizeF(newWidth, newHeight));
var cropRectangle = new RectangleF(-widthToCrop, -heightToCrop, newWidth, newHeight);
imageToAdd.Draw(cropRectangle);
imageToAdd = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
UIButton btnToAdd = new UIButton(new RectangleF(-widthToCrop, -heightToCrop, newWidth, newHeight));
btnToAdd.SetBackgroundImage(imageToAdd, UIControlState.Normal);
btnToAdd.TouchUpInside += (sender, e) =>
{
Task.Factory.StartNew(() =>
{
//Displays a loading spinner
GlobalSupport.EnableLoadingOverlay(true, NSBundle.MainBundle.LocalizedString("txtOneMoment", "", ""));
}).ContinueWith(task => InvokeOnMainThread(() =>
{
//Navigate to the full-screen image viewer
this.NavigationController.PushViewController(
new VCDCMPhotoDetails(
UIImage.LoadFromData(
NSData.FromArray(
Convert.FromBase64String(image.Content)))), true);
GlobalSupport.EnableLoadingOverlay(false, "");
}));
};
//Add btn to the scrollable view's children
scrollView.AddSubview(btnToAdd);
}
This method works great, except for one thing: if the new height is smaller than the new width, the new height and new width successfully get modified, but after the ImageToAdd.Draw(RectangleF) the Size of the ImageToAdd still has the new width value, instead of the cropped width (that is, if I can assume that, if I draw an image with -20 as its x-value, the width gets modified with -20 as well).
I don't know if this is the right way to do it. If it isn't, then all help is welcome! If this is the right way to do it, but I'm missing something, please let me know! Thanks in advance!
When cropping the image, the x-coordinate is equal to the width of the image, minus the max width of a thumbnail: http://i.stack.imgur.com/uNm7k.png
After cropping, the image still had the original new width, instead of the cropped width: http://i.stack.imgur.com/rabeY.png
The resulting grid of images: http://i.stack.imgur.com/opo2f.jpg
Let me just show you a proper procedure on how to crop the image with a FILL operation. The code is in Swift but you should have no trouble porting it:
public static func resampleImageToSize(image: UIImage!, size: CGSize) -> UIImage {
let originalWidth = image.size.width
let originalHeight = image.size.height
let originalRatio = originalWidth/originalHeight
let targetRatio = size.width/size.height
var targetFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
if originalRatio>targetRatio {
// crop width
let targetHeight = size.height
let targetWidth = targetHeight * originalRatio
targetFrame = CGRect(x: (size.width-targetWidth)*0.5, y: (size.height-targetHeight)*0.5, width: targetWidth, height: targetHeight)
} else if originalRatio<targetRatio {
// crop height
let targetWidth = size.width
let targetHeight = targetWidth / originalRatio
targetFrame = CGRect(x: (size.width-targetWidth)*0.5, y: (size.height-targetHeight)*0.5, width: targetWidth, height: targetHeight)
}
UIGraphicsBeginImageContext(size)
image.drawInRect(targetFrame)
let outputImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return outputImage
}
So this is a general method that takes in an original image and returns an image with a size of your choice. In your case that is the size of the icons you want to display. The operation here is fill, if you need to fit the image all you need to do is swap < and > in both if statements.
What you are doing is easiest to achieve with creating an image view with the size of your target image size. Then set your image to it and set the content mode to scale aspect fill and simply create a screenshot of the view (you can find very easy solutions for that on the web).
From your question I can not see if you have an issue at all or what it is. But one thing is bothering me. The line
RectangleF(-widthToCrop, -heightToCrop, newWidth, newHeight)
Will crop it the way you will always get the bottom right part of the image. You should most likely center it:
RectangleF(-widthToCrop*0.5, -heightToCrop*0.5, newWidth, newHeight)
Ok I think I found your problem. The frame you compute is the frame on which you should draw the image to put it on the square. But when you begin image context you should set the size to be a square. So if the width is larger the size should be (newHeight, newHeight) otherwise (newWidth, newWidth). The button frame makes no sense to me either. Why are you using the rectangle that should be used only to redraw the image?
I have a panel called "canvas". It is transparent. So the background is from the form image, which is dark blue. This shows in the panel or canvas.
When I save the canvas to image, it saves the background, but not what I have drawn thereon, my drawline pen is yellow. And I can see it drawing on the panel. But when I save it... there are not yellow lines in the image.
What am I missing? Where are my yellow lines?
I am running this with my timer tick... to get the view to update.
This tracks the position of a CNC type machine. Gives a visual of where
the machine is in relation to Zero.
My ultimate goal, is to have a "viewport" that is zoomable, thus getting it
into a image, for easy resizing, and displaying in a pictureBox, which will
handle the stretched image and center it automatically?
I have read some complex solutions, but I am after the simple ones.
Any help would be appreciated. Sincerely,
private void VMoveNow()//Draw on panel called "canvas".
{
double a = GV.MYa * -1; //Change Direction of Y Drawing.
xc = Convert.ToInt32(GV.MXa) + (canvas.Width / 2);
yc = Convert.ToInt32(a) + (canvas.Height / 2);
g = canvas.CreateGraphics();
g.DrawLine(p, x, y, xc, yc);
x = xc;
y = yc;
g.Dispose();
}
private void SaveBMP()
{
try
{
Bitmap mybmp = new Bitmap(canvas.Width, canvas.Height);
canvas.DrawToBitmap(mybmp, canvas.Bounds);
mybmp.Save("C:\\myimage.bmp");
}
catch
{
return;
}
}
Thanks for looking.
I answered my own problem, after several attempts... I have figured out that I can scale my var's used for this drawings... and the size of the Drawline will be scale as a result.
So I now have scaling of the Drawline drawing, in a panel, with no picture or picture box needed. Does what I wished.
Setting the Pen width to -1 takes care of it resizing.
I have a Picturebox and a ton of Bitmaps that can be displayed in it. The relative size of the Bitmap when compared to the others is of importance to the user. They need to be able to see that one image is smaller or bigger than another. The Bitmap must also fit in the picturebox entirely and the picturebox cannot be resized.
When simply displaying the Bitmaps unscaled in a huge picturebox the relative sizes of the bitmaps is easy to see, but when trying to fit them in a small box and having to scale them down my problem starts.
When using the Stretch PictureBoxSizeMode as you would imagine the images sometimes appear distorted due to the nonspecific sizes of the Bitmaps and the fact they then get stretched to fill the whole box regardless, but the Stretch sizemod is the closest to the kind I need.
None of the other sizemodes suit my needs so I know now I need to create a function to resize the Bitmap and here was the start of my attempt until I realized I was going in completely the wrong direction, the image returned here retains no 'scale'.
private Bitmap ResizeBitmap(Bitmap img)
{
int newWidth = 0;
int newHeight = 0;
double imgRatio;
if (img.Width > img.Height)
{
imgRatio = ((double)img.Height / (double)img.Width) * 100;
newWidth = pictureBox.Width;
newHeight = (int)(((double)newWidth / 100) * imgRatio);
}
else
{
imgRatio = ((double)img.Width / (double)img.Height) * 100;
newHeight = pictureBox.Height;
newWidth = (int)(((double)newHeight / 100) * imgRatio);
}
Bitmap newImg = new Bitmap(newWidth, newHeight);
using (Graphics g = Graphics.FromImage(newImg))
g.DrawImage(img, 0, 0, newWidth, newHeight);
return newImg;
}
I've been staring at the screen for a while now and the math to do the scaling currently eludes me, I'm hoping someone can point me in the right direction. It's almost 4am so maybe my brain just isn't grasping some simple concepts.
Set the PictureBoxSizeMode to Zoom. This maintains the aspect ratio.
I am trying to save an image from string.
so I want to know how I can set image height and width in inches at the time of saving the image.
my code follows for image saving :
private void Base64ToImage(string base64String)
{
Image fullSizeImg = null;
byte[] imageBytes = Convert.FromBase64String(base64String);
MemoryStream ms = new MemoryStream(imageBytes);
fullSizeImg = Image.FromStream(ms, true);
System.Drawing.Image.GetThumbnailImageAbort dummyCallBack = new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback);
System.Drawing.Image thumbNailImg = fullSizeImg.GetThumbnailImage(700, 800, dummyCallBack, IntPtr.Zero);
thumbNailImg.Save(ImagePath, System.Drawing.Imaging.ImageFormat.Png);
fullSizeImg.Dispose();
thumbNailImg.Dispose();
}
That doesn't work. We save in pixels because an inch/cm/mile does not convert to on-screen real estate. The reason for this is that we all use different DPI settings, albeit 92 DPI seems to be one of the more common settings nowadays.
There are also varying DPI settings for printers...
To calculate the pixels from inches, you could try:
pixels = inches * someDpiSetting
but bear in mind this will not result in inches on every screen, every printout, etc.
EDIT: If you take a look at WPF you'll find that it has fantastic support for DPI, and will translate a form to the same (give or take) size regardless of DPI. Maybe that helps?
Bitmaps don't have a size in inches, their size is measured in pixels. That said most modern bitmat formats have a piece of metadata called DPI (dots per inch) that is used to translate a size in pixels to a size in inches via the simple formula:
inches = pixels / dpi
For the Image class you set metadata using the SetPropertyItem Method where the pieces of metadata we are interested in are:
PropertyTagResolutionUnit - set this to "2" for inches
PropertyTagXResolution - Essentially the X DPI as long as PropertyTagResolutionUnit is in inches.
PropertyTagYResolution - The Y DPI as long as PropertyTagResolutionUnit is in inches
See Property Item Descriptions for details.
(Actually, I realised half way through writing this that the setting of property metadata using SetPropertyItem looks really complicated - you might just be better off using Bitmat instead, which has resolution properties making the whole thing a lot easier)
As a contrast to those imperial measures and formula-only's:
// inches = pixels / dpi
// pixel = inches * dpi
// 1 centimeter = 0.393700787 inch
// pixel = cm * 0.393700787 * dpi
single sngWidth = 2.25; //cm
single sngHeight = 1.0; //cm
sngWidth *= 0.393700787 * bmpImage.HorizontalResolution; // x-Axis pixel
sngHeight *= 0.393700787 * bmpImage.VerticalResolution; // y-Axis pixel
Like so:
public static int Cm2Pixel(double WidthInCm)
{
double HeightInCm = WidthInCm;
return Cm2Pixel(WidthInCm, HeightInCm).Width;
} // End Function Cm2Pixel
public static System.Drawing.Size Cm2Pixel(double WidthInCm, double HeightInCm)
{
float sngWidth = (float)WidthInCm; //cm
float sngHeight = (float)HeightInCm; //cm
using (System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(1, 1))
{
sngWidth *= 0.393700787f * bmp.HorizontalResolution; // x-Axis pixel
sngHeight *= 0.393700787f * bmp.VerticalResolution; // y-Axis pixel
}
return new System.Drawing.Size((int)sngWidth, (int)sngHeight);
} // End Function Cm2Pixel
If you are using a Bitmap then it has the method SetResolution (http://msdn.microsoft.com/en-us/library/system.drawing.bitmap.setresolution.aspx) that allows you to set the x and y dpi which can be easily derived from your knowledge of the height and width of the image in pixels and inches that you already have.
I'm hoping here that using a Bitmap instead of an Image shouldn't be a problem. Its a subclass so I would imagine it is likely you can.
MemoryStream ms = new MemoryStream();
new FileStream(Afiladdress, FileMode.Open).CopyTo(ms);
Bitmap myimage = new Bitmap(ms);
float Width = myimage.Width / myimage.HorizontalResolution;//in INCHES
float Height= myimage.Height/ myimage.VerticalResolution;//in INCHES
In my site, users can upload photos. I currently compress and resize the photo to make sure they aren't huge files. But this creates photos that are of varying dimensions... which makes the site a bit "ugly" in my opinion.
I'd like to ensure the thumbnails are square images, but not by using padding. It's ok if there is some loss of the photo in the thumbnail. I'd like to keep the fidelity of the photo high, even if it means some cropping needs to occur.
I wrote some code to do this exact thing. I chose to crop it because resizing without preserving aspect ratio looks pretty horrible. I do a crop then a resize to create a thumnail image:
public Bitmap CreateThumbnail(Bitmap RawImage)
{
int width = RawImage.Width;
int height = RawImage.Height;
Size ThumbnailDimensions = new Size();
ThumbnailDimensions.Width = 100;
ThumbnailDimensions.Height = 100;
Rectangle cropArea = new Rectangle();
if (width > height)
{
cropArea.Width = height;
cropArea.Height = height;
cropArea.X = 0;
cropArea.Y = 0;
}
else if (width < height)
{
cropArea.Width = width;
cropArea.Height = width;
cropArea.X = 0;
cropArea.Y = 0;
}
if(width != height) Bitmap thumbnail = CropImage(RawImage, cropArea);
thumbnail = ResizeImage(thumbnail, ThumbnailDimensions);
return thumbnail;
}
This just crops from the top left corner then resizes it to my thumbnail dimensions.
I would imagine you need to take the shortest dimension (either w or h), and use that as your dimension for creating the cropped image, essentially you can crop and then scale the image. Check out this article as an example for cropping an image. Also check out this Stack Overflow question regarding image quality.
Rather than cropping, I would make the div or whatever you put them in a fixed square size. Scale the image to fit inside that square.
How would you decide to crop it? From the top-left? Bottom-right? Center?
To make a rectangle into a square you need to either pad, resize without preserving aspect ratio or crop (or combinations).
Here's some code for cropping
http://snippets.dzone.com/posts/show/1484
(I work for Atalasoft -- in our DotImage Photo SDK), it's
AtalaImage img = new AtalaImage("filename.jpg");
AtalaImage img2 = new CropCommand( /*point and size of crop */).Apply(img).Image;
img2.Save("filename", new JpegEncoder(quality), null);