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?
Related
I save an image in preview mode. Preview mode contains Picturebox and Label control.
The problem is when I save an image. Likely I recorded my screen.
The export image is different with my expect, it does not keep aspect ratio.
So, all control in panel after save to example.jpg will wrong position.
My code use to ScaleImage:
public static Image ScaleImage(Image image, int maxWidth, int maxHeight)
{
var ratioX = (double) maxWidth/image.Width;
var ratioY = (double) maxHeight/image.Height;
var ratio = Math.Min(ratioX, ratioY);
var newWidth = (int) (image.Width*ratio);
var newHeight = (int) (image.Height*ratio);
var newImage = new Bitmap(newWidth, newHeight);
Graphics.FromImage(newImage).DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
My code save image:
Graphics g = Graphics.FromImage(img);
string s = lstImgAdded.Items[k].Text;
Bitmap bm = new Bitmap(#"" + s);
panel2.BackgroundImage = bm;
PointF p1 = StretchImageSize(postPoint, panel2);
g.DrawImage(
DrawText(lstAliasImage[i - valuesFrom], fontType, colorInput,
Color.Transparent),
Point.Round(StretchImageSize(postPoint, panel2))); // Point.Round(StretchImageSize(postPoint, panel2)) ở đây dùng nhìu lần
g.DrawImage(ctrl.Image, Point.Round(StretchImageSize(postPointPicturebox, panel2)).X, Point.Round(StretchImageSize(postPointPicturebox, panel2)).Y,
ctrl.Width, ctrl.Height); // panel2 có phải cái hình nhỏ ko? ko. picturebox moi la nho, panel la background lon
g.Dispose();
string linkLocation = txtAddress.Text;
ScaleImage(img, witdhImg, heightImg)
.Save(linkLocation + "\\" + lstAliasImage[i - valuesFrom] + "." + imgType,
ImageFormat.Jpeg);
And class StretchImage to scale control(Picturebox, Label) with a panel.
Image show before image in a panel is different after save.
In my software, Image shown in the panel. It does not scale like in Preview software:
Applying Aspect Ratio to Windows Forms
Much like we intercepted Windows message in our Windows 7 Style Form, to keep the aspect ratio of a Windows Form we are going to intercept the resizing Windows messages. This is done by overriding WndProc.
Here is a list of the constants that we need for this algorithm:
WM_SIZING = 0x214
WMSZ_LEFT = 1
WMSZ_RIGHT = 2
WMSZ_TOP = 3
WMSZ_BOTTOM = 6
The first one is the way our C# application can tell when the Form
is being resized. The rest of the constants tell us exactly which
side of the Form is being resized. Note that for corners the edges
combine. So for example, the lower left corner would be resizing the
right and bottom parts of the form and thus be 2 + 6 = 8.
The advantage of using WndProc instead of Windows Form events is
that we can prevent the Form from flickering. Resize events in .NET
are called until after the resizing has been applied, so modifying
size in an event produces flickering.
Make sure to download the sample C# application. Notice how no
matter where you resize the Form from, the aspect ratio is
maintained. A quick peek at the source code shows how either the
height or width is adjusted depending on which side of the Form was
being resized. Another cool thing is that the Form can still be
maximized to fill the entire screen without any problems.
try this
public void ResizeImage(string OriginalFile, string NewFile, int NewWidth, int MaxHeight, bool OnlyResizeIfWider)
{
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(OriginalFile);
// Prevent using images internal thumbnail
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
FullsizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
if (OnlyResizeIfWider)
{
if (FullsizeImage.Width MaxHeight)
{
// Resize with height instead
NewWidth = FullsizeImage.Width * MaxHeight / FullsizeImage.Height;
NewHeight = MaxHeight;
}
System.Drawing.Image NewImage = FullsizeImage.GetThumbnailImage(NewWidth, NewHeight, null, IntPtr.Zero);
// Clear handle to original file so that we can overwrite it if necessary
FullsizeImage.Dispose();
// Save resized picture
NewImage.Save(NewFile);
}
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 open an image, add a border to it and save the image in C#.
I got a code, I guess it was an answer from stack overflow. Here it is:
public Bitmap AddBorder(Bitmap image, Color color, int size)
{
Bitmap result = new Bitmap(image.Width + size * 2, image.Height + size * 2);
Graphics g = Graphics.FromImage(result);
g.Clear(color);
int x = (result.Width - image.Width) / 2;
int y = (result.Height - image.Height) / 2;
g.DrawImage(image, new Point(x, y));
g.Dispose();
return result;
}
I save the image using:resultimage.save(fileName);
I tested it with an image 5MP in size. And saved the image to the disk. But there is an error.
The result has a border in left side of it and on top of it. The image seems to be zoomed. For example the saved image would miss parts of it (from right size and bottom).
Am I doing any thing wrong?
Thanks in advance.
This will go wrong as described when the resolution of the input bitmap doesn't match the resolution of your video adapter. Something you can see with the debugger. Add watches for image.HorizontalResolution and result.HorizontalResolution. You'd only get a match by accident. DrawImage(Image, Point) will rescale the image to make the resolutions match so that the apparent size of the image is the same as on the machine on which the bitmap was designed.
You solve it by using the Graphics.DrawImage(Image, Rectangle) overload so you directly control the final size of the image. Fix:
g.DrawImage(image, new Rectangle(x, y, image.Width, image.Height));
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.
I am using the following code in my website, for thumbnail creation:
string furl = "~/images/thumbs/" + matchString;
lBlogThumb.ImageUrl = GetThumbnailView(furl, 200, 200);
private string GetThumbnailView(string originalImagePath, int height, int width)
{
//Consider Image is stored at path like "ProductImage\\Product1.jpg"
//Now we have created one another folder ProductThumbnail to store thumbnail image of product.
//So let name of image be same, just change the FolderName while storing image.
string thumbnailImagePath = originalImagePath.Replace("thumbs", "thumbs2");
//If thumbnail Image is not available, generate it.
if (!System.IO.File.Exists(Server.MapPath(thumbnailImagePath)))
{
System.Drawing.Image imThumbnailImage;
System.Drawing.Image OriginalImage = System.Drawing.Image.FromFile(Server.MapPath(originalImagePath));
imThumbnailImage = OriginalImage.GetThumbnailImage(width, height,
new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback), IntPtr.Zero);
imThumbnailImage.Save(Server.MapPath(thumbnailImagePath), System.Drawing.Imaging.ImageFormat.Jpeg);
imThumbnailImage.Dispose();
OriginalImage.Dispose();
}
return thumbnailImagePath;
}
public bool ThumbnailCallback() { return false; }
I would like to change this code, and be able to create a thumbnail defining width ONLY. What I have in mind is actually something like cropping/resizing image, using a static width, maintaining it's ratio. Is that possible;
You mention resizing and cropping. If you want the thumbnail heights to vary with a fixed width, the answers provided already will work for you.
The mention of cropping makes me think that you may want a fixed thumbnail size, with the width filled and any overflowing vertical portion cropped off. If that is the case, you'll need to do a bit more work. I needed something similar recently, and this is what I came up with.
This will create a thumbnail of the original that is sized and cropped in such a way that the source image completely fills the target thumbnail, cropping any overflow. There will be no borders within the thumbnail, even if the original and thumbnail aspect ratios are different.
public System.Drawing.Image CreateThumbnail(System.Drawing.Image image, Size thumbnailSize)
{
float scalingRatio = CalculateScalingRatio(image.Size, thumbnailSize);
int scaledWidth = (int)Math.Round((float)image.Size.Width * scalingRatio);
int scaledHeight = (int)Math.Round((float)image.Size.Height * scalingRatio);
int scaledLeft = (thumbnailSize.Width - scaledWidth) / 2;
int scaledTop = (thumbnailSize.Height - scaledHeight) / 2;
// For portrait mode, adjust the vertical top of the crop area so that we get more of the top area
if (scaledWidth < scaledHeight && scaledHeight > thumbnailSize.Height)
{
scaledTop = (thumbnailSize.Height - scaledHeight) / 4;
}
Rectangle cropArea = new Rectangle(scaledLeft, scaledTop, scaledWidth, scaledHeight);
System.Drawing.Image thumbnail = new Bitmap(thumbnailSize.Width, thumbnailSize.Height);
using (Graphics thumbnailGraphics = Graphics.FromImage(thumbnail))
{
thumbnailGraphics.CompositingQuality = CompositingQuality.HighQuality;
thumbnailGraphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
thumbnailGraphics.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraphics.DrawImage(image, cropArea);
}
return thumbnail;
}
private float CalculateScalingRatio(Size originalSize, Size targetSize)
{
float originalAspectRatio = (float)originalSize.Width / (float)originalSize.Height;
float targetAspectRatio = (float)targetSize.Width / (float)targetSize.Height;
float scalingRatio = 0;
if (targetAspectRatio >= originalAspectRatio)
{
scalingRatio = (float)targetSize.Width / (float)originalSize.Width;
}
else
{
scalingRatio = (float)targetSize.Height / (float)originalSize.Height;
}
return scalingRatio;
}
To use with your code, you could replace your call to OriginalImage.GetThumbnailImage with this:
imThumbnailImage = CreateThumbnail(OriginalImage, new Size(width, height));
Note that for portrait images, this code will actually shift the thumbnail's viewport slightly higher on the original image. This was done so that portrait shots of people wouldn't result in headless torsos when the thumbnails were created. If you don't want that logic, simply remove the if block following the "portrait mode" comment.
let's have originalWidth=the original image width and thumbWidth. You can simply choose the thumbWidth to your desired value, and calculate the thumbHeigth=originalHeigth*thumbWidth/originalWidth
I got sick of needed to do this and created a lib that does this easily: Link To Documentation & Download
A basic example without rounding with a thumbnail width of 140, below the 'file' is a HttpPostedFile uploaded from an ASP.Net FileUpload control, the HttpPostedFile exposes a stream.
// Save images to disk.
using (System.Drawing.Image image = System.Drawing.Image.FromStream(file.InputStream))
using (System.Drawing.Image thumbnailImage = image.GetThumbnailImage(140, Convert.ToInt32((image.Height / (image.Width / 140))), null, IntPtr.Zero))
{
if (image != null)
{
image.Save(imageFilePath);
thumbnailImage.Save(thumbnailImagePath);
}
else
throw new ArgumentNullException("Image stream is null");
}