Image resizing with GDI+ - c#

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();
}
}
}
}

Related

C# CopyFromScreen multiple times to same file

I am trying to take multiple screenshots and save them to a file. However, screenshots are taken decently often, and in order to not lose any of them, my current program will simply create a new file for every screenshot. Ideally, the program would simply "append" the most recent screenshot onto a single file every time.
Here is the code:
static Rectangle bounds = Screen.GetBounds(Point.Empty);
static Size rectSize = new Size(bounds.Width, bounds.Height);
public static void takeScreenshot(string path, int iteration, string filetype)
{
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(Point.Empty, Point.Empty, new Size
(rectSize.Width * iteration, rectSize.Height));
}
bitmap.Save(path + filetype);
}
}
iteration was the amount of times the method has been called. I was trying to just shift the next screenshot over by one screenshot's width while keeping every other screenshot, but it seems to overwrite the file anyways. Is it possible to do this?
Try this..
string n = string.Format("{0:yyyy-MM-dd_hh-mm-ss-tt}", DateTime.Now);
bitmap.Save(path + n + filetype);
Like TheGeneral said, you will run out of memory quickly. And every time the file grows it has a chance of needing to be moved on disc wearing down your drives.
Also, opening and viewing huge image files can be very slow, your computer will not be able to handle it very quickly.
But here it is, maybe at least the AppendImage function could be used for something like generating particle sprite strips in games or something.
If you're appending a screenshot, I wouldn't append more than maybe 10 times, with 1080P monitor.
public enum AppendLocation
{
Before,
After
}
void AppendScreenToFile(string filename, AppendLocation appendLocation = AppendLocation.After)
{
Rectangle bounds = Screen.PrimaryScreen.Bounds;
using (Bitmap screenShot = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(screenShot))
{
g.CopyFromScreen(UpperLeftSource, Point.Empty, bounds.Size);
}
if (!File.Exists(filename))
{
screenShot.Save(filename, ImageFormat.Png);
return;
}
//Not using Image.FromFile as it blocks saving.
Image onDisc;
using (Stream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
onDisc = Image.FromStream(fs);
using (Image appended = appendLocation == AppendLocation.Before ? AppendImage(screenShot, onDisc) : AppendImage(onDisc, screenShot))
appended.Save(filename, ImageFormat.Png);
}
}
public enum AppendAxis
{
Vertical,
Horizontal
}
Bitmap AppendImage(Image image, Image append, AppendAxis axis = AppendAxis.Vertical)
{
Bitmap bitmap;
Rectangle destinationRect;
RectangleF imageBounds = new Rectangle(0, 0, image.Width, image.Height);
RectangleF appendRect = new Rectangle(0, 0, append.Width, append.Height);
pictureBox_Item4.BackgroundImage = image;
pictureBox_Item3.BackgroundImage = append;
switch (axis)
{
case AppendAxis.Vertical:
destinationRect = new Rectangle(0, image.Height, append.Width, append.Height);
bitmap = new Bitmap(image.Width, image.Height + append.Height);
break;
case AppendAxis.Horizontal:
destinationRect = new Rectangle(image.Width, 0, append.Width, append.Width);
bitmap = new Bitmap(image.Width + append.Width, image.Height);
break;
default:
throw new ArgumentException("AppendAxis is invalid.");
}
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(image, imageBounds, imageBounds, GraphicsUnit.Pixel);
g.DrawImage(append, destinationRect, appendRect, GraphicsUnit.Pixel);
return bitmap;
}
}

GDI+ Fails to save PNG C#

