「C#」Bitmap/Image.Save()报错“GDI+ 中发生一般性错误”的一个案例总结

先说一下遇到这个错误的地方

static void Main(string[] args)
{
    string imgPath = "C:\\Users\\raink\\Desktop\\微信图片_20210724102738.jpg";
    Image bmp = GetImageByFileName(imgPath);
    //编码参数
    EncoderParameters encoderParameters = new EncoderParameters(1);
    //设置质量
    EncoderParameter encoderParameter = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 70L);
    encoderParameters.Param[0] = encoderParameter;
    var stream = new MemoryStream();
    //该行报错!!!!
    bmp.Save(stream, GetImageCodecInfo(ImageFormat.Jpeg), encoderParameters);
    
    //以下不重要,就是按文件流保存一下
    using (var fileStream = File.Create(Path.Combine("C:\\Users\\raink\\Desktop\\", "pp.jpg")))
    {
        stream.Seek(0, SeekOrigin.Begin);
        stream.CopyTo(fileStream);
        fileStream.Flush(true);
    }
}

//比较坑的一个方法
public static Image GetImageByFileName(string fileName)
{
    if (string.IsNullOrEmpty(fileName)) return null;
    FileStream s = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);            
    var img = Image.FromStream(s);            
    s.Close();
    s.Dispose();
    return img;
}
private static ImageCodecInfo GetImageCodecInfo(ImageFormat imageFormat)
{
    ImageCodecInfo[] imageCodecInfoArr = ImageCodecInfo.GetImageDecoders();
    foreach (ImageCodecInfo imageCodecInfo in imageCodecInfoArr)
    {
        if (imageCodecInfo.FormatID == imageFormat.Guid)
        {
            return imageCodecInfo;
        }
    }
    return null;
}

简单的说:

使用filestream读取文件到流s

使用Image.Fromstrea(s)到图片,然后关闭并dispose流s

那前面的图片取做解码压缩,

然后再使用Image.Save()方法保存到一个MemoryStream中,

这个保存就会保存,而且只针对JPG图像。

这个代码一开始是正常的,直到遇到某一张jpg出错后,所有的jpg都出错,以前能正常运行的jpg也变得不行了,重启电脑也没用。

真的是及其神奇!!!简直WC。。。

-------------------------------------

网上找了很多资料,大家同意说的原因如下

1.保存路径不存在或者错误;

2.权限问题

3.“Bitmap 对象或从一个文件构造一个 图像对象时,该文件仍保留锁定对于对象的生存期。 因此, 无法更改图像并将其保存回它产生相同的文件”

我觉得与我遇到的情况都不符合。

我尝试过不成功的方法很多:filestream创建时各种权限设置、文件换路径、换名字……

------------------------------------------------

现有的解决办法如下面的代码,Main方法中看注释

