6.4 Создание растрового редактора - часть 4. Завершение программы.

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

Сейчас мы добавим возможность загрузки изображений в формате jpg. Загрузчик будет основан на существующих классах .net, и быстродействие их конечно находятся на довольно низком уровне. Загружаемые изображения будут устанавливаться на главный слой, после чего можно будет проводить редактирование и сохранение проекта в формате jpg. По сути работа сводится к созданию функциональности меню «Файл»: создания новых чистых проектов, проектов на основе графического файла, а также сохранения текущего проекта в формате jpg.

Загружаемое изображение, в том случае если его размер будет меньше области редактирования, будет устанавливаться в левом нижнем углу рисунка. Также нам необходимо будет добавить в исходный код движка функции для создания итогового слоя перед его сохранением. Начнем как раз с этого момента. Первая функция, которую мы добавим в класс anEngine, будет функция создания результирующего слоя. Не оптимальная по быстродействию, она имеет крайне простую для понимания реализацию, поэтому мы остановимся на ней. Функция будет вызываться из оболочки при сохранении результатов работы в jpg-файл.

/*http://esate.ru, Anvi*/

// получение финального изображения 
public Bitmap GetFinalImage()
{

  // заготовка результирующего изображения 
  Bitmap resaultBitmap = new Bitmap(picture_size_x, picture_size_y);

  // данное решение также не является оптимальным по быстродействию, но при этом является самым простым способом решения задачи 
  for ( int ax = 0; ax < Layers.Count; ax++)
  { 
    // получаем массив пикселей данного слоя 
    int [,,] tmp_layer_data = ((anLayer)Layers[ax]).GetDrawingPlace();

    // пройдем двумя циклами по информации о пикселях данного слоя 
    for ( int a = 0; a < picture_size_x; a++)
    { 
      for( int b = 0; b < picture_size_y; b++)
      {
        // в случае, если пиксель не помечен как "прозрачный", 
        if (tmp_layer_data[a, b, 3] != 1)
        {
          // устанавливаем данный пиксель на результирующее изображение 
          resaultBitmap.SetPixel(a,b, Color.FromArgb(tmp_layer_data[a, b, 0], tmp_layer_data[a, b, 1], tmp_layer_data[a, b, 2]));
        }
        else
        {
          if (ax == 0) // нулевой слой - необходимо закрасить белым отсутствующие пиксели 
          {
          // закрашиваем белым цветом 
          resaultBitmap.SetPixel(a, b, Color.FromArgb(255, 255, 255));
          }
        }
      }
    }
  }

  // поворачиваем изображение для корректного отображения 
  resaultBitmap.RotateFlip(RotateFlipType.Rotate180FlipX);

  // возвращаем результат 
  return resaultBitmap;

}


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

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

/*http://esate.ru, Anvi*/

// получение изображения для главного слоя 
public void SetImageToMainLayer(Bitmap layer)
{

  // поворачиваем изображение (чтобы оно корректно отображалось в области редактирования). 
  layer.RotateFlip(RotateFlipType.Rotate180FlipX);

  // проходим 2-мя циклами по всем пикселям изображения, загруженного в класс Bitmap 
  // получая цвет пикселя, устанавливаем его в текущий слой с помощью функции Drawing // данный алгоритм является крайне медленным, но при этом и крайне простым. 
  // оптимальным решением здесь будет написание собственного загрузчика файлов изображений 
  // что даст возможность без "посредников" получать массив значений пикселей изображений 
  // но данная задача является на много более сложной, а для обучения мы идем более легкими путями 

  for ( int ax = 0; ax < layer.Width; ax++)
  { 
    for ( int bx = 0; bx < layer.Height; bx++)
    { 
      // получение цвета пикселя изображения 
      SetColor(layer.GetPixel(ax,bx));
      // отрисовка данного пикселя в слое 
      Drawing(ax, bx);
    }
  }

}


На этом все. Вернемся к оболочке нашей программы.