Edit:
This appears to be a filesize issue. The filesize that I'm working with is 10600 x 700 pixels. This ends up being a ~280 MB bitmap. I've attempted saving significantly smaller file sizes (10x10) as PNG, where they correctly save out. Is there a size limitation that is being hit with GDI+?
Pretty much full code:
class Converter
{
/* Each PNG below is 50x50 */
private static readonly int BLOCK_SIZE = 50;
private static Dictionary<char, Bitmap> BlockLookup => new Dictionary<char, Bitmap>
{
{'R', new Bitmap("Content/Map/Blocks/RedBlock.png")},
{'G', new Bitmap("Content/Map/Blocks/GreenBlock.png")},
{'B', new Bitmap("Content/Map/Blocks/BlueBlock.png")},
{'P', new Bitmap("Content/Map/Blocks/PurpleBlock.png")},
{'Y', new Bitmap("Content/Map/Blocks/YellowBlock.png")},
{'O', new Bitmap("Content/Map/Blocks/OrangeBlock.png")}
};
public void Convert(string textFilePath, string outputDirectory)
{
List<string> fileContents = new List<string>();
/* Irrelevant loading code snipped, loads text file into fileContents */
int width = fileContents.Max(line => line.Length) * BLOCK_SIZE; // 10600
int height = fileContents.Count * BLOCK_SIZE; // 700
/* Try to convert our text-file-as-image into a real image, mapping chars to their blocks */
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.WhiteSmoke);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
for (int y = 0; y < fileContents.Count; ++y)
{
for (int x = 0; x < fileContents[y].Length; ++x)
{
char currentCharacter = fileContents[y][x];
if (!blockLookup.ContainsKey(currentCharacter))
{
continue;
}
Bitmap mapBlock = blockLookup[currentCharacter];
var mapX = x * BLOCK_SIZE;
var mapY = y * BLOCK_SIZE;
graphics.DrawImage(mapBlock, new Point(mapX, mapY));
}
}
graphics.Flush(FlushIntention.Sync);
graphics.Save();
}
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
bitmap.Save(outputDirectory + mapName, ImageFormat.Png);
}
}
}
I have the following code:
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.WhiteSmoke);
/* SNIP, a bunch of image manipulation here */
graphics.Flush();
}
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
bitmap.Save(outputDirectory + imageName, ImageFormat.Bmp);
}
This code works great. However, if I change the ImageFormat from ImageFormat.Bmp to be ImageFormat.Png, I get a "A generic error occurred in GDI+.". I've scoured stackoverflow and google. The files do not exist, the path does. I've deleted all output files. I've tried 64bit & 32bit builds. Even stuff like copying the Bitmap and writing to memory streams fails (the below does not work):
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.WhiteSmoke);
/* SNIP, a bunch of image manipulation here */
graphics.Flush();
}
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
using (MemoryStream memoryStream = new MemoryStream())
{
Bitmap temp = new Bitmap(bitmap);
temp.Save(memoryStream, ImageFormat.Png);
}
}
I'd really like to be able to output PNG files. However, there seems to be some issue that I'm unaware of with (my version?) of GDI and PNG files. Is there anything I can do to get PNG output?
Sorry, but your Code is working great - I tested it with VS 2013 Express for Win Desktop on Windows Server 2012 R2 - not a problem at all.
Test 1:
private int width = 10600;
private int height = 700;
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.Black);
/* SNIP, a bunch of image manipulation here */
graphics.Flush();
}
bitmap.Save("image.png", ImageFormat.Png);
}
}
Test 2:
private int width = 10600;
private int height = 700;
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.Black);
/* SNIP, a bunch of image manipulation here */
//I read a little around - maybe your graphics operations are out of sync?
graphics.Flush(FlushIntention.Sync);
graphics.Save();
}
using (MemoryStream memoryStream = new MemoryStream())
{
Bitmap temp = new Bitmap(bitmap);
//temp.Save("image.png", ImageFormat.Png); //this worked obviously
temp.Save(memoryStream, ImageFormat.Png);
pictureBox1.Image = Image.FromStream(memoryStream); //a huge black box appears - everything is working fine
}
}
}
Maybe you have a system-issue?
I meant to comment this, but not enough reputation =,(
Maybe this isn't worth as an answer and should be a comment. But I can move it if you want to :)
Indeeed Windows 7 has some problems with big pictures, not sure its GDI+ related or just deeper in the core. But I programmed a photoediting software for university and noticed, that I couldn't load and/or format pictures larger than ~180MB. I found a workaround by piping it in an array and slicing it. But I don't know if you can use that. If I find that code I will update this post.

Compressing large image to small format

I have images stored in DB as binary, to show them I want to compress them to smaller images (4000 x 3000) to (400 x 300), which basicly works however the images look awefull, can somebody point me to the right direction?
I am using now:
System.IO.MemoryStream myMemStream = new System.IO.MemoryStream(bytes);
System.Drawing.Image fullsizeImage = System.Drawing.Image.FromStream(myMemStream);
Type typeoff = fullsizeImage.GetType();
double height = fullsizeImage.Height;
double width = Convert.ToDouble(fullsizeImage.Width);
double aspect = setWidth / width;
setHeight = Convert.ToInt32(aspect * height);
System.Drawing.Image newImage = fullsizeImage.GetThumbnailImage(Convert.ToInt32(setWidth), setHeight, null, IntPtr.Zero);
System.IO.MemoryStream myResult = new System.IO.MemoryStream();
using (System.IO.MemoryStream imageMemStream = new System.IO.MemoryStream(bytes))
{
using (Bitmap bitmap = new Bitmap(imageMemStream))
{
ImageFormat imageFormat = bitmap.RawFormat;
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Jpeg);
}
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Gif))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Gif);
}
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Bmp))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Bmp);
}
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Png))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Png);
}
if (bitmap.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Icon))
{
newImage.Save(myResult, System.Drawing.Imaging.ImageFormat.Icon);
}
}
}
_bytes = myResult.ToArray(); //Returns a new byte array.
Have been looking to this but have no idea yet how to implenet ths with my binary in and output:
Bitmap image = new Bitmap(fullsizeImage, Convert.ToInt32(newWidth), setHeight);
using (Graphics gr = Graphics.FromImage(image))
{
gr.SmoothingMode = SmoothingMode.HighQuality;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
gr.DrawImage(fullsizeImage, new Rectangle(0, 0, Convert.ToInt32(newWidth), setHeight));
_bytes = gr.T.ToArray();
}
Probebly I do something wrong but have no idea where to look to do this right, have not much experience in image compression.
Any help would be appriciated
UPDATE
trying to get out of the Image the mime type but not very lucky to get it, using this and cant find any other, code has a null return.
public byte[] imageToByteArray(System.Drawing.Image newImage)
{
System.IO.MemoryStream ms = new System.IO.MemoryStream();
ImageFormat format = newImage.RawFormat;
if (ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.FormatID == format.Guid) != null)
{
ImageCodecInfo codec = ImageCodecInfo.GetImageDecoders().FirstOrDefault(c => c.FormatID == format.Guid);
string mimeType = codec.MimeType;
}
newImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
return ms.ToArray();
You can use this function to create the reduced Images:
public static Image ShrinkImage(Image original, int scale)
{
Bitmap bmp = new Bitmap(original.Width / scale, original.Height / scale,
original.PixelFormat);
using (Graphics G = Graphics.FromImage(bmp))
{
G.InterpolationMode = InterpolationMode.HighQualityBicubic;
G.SmoothingMode = SmoothingMode.HighQuality;
Rectangle srcRect = new Rectangle(0,0,original.Width, original.Height);
Rectangle destRect = new Rectangle(0,0,bmp.Width, bmp.Height);
G.DrawImage(original, destRect, srcRect, GraphicsUnit.Pixel);
bmp.SetResolution( original.HorizontalResolution, original.VerticalResolution);
}
return (Image)bmp;
}
Note that it can only work with real Bitmap Images, not with Icons; but it makes little sense trying to reduce icons anyway!
Also note that you may or may not want to change the Dpi of the new Images. In the code I don't but maybe you want to scale it up or set it to a fixed value..
Don't forget to Dispose of your Images, when you're done with them!

