I am looking for a way to efficiently prepare large amount of images for website in .NET
Images are often largescale, unedited, 2-5MB 4000x8000px monster images from phone camera.
I would like to generate thumbnails quickly, and as efficient as possible. (not to slow down CPU) or performance for user.
I also have to consider caching.
Modern CMS systems are using Image pre-processor that you can invoke via front-end. So I want to make something like this also, but my own. Cause in my case, I can't use CMS here.
Here is my code: I have a static helper method from Helper class.
I call it in razor every time I need to render an image.
public static string GetImgThumbnail(string web_path, int _width = 0, int _height = 0)
{
//Default parameters
Image image;
Image thumbnail;
string thumb_url = "/img/noimg.png";
try
{
// web_path example input "/images/helloworld.jpg"
//system_path returns system path of Ogirinal image in the system f.e.: "C:\projects\websites\mysite\images\helloworld.jpg"
string system_path = HttpContext.Current.Server.MapPath(web_path);
image = Image.FromFile(system_path);
//Original image dimensions
int width = image.Width;
int height = image.Height;
//Get New image dimensions
if(_height == 0)
{
if(_width == 0)
{
_width = 700;
}
_height = (_width * height) / width;
}
if (_width == 0)
{
if (_height == 0)
{
_height = 700;
}
_width = (_height * width) / height;
}
//Generate Thumbnail, passing in new dimensions
thumbnail = image.GetThumbnailImage(_width, _height, null, IntPtr.Zero);
//Find out how to call newly created thumbnail.
//Original image example name = "/images/helloworld.jpg" thumbnail would be "/images/helloworld_thumb_700x250.jpg" or analogical with .png or JPEG etc...
//Suffix should be "_thumb_{width}x{height}"
string suffix = string.Format("_thumb_{0}x{1}", _width.ToString(),_height.ToString());
var bigImgFilename = String.Format("{0}{1}",
Path.GetFileNameWithoutExtension(system_path), Path.GetExtension(system_path));
var newImgFilename = String.Format("{0}{1}{2}",
Path.GetFileNameWithoutExtension(system_path), suffix, Path.GetExtension(system_path));
//New system path of new Thumbnail example: "C:\projects\websites\mysite\images\helloworld_thumb_700x250.jpg"
var newpath = system_path.Replace(bigImgFilename, newImgFilename);
//Set new web path, expect this to be: "/images/helloworld_thumb_700x250.jpg"
thumb_url = web_path.Replace(bigImgFilename, newImgFilename);
//Check if file exists, no need to overrite if file exists.
if (!File.Exists(newpath))
{
thumbnail.Save(newpath);
}
}
catch (Exception exc)
{
// If something goes wrong, just return backup image.
thumb_url = "/img/noimg.png";
}
// return thumbnail
return thumb_url;
}
Would love to hear some tips/suggestions or whether I am on the right path, or I should do it in different way?
So in your code, you calculate the thumbnail first. Then you calculate the filename and check if the thumbnail calculation was necessary.
I would do in a different order:
Load the image (since you need that to calculate the new filename)
Calculate the new filename
Check if the file is already there
If the file is already there, use it
If not, generate the thumbnail.
In code this would roughtly look like the following:
public static string GetImgThumbnail(string web_path, int _width = 0, int _height = 0)
{
[...]
string system_path = HttpContext.Current.Server.MapPath(web_path);
image = Image.FromFile(system_path);
// calculate new width and height
[...]
// calculate new filename
[...]
//New system path of new Thumbnail example: "C:\projects\websites\mysite\images\helloworld_thumb_700x250.jpg"
var newpath = system_path.Replace(bigImgFilename, newImgFilename);
//Set new web path, expect this to be: "/images/helloworld_thumb_700x250.jpg"
thumb_url = web_path.Replace(bigImgFilename, newImgFilename);
//Check if file exists, no need to overrite if file exists.
if (!File.Exists(newpath))
{
//Generate Thumbnail, passing in new dimensions
thumbnail = image.GetThumbnailImage(_width, _height, null, IntPtr.Zero);
thumbnail.Save(newpath);
}
My point is: Loading an image and calculating the size dosen't use much resources. The resizing part is a heavy on the CPU. So you will get faster responses, if you only transform the image if necessary.
Here are some points you can consider:
I call it in razor every time I need to render an image.
This will generate a thumbnail on every image view. This is very cpu heavy and most likely not what you want. Consider creating the thumbnail only once, save it to disk and start using the pre-rendered version.
Next issue:
//Check if file exists, no need to overrite if file exists.
if (!File.Exists(newpath))
{
thumbnail.Save(newpath);
}
You first compute the thumbnail and then check if the computation has already be done. It should be the other way round.
Related
What is the correct way to get the thumbnails of images when using C#? There must be some built-in system method for that, but I seem to be unable find it anywhere.
Right now I'm using a workaround, but it seems to be much heavier on the computing side, as generating the thumbnails of 50 images, when using parallel processing takes about 1-1,5 seconds, and during that time, my CPU is 100% loaded. Not to mention that it builds up quite some garbage, which it later needs to collect.
This is what my class currently looks like:
public class ImageData
{
public const int THUMBNAIL_SIZE = 160;
public string path;
private Image _thumbnail;
public string imageName { get { return Path.GetFileNameWithoutExtension(path); } }
public string folder { get { return Path.GetDirectoryName(path); } }
public Image image { get
{
try
{
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(stream))
{
var memoryStream = new MemoryStream(reader.ReadBytes((int)stream.Length));
return new Bitmap(memoryStream);
}
}
catch (Exception e) { }
return null;
}
}
public Image thumbnail
{
get
{
if (_thumbnail == null)
LoadThumbnail();
return _thumbnail;
}
}
public void LoadThumbnail()
{
if (_thumbnail != null) return;
Image img = image;
if (img == null) return;
float ratio = (float)image.Width / (float)image.Height;
int h = THUMBNAIL_SIZE;
int w = THUMBNAIL_SIZE;
if (ratio > 1)
h = (int)(THUMBNAIL_SIZE / ratio);
else
w = (int)(THUMBNAIL_SIZE * ratio);
_thumbnail = new Bitmap(image, w, h);
}
I am saving up the thumbnail once generated, to save up some computing time later on. Meanwhile, I have an array of 50 elements, containing picture boxes, where I inject the thumbnails into.
Anyways... when I open a folder, containing images, my PC certainly doesn't use up 100% CPU for the thumbnails, so I am wondering what is the correct method to generate them.
Windows pregenerates the thumbnails and stores them in the thumbs.db-File (hidden) for later use.
So unless you either access the thumbs.db file and are fine with relying on it being available or cache the thumbnails yourself somewehere you always will have to render them in some way or another.
That being said, you can probably rely on whatever framework you are using for your UI to display them scaled down seeing as you load them into memory anyway.
What I am currently doing is:
Capture Some Screen shots
Copy All the captured Screen Shots in a Bitmap List: List
Save All the Screenshots in List to hard drive
Feed All the pictures in the directory to the VideoWriter Object of DotImaging Library.
My source code for writing the video is:
private void MakeVideo()
{
var saveDialog = new SaveFileDialog { Filter = #"AVI Video(.avi)|*.avi" };
if (saveDialog.ShowDialog() == DialogResult.OK)
{
using (var videoWriter = new VideoWriter(saveDialog.FileName, new Size(_screenWidth, _screenHeight), FrameRate, true))
{
var ir = new ImageDirectoryCapture(_path, "*.jpg");
while (ir.Position < ir.Length)
{
IImage image = ir.Read();
videoWriter.Write(image);
}
videoWriter.Close();
DeleteFiles(); // Deletes The Files from hard drive
}
}
}
What I want to do is:
Skip the saving screenshots in hard drive.
Feed the List to the Video Writer Object directly.
I am unable to do so because it takes the directory path and not the images itself directly.
I want to do it because of the fact that writing all images to hard drive and then making video is much slower, Any Alternatives of DotImaging are also good.
Or maybe you can let me know if I can cast Bitmap Images to the IImage Format that VideoWriter.Write() method is accepting as a parameter.
There is an extension library called DotImaging.BitmapInterop.
Once it's installed in your project, you can write something like:
IEnumerable<Bitmap> frames = GetFrames();
using (var writer = new VideoWriter(target), new Size(width, height)))
{
foreach (var bitmap in frames)
{
var imageBuffer = bitmap.ToBgr();
writer.Write(imageBuffer.Lock());
}
}
I am using .NET4.5, Windows Forms and C#.
I am loading an image onto a button using:
theButton.BackgroundImage = Image.FromFile("file.png");
The issue is that my button is 128x128 and the image is 4000x8000. The line above consumes very large amounts of memory because file.png is so large.
Does anyone know of a technique I can use to reduce this memory footprint? I am thinking of some function like this:
Image.FromFile(file,width,height);
Any pointers? Thanks.
Yes it works. It's quite simple to resize the image and then display it on button.
But, I don't think that the above code maintains the aspect ratio of the image.
It's quite simple to resize the image with aspect ratio; and then display it on button.
Below is the sample code helps you to resize the image by maintaining the aspect ratio.
You can define a new class or implement the "ResizeImage" method in an existing class. Whichever is comfortable to you.
public class ImageManipulation
{
public static Bitmap ResizeImage(Bitmap originalBitmap, int newWidth, int maxHeight, bool onlyResizeIfWider)
{
if (onlyResizeIfWider)
{
if (originalBitmap.Width <= newWidth)
{
newWidth = originalBitmap.Width;
}
}
int newHeight = originalBitmap.Height * newWidth / originalBitmap.Width;
if (newHeight > maxHeight)
{
// Resize with height instead
newWidth = originalBitmap.Width * maxHeight / originalBitmap.Height;
newHeight = maxHeight;
}
var alteredImage = new Bitmap(originalBitmap, new Size(newWidth, newHeight));
alteredImage.SetResolution(72, 72);
return alteredImage;
}
}
USAGE:
private void DisplayPhoto()
{
// make sure the file is JPEG or GIF
System.IO.FileInfo testFile = new System.IO.FileInfo(myFile);
// Create a new stream to load this photo into
FileStream myFileStream = new FileStream(myFile, FileMode.Open, FileAccess.Read);
// Create a buffer to hold the stream of bytes
photo = new byte[myFileStream.Length];
// Read the bytes from this stream and put it into the image buffer
myStream.Read(photo, 0, (int)myFileStream.Length);
// Close the stream
myFileStream.Close();
// Create a new MemoryStream and write all the information from
// the byte array into the stream
MemoryStream myStream = new MemoryStream(photo, true);
myStream.Write(photo, 0, photo.Length);
// Use the MemoryStream to create the new BitMap object
Bitmap FinalImage = new Bitmap(myStream);
upicPhoto.Image = ImageManipulation.ResizeImage(
FinalImage,
upicPhoto.Width,
upicPhoto.Height,
true);
// Close the stream
myStream.Close();
}
I think your best path here is to just resize the image, to 128x128.
An image that large is always going to take up a lot of memory, no matter what you do with it.
This will also allow you to make the image something that will look good at that size.
This is quite a general problem, AFAIK you have few possibilities
Compress image before uploading , in real world this will not work.
Put a check on size and dimensions of image, in real world it works, even linkedin, facebook they won't allow us to upload images above there specified dimensions.
Use buffering, this is cleanest way you can do in .net
Use some third party plugins or development enviornment, I have done it in Silverlight
Im getting some images from a webpage at a specified url, i want to get their heights and widths. I'm using something like this:
Stream str = null;
HttpWebRequest wReq = (HttpWebRequest)WebRequest.Create(ImageUrl);
HttpWebResponse wRes = (HttpWebResponse)(wReq).GetResponse();
str = wRes.GetResponseStream();
var imageOrig = System.Drawing.Image.FromStream(str);
int height = imageOrig.Height;
int width = imageOrig.Width;
My main concern with this is that that the image file may actually be very large,
Is there anything I can do? ie specify to only get images if they are less than 1mb?
or is there a better alternative approach to getting the dimension of an image from a webpage?
thanks
Some (all?) image formats include the width and height property in the header of the file. You could just request enough bytes to be able to read the header and then parse them yourself. You can add a range header to your web request that will request only the first 50 bytes (50 is just an example, you'd probably need less) of the image file with:
wReq.AddRange(0, 50);
I suppose this will only work if you know that the formats you are working with include this data.
Edit: Looks like I misunderstood the AddRange method before. It's fixed now. I also went ahead and tested it out by getting the width and height of a png using this documentation.
static void Main(string[] args)
{
string imageUrl = "http://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
byte[] pngSignature = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 };
HttpWebRequest wReq = (HttpWebRequest)WebRequest.Create(imageUrl);
wReq.AddRange(0, 30);
WebResponse wRes = wReq.GetResponse();
byte[] buffer = new byte[30];
int width = 0;
int height = 0;
using (Stream stream = wRes.GetResponseStream())
{
stream.Read(buffer, 0, 30);
}
// Check for Png
// 8 byte - Signature
// 4 byte - Chunk length
// 4 byte - Chunk type - IDHR (Image Header)
// 4 byte - Width
// 4 byte - Height
// Other stuff we don't care about
if (buffer.Take(8).SequenceEqual(pngSignature))
{
var idhr = buffer.Skip(12);
width = BitConverter.ToInt32(idhr.Skip(4).Take(4).Reverse().ToArray(), 0);
height = BitConverter.ToInt32(idhr.Skip(8).Take(4).Reverse().ToArray(), 0);
}
// Check for Jpg
//else if (buffer.Take(?).SequenceEqual(jpgSignature))
//{
// // Do Jpg stuff
//}
// Check for Gif
//else if (etc...
Console.WriteLine("Width: " + width);
Console.WriteLine("Height: " + height);
Console.ReadKey();
}
You only need to download the header from a graphics file in order to find the picture size, see...
BMP: (only 26 bytes needed)
http://www.fileformat.info/format/bmp/corion.htm
JPG: (scan for "Star of Frame" marker)
http://wiki.tcl.tk/757
GIF: (10 bytes needed, i.e. first two words of Logical Screen Descriptor)
http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
also, notice how you can read the first couple of bytes to find out what the file-type really is (don't rely on the extension of the filename. For example, a bmp could be named ".gif" by accident. Once you know the filetype you look at the spec to know what offset to read.
P.S. get yourself a hex editor, such as "Hex Editor XVI32", to see the file structure.
You can download XVI32 here: http://www.chmaas.handshake.de/delphi/freeware/xvi32/xvi32.htm
You may want to write a web service where given the input image name, provides the size as response.
Based on the resultant value, you can choose to download the image.
If you want something light weight than a web service, go for a HTTP Handler.
You could try using the FileInfo class like so to find the file size.
long fileSizeInKb = new FileInfo(fileName).Length / 1000;
if (fileSizeInKb < 1000)
{
// get image
}
The Length property returns the number of bytes in the current file.
How to read a tiff file's dimension (width and height) and resolution (horizontal and vertical) without first loading it into memory by using code like the following. It is too slow for big files and I don't need to manipulate them.
Image tif = Image.FromFile(#"C:\large_size.tif");
float width = tif.PhysicalDimension.Width;
float height = tif.PhysicalDimension.Height;
float hresolution = tif.HorizontalResolution;
float vresolution = tif.VerticalResolution;
tif.Dispose();
Edit:
Those tiff files are Bilevel and have a dimension of 30x42 inch. The file sizes are about 1~2 MB. So the method above works Ok but slow.
Ran into this myself and found the solution (possibly here). Image.FromStream with validateImageData = false allows you access to the information you're looking for, without loading the whole file.
using(FileStream stream = new FileStream(#"C:\large_size.tif", FileMode.Open, FileAccess.Read))
{
using(Image tif = Image.FromStream(stream, false, false))
{
float width = tif.PhysicalDimension.Width;
float height = tif.PhysicalDimension.Height;
float hresolution = tif.HorizontalResolution;
float vresolution = tif.VerticalResolution;
}
}
As far as I know, all classes from System.Drawing namespace load image data immediately when image is open.
I think LibTiff.Net can help you to read image properties without loading image data. It's free and open-source (BSD license, suitable for commercial applications).
Here is a sample for your task (error checks are omitted for brevity):
using BitMiracle.LibTiff.Classic;
namespace ReadTiffDimensions
{
class Program
{
static void Main(string[] args)
{
using (Tiff image = Tiff.Open(args[0], "r"))
{
FieldValue[] value = image.GetField(TiffTag.IMAGEWIDTH);
int width = value[0].ToInt();
value = image.GetField(TiffTag.IMAGELENGTH);
int height = value[0].ToInt();
value = image.GetField(TiffTag.XRESOLUTION);
float dpiX = value[0].ToFloat();
value = image.GetField(TiffTag.YRESOLUTION);
float dpiY = value[0].ToFloat();
}
}
}
}
Disclaimer: I am one of the maintainers of the library.
Try this, it seems to be what you are looking for. Just skip everything after:
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, ref w); //your width
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, ref h); //your height
TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, ref bits);
TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, ref samples);
Don't forget to close after you:
TIFFClose(tif);
The only way I can think of is reading the tiff binary header.
Here you can download the specification: http://partners.adobe.com/public/developer/tiff/index.html
Here is some code used to read Tiffs that you can use to learn:
http://www.koders.com/csharp/fidF6632006F25B8E5B3BCC62D13076B38D71847929.aspx?s=zoom
I created a library to read the tiff headers some time ago (with this two resources as base) but it was part of my employer code so I can't post my code here and I can say it is no really hard.
I Hope this helps.