Reputation: 37
I am working on a project which involves uploading of DSLR Camera images from user and resizing to 10 different sizes...
I am using ImageMagick to resize on server side.... but it is taking too much time to process images.. which is more than 3 minutes ... end user will be irritated waiting for it to be done...
So I want to reduce the time and enhance the performance.... Please help me on what changes to be made.
As i tried the same file (4mb--6mb) to upload on Flickr,500px and facebook they did it in less time....
I am not a professional programmer ..... I am just using simple mechanism to upload file through input and process the images in action of controller on server side...
I used the following code to resize each image...
Below is my controller action to process the images
updated the code below as per the suggestions which is taking around 1.6 min to process to the following diff sizes in code
#region Actions
/// <summary>
/// Uploads the file.
/// </summary>
/// <returns></returns>
[HttpPost]
public virtual ActionResult UploadImg()
{
HttpPostedFileBase myFile = Request.Files["UploadImage"];
bool isUploaded = false;
string message = "File upload failed";
var filename = Path.GetExtension(myFile.FileName).ToLowerInvariant();
if ((filename == ".jpg" || filename == ".jpeg") && myFile != null && myFile.ContentLength != 0)
{
// Paths
string Upath = Server.MapPath(@"~/photos/");
string ImgName = "_org.jpg";
string imageTo = "";
//Image names
string OrgImgName = System.IO.Path.GetFileName(myFile.FileName);
myFile.SaveAs(Path.Combine(Upath, myFile.FileName));
if (this.CreateFolderIfNeeded(Upath))
{
try
{
using (MagickImage original = new MagickImage(Upath + OrgImgName))
{
original.AutoOrient();
original.Write(Upath + ImgName);
original.ColorSpace = ColorSpace.Lab;
original.SetAttribute("density", "72x72");
int[] sizes = new int[] { 2048, 1600, 1024, 800, 500, 640, 320, 240, 150, 100, 75, 50 };
Parallel.For(0, sizes.Length, delegate(int index)
{
int size = sizes[index];
if (original.Width > size || original.Height > size)
{
if (size == 150 || size == 75 || size == 50)
{
string gmt = size.ToString() + 'x' + size.ToString();
MagickGeometry g = new MagickGeometry(gmt);
using (MagickImage resized = original.Clone())
{
resized.SetDefine(MagickFormat.Jpeg, "sampling-factor", "4:4:4");
resized.Blur(1, 0.375);
resized.FilterType = FilterType.LanczosSharp;
g.FillArea = true;
resized.Resize(g);
resized.Crop(size, size, Gravity.Center);
resized.ColorSpace = ColorSpace.sRGB;
Unsharpmask(resized, size);
resized.Quality = 85;
if (size == 150)
{
imageTo = Upath + GetOutputName(size);
}
else if (size == 75)
{
imageTo = Upath + GetOutputName(size);
}
else if (size == 50)
{
imageTo = Upath +GetOutputName(size);
}
resized.Write(imageTo);
}
}
else
{
using (MagickImage resized = original.Clone())
{
resized.SetDefine(MagickFormat.Jpeg, "sampling-factor", "4:4:4");
resized.Blur(1, 0.375);
resized.FilterType = FilterType.LanczosSharp;
resized.Resize(size, size);
resized.ColorSpace = ColorSpace.sRGB;
Unsharpmask(resized, size);
resized.Quality = 85;
if (size == 2048)
{
imageTo = Upath + GetOutputName(size);
}
else if (size == 1600)
{
imageTo = Upath + GetOutputName(size);
}
else if (size == 1024)
{
imageTo = Upath + GetOutputName(size);
}
else if (size == 800)
{
imageTo = Upath + GetOutputName(size);
}
else if (size == 640)
{
imageTo = Upath + GetOutputName(size); ;
}
else if (size == 500)
{
imageTo = Upath + GetOutputName(size);
}
else if (size == 320)
{
imageTo = Upath +GetOutputName(size);
}
else if (size == 240)
{
imageTo = Upath +GetOutputName(size);
}
else if (size == 100)
{
imageTo = Upath +GetOutputName(size);
}
else
{
imageTo = "";
}
resized.Write(imageTo);
}
}
}
});
}
isUploaded = true;
message = "File uploaded successfully!";
}
catch (Exception ex)
{
message = string.Format("File upload failed: {0}", ex.Message);
}
}
}
return Json(new { isUploaded = isUploaded, message = message }, "text/html");
}
#endregion
#region Private Methods
/// <summary>
/// Creates the folder if needed.
/// </summary>
/// <param name="path">The path.</param>
/// <returns></returns>
private bool CreateFolderIfNeeded(string path)
{
bool result = true;
if (!Directory.Exists(path))
{
try
{
Directory.CreateDirectory(path);
}
catch (Exception)
{
/*TODO: You must process this exception.*/
result = false;
}
}
return result;
}
private void Unsharpmask(MagickImage resized, int size)
{
if (size == 2048)
resized.Unsharpmask(2, 1, 1.7, 0.2);
else if (size == 1600)
resized.Unsharpmask(1.6, 0.5, 1.7, 0.25);
else if (size == 1024)
resized.Unsharpmask(2.8, 1, 0.7, 0.2);
else if (size == 800)
resized.Unsharpmask(1.2, 0.8, 0.7, 0.08);
else if (size == 640)
resized.Unsharpmask(2, 1, 0.7, 0.02);
else if (size == 500)
resized.Unsharpmask(1.5, 0.8, 1, 0.02);
else if (size == 320)
resized.Unsharpmask(1.5, 0.6, 0.7, 0.02);
else if (size == 240)
resized.Unsharpmask(1.3, 1.5, 1.9, 0.01);
else if (size == 150)
resized.Unsharpmask(0.5, 1, 0.5, 0.002);
else if (size == 100)
resized.Unsharpmask(0.8, 0.4, 2.5, 0);
else if (size == 75)
resized.Unsharpmask(2, 1, 1.8, 0.05);
else if (size == 50)
resized.Unsharpmask(1, 0.4, 1.8, 0.02);
else
throw new NotImplementedException();
}
private string GetOutputName(int size)
{
string imagename = "";
if (size == 2048)
{
imagename = "_1.jpg";
}
else if (size == 1600)
{
imagename = "_2.jpg";
}
else if (size == 1024)
{
imagename = "_3.jpg";
}
else if (size == 800)
{
imagename = "_4.jpg";
}
else if (size == 640)
{
imagename = "_5.jpg";
}
else if (size == 500)
{
imagename = "_6.jpg";
}
else if (size == 320)
{
imagename = "_7.jpg";
}
else if (size == 240)
{
imagename = "_8.jpg";
}
else if (size == 150)
{
imagename = "_9.jpg";
}
else if (size == 100)
{
imagename = "_10.jpg";
}
else if (size == 75)
{
imagename = "_11.jpg";
}
else if (size == 50)
{
imagename = "_12.jpg";
}
else
{
imagename = "_noimage.jpg";
}
return imagename;
}
#endregion
Here is the working example of the above code:
Upvotes: 2
Views: 2112
Reputation: 8153
As posted by Mark Setchell, you should not keep reading the image the image from disk but Clone the original. The Blur operation is also an expensive operation that you only need to do once. It might also help to do the loop in Parallel but on my machine this was actually slower.
Your code roughly translates to example below. Next time feel free to also ask for help here: https://magick.codeplex.com/discussions.
void Resize()
{
using (MagickImage original = new MagickImage("original.jpg"))
{
original.AutoOrient();
original.Strip();
original.ColorSpace = ColorSpace.Lab;
original.SetAttribute("density", "72x72");
original.Blur(1, 0.375);
int[] sizes = new int[] { 2048, 1600, 1024, 800, 640 };
//Parallel.For(0, sizes.Length, delegate(int index)
for (int i = 0; i < sizes.Length; i++)
{
int size = sizes[index];
if (original.Width <= size && original.Height <= size)
return;
using (MagickImage resized = original.Clone())
{
if (size == 2048)
resized.SetDefine(MagickFormat.Jpeg, "sampling-factor", "4:4:4");
resized.FilterType = FilterType.LanczosSharp;
resized.Resize(size, size);
resized.ColorSpace = ColorSpace.sRGB;
Unsharpmask(resized, size);
resized.Quality = 85;
resized.Write(GetOutputName(size));
}
}//);
}
}
private void Unsharpmask(MagickImage resized, int size)
{
if (size == 2048)
resized.Unsharpmask(2, 1, 1.7, 0.2);
else if (size == 1600)
resized.Unsharpmask(1.6, 0.5, 1.7, 0.25);
else if (size == 1024)
resized.Unsharpmask(2.8, 1, 0.7, 0.2);
else if (size == 800)
resized.Unsharpmask(1.2, 0.8, 0.7, 0.08);
else if (size == 640)
resized.Unsharpmask(2, 1, 0.7, 0.02);
else
throw new NotImplementedException();
}
string GetOutputName(int size)
{
return "ModifyThisMethod.jpg";
}
Upvotes: 3
Reputation: 207455
I am no expert in C# but I have a fair bit of experience with ImageMagick, so I am just trying to help you in the right direction, but probably cannot give you the exact answer.
You want to avoid reading the image multiple times from disk and redoing the same processing again and again - especially at the higher resolutions. At the command line, you would implement your code along these lines:
convert original.jpg -auto-orient -strip -colorspace Lab -filter lanczos -quality 85%\
\( +clone -resize 2048x2048! -colorspace sRGB -unsharp 2x1+1.7+0.2 -write 0.jpg +delete \) \
\( +clone -resize 1200x1200! -colorspace sRGB -unsharp 1.6x0.5+1.7+0.25 -write 1.jpg +delete \) \
\( +clone -resize 1000x1000! -colorspace sRGB -unsharp 2.8x1+0.7+0.2 -write 2.jpg +delete \) \
\( +clone -resize 800x800! -colorspace sRGB -unsharp 2x1+0.7+0.02 -write 3.jpg +delete \) \
\( +clone -resize 600x600! -colorspace sRGB -write 4.jpg +delete \) \
\( +clone -resize 500x500! -colorspace sRGB -write 5.jpg +delete \) \
\( +clone -resize 300x300! -colorspace sRGB -write 6.jpg +delete \) \
\( +clone -resize 100x100! -colorspace sRGB -write 7.jpg +delete \) \
-resize 75x75 -colorspace sRGB 8.jpg
This reads the image in ONCE in the first line, does as much processing up-front as possible (orientation, strip, colorspace Lab and Lanczos filtering) to get it set up for the remaining steps, then, for each subsequent size, it clones the pre-Lanczosed, already Lab-space image (memory to memory, not disk) and does filtering, blurring, resizing and colourspace conversions and writes each size to disk.
Using a 5000 x 3500 pixel original image, the above sequence runs in 17 seconds on a reasonable specification iMac. Maybe you can see how to adapt the sequence to your C# environment. At worst case, maybe you could shell out from C# and use a variant of my command.
The above code doesn't take advantage of parallelisation, but does avoid unnecessary operations. Another approach takes advantage of all CPU cores and parallelisation by using background tasks (note the &
at the end of each line) but has to repeat some operations, so you could consider going this way instead.
convert original.jpg -auto-orient -strip -colorspace Lab -filter lanczos -quality 85% -resize 2048x2048! -colorspace sRGB -unsharp 2x1+1.7+0.2 0.jpg &
convert original.jpg -auto-orient -strip -colorspace Lab -filter lanczos -quality 85% -resize 1200x1200! -colorspace sRGB -unsharp 2x1+1.7+0.1 1.jpg &
convert original.jpg -auto-orient -strip -colorspace Lab -filter lanczos -quality 85% -resize 1000x1000! -colorspace sRGB -unsharp 2x1+1.7+0.2 2.jpg &
convert original.jpg -auto-orient -strip -colorspace Lab -filter lanczos -quality 85% -resize 800x800! -colorspace sRGB -unsharp 2x1+1.7+0.2 3.jpg &
convert original.jpg -auto-orient -strip -colorspace Lab -filter lanczos -quality 85% -resize 600x600! -colorspace sRGB -unsharp 2x1+1.7+0.2 4.jpg &
convert original.jpg -auto-orient -strip -colorspace Lab -filter lanczos -quality 85% -resize 500x500! -colorspace sRGB -unsharp 2x1+1.7+0.2 5.jpg &
convert original.jpg -auto-orient -strip -quality 85% -resize 300x300! 6.jpg &
convert original.jpg -auto-orient -strip -quality 85% -resize 100x100! 7.jpg &
convert original.jpg -auto-orient -strip -quality 85% -resize 75x75! 8.jpg &
wait # for all taks to complete
The above script takes around 18 seconds. If, I change it from essentially this, where everything is done in parallel (that's the &
at the end):
convert ... &
convert ... &
...
wait
to this, where everything is done sequentially which is what your code does:
convert ...
convert ...
...
convert ...
it takes 1 minute 20 seconds - or 4x longer.
My point is, either do as in my first example where you do everything in an optimised order in one go, or do everything in parallel - maybe you can use threads in C# - I am sorry I don't know that language or environment at all.
Upvotes: 1