I have used the code from http://www.codeproject.com/Tips/552141/Csharp-Image-resize-convert-and-save to resize images programmatically. However, that project uses the System.Drawing libraries, which are not available to Windows 10 applications.
I have tried using the BitmapImage class from Windows.UI.Xaml.Media.Imaging instead, but it does not seem to offer the functionality that was found in System.Drawing..
Has anyone had any luck being able to resize (scale down) images in Windows 10? My application will be processing images from multiple sources, in different formats/sizes, and I am trying to resize the actual images to save space, rather than just letting the application size it to fit the Image in which it is being displayed.
EDIT
I have modified the code from the above mentioned link, and have a hack which works for my specific need. Here it is:
public static BitmapImage ResizedImage(BitmapImage sourceImage, int maxWidth, int maxHeight)
{
var origHeight = sourceImage.PixelHeight;
var origWidth = sourceImage.PixelWidth;
var ratioX = maxWidth/(float) origWidth;
var ratioY = maxHeight/(float) origHeight;
var ratio = Math.Min(ratioX, ratioY);
var newHeight = (int) (origHeight * ratio);
var newWidth = (int) (origWidth * ratio);
sourceImage.DecodePixelWidth = newWidth;
sourceImage.DecodePixelHeight = newHeight;
return sourceImage;
}
This way seems to work, but ideally rather than modifying the original BitmapImage, I would like to create a new/copy of it to modify and return instead.
Here is a shot of it in action:
I may want to return a copy of the original BitmapImage rather than modifying the original.
There is no good method to directly copy a BitmapImage, but we can reuse StorageFile for several times.
If you just want to select a picture, show it and in the meanwhile show the re-sized picture of original one, you can pass the StorageFile as parameter like this:
public static async Task<BitmapImage> ResizedImage(StorageFile ImageFile, int maxWidth, int maxHeight)
{
IRandomAccessStream inputstream = await ImageFile.OpenReadAsync();
BitmapImage sourceImage = new BitmapImage();
sourceImage.SetSource(inputstream);
var origHeight = sourceImage.PixelHeight;
var origWidth = sourceImage.PixelWidth;
var ratioX = maxWidth / (float)origWidth;
var ratioY = maxHeight / (float)origHeight;
var ratio = Math.Min(ratioX, ratioY);
var newHeight = (int)(origHeight * ratio);
var newWidth = (int)(origWidth * ratio);
sourceImage.DecodePixelWidth = newWidth;
sourceImage.DecodePixelHeight = newHeight;
return sourceImage;
}
In this scenario you just need to call this task and show the re-sized image like this:
smallImage.Source = await ResizedImage(file, 250, 250);
If you want to keep the BitmapImage parameter due to some reasons (like the sourceImage might be a modified bitmap but not directly loaded from file), and you want to re-size this new picture to another one, you will need to save the re-sized picture as a file at first, then open this file and re-size it again.
Related
I'm making an app that can save to PDF image of my controls (datagrids etc).
The controls are big - width and height, with scrolls.
And my app can paginate view of this controls for saving to PDF or can save without paginating.
If i add my control to Fixed Page, it works fine, the quality is superior, but it takes a long time to save such file and it "eats" lot of space. But if i firstly make a bitmap image of this control, then save it to PDF, the quality is getting poor for some page sizes, though it makes PDF file much more lightweight.
The process goes that way:
Paginating (or not - depends on settings) with visualbrush - > Make a bitmap (bmp) -> Save to PDF.
If my page size is about A4, A3 or A2 - the quality is OK. But if i set bigger size - A1 or A0, the quality is getting so poor, that text can't be read.
If i make some other way: Paginating (or not - depends on settings) with visualbrush - > Save to PDF - without part of making bitmap image of my control, everything is good, A1 and A0 ...and even bigger page sizes looks good, but PDF file becomes much more bigger and it takes more time to save.
Is this because of some bitmap features for big sizes or something goes wrong? I mean, why the quality for A4, A3 and A2 sizes is OK, and for bigger sizes is poor?
I've also tried to change dpi or render sizes and some other attributes, but i couldn't get some good result.
Here is my code for making image of control:
private Image GetPageImage(FrameworkElement element, Size pageSize, double dpiX = 0, double dpiY = 0)
{
//Get current dpi
if (dpiX == 0 || dpiY == 0) { dpiX = PrintProperties.GetDpi()[0];dpiY = PrintProperties.GetDpi()[1]; }
//Check element measure
if (!element.IsMeasureValid)
{
Size size = new Size(element.Width, element.Height);
element.Measure(size);
element.Arrange(new Rect(size));
}
element.ApplyTemplate();
element.UpdateLayout();
Image pageImage = new Image();
double renderHeight = element.Height;
double renderWidth = element.Width;
if (element.DesiredSize.Width > (pageSize.Width-2*_margin) || element.DesiredSize.Height> (pageSize.Height-2*_margin))
{
if(element.Height>element.Width)
{
renderHeight = pageSize.Height - _margin * 2;
renderWidth = element.Width*(renderHeight / element.Height);
}
else
{
renderWidth = pageSize.Width - _margin * 2;
renderHeight = element.Height * (renderWidth / element.Width);
}
}
element.RenderSize = new Size(renderWidth, renderHeight);
var rect = new Rect(element.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(element), null, rect);
}
var bitmap = new RenderTargetBitmap((int)element.Width, (int)element.Height, dpiX, dpiY, PixelFormats.Default);
bitmap.Render(visual);
pageImage.Source = bitmap;
return pageImage;
}
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);
}
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 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 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");
}