Полнофункциональное приложение для мобильной камеры: C4FCamera

 

 

 

Опубликовано 7 июля 2009 в 12:01 | Coding4Fun

clip_image001clip_image002

Роберт Фишер (Robert Fischer (EN)), разработчик архитектуры, Daugherty Business Solutions
Пьер Лафромбуа (Pierre Lafromboise (EN)), бизнес-аналитик, Daugherty Business Solutions, и Энн Фишер (Anne Fischer).

Это статья для тех, кто давно хотел научиться создавать приложения для Windows Mobile 6.x, но не знал с чего начать. Прелесть разработки мобильных приложений в том, что этот процесс во многих отношениях проще программирования для настольных систем. Объясняется это меньшим набором возможностей, который .NET Compact Framework предоставляет разработчикам, поскольку Compact Framework — это подмножество .NET Framework. В данной статье рассматривается создание приложения для камеры мобильного устройства с применением Windows Mobile 6 SDK и .NET Compact Framework версии 3.5. Это приложение — прекрасное учебное пособие для изучения Compact Framework, поскольку в нем используются различные сценарии, с которыми вы, вероятно, столкнетесь при разработке мобильных приложений, но вряд ли для традиционных программ. Основным источником сведений о разработке для Windows Mobile является сайт Microsoft: https://msdn.microsoft.com/en-us/windowsmobile/default.aspx (EN).

Для разработки и исполнения описываемого демонстрационного приложения вам потребуются следующие ресурсы:

  • Исходный текст : Загрузить
  • Сложность: средняя
  • Необходимое время : 5 часов
  • Затраты : Нулевые
  • Необходимое ПО : Visual Studio Professional (EN) (студенты могут загрузить здесь: Dream Spark), Windows Mobile 6 SDK (EN).
  • Оборудование: телефон с сенсорным экраном и Windows Mobile

В статье не описывается процесс установки и настройки среды разработки, поскольку это заняло бы слишком много времени. Об этом можно прочитать в статье MSDN «Средства разработки и ресурсы для Windows Mobile 6» (Development Tools and Resources for Windows Mobile 6 (EN)) .

Обзор решения C4FCamera

Решение C4FCamera в Visual Studio содержит три проекта: C4FCamera, C4FCameraLib и CameraTests. Наличие трех проектов является необходимым условием. В приложении C4FCamera используется класс CameraCaptureDialog. Этот класс является простой оболочкой, обеспечивающей независимость от конкретного типа мобильного устройства при работе с функциями камеры. Такой подход позволяет абстрагироваться от конкретных реализаций этих функций различными производителями и избежать головной боли из-за доступа к устройствам посредством их драйверов. При фотографировании этот класс возвращает JPEG.

Сложности возникают при тестировании класса CameraCaptureDialog в процессе разработки в режиме эмулятора мобильного устройства. Объясняется это тем, что эмулятор не имеет реальной камеры. Проект C4FCamera позволяет сохранить изображение как цветное (Color), черно-белое (Black and White) или сепию (Sepia). Но как можно проверить эти фильтры при отсутствии камеры? Нашим решением данной проблемы было создание библиотеки мобильного приложения и последующее тестирование этого приложения в проекте Test Visual Studio. Для тех, кто практикует проектирование, направляемое тестированием (Test Driven Design, TDD), это будет привычным делом. Для не знакомых с методологией TDD это может послужить примером многочисленных достоинств такого подхода.

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

Описание проекта C4FCamera

Главный интерфейс приложения: Coding4FunCameraForm

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

 

clip_image001[4]clip_image001[6]

Многие параметры управления и стандартные значения хранятся в файле ресурсов. Этот файл — удобное средство хранения локализованных параметров, поскольку большинство текстовых строк в приложении привязаны к культуре и региону. Файлы ресурсов могут содержать строки, изображения, значки и даже звуковые файлы.

Класс CameraCaptureDialog

