2

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:

Kurt Pfeifle
  • 86,724
  • 23
  • 248
  • 345
kumar
  • 37
  • 1
  • 3
  • 10
  • Your code only writes one image - yet you say you have 10 different sizes... maybe show us how you create the other 9 as it may be possible to remove filtering/resampling that is common to all 10 image sizes, or parallelise, or remove intermediate files... – Mark Setchell Sep 23 '14 at 12:30
  • same code copy paste for 9 sizes just change in the size values like 1200,1000,800,600,500,300,100,75 and small changes in unsharpmask values – kumar Sep 23 '14 at 12:38
  • I don't do C#, maybe @dlemstra will take a look for you. – Mark Setchell Sep 23 '14 at 20:43
  • This post: [I need a very fast image scaling algorithm](http://stackoverflow.com/a/8365035/176769), might give you some interesting information. – karlphillip Sep 23 '14 at 23:09
  • In every iteration you should use previously resized image as source. This will surely result in lowering calculations. In particular for final image don't resize 1600 to 75 but 100 to 75. – rostok Sep 24 '14 at 09:23
  • @rostok i can't do as you said... doing so will reduce image quality and details... as it is jpeg resize.. in jpeg resize for every compression there is reduction in quality... so i must use the original file to resize for all sizes.... – kumar Sep 24 '14 at 12:19
  • JPEG compression is the last stage of every iteration. So basically you should swap output with source and reiterate. You don't have to read source every time, this also takes CPU time as it has to uncompress it into memory. – rostok Sep 24 '14 at 12:24
  • swap output with source...? i dint get this clearly... i mean how can i do that....please can u brief me with sample code example...if possible – kumar Sep 24 '14 at 12:33
  • I don't see your full code. But my idea is that once you resize it with ```Image.Resize(1600,1600);``` you then use the same ```Image``` object for all successive resizes. Just use what you already have in memory and don't reload big jpeg in every iteration. I just checked this with command line version of imagemagick: resizing to your 9 sizes from single 5000px wide picture takes ~10 seconds, and resizing from only slightly bigger picture takes 2.5 seconds. – rostok Sep 24 '14 at 15:28
  • @rostok thank you for you support... I will try what you said and reply back with details... As quality is the main concern to me... I have to check if there is any change in quality of image using your method.... – kumar Sep 25 '14 at 03:28
  • @rostok i updated my question with controller action code... i am new to mvc ... i searched google on how to use the image object already had in memory... but i dint get any.... please help me in this on how to get it done... – kumar Sep 27 '14 at 14:37
  • Your CPU probably has 2-4 cores, so you could maybe think about parallelising this and doing more than 1 resolution at a time... – Mark Setchell Sep 27 '14 at 14:45
  • @MarkSetchell please can you make me clear on how i can do that in code... what changes i must do to process parallel...actually when i open two diff browsers and upload pics both are processed parallelly without waiting for other to complete... same is the case with two tabs.. – kumar Sep 27 '14 at 14:57
  • @kumar with every resolution you open the same input image ```new MagickImage(Upath + OrgImgName)``` while in my opinion you should try using the first ```Large```. That is the first image object that should be getting successively scaled down to avoid opening the large picture every time and scale it down. Also to get your code more organised you should use some loop statemnt like ```for``` to scale to every output resolution. Maybe add two arrays with sizes and output file names and then iterate through them. – rostok Sep 27 '14 at 20:36
  • @rostok thank you for reply... i will try what you suggested...can you suggest me which one is better either using magick.net or commandline like mark suggested... is there will be any diff in performance between these two ways of doing things.. – kumar Sep 28 '14 at 06:32
  • There is no shame in using external library through command line. To see the real difference in performance you should measure the results. I think it may be faster to use internal library (magick.net) as it will not create any additional processes on the server side. diemstra's answer seems perfect. – rostok Sep 28 '14 at 18:44

2 Answers2

3

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";
}
dlemstra
  • 7,813
  • 2
  • 27
  • 43
  • but even with the one suggeted by @dlemstra it is taking 2.2 minutes to do the whole process... i want to reduce it further.. is there any way to do that... – kumar Oct 02 '14 at 17:13
  • @kumar It does seem odd that your machine takes 2 minutes when my lowly desktop iMac can do much the same in 17 seconds. Can you try my commandline version on your server? Can you also click `edit` under your question and paste in the output from `identify -version` and `identify -list configure` so I can see your settings. What hardware/software are you running on? Likewise add this info to your original question. – Mark Setchell Oct 02 '14 at 19:15
  • @MarkSetchell As i am not pro programmer i dint get how to use imac commandline in c# or mvc.... i tried googling but ended with no solution... tried diagnostics.process method with no error no processing... Please help me in this on how to use command line or improve the speed in any other way.... i am using magick.net-q16-anycpu version:7.0.0.0003 nuget package and tried ImageMagick-6.8.9-8-Q16-x86-windows.zip for command line... please help me – kumar Oct 04 '14 at 06:32
1

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.

Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • Thank you very much for your detailed explaination.... iF you dont mind i have few doubts... like is there will be any locking or kill or die of process due to heavy user request or uploads by using command line of imagemagick.... and one more is http://msdn.microsoft.com/en-us/library/system.diagnostics.process.startinfo.aspx is the only way to use command line or any other methods available... is there any option to command through magick.net of mvc... please suggest me with the best in these things – kumar Sep 27 '14 at 18:04
  • I am really sorry, I don't know enough about Windows (I work in Unix/Linux) to answer you. Maybe try asking another question on here... – Mark Setchell Sep 27 '14 at 18:32