Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

Building a Full Featured Mobile Camera Application: C4FCamera

Code4FunPicture4Code4FunPicture2

Maybe you have always wanted to get started developing a Windows Mobile 6.x application but weren't sure where or how to get started. The nice thing about mobile development is that it is actually simpler than desktop application development in many ways. This is due to the fact that when developing against the .NET Compact Framework you have fewer options. The .NET Compact Framework contains a subset of the .NET Framework. This article focuses on building a Mobile Camera Phone application using the Windows Mobile 6 SDK and the .NET Compact Framework Version 3.5. It is a great application for learning the Compact Framework since it covers a variety of scenarios you are likely to experience when developing a mobile application that you wouldn't typically come across when developing a desktop application.  For a good source of background information for Windows Mobile Development, check out the following Microsoft site: http://msdn.microsoft.com/en-us/windowsmobile/default.aspx

To build and run this application sample, you will need to install the following:

This article will not go through the steps of installing and configuring your development environment since it can get pretty involved. However a good starting point would be MSDN's article: Development Tools and Resources for Windows Mobile 6.

C4FCamera Solution Overview

The C4FCamera Visual Studio solution contains three projects: C4FCamera, C4FCameraLib and CameraTests. Three projects were created due to necessity. The C4FCamera application makes use of the CameraCaptureDialog class. This class provides an easy, mobile-device-neutral wrapper for the camera and video functionality provided by the mobile device. This is very convenient because it abstracts away the specific implementations that would occur from the different mobile device manufacturers and the headaches that would occur if you needed to access each device through hardware specific drivers. This class returns a JPEG when used to capture still images.

A difficulty arises when developing with the CameraCaptureDialog class and testing the application using a mobile emulator. This is due to the emulator not having a real camera attached to it. The C4FCamera project allows you to save the picture as Color, Black and White, or Sepia tone. But since there is not an actual camera attached to the phone, how do you test the filters? In this project, we solved the problem by creating a mobile application library and then testing the application using a Visual Studio Test project. For Test Driven Design (TDD) practitioners, this would be just the beginning. For those not familiar with TDD, this gives you an example of one of the many benefits that come from using TDD.

Superficial exploration of C4FCamera will reveal that it demonstrates many common tasks and scenarios that would likely be encountered when developing a mobile application. First the project makes use of multiple mobile device forms and controls. Second, the application demonstrates several workarounds that arise from limitations in the compact framework. Altogether, the application should help the new mobile developer gain an understanding of how mobile development differs from other types of software development.

Exploring the C4FCamera Project

The Main Application Interface: Coding4FunCameraForm

The project has three forms. The main screen provides the user with the ability to set the location where pictures are stored, the resolution of the pictures, and the default file name. When developing a professional application, it may not make sense to offer all the settings to a user on the main interface. It is up to you to decide based on your knowledge of the target user's profile. The majority of the settings on the main form relate to the properties of the CameraCaptureDialog class. The filter selection radio buttons are not directly related to the CameraCaptureDialog class.

clip_image002[7] clip_image004[7]

Many of the control settings and default values are kept in a resource file. A resource file provides a convenient location where localized settings can be kept. Since most text in an application is culture and location specific, it makes sense to use resource files as a matter of habit. Resource files can hold strings, images, icons, and even audio files.

clip_image006[7]

We will cover other aspects of the user interface later in this article. For now, it is important to recognize that the user interface directly relates to information we need to present to the CameraCaptureDialog class.

CameraCaptureDialog Class

The C4FCamera application revolves around the CameraCaptureDialog class. This class is extremely simple to use. Below is the code from the C4FCamera project.

CameraCaptureDialog cameraCapture = new CameraCaptureDialog();
cameraCapture.StillQuality = this.StillQuality;
cameraCapture.Owner = this;
cameraCapture.DefaultFileName = textDefaultFileName.Text;
cameraCapture.InitialDirectory = PictureDirectoryPath.Text;
cameraCapture.Resolution = new Size(Convert.ToInt32(textWidth.Text), Convert.ToInt32(textHeight.Text));

// Displays the "Camera Capture" dialog box
if (DialogResult.OK == cameraCapture.ShowDialog())
{
   string fileName = cameraCapture.FileName.Replace("___TEMP", "");
   ICameraFilter filter = GetPictureFilter(cameraCapture.FileName);
   string saveFileName = CameraFileUtilities.incrementFileNameNumber(fileName);
   filter.Apply().Save(saveFileName, ImageFormat.Jpeg);
   File.Delete(cameraCapture.FileName);
   // The method completed successfully.
   MessageBox.Show(
      "The picture has been successfully captured and saved to:\n\n" + saveFileName, this.Text,
MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1); }

