I am trying to upload an image to a server through a ASP.net core. My frontend sends a base64 string of an image to my server, and I want my server to resize it to 480*680 px and save it. I convert the image to a byte array:
byte[] bytes = Convert.FromBase64String(newItem.Image);
And use this function to Resize and Save:
private string ResizeAndSave(byte[] bytes)
{
Guid guid = Guid.NewGuid();
string newFileName = $"{guid}.jpg";
using (var ms = new MemoryStream(bytes))
{
var image = new Bitmap(ms);
var destRect = new Rectangle(0, 0, 640, 480);
var destImage = new Bitmap(640, 480);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
};
var saveDir = _hostingEnvironment.WebRootPath + "\\images";
if (!Directory.Exists(saveDir))
{
Directory.CreateDirectory(saveDir);
}
destImage.Save($"{saveDir}/{newFileName}", ImageFormat.Jpeg);
};
return newFileName;
}
If I save the ByteArray directly, everything works, and the image comes out as I want orientation wise. But since I plan on uploading directly from a phone I want to scale the images down some. So I convert it to a Bitmap and work some magic.
However when the bitmap is loaded the image is flipped 90 degrees on the side.
I thought whatever and switched the width/height of the BitMap and Rectangle functions. Which works. Except the image was on its side, so I added a:
destImage.RotateFlip(RotateFlipType.Rotate90FlipNone);
Before the image is saved again. It works. For IPhone - But if I upload from an Android the image is completely upside down.
I take that I need to read some EXIF tags, but there doesn't seem to be any orientation exif tags on the image I am testing with (and the bitmap PropertyIdList is empty), and even without EXIF tags it shouldn't rotate the image just by making a new Bitmap - Right?
Can anyone suggest a good way to get the correct rotation as well as downscaling of the image to a manageable size?
Related
I'm facing an issue (ghost border) when an image is resized on the live server, but not on localhost. This is really weird. I did some research on web and found similar solutions:
https://mariusschulz.com/blog/preventing-ghost-borders-when-resizing-images-with-system-drawing
https://www.codeproject.com/Articles/11143/Image-Resizing-outperform-GDI
Ghost-borders ('ringing') when resizing in GDI+
Below is the code that I'm using, inside I have commented old code and new code.
protected byte[] ApplyResize(byte[] byteArray, int targetSize, Size originalSize = default(Size))
{
using (MemoryStream ms = new MemoryStream(byteArray))
{
if (targetSize <= 0)
{
targetSize = 800;
}
var image = Image.FromStream(ms);
var size = default(Size);
if (originalSize != default(Size))
{
size = CalculateDimensions(originalSize, targetSize);
}
else
{
size = new Size(targetSize, targetSize);
}
var resized = new Bitmap(size.Width, size.Height);
using (var graphics = Graphics.FromImage(resized))
{
//old code
//graphics.CompositingQuality = CompositingQuality.HighSpeed;
//graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
//graphics.CompositingMode = CompositingMode.SourceCopy;
//graphics.DrawImage(image, 0, 0, size.Width, size.Height);
//new code
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingMode = CompositingMode.SourceCopy;
var attributes = new ImageAttributes();
attributes.SetWrapMode(WrapMode.TileFlipXY);
var destination = new Rectangle(0, 0, size.Width, size.Height);
graphics.DrawImage(image, destination, 0, 0, size.Width, size.Height, GraphicsUnit.Pixel, attributes);
}
using (var ms2 = new MemoryStream())
{
resized.Save(ms2, image.RawFormat);
return ms2.ToArray();
}
}
}
I tested on the live server, yes, the ghost border is gone, but the image has gotten a wrong position.
This image is the expected result after resize, all clear, no problem
This is what happens on localhost and live server, when using the new code, image gets cut with weird position
To me it looks like the DPI (resolution) of the resized image is different than the real image. This would make sense, as the resolution of the new bitmap is dependent of the resolution of the display. And your server might not really have a display...
To make sure you get the expected results you should call setResolution on it with the resolution of the original image, right after creating the new image and before drawing to it, like this:
var resized = new Bitmap(size.Width, size.Height);
resized.SetResolution(image.HorizontalResolution, image.VerticalResolution);
This should be a good fix in general, as you might also get issues when running locally with image with have a different DPI than the one you are testing with.
On top of that, as was mentioned above, you should change the call to DrawImage to the following, as you forgot to specify the source rectangle:
graphics.DrawImage(image, destination, 0, 0, originalSize.Width, originalSize.Height, GraphicsUnit.Pixel, attributes);
Additionally you might need to explain what CalculateDimensions does, as this might produce something which is wrong and as is not visible here we can only guess.
I solve my issue by using using (ImageAttributes wrapMode = new ImageAttributes()) inside and upload image size must more than 415x415 , not sure why, but upload lower than 415x415 border will still appear and draw in correct position
protected byte[] ApplyResize(byte[] byteArray, int targetSize, Size originalSize = default(Size))
{
using (MemoryStream ms = new MemoryStream(byteArray))
{
if (targetSize <= 0)
{
targetSize = 800;
}
var image = Image.FromStream(ms);
var size = default(Size);
if (originalSize != default(Size))
{
size = CalculateDimensions(originalSize, targetSize);
}
else
{
size = new Size(targetSize, targetSize);
}
var resized = new Bitmap(size.Width, size.Height);
using (var graphics = Graphics.FromImage(resized))
{
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingMode = CompositingMode.SourceCopy;
var destination = new Rectangle(0, 0, size.Width, size.Height);
using (ImageAttributes wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destination, 0, 0, originalSize.Width, originalSize.Height, GraphicsUnit.Pixel, wrapMode);
}
}
using (var ms2 = new MemoryStream())
{
resized.Save(ms2, image.RawFormat);
return ms2.ToArray();
}
}
}
For uploading image I am using plupload on client side. Then in my controlled I have next logic:
public ActionResult UploadFile()
{
try
{
var file = Request.Files.Count > 0 ? Request.Files[0] : null;
using (var fileStream = new MemoryStream())
{
using (var oldImage = new Bitmap(file.InputStream))
{
var format = oldImage.RawFormat;
using (var newImage = ImageUtility.ResizeImage(oldImage, 800, 2000))
{
newImage.Save(fileStream, format);
}
byte[] bits = fileStream.ToArray();
}
}
{
catch (Exception ex)
{
}
}
ImageUtility.ResizeImage Method:
public static class ImageUtility
{
public static Bitmap ResizeImage(Bitmap image, int width, int height)
{
if (image.Width <= width && image.Height <= height)
{
return image;
}
int newWidth;
int newHeight;
if (image.Width > image.Height)
{
newWidth = width;
newHeight = (int)(image.Height * ((float)width / image.Width));
}
else
{
newHeight = height;
newWidth = (int)(image.Width * ((float)height / image.Height));
}
var newImage = new Bitmap(newWidth, newHeight);
using (var graphics = Graphics.FromImage(newImage))
{
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphics.FillRectangle(Brushes.Transparent, 0, 0, newWidth, newHeight);
graphics.DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
}
}
The issue which i have here that Image size is increased.
I uploaded image of 1.62MB and after this controller is called and it creates instance if Bitmap and then save Bitmap to filestream and read bits with "fileStream.ToArray();" I am getting 2.35MB in "bits".
Can anyone tell me what's the reason of increasing the image size after I save it as bitmap. I need Bitmap because I need to check with and height of uploaded image and resize it if I need.
The answer is simple, the bitmap takes up more memory the whatever format the image was in previously because it's uncompressed it stays in that uncompressed format after saving it.
jpeg, png, gif, etc. are compressed and therefore use less bytes tha a bitmap which is uncompressed.
If you just want to save the original image, just save file.InputStream.
If you need to resize, you can use a library to apply jpg/png/etc compression and then save the result.
What is the goal here? Are you merely trying to upload an image? Does it need to be validated as an image? Or are you just trying to upload the file?
If upload is the goal, without any regard to validation, just move the bits and save them with the name of the file. As soon as you do this ...
using (var oldImage = new Bitmap(file.InputStream))
... you are converting to a bitmap. Here is where you are telling the bitmap what format to use (raw).
var format = oldImage.RawFormat;
If you merely want to move the file (upload), you can run the memory stream to a filestream object and you save the bits.
If you want a few checks on whether the image is empty, etc, you can try this page (http://www.codeproject.com/Articles/1956/NET-Image-Uploading), but realize it is still putting it in an image, which is not your desire if you simply want to save "as is".
I'm doing image resizing as follows:
private byte[] ResizeImage(System.Drawing.Image image, double scaleFactor)
{
//a holder for the result
int newWidth = (int)(image.Width * scaleFactor);
int newHeight = (int)(image.Height * scaleFactor);
Bitmap result = new Bitmap(newWidth, newHeight);
//use a graphics object to draw the resized image into the bitmap
using (Graphics graphics = Graphics.FromImage(result))
{
//set the resize quality modes to high quality
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
graphics.DrawImage(image, 0, 0, result.Width, result.Height);
}
//return the resulting bitmap
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(result, typeof(byte[]));
}
Whilst it all appears to work perfectly and for the most part is fine, users are saying they are getting error messages when they try to open the resized images in Adobe software.
Illustrator error:
The file "MyPhoto.jpg" is in an unknown format and cannot be opened.
Photoshop error:
Could not complete your request because an unknown or invalid JPEG
marker type is found.
As I say I can open the image fine in Windows viewer, Picasa, GIMP etc.
Just seems to be Adobe software with the issue.
Any ideas? Thanks
This can be resolved simply by including the ImageFormat when saving.
image.Save("filename.jpg", ImageFormat.Jpeg)
I'm really trying to nail out a little more performance out of this tidbit of code. It's not a heavly used bit of code but is used every time a new image is uploaded, and 4 times for each image (100px, 200px, 500px, 700px). So when there are any more than 2 or 3 images processing, it gets a little busy on the server. Also I'm trying to figure out how to make it correctly process images with a low resolution. Currently it just chops it off half way through, not plesent.
Examples: Original, large, xLarge
public static byte[] ResizeImageFile(byte[] imageFile, int targetSize)
{
using (System.Drawing.Image oldImage = System.Drawing.Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format32bppRgb))
{
newImage.SetResolution(oldImage.HorizontalResolution, oldImage.VerticalResolution);
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
}
private static Size CalculateDimensions(Size oldSize, int targetSize)
{
Size newSize = new Size();
if (oldSize.Width > oldSize.Height)
{
newSize.Width = targetSize;
newSize.Height = (int)(oldSize.Height * (float)targetSize / (float)oldSize.Width);
}
else
{
newSize.Width = (int)(oldSize.Width * (float)targetSize / (float)oldSize.Height);
newSize.Height = targetSize;
}
return newSize;
}
Thanks for and help!
The first thought that comes to mind is, have you thought about Multithreading it? i.e. calling this method for each image (or batch of images) in a separate thread? That way, if your server has a few cores you can get things done quicker. Just a thought...
(Threading is a great tip.)
Try to call your method with the smallest possible image as input each time, instead of the original image. If the original image is, say 2000px, then create the 700px image from it and then use your newly created 700px image to create the 500px, etc...
With the HighQualityBicubic setting I doubt that you'll notice any difference in the 100px image. (But it of course it needs to be verified.)
For completeness, here is the solution to the second part of the question which was never answered. When processing a low resolution image the image was being cut off. The solution now, seems obvious. The problem lies in this bit of code from above:
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height,
PixelFormat.Format32bppRgb))
The problem being that I'm selecting the PixelFormat, not letting it be the format of the original image. The correct code is here:
public static byte[] ResizeImageFile(byte[] imageFile, int targetSize)
{
using (System.Drawing.Image oldImage = System.Drawing.Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height,
oldImage.PixelFormat))
{
newImage.SetResolution(oldImage.HorizontalResolution,
oldImage.VerticalResolution);
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
}
i am using this code to take a jpg image and save it as a thumbnail but it seems very slow ..
Image thumbNail = image.GetThumbnailImage(width, height, null, new IntPtr());
is there any faster way to generate a large amount of thumbnails from a directory of images?
Try Draw Image - Re Edited
public Image ResizeImage(Image openImage, int NewWidth, int NewHeight) {
var openBitmap = new Bitmap(openImage);
var newBitmap = new Bitmap(NewWidth, NewHeight);
using (Graphics g = Graphics.FromImage(openBitmap))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawImage(newBitmap, new Rectangle(0, 0, NewWidth, NewHeight));
}
openBitmap.Dispose(); //Clear The Old Large Bitmap From Memory
return (Image)newBitmap;
}
Typical 3-4mb Image Takes Between 4-8ms
1) By far the fastest and most reliable way to create .Jpg thumbnails is to use partial decompression.
Jpg's have a unique aspect in that it's possible to extract a 1/8 size or 1/16 size (or any power of 2 size) copy of the original without decompressing or sampling the entire original image.
Ever notice how programs like Picassa or Windows Explorer seem to create thumbnails super fast? This is how they do it (when they are not already cached).
This functionality is easily available in any library based on the Independent JPEG Group library code, and most of them are. For example ImageMagick which has a .NET layer available.
2) You can further increase speed by using a hardware accelerated library like libjpeg turbo, although it may require interop.
3) Some explanation of this special .jpg feature is here.
Try it:
public bool GenerateThumbNail(string fileName, string thumbNailFileName,
ImageFormat format, int height, int width)
{
try
{
using (Image img = Image.FromFile(fileName))
{
Image thumbNail = new Bitmap(width, height, img.PixelFormat);
Graphics g = Graphics.FromImage(thumbNail);
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
Rectangle rect = new Rectangle(0, 0, width, height);
g.DrawImage(img, rect);
thumbNail.Save(thumbNailFileName, format);
}
return true;
}
catch (Exception)
{
return false;
}
}
It uses DrawImage too.