13.7. Работа с картинками в C#

Следующий интересный пример, который мы рассмотрим, — работа с картинками. Благодаря изображениям и хорошо продуманному интерфейсу программами могут пользоваться даже дети. Например, мой сын, начиная с четырех лет, без проблем ориентируется в Windows, абсолютно не умея читать. Он уже в три года начал играть в компьютерные игры, а сейчас управляется с компьютером без меня, и я его не учил специально.

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

Для воплощения этой идеи в жизнь нам понадобится новое приложение с кнопкой и панелью. По нажатию кнопки мы будем открывать стандартное окно выбора файла, и если пользователь выбрал файл, то отобразим его в панели. Почему именно в панели, а не прямо на форме? Потому что это удобное решение для ограничения области вывода картинки. К тому же, мы сделаем так, что если картинка не помещается на панели, появлялись бы полосы прокрутки для просмотра содержимого панели. На рис. 13.2 показан результат работы программы.

Рис. 13.2. Результат работы программы загрузки и отображения картинки

Для работы с изображениями в .NET есть класс Image. Он умеет создавать изображения на основе данных из таких графических файлов, как BMP, JPG, PNG, и даже на основе метафайлов Windows Metafile Format (WMF) или Enhanced Metafile Format (EMF). Правда, метаданные могут только загружаться, при попытке сохранить информацию файл будет сохранен в формате PNG, потому что в .NET Framework нет кодировщика, способного сохранять файлы в EMF или WMF. Для сохранения файлов в этих форматах придется писать собственные методы.

Итак, для работы с картинкой нам понадобится переменная класса Image. Объявите ее в качестве члена формы:

   Image image;

Теперь посмотрим, как можно загрузить картинку в объект image. Для кнопки пишем код из листинга 13.2.

Листинг 13.2. Код загрузки изображения в программу

private void loadImageButton_Click(object sender, EventArgs e)
{
  OpenFileDialog openDialog = new OpenFileDialog();
  openDialog.Filter = "Файлы изображений|*.bmp;*.png;*.jpg";
  if (openDialog.ShowDialog() != DialogResult.OK)
    return;

  try
  {
    image = Image.FromFile(openDialog.FileName);
  }
  catch (OutOfMemoryException ex)
  {
    MessageBox.Show("Ошибка чтения картинки");
    return;
  }

  panel1.AutoScroll = true;
  panel1.AutoScrollMinSize = image.Size;
  panel1.Invalidate();
}

Сначала создается объект класса OpenFileDialog. Этот класс предназначен для отображения стандартного окна выбора файла. Если быть точнее, то этот компонент и соответствующий значок есть в разделе Dialogs панели Toolbox. Но поскольку этот компонент не визуальный, я не вижу смысла бросать его на форму, поэтому создаю вручную в коде, ведь это не так уж и сложно.

Конструктор объекта окна выбора файла не получает никаких параметров. Единственное, что желательно сделать для удобства пользователя, — задать поле Filter, где указывается шаблон фильтра допустимых расширений. Фильтр состоит из двух составляющих: имя и список расширений через точку с запятой. Обе составляющие разделяются символом вертикальной черты. В нашем случае в качестве имени шаблона указано название "Файлы изображений", а в качестве расширений идет список из основных графических файлов "*.bmp;*.png;*.jpg".

В качестве фильтра можно указывать список из нескольких шаблонов, и они также должны быть разделены вертикальной чертой. Например, следующая строка создаст два шаблона: "Файлы изображений" и "Все файлы":

   "Файлы изображений|*.bmp;*.png;*.jpg|Все файлы|*.*"

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

Имея диалоговое окно для открытия файла, мы можем отобразить его точно так же, как отображали свои собственные диалоговые окна в главе 11, а именно — с по- мощью метода ShowDialog(). В качестве результата метод возвращает значение перечисления DialogResult, и если пользователь выбрал файл и нажал кнопку OK, то этот результат будет равен DialogResult.OK. В нашем примере, если пользователь ничего не выбрал, то выполнение метода обработки события прерывается.

Теперь у нас есть имя файла, который нужно загрузить, и оно находится в openDialog.FileName. Можно создавать объект картинки. Но у объекта нет конструктора, принимающего картинку, и нет метода для загрузки. Вместо этого у него есть статичный метод FromFile(), который создает новый объект Image, загружает в него картинку и возвращает результат. Им мы и пользуемся в нашем примере.