The code in this example creates a new instance of the CameraCaptureDialog class and then calls the ShowDialog method. The ShowDialog method brings up the camera interface including the viewfinder of the camera. There really isn't much you can do with this interface, but fortunately you don't need to. When the ShowDialog method returns, the class has already written the image to a file. What file? A file is created with the name specified in DefaultFileName property. If a file with the same file name already exists it is overwritten. For a consumer application this would be unacceptable, so a method was added to prevent this.

Imagine you are at a friend's wedding. You capture a picture that exemplifies the specialness of the event. Later, you go to take a picture of one of your friends acting goofy at the reception. Without purposely creating some sort of file versioning logic, you are going to lose the original image. To address this issue, the CameraFileUtilities class and a static method called incrementFileNameNumber are included with the project. This method performs a check to determine if a file with the same name exists. If one does, it increments the version number by one.

public static string incrementFileNameNumber(string file)
{
    string newFileName;
    int fileVersionNumber = 0;

    FileInfo fileInfo = new FileInfo(file);
    newFileName = fileInfo.FullName;
    if (File.Exists(newFileName))
    {
        do
      {
        newFileName = fileInfo.DirectoryName + "\\" + 
        fileInfo.Name.Replace(fileInfo.Extension, "
") + (++fileVersionNumber) + fileInfo.Extension; } while (File.Exists(newFileName)); } return newFileName; }

There are many strategies you could employ here, but appending an integer value to the end of the file name before the extension is probably the simplest. We actually delete the file created by CameraCaptureDialog. That is because we are applying a filter to the still image that we have captured. Even if we are applying the Color filter to the image, we want to call the ICameraFilter interface and use the Apply method. Since the Apply method returns an instance of the Bitmap class, we use the short hand of call Save on the Bitmap instance.

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

The ICameraFilter is an interface. We are using this interface in a way that roughly resembles the Gang of Four Abstract Factory design pattern. The interface allows us to handle the ColorFilter class, the BlackAndWhiteFilter class, and the SepiaFilter class in same way at runtime. Because they all implement the ICameraFilter interface, we don't even care which one is selected, they all adhere to the contract defined in the ICameraFilter interface. This allows receiving the correct class already instantiated (in other word: built), hence it is a factory pattern.

The ICameraFilter Interface and Filter Implementations

The ICameraFilter interface is very simple. We are only expecting each filter to return a bitmap.

public interface ICameraFilter
{
   Bitmap Apply();
}

The actual implementation of the filters is little bit more complicated. Let's look at the code for the BlackAndWhiteFilter class.

public class BlackAndWhiteFilter : ICameraFilter
{
    private string _fileName;
    public BlackAndWhiteFilter(string fileName)
    {
        if (!File.Exists(fileName))
        {
            throw new ApplicationException(
    "Cannot apply Camera filter to a file that doesn't exist");
        }
        else
        {
            this._fileName = fileName;
        }
    }

    #region ICameraFilter Members

    public Bitmap Apply()
    {
        Bitmap newBitmap;

        using (Bitmap original = new Bitmap(_fileName))
        {
            //make an empty Bitmap the same size as original
            newBitmap =
               new Bitmap(original.Width, original.Height);

            for (int i = 0; i < original.Width; i++)
            {
                for (int j = 0; j < original.Height; j++)
                {
                    //get the pixel color from the original image
                    Color originalColor = original.GetPixel(i, j);

                    //create the grayscale pixel value
                    int grayScale =
                       (int)((originalColor.R * .3) +
                              (originalColor.G * .59) +
                              (originalColor.B * .11));

                    //create the color object
                    Color newColor =
                       Color.FromArgb(grayScale, grayScale, grayScale);

                    //set the new image's pixel to the grayscale pixel //value
                    newBitmap.SetPixel(i, j, newColor);
                }
            }
        }
        return newBitmap;
    }

    #endregion
}

In the BlackAndWhiteFilter, we are creating a grayscale version of the image we received from the CaptureCameraDialog class. The code adjusts every pixel, removing the red, green, and blue values from the pixel. This would actually be one of the least efficient ways to process the image if we were using the full .NET framework. However, we are using the .NET Compact Framework and this is one area where the differences become obvious. Using the full .NET Framework we would make use of the ColorMatrix class. The ColorMatrix class is not available in the .NET Compact Framework and is rather complex to use. However, it is used commonly and most code samples involving grayscale will employ this method, so it is important to know about its existence.

The SepiaFilter is very much like the BlackAndWhiteFilter, with a couple of changes in the 2nd for loop.

Color originalColor = original.GetPixel(i, j);

double outputRed = (originalColor.R * .393) + (originalColor.G * .769) + (originalColor.B * .189);
double outputGreen = (originalColor.R * .349) + (originalColor.G * .686) + (originalColor.B * .168);
double outputBlue = (originalColor.R * .272) + (originalColor.G * .534) + (originalColor.B * .131);

int ioutputRed = Convert.ToInt16(outputRed);
int ioutputGreen = Convert.ToInt16(outputGreen);
int ioutputBlue = Convert.ToInt16(outputBlue);                        

if (ioutputRed > 255)
      ioutputRed = 255;
if (ioutputGreen > 255)
    ioutputGreen = 255;
if (ioutputBlue > 255)
    ioutputBlue = 255;

Since the filters are located in a Mobile Device Library, we can test the filters without using the mobile device camera. You simple create a test project and reference the library. Visual Studio will give a warning about using a mobile library with a test project. We ignored the warning without any ramifications. If you are concerned, paste the filters in a regular Windows Library project.

Once the filter has been applied, the image is written to … where? If you haven't set the main form to use a specific directory location, a default location will be set when the main form initializes. Since it is always unwise to assume the directory layout of any system, the code uses an environment defined enum that can be used to retrieve the initial path: Environment.SpecialFolder.Personal. Using this enum, we can retrieve the user's personal directory.

private static string _personaldirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
public static string PersonalDirectory
{
      get { return _personaldirectory; }
}

Directory Browser Form

The compact framework doesn't ship with a directory browser dialog. This makes it difficult for the user to configure the target storage location for the pictures. As this is a common problem when working in a Windows Mobile Smart Phone environment, a Directory Browser form has been added to the project. The form simply allows the user to browse the local file system and select an appropriate storage location for the images.

clip_image017[7]

The form itself consists of a TreeView control and a label to display the currently selected path. On initialization the form calls the PopulateTree method which, as the name suggests, populates the TreeView control:

public void PopulateTree(string directoryValue, TreeNode parentNode)
{
    string[] directoryArray = Directory.GetDirectories(directoryValue);

    try
    {
        if (directoryArray.Length != 0)
        {
            foreach (string directory in directoryArray)
            {
                substringDirectory = directory.Substring(
                directory.LastIndexOf('\\') + 1,
                directory.Length - directory.LastIndexOf('\\') - 1);

                TreeNode myNode = new TreeNode(substringDirectory);
                myNode.ImageIndex = 0;
                myNode.SelectedImageIndex = 1;

                parentNode.Nodes.Add(myNode);

                PopulateTree(directory, myNode);
            }
        }
    }
    catch (UnauthorizedAccessException)
    {
        parentNode.Nodes.Add("Access denied");
    } // end catch
}

This method recursively walks the file system starting at the root to build a complete directory tree. It also has some rudimentary error handling for unauthorized file system nodes.

The C4FCamera application employs this directory browser on the main configuration form. The user simply selects the target directory on the tree. The path of this directory is stored as a string in the SelectedDirectory property of the form. On return, the configuration form uses the property to fetch the desired target path:

private void btnSetPictureDirectory_Click(object sender, EventArgs e)
{
    DirectoryBrowser directory = new DirectoryBrowser();
    directory.ShowDialog();
    if (directory.DialogResult == DialogResult.OK
        && directory.SelectedDirectory != string.Empty)
    {
        PictureDirectoryPath.Text = directory.SelectedDirectory;
    }
}

Building a directory browser is a simple way to add greater usability to your application.

Browse Pictures Form

In addition to the directory browser the application needs a simple way for users to be able to browse the results of their photography. To achieve this, a basic image browser was added to the project.

clip_image021[7]

The Browse Pictures form provides the ability to view and delete images that are stored in the application's picture storage path. On initialization the form calls the InitializeViewer method. This method checks the file type of each file in the storage directory. If the file is an image, the file name is added to a ComboBox list and the first image file is loaded in a PictureBox control:

private void InitializeViewer(string directory)
{
    bool filesFound = false;
    dropdownPictures.Items.Clear();
    InitialDirectory = directory;
    string[] files = Directory.GetFiles(InitialDirectory);
    foreach (string file in files)
    {
        FileInfo fileinfo = new FileInfo(file);
        if (fileinfo.Extension.ToLower() == ".jpg" ||
        fileinfo.Extension.ToLower() == ".bmp" ||
        fileinfo.Extension.ToLower() == ".jpeg")
        {
            dropdownPictures.Items.Add(fileinfo.Name);
            filesFound = true;
        }

    }

    if (filesFound)
    {
        string file = files[0];
        Bitmap picture = new Bitmap(file);
        pictureViewer.Image = picture;
        dropdownPictures.SelectedIndex = 0;
    }
}

A user can navigate the images in the directory by changing the selection in the ComboBox. The currently selected image can be deleted by selecting Delete in the form menu.

The Picture Browser can be accessed through the application's main menu.

Conclusion

This article has shown you how to use an internal mobile camera with the .NET Compact Framework. The resulting application also addresses some of the limitations imposed by the mobile platform by presenting a basic directory browser. Additionally, it introduces some common image filtering methods for grayscale and sepia toned photos. Hopefully this will inspire you to go forward and create your own Windows Mobile applications.

Tags:

Follow the Discussion

  • marauderzmarauderz

    I haven't tried out your code yet, but have you tried to run your program on images from a 5MP camera, which are about 900k in size? I'm getting an out of memory exception when I try to create the bitmap object.

  • HowlerHowler

    Is there a way to easily get an image from the camera without using the CameraDialog class? I want my program to be able to take and store images without having to show the preview and/or waiting for the user to actually take the picture.

  • Clint RutkasClint I'm a "developer"

    @Howler, So what I've learned is you can do it with native code via DirectShow but it is OEM dependent which means just because the device has a camera, it doesn't mean you can use DirectShow.

    I'd do some tests with the CameraDialog class to see if you can set visiblity and programmatically take pictures.

  • Clint RutkasClint I'm a "developer"

    @RobG, Yes you can if you do the camera stuff via an ActiveX Control.

    http://blogs.msdn.com/iemobile/archive/2007/06/20/ie-mobile-support-of-activex.aspx

  • Clint RutkasClint I'm a "developer"

    @RobG you want a pure web app that lets you use the camera?  Let me find out, I'm leaning toward it isn't possible.  I have a few ideas that i know will work on Windows but not sure if they can be done on Mobile.

  • RobGRobG

    Is there a way to do this within a browser based application that uses PIE or IE on WinMo 6.x?

  • Clint RutkasClint I'm a "developer"

    @RobG  There are samples in the WM SDK that you could merge together to get it running...

    http://msdn.microsoft.com/en-us/library/bb158780.aspx

    The AXSample is an IE Mobile ActiveX sample.  There is also a Camera Capture sample that shows you how to call the native Camera APIs.    You could start with AXSample and plug in the camera code and then you would just need to do some basic smoke testing to make sure the UI works the way you want from inside IE.

  • Clint RutkasClint I'm a "developer"

    @Neeraj I don't have a WM6.1 Pro device handy / SDK installed so can't be of much help, sorry.  Can you tell me where it is failing?

  • NeerajNeeraj

    This applcation is not working on Windows Mobile 6.1 Professional. Actually the InitialDirectory and DefaultFileName is not having any effect on the saved filename. Whatever you name the file it is being saved in /Storage Card/DCIM folder.

    But it is woking fine with Windows Mobile 6 Professional.

    Any ideas ?

  • Clint RutkasClint I'm a "developer"

    @Neeraj, i don't have a device but could be how they setup their rom.  I can't be positive as I don't have a few devices including that one to test on.  sorry.

  • NeerajNeeraj

    Actually I am using Micromax W900 model having WM6.1 Professional(available in india only...I guess).

    The application is working fine with HTC Touch Dual.

    In Micromax following two properties in TakeStillPicture() is  having no effect:

    cameraCapture.DefaultFileName = textDefaultFileName.Text;

    cameraCapture.InitialDirectory = PictureDirectoryPath.Text;

    Regardless of the value you supply to these parameters the Photographs are being stored in \Storage Card\DCIM folder or \My Documents\My Pictures folder depending on the storage location you select on the device.

    I dont know if its a bug with WM 6.1 or my phone has some camera implementation issues.

    Hope I am making it clear.

    Please Help...

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.