C# WinForms. Некорректная запись image в файл. System.Runtime.InteropServices.ExternalException: "A generic error occurred in GDI+."

Рейтинг: 2Ответов: 1Опубликовано: 25.04.2023

Пишу программу, что открывает картинки через MDI WinForms. Пытаясь сохранить картинку, я натыкаюсь на ошибку: System.Runtime.InteropServices.ExternalException: "A generic error occurred in GDI+.".

Иллюстрация ошибки: Вот так выглядит ошибка.

Пытаясь её поправить, я проверил, что при выполнении функции File.Exists(path), директория существует, разрешения даны. Так же, при тестировании мною было проверено, что в ImageBuffer активной формы имеется картинка и ImagePath. Так же корректно обрабатывается ImageFormat. Изначально я пытался так же сохранять просто Image, но по совету из другой статьи я инициализирую Bitmap, и это всё равно не работает. Заранее благодарю за помощь! Приведу пример функции отрисовки картинки:

public partial class FormChild : Form
    {
        public Image ImageBuffer { get; private set; }
        public ImageFormat ImageFormat { get; private set; }
        
        public string ImagePath { get; private set; }

        private FormGeneral _parent { get; set; }
        private Graphics _grfx { get; set; }
        private Rectangle _rect{ get; set; }

        public FormChild(FormGeneral parent, string caption)
        {
            InitializeComponent();

            // Присваиваем публичным свойствам значения
            ImageBuffer = null;
            ImagePath = string.Empty;

            // Присваиваем приватным свойствам значения
            _parent = parent;
            _grfx = null;
            _rect = ClientRectangle;
            _defaultColor = Color.White;

            // Подписываем события
            this.Paint += FormChild_Paint;
            this.Resize += FormChild_Resize;

            // Присваивание контейнеру родителя данной формы
            this.MdiParent = parent;

            // Задание заголовка
            this.Text = caption;
        }
        public void FormChild_Paint(object sender, PaintEventArgs e)
        {
            if (ImageBuffer == null)
                return;

            _grfx = e.Graphics;

            _grfx.DrawImage(ImageBuffer, 0, 0, _rect.Width, _rect.Height);

            _grfx.Dispose();
        }
        public void UploadImageToBuffer()
        {
            if (_parent.ImageBuffer == null || _parent.ImagePath == string.Empty)
                return;

            ImageBuffer = _parent.ImageBuffer;
            ImagePath = _parent.ImagePath;
            ImageFormat = GetImageFormat();
        }
        public ImageFormat GetImageFormat() {
            string extension = Path.GetExtension(ImagePath).ToLower();

            switch (extension)
            {
                case ".bmp":
                    return ImageFormat.Bmp;
                case ".gif":
                    return ImageFormat.Gif;
                case ".png":
                    return ImageFormat.Png;
                case ".tiff":
                    return ImageFormat.Tiff;
                case ".jpeg":
                    return ImageFormat.Jpeg;
                case ".jpg":
                    return ImageFormat.Jpeg;
                default:
                    return null;
            }
        }
        private void FormChild_Resize(object sender, EventArgs e) =>
            Invalidate();//перерисовать

    }

Пример функции сохранения(класс FormGeneral):

private void SaveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (FormChild activeChildForm = (FormChild)this.ActiveMdiChild) { 
                if (activeChildForm != null)
                {
                    DialogResult dialogResult =
                         MessageBox.Show("Вы хотите сохранить картинку в том же файле?", "Внимание!", MessageBoxButtons.YesNo);
                    if (dialogResult == DialogResult.Yes)
                    {
                        if (activeChildForm.ImageBuffer != null && !string.IsNullOrEmpty(activeChildForm.ImagePath) && activeChildForm.ImageFormat != null)
                        {
                            Image image = Image.FromFile(activeChildForm.ImagePath);
                            
                            Bitmap images = new Bitmap(image);
                            activeChildForm.ImageBuffer.Save(activeChildForm.ImagePath, activeChildForm.GetImageFormat());
                            
                            activeChildForm.Close();
                        }
                    }
                }
                else MessageBox.Show("ActiveMdiChild == null!");
            }
        }

Функция открытия файла(класс FormGeneral)