Сейчас мы должны реализовать функциональные возможности меню «Файл». Воспользовавшись окном ToolBox, добавьте к проекту объекты openFileDialog и saveFileDialog. Визуально они разместятся рядом с объектами таймера и панелей инструментов. Теперь двойными щелчками мыши добавьте обработчики события нажатия всех элементов меню «Файл»:

- Чистый проект
- Из файла
- Сохранить
- Выход

Начнем с функции для «чистого проекта». Данная функция создает новый объект для ProgrammDrawingEngine, оставляя все ранее созданные подклассы и переменные в данном экземпляре этого класса на растерзание сборщика мусора, реализованного в среде .NET.

Функция довольно проста – мы предлагаем пользователю сохранить результат его работы на компьютере. В случае положительного ответа мы сохраняем проект, используя рассмотренную чуть ранее функцию GetFinalImage для получения результирующего изображения (суммированием всех слоев).

Код обработчика данного пункта меню будет выглядеть следующим образом:

/*http://esate.ru, Anvi*/

// функция создания нового проекта для рисования 
  private void чистыйПроектToolStripMenuItem_Click( object sender, EventArgs e)
  {
  
    // вызываем диалог подтверждения 
    DialogResult reslt = MessageBox.Show("В данный момент проект уже начат, сохранить изменения перед закрытием проекта?", "Внимание!", MessageBoxButtons.YesNoCancel);
    // если отказ пользователя 
    // в зависимости от результата нажатия кнопки пользователем в окне MessageBox 
    
    switch (reslt)
    { 
      case DialogResult.No:
      { 
        // просто создаем чистый проект 
        ProgrammDrawingEngine = new anEngine(AnT.Width, AnT.Height, AnT.Width, AnT.Height);
        // очищаем информацию о добавляемых ранее слоях 
        LayersControl.Items.Clear();
        // по новой инициализируем нулевой слой: 
        // текущий активный слой 
        ActiveLayer = 0;
        // счетчик слоев 
        LayersCount = 1;
        // счетчик всех создаваемых слоев для генерации имен 
        AllLayrsCount = 1;
        // добавление элемента, отвечающего за управления главным слоем в объект LayersControl 
        LayersControl.Items.Add("Главный слой", true );
        
        break;
      }
      
      case DialogResult.Cancel:
      { 
        // возвращаемся 
        return;
      }
      
      case DialogResult.Yes:
      { 
        // открываем окно сохранения файла, и если имя файла указано и DialogResult вернуло сигнал об успешном нажатии кнопки ОК 
        if (saveFileDialog1.ShowDialog() == DialogResult.OK)
        { 
          // получаем результирующее изображение слоя 
          Bitmap ToSave = ProgrammDrawingEngine.GetFinalImage();
          // сохраняем, используя имя файла, указанное в диалоговом окне сохранения файла 
          ToSave.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
          
          // сохранили - начинаем новый проект: 
          
          // создаем новый объект "движка" программы 
          ProgrammDrawingEngine = new anEngine(AnT.Width, AnT.Height, AnT.Width, AnT.Height);
          
          // очищаем информацию о добавляемых ранее слоях 
          LayersControl.Items.Clear();
          // по новой инициализируем нулевой слой: 
          
          // текущий активный слой 
          ActiveLayer = 0;
          // счетчик слоев 
          LayersCount = 1;
          // счетчик всех создаваемых слоев для генерации имен 
          AllLayrsCount = 1;
          // добавление элемента, отвечающего за управления главным слоем в объект LayersControl 
          LayersControl.Items.Add("Главный слой", true );
          
        }
        else
        { 
          // если сохранение не завершилось нормально (скорее всего пользователь закрыл окно сохранения файла 
          // возвращаемся в проект 
          return;
        }
      
      break;
      
      }
 
  }
  
  


Функция для элемента меню «Из файла» может показаться более сложной, но на самом деле все так же просто, только в этот раз дополнительно будет осуществляться взаимодействие с объектом openFileDialog, для того чтобы получить адрес открываемого файла (кроме того, дополнительно будет проводится проверка его существования).

Код крайне простой, хоть и объемный. Комментарии полностью его поясняют:

/*http://esate.ru, Anvi*/

// функция создания нового проекта для рисования 
private void чистыйПроектToolStripMenuItem_Click( object sender, EventArgs e)
{

  // вызываем диалог подтверждения 
  DialogResult reslt = MessageBox.Show("В данный момент проект уже начат, сохранить изменения перед закрытием проекта?", "Внимание!", MessageBoxButtons.YesNoCancel);

  // если отказ пользователя 
  switch (reslt)
  { 
    case DialogResult.No:
    { 
      // просто создаем чистый проект 
      ProgrammDrawingEngine = new anEngine(AnT.Width, AnT.Height, AnT.Width, AnT.Height);
      // очищаем информацию о добавляемых ранее слоях 
      LayersControl.Items.Clear();

      // по новой инициализируем нулевой слой: 
      // текущий активный слой 
      ActiveLayer = 0;
      // счетчик слоев 
      LayersCount = 1;
      // счетчик всех создаваемых слоев для генерации имен 
      AllLayrsCount = 1;
      // добавление элемента, отвечающего за управления главным слоем в объект LayersControl 
      LayersControl.Items.Add("Главный слой", true );

      break;
    }

    case DialogResult.Cancel:
    { 
      // возвращаемся 
      return;
    }
    
    case DialogResult.Yes:
    { 
      // открываем окно сохранения файла, и если имя файла указано и DialogResult вернуло сигнал об успешном нажатии кнопки ОК 
      if (saveFileDialog1.ShowDialog() == DialogResult.OK)
      {
        // получаем результирующее изображение слоя 
        Bitmap ToSave = ProgrammDrawingEngine.GetFinalImage();

        // сохраняем, используя имя файла, указанное в диалоговом окне сохранения файла 
        ToSave.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);

        // сохранили - начинаем новый проект: 

        // создаем новый объект "движка" программы 
        ProgrammDrawingEngine = new anEngine(AnT.Width, AnT.Height, AnT.Width, AnT.Height);

        // очищаем информацию о добавляемых ранее слоях 
        LayersControl.Items.Clear();
        // по новой инициализируем нулевой слой: 

        // текущий активный слой 
        ActiveLayer = 0;
        // счетчик слоев 
        LayersCount = 1;
        // счетчик всех создаваемых слоев для генерации имен 
        AllLayrsCount = 1;
        // добавление элемента, отвечающего за управления главным слоем в объект LayersControl 
        LayersControl.Items.Add("Главный слой", true );
      }
      else
      {
        // если сохранение не завершилось нормально (скорее всего пользователь закрыл окно сохранения файла 
        // возвращаемся в проект 
        return ;
      }

      break ;
    }

  }

}