Все приложение C4FCamera строится вокруг класса CameraCaptureDialog. Использовать его исключительно просто. Вот код из проекта C4FCamera:

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

    1: CameraCaptureDialog cameraCapture = new CameraCaptureDialog();
    2: cameraCapture.StillQuality = this.StillQuality;
    3: cameraCapture.Owner = this;
    4: cameraCapture.DefaultFileName = textDefaultFileName.Text;
    5: cameraCapture.InitialDirectory = PictureDirectoryPath.Text;
    6: cameraCapture.Resolution = new Size(Convert.ToInt32(textWidth.Text), Convert.ToInt32(textHeight.Text));
    7:  
    8: // Вывод диалогового окна "Camera Capture"
    9: if (DialogResult.OK == cameraCapture.ShowDialog())
   10: {
   11:    string fileName = cameraCapture.FileName.Replace("___TEMP", "");
   12:    ICameraFilter filter = GetPictureFilter(cameraCapture.FileName);
   13:    string saveFileName = CameraFileUtilities.incrementFileNameNumber(fileName);
   14:    filter.Apply().Save(saveFileName, ImageFormat.Jpeg);
   15:    File.Delete(cameraCapture.FileName);
   16:    // Метод выполнен успешно.
   17:    MessageBox.Show(
   18:       "The picture has been successfully captured and saved to:\n\n" + saveFileName, this.Text,
   19:        MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1);
   20: }

 

В этом фрагменте создается новый экземпляр класса CameraCaptureDialog, а затем вызывается метод ShowDialog, который выводит интерфейс камеры, включая видоискатель. С этим интерфейсом особо ничего не сделаешь, да этого и не нужно. При возврате из метода ShowDialog класс уже записал изображение в файл, имя которого задается свойством DefaultFileName. Если файл с таким именем уже существует, он переписывается, что не допустимо.

Необходимо реализовать логику поддержки версий файлов, иначе можно потерять какие-то кадры. Для решения этой задачи в проекте служат класс CameraFileUtilities и статический метод incrementFileNameNumber. Метод проверяет наличие файла с тем же именем. Если он существует, метод увеличивает номер версии на единицу.

    1: public static string incrementFileNameNumber(string file)
    2: {
    3:     string newFileName;
    4:     int fileVersionNumber = 0;
    5:  
    6:     FileInfo fileInfo = new FileInfo(file);
    7:     newFileName = fileInfo.FullName;
    8:     if (File.Exists(newFileName))
    9:     {
   10:         do
   11:       {
   12:         newFileName = fileInfo.DirectoryName + "\\" + 
   13:     fileInfo.Name.Replace(fileInfo.Extension, "") + (++fileVersionNumber) + fileInfo.Extension;
   14:       }
   15:       while (File.Exists(newFileName));
   16:     }
   17:  
   18:     return newFileName;
   19: }

 

Можно использовать массу других решений, но добавление целого числа в конец имени файла перед расширением — вероятно, простейшее. На самом деле мы удаляем файл, созданный CameraCaptureDialog, поскольку применяем фильтр к полученному с камеры изображению. Даже если применяется фильтр Color, нам надо вызвать интерфейс ICameraFilter и использовать метод Apply. Поскольку метод Apply возвращает экземпляр класса Bitmap, мы используем краткую запись вызова Save для экземпляра Bitmap.

    1: ICameraFilter filter = GetPictureFilter(cameraCapture.FileName);
    2: string saveFileName = CameraFileUtilities.incrementFileNameNumber(fileName);
    3: filter.Apply().Save(saveFileName, ImageFormat.Jpeg);
    4: File.Delete(cameraCapture.FileName);

 

ICameraFilter — это интерфейс. Мы используем его в точном соответствии с паттерном проектирования «Абстрактная фабрика», описанным «Бандой четырех». Этот интерфейс позволяет нам единообразно манипулировать классами ColorFilter, BlackAndWhiteFilter и SepiaFilter в период выполнения. Поскольку все они реализуют интерфейс ICameraFilter, нам даже не надо знать, какой из них выбран, все они соответствуют контракту, определенному в интерфейсе ICameraFilter. Это позволяет получать уже созданный экземпляр нужного класса, отсюда и слово «фабрика» в названии паттерна.

Интерфейс ICameraFilter и реализация фильтров

Интерфейс ICameraFilter весьма прост, от него требуется вернуть битовую карту.

    1: public interface ICameraFilter
    2: {
    3:    Bitmap Apply();
    4: }