private void OpenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //если результат равен Cancel, то выходим
            if (OpenFileDialogWindow.ShowDialog() == DialogResult.Cancel)
                return;

            // получаем выбранный файл
            ImagePath = OpenFileDialogWindow.FileName;

            try
            {
                ImageBuffer = Image.FromFile(ImagePath);
            }
            catch
            {
                MessageBox.Show("Cannot find file " + ImagePath +
               "!", Text, MessageBoxButtons.OK, MessageBoxIcon.Hand);
                return;
            }

            // Определение активного дочернего MDI-окна
            FormChild activeChildForm = (FormChild)this.ActiveMdiChild;

            if (activeChildForm == null)
            {
                DialogResult dialogResult =
                    MessageBox.Show("Вы хотите создать форму для отображения картинки?", "ПРЕДУПРЕЖДЕНИЕ", MessageBoxButtons.YesNo);
                if (dialogResult == DialogResult.Yes)
                    CreateToolStripMenuItem_Click(null, null);//создает дочернюю форму
            }
            else {
                activeChildForm.UploadImageToBuffer();
                activeChildForm.Invalidate();
            }
        }

Ответы

▲ 2Принят
ImageBuffer = Image.FromFile(ImagePath);

Это и есть Bitmap. Вы можете сделать поле

public Bitmap ImageBuffer { get; private set; }

И грузить туда вот так

ImageBuffer = (Bitmap)Image.FromFile(ImagePath);

С этой типом разобрались.

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

Объехать можно так

using (var bmp = (Bitmap)Image.FromFile(ImagePath))
{
    ImageBuffer = new Bitmap(bmp);
}

Здесь файл будет гарантированно закрыт сразу же после чтения. Я не знаю, почему FromFile оставляет файл открытым.

Далее, вы определяете формат файла по расширению, не надо так делать. Определяйте формат файла по формату данных

public ImageFormat GetImageFormat()
{
    return ImageBuffer.RawFormat;
}

Вот и вся магия.


А вот расширение можно определять в обратную сторону.

Например так

public static string GetExtension(ImageFormat format)
{
    if (format == ImageFormat.Icon)
        return ".ico";
    if (format == ImageFormat.MemoryBmp)
        return ".bmp";
    return ImageCodecInfo.GetImageEncoders()
        .FirstOrDefault(x => x.FormatID == format.Guid)?
        .FilenameExtension
        .Split(';')[0]
        .TrimStart('*')
        .ToLower() ?? $".{format.ToString().ToLower()}";
}

Страшновато, но зато поддерживает все известные ImageFormat

ImageFormat[] formats = new[]
{
    ImageFormat.Bmp, ImageFormat.Emf, ImageFormat.Exif, ImageFormat.Gif,
    ImageFormat.Icon, ImageFormat.Jpeg, ImageFormat.MemoryBmp, ImageFormat.Png,
    ImageFormat.Tiff, ImageFormat.Wmf
};

foreach (var format in formats)
{
    Console.WriteLine(GetExtension(format));
}

Даст вывод

.bmp
.emf
.exif
.gif
.ico
.jpg
.bmp
.png
.tif
.wmf

Но вам по сути это даже не понадобится, потому что сама картинка ImageBuffer содержит в себе целевой формат.


Теперь как сохранить, убрал все лишнее.

private void SaveToolStripMenuItem_Click(object sender, EventArgs e)
{
    if (this.ActiveMdiChild is null) 
    {
        MessageBox.Show("ActiveMdiChild == null!");
        return; 
    }
    if (MessageBox.Show("Вы хотите сохранить картинку в том же файле?", "Внимание!", MessageBoxButtons.YesNo) == DialogResult.Yes)
    {
        try
        {
            FormChild activeChildForm = (FormChild)this.ActiveMdiChild;
            if (activeChildForm.ImageBuffer is null)
                throw new ArgumentException("Нечего сохранять, изображение не загружено");
            if (string.IsNullOrWhiteSpace(activeChildForm.ImagePath))
                throw new ArgumentException("Не задан путь сохранения");

            activeChildForm.ImageBuffer.Save(activeChildForm.ImagePath, activeChildForm.ImageBuffer.ImageFormat);
            activeChildForm.Close();
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message, ex.GetType().Name);
        }
    }
}

using не нужен, так как вы все равно ее закрываете через Close()