/*http://esate.ru, Anvi*/

// загрузка изображения в рабочую область программы 
private void изФайлаToolStripMenuItem_Click( object sender, EventArgs e)
{

  // вызываем диалог подтверждения 
  DialogResult reslt = MessageBox.Show("В данный момент проект уже начат, сохранить изменения перед закрытием проекта?", "Внимание!", MessageBoxButtons.YesNoCancel); // если отказ пользователя 
  switch (reslt)
  { 
    case DialogResult.No:
    {

      // просто создаем проект, подгружая изображения 
      if (openFileDialog1.ShowDialog() == DialogResult.OK)
      {
        // проверяем существование файла 
        if ( System.IO.File.Exists(openFileDialog1.FileName))
        {
          // загружаем изображение в экземпляр класса Bitmap 
          Bitmap ToLoad = new Bitmap(openFileDialog1.FileName);

          // если размер изображения не корректен 
          if (ToLoad.Width > AnT.Width || ToLoad.Height > AnT.Height)
          {
            // сообщаем пользователю об ошибке 
            MessageBox.Show("Извините, но размер изображения превышает размеры области рисования", "Внимание", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            // возвращаемся к функции 
            return ;

          }

          // если размер был меньше области редактирования программы 

          // создаем новый экземпляр класса anEngine 
          ProgrammDrawingEngine = new anEngine(AnT.Width, AnT.Height, AnT.Width, AnT.Height);
          // копируем изображение в нижний левый угол рабочей области 
          ProgrammDrawingEngine.SetImageToMainLayer(ToLoad);

          // очищаем информацию о добавляемых ранее слоях 
          LayersControl.Items.Clear();
          // по новой инициализируем нулевой слой: 

          // текущий активный слой 
          ActiveLayer = 0;
          // счетчик слоев 
          LayersCount = 1;
          // счетчик всех создаваемых слоев для генерации имен 
          AllLayrsCount = 1;
          // добавление элемента, отвечающего за управления главным слоем в объект LayersControl 
          LayersControl.Items.Add("Главный слой", true );
        }
      }
      break;
    }

    case DialogResult.Cancel:
    {
      // возвращаемся 
      return ;
    }

    case DialogResult.Yes:
    {

      // открываем окно сохранения файла, и если имя файла указано и DialogResult вернуло сигнал об успешном нажатии кнопки ОК 
      if (saveFileDialog1.ShowDialog() == DialogResult.OK)
      {

        // получаем результирующее изображение слоя 
        Bitmap ToSave = ProgrammDrawingEngine.GetFinalImage();

        // сохраняем, используя имя файла, указанное в диалоговом окне сохранения файла 
        ToSave.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);

        // сохранили - начинаем новый проект: 

        // просто создаем проект, подгружая изображения 
        if (openFileDialog1.ShowDialog() == DialogResult.OK)
        {

          // проверяем существование файла 
          if ( System.IO.File.Exists(openFileDialog1.FileName))
          {

            // загружаем изображение в экземпляр класса Bitmap 
            Bitmap ToLoad = new Bitmap(openFileDialog1.FileName);

            // если размер изображения не корректен 
            if (ToLoad.Width > AnT.Width || ToLoad.Height > AnT.Height)
            {
              // сообщаем пользователю об ошибке 
              MessageBox.Show("Извините, но размер изображения превышает размеры области рисования", "Внимание", MessageBoxButtons.OK, MessageBoxIcon.Stop);
              // возвращаемся и функции 
              return;
            }

            // если размер был меньше области редактирования программы 

            // создаем новый экземпляр класса anEngine 
            ProgrammDrawingEngine = new anEngine(AnT.Width, AnT.Height, AnT.Width, AnT.Height);
            // копируем изображение в нижний левый угол рабочей области 
            ProgrammDrawingEngine.SetImageToMainLayer(ToLoad);

            // очищаем информацию о добавляемых ранее слоях 
            LayersControl.Items.Clear();
            // по новой инициализируем нулевой слой: 

            // текущий активный слой 
            ActiveLayer = 0;
            // счетчик слоев 
            LayersCount = 1;
            // счетчик всех создаваемых слоев для генерации имен 
            AllLayrsCount = 1;
            // добавление элемента, отвечающего за управления главным слоем в объект LayersControl 
            LayersControl.Items.Add("Главный слой", true );

          }

        }
        break ;



      }
      else
      {
        return ;
      }
      break;
    }

  }

}


Нам остается лишь рассмотреть функцию-обработчик сохранения в меню «Файл» (код довольно прост, поэтому обойдемся комментариями).

/*http://esate.ru, Anvi*/

// обработка нажатия кнопки "сохранить" в меню "файл" 
private void сохранитьToolStripMenuItem_Click( object sender, EventArgs e)
{

  // открываем окно сохранения файла, и если имя файла указано и DialogResult вернуло сигнал об успешном нажатии кнопки ОК 
  if (saveFileDialog1.ShowDialog() == DialogResult.OK)
  { 
    // получаем результирующее изображение слоя 
    Bitmap ToSave = ProgrammDrawingEngine.GetFinalImage();
    // сохраняем используя имя файла указанное в диалоговом окне сохранения файла 
    ToSave.Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
  }

}


И код функции-обработчика события нажатия на элемент «Выход» в меню файл:

/*http://esate.ru, Anvi*/

private void выходToolStripMenuItem_Click( object sender, EventArgs e)
{
  Application.Exit();
} 


Нет доступа к просмотру комментариев.

^