static void Main(string[] args)
{
    string imgPath = "C:\\Users\\raink\\Desktop\\微信图片_20210724102738.jpg";
    Image bmp = GetImageByFileName(imgPath);
    //解决方法1
    //Image bmp = GetImageByFileNameV2(imgPath);  // FileStream通过byte[]转换MemoryStream再转换成img
    //解决方法2
    //使用原有filstream方案,读图后复制图
    //Bitmap img = CopyImgByBytes(bmp)  //或者使用CopyImgByDraw(bmp)
    //bmp.dispose   //后面全部用img
    //解决方法3:
    //对filestream读上来的图像先加锁再解锁
    //Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    //System.Drawing.Imaging.BitmapData bmpData =
    //    bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
    //    bmp.PixelFormat);
    //bmp.UnlockBits(bmpData);
    //编码参数
    EncoderParameters encoderParameters = new EncoderParameters(1);
    //设置质量
    EncoderParameter encoderParameter = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 70L);
    encoderParameters.Param[0] = encoderParameter;
    var stream = new MemoryStream();
    //该行报错!!!!System.Runtime.InteropServices.ExternalException:“GDI+ 中发生一般性错误。”
    bmp.Save(stream, GetImageCodecInfo(ImageFormat.Jpeg), encoderParameters);
    //解决方法4:
    //基于原始图像创建新的bitmap对象(类似于方法2)
    //new Bitmap(bmp).Save(stream, GetImageCodecInfo(ImageFormat.Jpeg), encoderParameters);
    //以下不重要
    using (var fileStream = File.Create(Path.Combine("C:\\Users\\raink\\Desktop\\", "pp.jpg")))
    {
        stream.Seek(0, SeekOrigin.Begin);
        stream.CopyTo(fileStream);
        fileStream.Flush(true);
    }
}
public static Image GetImageByFileName(string fileName)
{
    if (string.IsNullOrEmpty(fileName)) return null;
    FileStream s = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    //return Image.FromFile(fileName);
    var img = Image.FromStream(s);
    s.Close();
    s.Dispose();
    return img;
}
private static ImageCodecInfo GetImageCodecInfo(ImageFormat imageFormat)
{
    ImageCodecInfo[] imageCodecInfoArr = ImageCodecInfo.GetImageDecoders();
    foreach (ImageCodecInfo imageCodecInfo in imageCodecInfoArr)
    {
        if (imageCodecInfo.FormatID == imageFormat.Guid)
        {
            return imageCodecInfo;
        }
    }
    return null;
}
/// 
/// FileStream通过byte[]转换MemoryStream再转换成img
/// 
/// 
/// 
private static Image GetImageByFileNameV2(string fileName)
{
    FileStream s = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    BinaryReader r = new BinaryReader(s);
    r.BaseStream.Seek(0, SeekOrigin.Begin);    //将文件指针设置到文件开
    byte[] bytes = r.ReadBytes((int)r.BaseStream.Length);
    r.Close();
    r.Dispose();
    s.Close();
    s.Dispose();
    MemoryStream memoryStream = new MemoryStream(bytes);
    Image img = Image.FromStream(memoryStream);
    memoryStream.Close();
    memoryStream.Dispose();
    return img;
}
//图像拷贝
public static Bitmap CopyImgByBytes(Bitmap bmp)
{
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    Bitmap img = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
    BitmapData imgData = img.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
    IntPtr optr = bmpData.Scan0;
    IntPtr nptr = imgData.Scan0;
    int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
    byte[] OriginalImgBytes = new byte[bytes];
    //把原始图像数据复制到byte[]数组中
    Marshal.Copy(optr, OriginalImgBytes, 0, bytes);
    //把原始图像byte[]数据复制到新图像中
    Marshal.Copy(OriginalImgBytes, 0, nptr, bytes);
    //解除锁定
    bmp.UnlockBits(bmpData);
    img.UnlockBits(imgData);
    return img;
}
public static Bitmap CopyImgByDraw(Bitmap bmp)
{
    Bitmap nbmp = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat);
    Graphics graphics = Graphics.FromImage(nbmp);
    graphics.DrawImage(bmp, 0, 0);
    graphics.Dispose();
    return nbmp;
}

做一个大概说明:

1、FileStream先转换到MemoryStream,

没有直接转换的方法,中间通过byte[]做中介。

2、按原来的方法,通过filestream拿到image,先把image深拷贝到另一个对象中,然后dispose掉前面从fs中拿到的image,后续使用新复制的这个对象就好

3、按原来的方法,通过filestream拿到image,然后对他的BitmapData数据去先加锁再解锁就好。

4、类似于方法2,基于从fs中得到的image,new一个新的bitmap,去执行后面的操作。

---------------------------------

还有2个方法能解决报错

1、读取图片使用Image.FromFile(),但是这个方法会一直占用文件资源,因为业务逻辑的原因,也不能很快释放,导致其他部位访问时会出现文件被占用的情况。

2、就是在filestream读取完图片后,不执行s.close()和s.dispose(),但是这样和上面1 的原因就一样了,文件会被占用。

所以这两个方法没有采用

你可能感兴趣的:(假装会写C#,c#,gdi/gdi+,bitmap)