Search code examples
c#imagebitmapgdi+gdi

Thumbnails on Server - A generic error occurred in GDI+


We've been getting this issue for a long time now, and tried a lot of different fixes from the net. So far nothing worked.

Issue: The Base Image is saving just fine, thumbnails are failing on the save. Before the solution below, I have tried creating separate streams for all images (base image, 600x600 and 300x300 thumb) and this didn't work either. All of the streams were constructed from the same byte array. Keep this in mind: This works just fine on Development environment, Test environment and Test2 environment however fails to work on Production environment. I have checked all the settings / environment variables / permissions on folders and everything is setup the same as the Test environments.

Paths are coming in as follows:

  • Base Path: "~/images/imageUpl/"
  • Thumbnail Add. Path: "thumbM/"
  • Thumbnail Add. Path: "thumbS/"
  • Image name struct: "X_YYYYMMDD_HHMMSS_XX.png"

The paths are all correct - as it works Dev/Test/Test2 environments.

Any help on this is much appreciated!

EDIT: What we tried so far:

  • Set permissions for Network and IISUser
  • Use separate streams for each image constructed from original source data
  • Adding thread.sleep(30+) as per other examples
  • Creating a fresh Bitmap from the resized one and saving that
  • Different path to test if its directory issue on production

EDIT 2: For reference, this is a ASP.NET MVC5 Web Application, running of .NET Framework 4.7.2.

Image Processor Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Drawing.Drawing2D;
using Newtonsoft.Json;
using System.Threading;

namespace CMS_MVC.Classes
{
    public class ImageProcessor : IDisposable
    {
        public enum PathType : int
        {
            Relative = 1,
            Static = 2
        }

        private Stream _ImageStream { get; set; }
        private HttpContext _Context { get; set; }
        public Image BaseImage { get; set; }
        private int _instanceId { get; set; }

        public ImageProcessor(int instanceId, Stream imageStream, HttpContext context)
        {
            this._ImageStream = imageStream;
            this._Context = context;
            this._instanceId = instanceId;

            this.BaseImage = Image.FromStream(this._ImageStream);
        }

        public ImageProcessor(int instanceId, byte[] imageByteArray, HttpContext context)
        {
            this._Context = context;
            this._instanceId = instanceId;

            this._ImageStream = new MemoryStream(imageByteArray);
            this.BaseImage = Image.FromStream(this._ImageStream);
        }

        public ImageProcessor(int instanceId, string imagePath, PathType pathType, HttpContext context)
        {
            this._Context = context;
            this._instanceId = instanceId;

            if (pathType == PathType.Relative)
            {
                this._ImageStream = new MemoryStream(File.ReadAllBytes(this._Context.Server.MapPath(imagePath)));
                this.BaseImage = Image.FromStream(this._ImageStream);
            }
            else
            {
                this._ImageStream = new MemoryStream(File.ReadAllBytes(imagePath));
                this.BaseImage = Image.FromStream(this._ImageStream);
            }
        }

        public Dictionary<string, bool> SaveImages(string baseImageSavePath, string imageName, Dictionary<string, Tuple<int, int>> thumbnails = null)
        {
            Dictionary<string, bool> results = new Dictionary<string, bool>();
            string lastResult = "main";
            results.Add(lastResult, true);

            try
            {
                this.BaseImage.Save(this._Context.Server.MapPath(Path.Combine(baseImageSavePath, imageName)), ImageFormat.Png);

                if (thumbnails != null)
                {
                    foreach (var thumbnail in thumbnails)
                    {
                        lastResult = thumbnail.Value.Item1.ToString() + "_" + thumbnail.Value.Item2.ToString();
                        results.Add(lastResult, true);

                        using (Bitmap thumbBitmap = this.ResizeImage(thumbnail.Value.Item1, thumbnail.Value.Item2))
                        {
                            Thread.Sleep(50);
                            thumbBitmap.Save(this._Context.Server.MapPath(Path.Combine(baseImageSavePath + thumbnail.Key, imageName)), ImageFormat.Png);
                            Thread.Sleep(50);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                results[lastResult] = false;

                // Log event
            }

            return results;
        }

        private Bitmap ResizeImage(int targetWidth, int targetHeight)
        {
            Tuple<int, int> destSize = this.CalculateThumbnailSizeAspectRatio(targetWidth, targetHeight);

            var destRect = new Rectangle(0, 0, destSize.Item1, destSize.Item2);
            var destImage = new Bitmap(destSize.Item1, destSize.Item2);

            destImage.SetResolution(this.BaseImage.HorizontalResolution, this.BaseImage.VerticalResolution);

            using (var graphics = Graphics.FromImage(destImage))
            {
                graphics.CompositingMode = CompositingMode.SourceCopy;
                graphics.CompositingQuality = CompositingQuality.HighQuality;
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.SmoothingMode = SmoothingMode.HighQuality;
                graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

                using (var wrapMode = new ImageAttributes())
                {
                    wrapMode.SetWrapMode(WrapMode.TileFlipXY);
                    graphics.DrawImage(this.BaseImage, destRect, 0, 0, this.BaseImage.Width, this.BaseImage.Height, GraphicsUnit.Pixel, wrapMode);
                }
            }

            return destImage;
        }

        private Tuple<int, int> CalculateThumbnailSizeAspectRatio(int targetWidth, int targetHeight)
        {
            // Resize calculations
        }

        public void Dispose()
        {
            if (this._ImageStream != null) this._ImageStream.Dispose();
            if (this.BaseImage != null) this.BaseImage.Dispose();
        }
    }
}

Solution

  • From the documentation site:

    Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.

    That site then directs you to WICs but that is not a good idea. The WPF Media classes are also not supported on a server (but an equally clear statement about this is missing).

    Various alternatives have come up, here is an older SO question.

    Your best bet is to look for a fully managed solution, a.o. ImageSharp.

    I'm still looking for a similar statement or insight about System.Drawing.Common, a .net core library.