Реализация фильтров немного сложнее. Рассмотрим код класса BlackAndWhiteFilter.

    1: public class BlackAndWhiteFilter : ICameraFilter
    2: {
    3:     private string _fileName;
    4:     public BlackAndWhiteFilter(string fileName)
    5:     {
    6:         if (!File.Exists(fileName))
    7:         {
    8:             throw new ApplicationException(
    9:     "Cannot apply Camera filter to a file that doesn't exist");
   10:         }
   11:         else
   12:         {
   13:             this._fileName = fileName;
   14:         }
   15:     }
   16:  
   17:     #region ICameraFilter Members
   18:  
   19:     public Bitmap Apply()
   20:     {
   21:         Bitmap newBitmap;
   22:  
   23:         using (Bitmap original = new Bitmap(_fileName))
   24:         {
   25:             //создаем новый объект Bitmap размером с исходный
   26:             newBitmap =
   27:                new Bitmap(original.Width, original.Height);
   28:  
   29:             for (int i = 0; i < original.Width; i++)
   30:             {
   31:                 for (int j = 0; j < original.Height; j++)
   32:                 {
   33:                     // получить цвет пиксела исходного изображения
   34:                     Color originalColor = original.GetPixel(i, j);
   35:  
   36:                     // создать значение в оттенках серого
   37:                     int grayScale =
   38:                        (int)((originalColor.R * .3) +
   39:                               (originalColor.G * .59) +
   40:                               (originalColor.B * .11));
   41:  
   42:                     // создать объект цвета
   43:                     Color newColor =
   44:                        Color.FromArgb(grayScale, grayScale, grayScale);
   45:  
   46:                     // присвоить пикселу нового изображения значения серого пиксела
   47:                     newBitmap.SetPixel(i, j, newColor);
   48:                 }
   49:             }
   50:         }
   51:         return newBitmap;
   52:     }
   53:  
   54:     #endregion
   55: }

В версии BlackAndWhiteFilter мы преобразуем изображение, полученное из класса CaptureCameraDialog, в оттенки серого цвета. Программа обрабатывает каждый пиксел, убирая красную, зеленую и голубую составляющие. При использовании полновесной версии .NET Framework такой метод был бы наименее эффективным. Однако у нас .NET Compact Framework и мы имеем дело с одним из тех случаев, когда разница в версиях инфраструктуры очевидна. При работе с .NET Framework мы бы применяли класс ColorMatrix. В .NET Compact Framework класс ColorMatrix не используется, вернее его сложно использовать. Между тем знать о его наличии полезно.

Фильтр SepiaFilter аналогичен BlackAndWhiteFilter с некоторыми отличиями во втором цикле for.

    1: Color originalColor = original.GetPixel(i, j);
    2:  
    3: double outputRed = (originalColor.R * .393) + (originalColor.G * .769) + (originalColor.B * .189);
    4: double outputGreen = (originalColor.R * .349) + (originalColor.G * .686) + (originalColor.B * .168);
    5: double outputBlue = (originalColor.R * .272) + (originalColor.G * .534) + (originalColor.B * .131);
    6:  
    7: int ioutputRed = Convert.ToInt16(outputRed);
    8: int ioutputGreen = Convert.ToInt16(outputGreen);
    9: int ioutputBlue = Convert.ToInt16(outputBlue);                        
   10:  
   11: if (ioutputRed > 255)
   12:       ioutputRed = 255;
   13: if (ioutputGreen > 255)
   14:     ioutputGreen = 255;
   15: if (ioutputBlue > 255)
   16:     ioutputBlue = 255;

 

Поскольку наши фильтры помещены в библиотеку Mobile Device Library, мы можем тестировать их без использования камеры мобильного устройства. Вы просто создаете тестовый проект и ссылаетесь на эту библиотеку. Visual Studio выдаст предупреждение об использовании мобильной библиотеки с тестовым проектом. Мы можем спокойно игнорировать это предупреждение. Можете попробовать добавить наши фильтры в обычный проект Windows Library.

При применении фильтра изображение сохраняется в… Где оно сохраняется? Если вы не установили определенный каталог для основной формы, местоположение по умолчанию будет установлено при инициализации главной формы. Полагаться на предопределенную структуру каталогов для произвольной системы неразумно, поэтому в нашей программе используется перечисление для получения исходного пути: Environment.SpecialFolder. Personal. С его помощью мы можем получить личный каталог пользователя.

    1: private static string _personaldirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
    2: public static string PersonalDirectory
    3: {
    4:       get { return _personaldirectory; }
    5: }

 

Форма обозревателя каталога

