c# - 上传文件被GDI读取后锁定

标签 c# io gdi

我有以下 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.jpgavatar_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);

并删除我用来阅读的图像——它们不再需要了。我不介意他们留在那里,只要我可以从上传者那里随意覆盖他们...

最佳答案

System.Drawing.Image.FromFile()在您对图像调用 Dispose 之前不会关闭文件。

Bitmap and Image constructor dependencies :

When either a Bitmap object or an Image object is constructed from a file, the file remains locked for the lifetime of the object. As a result, you cannot change an image and save it back to the same file where it originated.

Additionally, if the stream was destroyed during the life of the Bitmap object, you cannot successfully access an image that was based on a stream. For example, the Graphics.DrawImage() function may not succeed after the stream has been destroyed

Image.FromFile() 是一个非常糟糕的 API 方法(从某种意义上说,它会让开发人员为失败做好准备!)。问题是由以下原因引起的:

GDI+, and therefore the System.Drawing namespace, may defer the decoding of raw image bits until the bits are required by the image. Additionally, even after the image has been decoded, GDI+ may determine that it is more efficient to discard the memory for a large Bitmap and to re-decode later. Therefore, GDI+ must have access to the source bits for the image for the life of the Bitmap or the Image object.

To retain access to the source bits, GDI+ locks any source file, and forces the application to maintain the life of any source stream, for the life of the Bitmap or the Image object.

再次引用支持文章:

To work around this problem, create new Bitmap images by using one of the following methods (as described later in this section):

  • Create a non-indexed image.
  • Create an indexed image.

In both cases, calling the Bitmap.Dispose() method on the original Bitmap removes the lock on the file or removes the requirement that the stream or memory stay alive.

关于c# - 上传文件被GDI读取后锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7038903/

相关文章:

c# - 我的应用程序和 Access 查询向导之间的不同 LIKE 行为

java - 扫描仪在使用 next() 或 nextFoo() 后跳过 nextLine()?

qt - 在 Qt 中通过 Windows GDI 绘制

c - Pascal 到 C 的翻译

c# - .Net Graphicspath 包络失真

c# - 将 ViewModel 数据返回到 Javascript 函数 MVC 4?

c# - 流程图建模

javascript - 查询字符串参数

file - Golang,倒带文件指针的正确方法

python - 大量小文件的 C 与 Python I/O 性能对比