I have made an httphandler that shrinks an image and returns it as PNG. This works fine on my Vista PC with IE 9, but not on some old XP machines with IE 8. It seems strange that this should be a browser issue, but to me it looks like that. But, I am thinking that since I produce the PNG on the server, I must do something wrong in the code.
The httphandler (simplified):
<%# WebHandler Language="C#" Class="ShowPicture" %>
using System.Data;
using System;
using System.IO;
using System.Web;
public class ShowPicture : IHttpHandler {
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "image/png";
// byteArray comes from database
// maxWidth and maxHeight comes from Request
context.Response.BinaryWrite(
Common.ResizeImageFromArray(byteArray, maxWidth, maxHeight));
}
And the function called (simplified as well):
public static byte[] ResizeImageFromArray(byte[] array, int maxWidth, int maxHeight)
{
byte[] picArray = array;
if (maxWidth > 0 || maxHeight > 0) // Resize the image
{
Bitmap dbbmp = (Bitmap)Bitmap.FromStream(new MemoryStream(array));
if (dbbmp.Width > maxWidth || dbbmp.Height > maxHeight)
{
// Calculate the max width/height factor
Bitmap resized = new Bitmap(dbbmp, newWidth, newHeight);
MemoryStream ms = new MemoryStream();
resized.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
picArray = new Byte[ms.Length - 1];
ms.Position = 0;
ms.Read(picArray, 0, picArray.Length);
ms.Close();
}
}
return picArray;
}
I appreciate any ideas and/or input. Thanks in advance.
To resize with different format:
Bitmap resizedBitmap = new Bitmap(newWidth, newHeight, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(resizedBitmap);
g.DrawImage(originalBitmap, new Rectangle(Point.Empty, resizedBitmap.Size), new Rectangle(Point.Empty, originalBitmap.Size), GraphicsUnit.Pixel);
g.Dispose();
With that you will have yopur scaled bitmap, also you can play with the Graphics object options to get better quality/faster processing times.
Also, to convert your MemoryStream to array is better to use ms.ToArray();
picArray = ms.ToArray();
This way you don't need to create the array by yourself.
Related
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".
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.
I am currently trying to resize an image to a thumbnail, to show as a preview when its done uploading. I am using fineuploader plugin for the uploading part of the image. I consistently keep getting a "parameter is not valid". I've seen many posts related to this, and tried most of the solution, but have no success. Here is the snippet of the code:
public static byte[] CreateThumbnail(byte[] PassedImage, int LargestSide)
{
byte[] ReturnedThumbnail = null;
using (MemoryStream StartMemoryStream = new MemoryStream(),
NewMemoryStream = new MemoryStream())
{
StartMemoryStream.Write(PassedImage, 0, PassedImage.Length); //error being fire in this line
System.Drawing.Bitmap startBitmap = new Bitmap(StartMemoryStream);
int newHeight;
int newWidth;
double HW_ratio;
if (startBitmap.Height > startBitmap.Width)
{
newHeight = LargestSide;
HW_ratio = (double)((double)LargestSide / (double)startBitmap.Height);
newWidth = (int)(HW_ratio * (double)startBitmap.Width);
}
else
{
newWidth = LargestSide;
HW_ratio = (double)((double)LargestSide / (double)startBitmap.Width);
newHeight = (int)(HW_ratio * (double)startBitmap.Height);
}
System.Drawing.Bitmap newBitmap = new Bitmap(newWidth, newHeight);
newBitmap = ResizeImage(startBitmap, newWidth, newHeight);
newBitmap.Save(NewMemoryStream, System.Drawing.Imaging.ImageFormat.Jpeg);
ReturnedThumbnail = NewMemoryStream.ToArray();
}
return ReturnedThumbnail;
}
I'm out of ideas, any help is appreciated.
Your error is in the new Bitmap(startMemoryStream) line, not the line above.
The documentation states that this exception can occur when:
stream does not contain image data or is null.
-or-
stream contains a PNG image file with a single dimension greater than 65,535 pixels.
You should check that you have a valid PNG file in there. For example, write it to a file and try opening it in an image viewer.
That code is dangerous - every instance of a System.Drawing class must be placed in a using(){} clause.
Here's an alternate solution that uses the ImageResizer NuGet package and resizes the image safely.
var ms = new MemoryStream();
ImageResizer.Current.Build(PassedImage, ms, new ResizeSettings(){MaxWidth=LargestSide, MaxHeight=LargestSide});
return ImageResizer.ExtensionMethods.StreamExtensions.CopyToBytes(ms);
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();
}
}
}
}
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();
}
}
}
}