В Compact Framework нет готового диалогового окна обозревателя каталога. Это усложняет выбор местоположения хранилища для снимков. Решением проблемы будет добавление в проект формы Directory Browser. Она позволяет пользователю просматривать локальные папки и выбирать каталог для хранения снимков.

clip_image001[11]

Сама форма содержит элемент управления TreeView и текст, отображающий текущий выбранный путь. При инициализации формы вызывается метод PopulateTree, который заполняет узлы дерева каталогов:

    1: public void PopulateTree(string directoryValue, TreeNode parentNode)
    2: {
    3:     string[] directoryArray = Directory.GetDirectories(directoryValue);
    4:  
    5:     try
    6:     {
    7:         if (directoryArray.Length != 0)
    8:         {
    9:             foreach (string directory in directoryArray)
   10:             {
   11:                 substringDirectory = directory.Substring(
   12:                 directory.LastIndexOf('\\') + 1,
   13:                 directory.Length - directory.LastIndexOf('\\') - 1);
   14:  
   15:                 TreeNode myNode = new TreeNode(substringDirectory);
   16:                 myNode.ImageIndex = 0;
   17:                 myNode.SelectedImageIndex = 1;
   18:  
   19:                 parentNode.Nodes.Add(myNode);
   20:  
   21:                 PopulateTree(directory, myNode);
   22:             }
   23:         }
   24:     }
   25:     catch (UnauthorizedAccessException)
   26:     {
   27:         parentNode.Nodes.Add("Access denied");
   28:     } // end catch
   29: }

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

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

    1: private void btnSetPictureDirectory_Click(object sender, EventArgs e)
    2: {
    3:     DirectoryBrowser directory = new DirectoryBrowser();
    4:     directory.ShowDialog();
    5:     if (directory.DialogResult == DialogResult.OK
    6:         && directory.SelectedDirectory != string.Empty)
    7:     {
    8:         PictureDirectoryPath.Text = directory.SelectedDirectory;
    9:     }
   10: }

 

Наличие обозревателя каталогов повышает эргономичность приложения.

Форма обозревателя снимков

Кроме обозревателя каталогов пользователю нужно удобное средство просмотра сделанных снимков. В нашем проекте оно реализовано в виде простого обозревателя изображений.

clip_image001[13]

Форма Browse Pictures предоставляет возможность просматривать и удалять снимки, хранящиеся в каталоге изображений приложения. При инициализации форма вызывает метод InitializeViewer. Этот метод анализирует тип каждого файла в каталоге снимков. Если файл является изображением, его имя добавляется в список ComboBox и первый из файлов загружается в элемент управления PictureBox:

    1: private void InitializeViewer(string directory)
    2: {
    3:     bool filesFound = false;
    4:     dropdownPictures.Items.Clear();
    5:     InitialDirectory = directory;
    6:     string[] files = Directory.GetFiles(InitialDirectory);
    7:     foreach (string file in files)
    8:     {
    9:         FileInfo fileinfo = new FileInfo(file);
   10:         if (fileinfo.Extension.ToLower() == ".jpg" ||
   11:         fileinfo.Extension.ToLower() == ".bmp" ||
   12:         fileinfo.Extension.ToLower() == ".jpeg")
   13:         {
   14:             dropdownPictures.Items.Add(fileinfo.Name);
   15:             filesFound = true;
   16:         }
   17:  
   18:     }
   19:  
   20:     if (filesFound)
   21:     {
   22:         string file = files[0];
   23:         Bitmap picture = new Bitmap(file);
   24:         pictureViewer.Image = picture;
   25:         dropdownPictures.SelectedIndex = 0;
   26:     }
   27: }

Пользователь переходит с одного снимка на другой, меняя положение выделения в ComboBox. Выбранный в данный момент снимок можно удалить, выбрав пункт Delete в меню формы.

Обозреватель снимков Picture Browser вызывается из главного меню приложения.

Завершение

В статье описана работа с встроенной камерой мобильного устройства в .NET Compact Framework. Описанное приложение также демонстрирует обход некоторых ограничений, присущих данной платформе, например реализацию обозревателя каталогов. Кроме того, представлены методы преобразования снимков в изображения в оттенках серого и реализации эффекта сепии. Надеемся, эта статья вдохновит вас на дальнейшие успехи и создание собственных приложений для платформы Windows Mobile.