Background
On several occasions I have been tasked with creating a web application that would allow the user to upload and store files image files (.jpg, .png, .gif, etc.) along with a corresponding text description. The first time was a problem because I have never done this before. Googling "store image files database file system" reveals a plethora techniques and opinions both pro and con on storing image data in databases or the file system. However, this paper, To BLOB or Not To BLOB: Large Object Storage in a Database or a Filesystem summarizes "... objects smaller than 256K are best stored in a database while objects larger than 1M are best stored in the filesystem.". A similar search on resizing images on the fly also returned a variety of programming techniques. The resulting code presented here is the result of this research.
This article will not address the pro's and con's of either of these approaches to image file storage and resizing. We will just make it happen starting with storing images on the file system while storing meta data pertaining to the image in a SQL Server database. I will follow up with an additional article that will describe storing images in a database at a later date.
Using the code
The web project uses the .NET 4.51 framework so be sure you have it installed on your system. Download and unzip the Visual Studio solution to a folder of your choice, and open it with Visual Studio 2012 or better.
The default aspx page uses the standard FileUpload, TextBox, Button, and GridView controls. The GridView columns have been converted to templates and the GridView is bound to a SqlDataSource control. The SqlDataSource control is in turn bound to the localDB found in the App_Data folder It contains a single table where the file name, file path, and text description of the image is stored. For simplicity, this application only accepts graphic files that are of MIME type "image/jpeg". Other types such as PNG and GIF can be processed with minor changes in the code. This project was developed and tested using Internet Explorer 11.
Run the code in Internet Explorer and choose a JPG image to upload. Clicking the 'Submit Photo' button starts the process. The FileUpload control receives the file during the resulting postback. The button's click event handler checks for the existance of a file. If none exists, or if not a JPEG mime type, no data is processed. Otherwise control is passed to the ResizeAndSaveImage method.
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;
namespace StoringImages
{
public partial class Default : System.Web.UI.Page
{
protected void Button1_Click(object sender, EventArgs e)
{
if (FileUpload1.HasFile == false || FileUpload1.PostedFile.ContentType.ToLower() != "image/jpeg")
{
return;
}
ResizeAndSaveImage();
SqlDataSource1.InsertParameters["FilePath"].DefaultValue = Server.MapPath("SavedImages");
SqlDataSource1.InsertParameters["FileName"].DefaultValue = System.IO.Path.GetFileName(FileUpload1.PostedFile.FileName);
SqlDataSource1.InsertParameters["Description"].DefaultValue = TextBox1.Text;
SqlDataSource1.Insert();
}
The first thing that must be done is to get the uploaded image into a Bitmap object and extract vital information:
protected void ResizeAndSaveImage()
{
using (Bitmap uploadedBmp = new Bitmap(FileUpload1.FileContent))
{
decimal origHeight = uploadedBmp.Height;
decimal origWidth = uploadedBmp.Width;
int newHeight = 500;
int newWidth = Convert.ToInt32(newHeight / (origHeight / origWidth));
Here the original image height and width are saved and the height of the new, resized image is determined. In this case the new height will be 500 pixels. It can be any value, even larger than the original. The new width is calculated so that the resized image doesn't appear squashed or stretched. Next a Graphics object is created. This object will process the image data. We set several rendering properties to produce a high quality image:
using (Graphics resizedGr = Graphics.FromImage(uploadedBmp))
{
resizedGr.CompositingMode = CompositingMode.SourceCopy;
resizedGr.CompositingQuality = CompositingQuality.HighQuality;
resizedGr.InterpolationMode = InterpolationMode.HighQualityBicubic;
resizedGr.SmoothingMode = SmoothingMode.HighQuality;
resizedGr.PixelOffsetMode = PixelOffsetMode.HighQuality;
At this point a new Bitmap is instantiated with the new, smaller height and width properties:
using (Bitmap resizedBmp = new Bitmap(uploadedBmp, newWidth, newHeight))
{
The Graphics object is commanded to draw the new image into the new smaller Bitmap. A MemoryStream object is created and the new Bitmap then saves the file to memory with the proper MIME type encoding. The memory file is converted to a byte array that is in turn saved to disk:
resizedGr.DrawImage(resizedBmp, 0, 0);
using (MemoryStream resizedMs = new MemoryStream())
{
System.Drawing.Imaging.EncoderParameters encParms = new System.Drawing.Imaging.EncoderParameters(1);
encParms.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)90);
resizedBmp.Save(resizedMs, GetImgCodecInf("image/jpeg"), encParms);
long msLen = resizedMs.Length;
byte[] resizedData = new byte[msLen];
resizedData = resizedMs.ToArray();
using (System.IO.FileStream fStream = new System.IO.FileStream(Server.MapPath("SavedImages") + @"\" + Path.GetFileName(FileUpload1.PostedFile.FileName), System.IO.FileMode.Create))
{
fStream.Write(resizedData, 0, resizedData.Length);
}
The image now has been resized and stored on disk. The next step is to create yet another smaller corresponding thumbnail image that can be used as a preview in the GridView control. This step is essentially the same as the first image processing routine accept the height of the finished image is 100 pixels. Also the image quality parameters have been left out and to appease those who prefer not to use the C# using statement, the FileStream and Graphics object are closed and disposed of programmatically:
origHeight = resizedBmp.Height;
origWidth = resizedBmp.Width;
int thumbHeight = 100;
int thumbWidth = Convert.ToInt32(thumbHeight / (origHeight / origWidth));
Bitmap thumbBmp = new Bitmap(resizedBmp, thumbWidth, thumbHeight);
Graphics thumbGr = Graphics.FromImage(thumbBmp);
thumbGr.DrawImage(thumbBmp, 0, 0);
MemoryStream thumbMs = new MemoryStream();
thumbBmp.Save(thumbMs, System.Drawing.Imaging.ImageFormat.Jpeg);
long thumbmsLen = thumbMs.Length;
byte[] thumbData = new byte[thumbmsLen];
thumbData = thumbMs.ToArray();
System.IO.FileStream tStream = new System.IO.FileStream(Server.MapPath("SavedImages") + @"\thmb_" + Path.GetFileNameWithoutExtension(FileUpload1.PostedFile.FileName) + Path.GetExtension(FileUpload1.PostedFile.FileName), System.IO.FileMode.Create);
tStream.Write(thumbData, 0, thumbData.Length);
tStream.Close();
thumbGr.Dispose();
thumbBmp.Dispose();
thumbMs.Dispose();
}
}
}
}
}
protected ImageCodecInfo GetImgCodecInf(string mimeType)
{
ImageCodecInfo[] imgCodecInfo = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo infoItem in imgCodecInfo)
{
if (infoItem.MimeType.ToString().ToLower() == mimeType.ToLower())
{
return infoItem;
}
}
return null;
}
}
}
Finally, control is returned back to the button click event handler where the SqlDataSource insert parameters are updated and a record is inserted into the table. Since all of the action occurs during a postback, the GridView is automatically updated with the new record.
Conclusion
We now have uploaded a JPG image file, resized it smaller with the aspect ratio intact and saved it to disk with a corresponding thumbnail image. Could this have been done differently? More efficiently? Absolutely. This is only one of dozens different techniques possible. I have successfully used this technique on several web projects. Feel free to leave your comments and suggestions below.
History
Initial version, October 2014