ASP.net c# Resize Image SQL Upload

I have been looking all over for a way to resize an image before it is uploaded to my database. Right now the files are just upload and if they are not the correct size then my pages look like a mess. How would I resize the image before I upload it to my database, I would like to upload an original sized image, and correct size. Is this possible with ASP.net. I have seen some tutorials on image resizing but none of them were helpful, if anyone can help that would be great. I started looking at this tutorial but wasnt able to implement it in my SQL upload.
Thanks
Something like this, I am using MVC thus the HttpPostedFileBase. However this is taking the input of a file input type and returning a byte array, perfect for uploading to DB.
using System.Drawing;
using System.Drawing.Drawing2D;
private static byte[] PrepImageForUpload(HttpPostedFileBase FileData)
{
using (Bitmap origImage = new Bitmap(FileData.InputStream))
{
int maxWidth = 165;
int newWidth = origImage.Width;
int newHeight = origImage.Height;
if (origImage.Width < newWidth) //Force to max width
{
newWidth = maxWidth;
newHeight = origImage.Height * maxWidth / origImage.Width;
}
using (Bitmap newImage = new Bitmap(newWidth, newHeight))
{
using (Graphics gr = Graphics.FromImage(newImage))
{
gr.SmoothingMode = SmoothingMode.AntiAlias;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.PixelOffsetMode = PixelOffsetMode.HighQuality;
gr.DrawImage(origImage, new Rectangle(0, 0, newWidth, newHeight));
MemoryStream ms = new MemoryStream();
newImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
return ms.ToArray();
}
}
}
}

generating jpg thumbnails in dotnet

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.

Categories