Загруженные файлы блокируются после прочтения GDI - PullRequest
1 голос
/ 12 августа 2011

У меня есть следующий класс ImageObject:

public class ImageObject
{
    public static Image CropImage(Image img, Rectangle cropArea)
    {
        Bitmap bmpImage = new Bitmap(img);
        Bitmap target = new Bitmap(cropArea.Width, cropArea.Height);
        using(Graphics g = Graphics.FromImage(target))
        {
            g.DrawImage(bmpImage, new Rectangle(0, 0, target.Width, target.Height), cropArea, GraphicsUnit.Pixel);
            g.Dispose();
        }
        return (Image)target;
    }

    public static Image ResizeImage(Image imgToResize, Size size)
    {
        int sourceWidth = imgToResize.Width;
        int sourceHeight = imgToResize.Height;

        float nPercent = 0;
        float nPercentW = 0;
        float nPercentH = 0;

        nPercentW = ((float)size.Width / (float)sourceWidth);
        nPercentH = ((float)size.Height / (float)sourceHeight);

        if (nPercentH < nPercentW)
            nPercent = nPercentH;
        else
            nPercent = nPercentW;

        int destWidth = (int)(sourceWidth * nPercent);
        int destHeight = (int)(sourceHeight * nPercent);

        Bitmap b = new Bitmap(destWidth, destHeight);
        //Graphics g = Graphics.FromImage((Image)b);
        using(Graphics g = Graphics.FromImage((Image)b))
        {
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
            g.Dispose();    
        }
        return (Image)b;
    }

    public static void SaveJpeg(string path, System.Drawing.Image source, long quality)
    {
        EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
        ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
        if (jpegCodec == null)
            return;
        EncoderParameters encoderParams = new EncoderParameters(1);
        encoderParams.Param[0] = qualityParam;
        source.Save(path, jpegCodec, encoderParams);
    }

    private static ImageCodecInfo GetEncoderInfo(string mimeType)
    {
        // Get image codecs for all image formats
        ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();

        // Find the correct image codec
        for (int i = 0; i < codecs.Length; i++)
            if (codecs[i].MimeType == mimeType)
                return codecs[i];
        return null;
    }
}

И я ссылаюсь на этот код из функции другого класса:

public static void CreateAvatar(string filepath, int x, int y, int w, int h)
{
    var big = filepath + "100x100.jpg";
    var medium = filepath + "40x40.jpg";
    var small = filepath + "25x25.jpg";
    var full_path = filepath + "avatar.jpg";
    var temp_path = filepath + "avatar_t.jpg";

    if (File.Exists(big))
    {
        File.Delete(big);
    }
    if (File.Exists(medium))
    {
        File.Delete(medium);
    }
    if (File.Exists(small))
    {
        File.Delete(small);
    }
    if (File.Exists(temp_path))
    {
        File.Delete(temp_path);
    }

    System.Drawing.Image img = System.Drawing.Image.FromFile(full_path);
    System.Drawing.Rectangle rect = new Rectangle(x, y, w, h);
    System.Drawing.Size hundred = new Size(100, 100);
    System.Drawing.Size forty = new Size(40, 40);
    System.Drawing.Size twentyfive = new Size(25, 25);

    //we crop, then we resize...
    var cropped = ImageObject.CropImage(img, rect);
    ImageObject.SaveJpeg(temp_path, cropped, 100L);

    //problems usually from here. can't save big, because it can't read temp_path - it's locked...
    var resize_big = ImageObject.ResizeImage(System.Drawing.Image.FromFile(temp_path), hundred);
    ImageObject.SaveJpeg(big, resize_big, 100L);

    var resize_forty = ImageObject.ResizeImage(System.Drawing.Image.FromFile(temp_path), forty);
    ImageObject.SaveJpeg(medium, resize_forty, 100L);

    var resize_twentyfive = ImageObject.ResizeImage(System.Drawing.Image.FromFile(temp_path), twentyfive);
    ImageObject.SaveJpeg(small, resize_twentyfive, 100L);
}

Этот метод вызывается веб-службой. При первом выполнении этого кода (после перезапуска IIS) все хорошо, но при повторном использовании он зависает. Я знаю, что это связано с двумя изображениями, которые я создал: avatar.jpg и avatar_t.jpg. Я знаю это, потому что не могу удалить или переименовать изображения в Проводнике:

locked file when trying to rename

Я гарантировал, что у меня есть Dispose объекты Graphics, как это предлагали многие, но я не могу понять, почему блокировки не снимаются? Кто-нибудь может увидеть проблему?

В идеале я хотел бы сделать это внизу:

var resize_twentyfive = ImageObject.ResizeImage(System.Drawing.Image.FromFile(temp_path), twentyfive);
            ImageObject.SaveJpeg(small, resize_twentyfive, 100L);

//clean up, delete avatar.jpg and avatar_t.jpg
File.Delete(temp_path);
File.Delete(full_path);

И удалите изображения, с которых я читал - они больше не нужны. Я не против, чтобы они оставались там, пока я могу перезаписать их по желанию от загрузчика ...

1 Ответ

6 голосов
/ 12 августа 2011

System.Drawing.Image.FromFile() не закрывает файл, пока вы не вызовете команду Dispose на изображении.

Зависимости конструктора растровых изображений и изображений :

Когда объект Bitmap или объект Image создается из файл, файл остается заблокированным на время существования объекта. Как результат, вы не можете изменить изображение и сохранить его обратно в тот же файл где это произошло.

Кроме того, если поток был разрушен в течение жизни Растровый объект, вы не можете успешно получить доступ к изображению, которое было в потоке. Например, функция Graphics.DrawImage () может не успешно после того, как поток был уничтожен

Image.FromFile() - очень плохой метод API (в том смысле, что он настраивает разработчика на неудачу!). Проблема вызвана:

GDI + и, следовательно, пространство имен System.Drawing, может отложить декодирование необработанных битов изображения до тех пор, пока биты не потребуются для изображения . Кроме того, даже после того, как изображение было декодировано, GDI + может определить, что более эффективно отбрасывать память для большого Растровое изображение и перекодировать позже. Поэтому GDI + должен иметь доступ к исходные биты для изображения для жизни растрового изображения или изображения объект.

Чтобы сохранить доступ к исходным битам, GDI + блокирует любой исходный файл и заставляет приложение поддерживать жизнь любого исходного потока, для жизнь растрового изображения или объекта изображения.

Опять цитата из статьи поддержки:

Чтобы обойти эту проблему, создайте новые растровые изображения с помощью одного из следующие методы (как описано далее в этом разделе):

  • Создать неиндексированное изображение.
  • Создать индексированное изображение.

В обоих случаях вызов метода Bitmap.Dispose () для оригинала Растровое изображение снимает блокировку файла или снимает требование поток или память остаются живыми.

...