Единственное важное замечание — загрузку файла лучше заключить в блок исключительных ситуаций try, чтобы отловить ошибку OutOfMemoryException. Название исключения не совсем подходит для описываемой ситуации, но эта ошибка генерируется во множестве случаев, в том числе и при некорректном формате файла. Итак, если изображение из файла не может быть загружено, то мы должны отловить ошибку OutOfMemoryException, чтобы корректно сообщить пользователю о проблеме и продолжить работу программы.

Если картинка загружена, то делаем с панелью небольшой трюк так, чтобы в случае необходимости появились полосы прокрутки. Это можно сделать автоматически, возложив всю работу по поддержанию прокрутки на панель. Для начала нужно установить свойство AutoScroll в true. По умолчанию это свойство равно false. Теперь в свойство AutoScrollMinSize нужно поместить размер картинки. Это будет гарантировать, что с помощью прокрутки мы сможем пролистать и увидеть всю указанную в свойстве AutoScrollMinSize область.

Последним этапом я заставляю панель перерисоваться с помощью Invalidate(). В общем-то, этого можно и не делать, ведь после изменения свойства AutoScrollMinSize перерисовка все равно должна произойти, но я на всякий случай явно вызываю метод Invalidate().

Теперь создадим обработчик события Paint для панели, потому что рисовать мы будем именно на ее поверхности, а в обработчике напишем следующий код:

   if (image == null)
     return;

   e.Graphics.DrawImage(
       image,
       panel1.AutoScrollPosition.X,
       panel1.AutoScrollPosition.Y,
       image.Size.Width,
       image.Size.Height
   );

Если картинка равна нулю, то пользователь еще ничего не загружал, и нужно выйти, иначе любое обращение к объекту image приведет к исключительной ситуации. Если же объект не равен нулю, то выполнение метода продолжится.

За рисование картинки у класса Graphics отвечает метод DrawImage(). У меня Visual Studio показывает, что она знает о существовании аж 30 различных перегруженных вариантов этого метода, — нам остается выбрать любой на вкус и цвет или по необходимости. Я выбрал для нашего примера вариант, который принимает 5 параметров: объект картинки, смещение по осям X и Y, ширину и высоту копируемой картинки. Если указать размеры больше или меньше реальных, то при копировании будет организовано масштабирование. В качестве позиций X и Y в примере указываются значения полей свойства AutoScrollPosition, в котором находится текущее смещение полос прокрутки. Тут нужно также заметить, что при изменении позиции прокруток перерисовка вызывается автоматически.

Пример готов. Можете запустить его и убедиться в его работоспособности. Пример в папке Source\Chapter13\ImageProject сопровождающего книгу электронного архива (см. приложение) не просто отображает картинку, но и увеличивает ее в два раза.

Чаще всего мне приходилось создавать картинки с помощью метода FromFile() класса Image, но у этого класса существуют еще два варианта, о которых следует знать, — это статичные методы FromHbitmap() и FromStream(). Первый из них создает картинку на основе битового массива Windows Handle, а второй создает изображение на основе потока данных. Самое интересное тут Windows Hanlde — это описатель ОС Windows, который используется для хранения изображения в самой ОС. Именно с такими описателями приходится иметь дело программистам, работающим с неуправляемым кодом. Если вы будете получать картинку из системы, то с помощью метода FromHbitmap() можно получить управляемый объект для работы с системной картинкой.

Теперь пробежимся по свойствам и методам класса Image, которые могут вам пригодиться в работе:

  • Height — высота картинки;
  • HorizontalResolution — горизонтальное разрешение;
  • Palette — палитра в виде класса ColorPalette;
  • PhysicalDimension — физические размеры картинки;
  • PixelFormat — формат пиксела в виде перечисления PixelFormat;
  • RawFormat — формат файла изображения;
  • Size — размер картинки в пикселах;
  • VerticalResolution — вертикальное разрешение;
  • Width — ширина.

Теперь посмотрим на наиболее интересные методы класса Image (статичные методы создания объекта мы уже рассмотрели):

  • Clone() — вернуть копию объекта;
  • GetBounds() — вернуть размеры картинки;
  • GetThumbnailImage() — вернуть небольшое изображение;
  • RotateFlip() — вращать или переворачивать изображение;
  • Save() — сохранить в файл.

Предыдущая глава

13.6. Кисти Brush в .NET

Следующая глава

13.8. Графический дизайнер

О блоге

Программист, автор нескольких книг серии глазами хакера и просто блогер. Интересуюсь безопасностью, хотя хакером себя не считаю

Обратная связь

Без проблем вступаю в неразборчивые разговоры по e-mail. Стараюсь отвечать на письма всех читателей вне зависимости от страны проживания, вероисповедания, на русском или английском языке.

Пишите мне