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

PicFx – Windows Phone Picture Effects Application – Part 1

At the MIX 2010 conference, Microsoft announced its new mobile strategy with the Windows Phone 7 platform. Windows Phone 7 is a completely new mobile OS, and the best part is that Silverlight is THE technology for Windows Phone 7 application development. Apps are written with a special version of Silverlight based on Silverlight 3 plus some additions. Games with 3D hardware acceleration can be made with the XNA Game Studio 4

Most of the first technical previews have stated that the Windows Phone has a pretty impressive photo application, and we can surely expect some pretty good cameras from the different phone hardware vendors. A Windows Phone 7 is required to have at least a 5 Megapixel camera. Another cool feature requirement of the Windows Phone camera system is called “pocket to picture.” This represents a special hardware button that launches the camera app even if the phone is locked. This feature will be great for capturing all those spontaneous moments in life. How often have you missed such snapshots because you were fumbling to unlock your phone and starting the camera app? I know this has happened to me quite often.

The ability to so quickly take pictures will result in some very cool photos. The original pictures might already look nice, but some will look even better if they are enhanced with a nice image effect. It's in this manner that this article and the PicFx application come into play.

This is the first article in a short series describing how to write picture effects applications for Windows Phone. The first article will show how to create the base Windows Phone application and how to implement some basic effects. Spoiler: The next part will demonstrate how to write some more advanced effects you probably don't want to miss.

The App in Action

The video below demonstrates the application's features and also shows how the application can be used. It was recorded with the application running in the emulator.

How it works

The following sections will describe how this picture effects application was made. We will drill down from the User Interface (UI) Crust through the UI Mantle until we finally reach the Effects Core. Cross your fingers so a leak doesn't stop us.

image

The User Interface

We start our journey on the surface of the application.

The app uses the so-called Pivot control as the main UI template (Figure 1). Along with the Panorama control, the Pivot control is the dominating UI template used in standard Windows Phone applications / hubs. Both controls usually contain multiple items or views that can be navigated with a flick gesture. You can read more about these controls and the general design of Windows Phone applications in the official Windows Phone UI Design and Interaction Guide.

Since the official Silverlight version of the Pivot and Panorama controls haven't been released yet, we'll use this nice open source implementation from CodePlex by Stephane Crozatier.

image

Figure 1: The PicFx application layout using the Pivot control

The relevant part from the MainPage.xaml: 

XAML

<phoneCtrls:PivotControl Name="PivotCtrl" >

    <phoneCtrls:PivotItem 
            Name="PivotItemPic" 
            Title="PicFx" 
            Header="Picture " >
        <Image Name="Viewport" />
    </phoneCtrls:PivotItem>

    <phoneCtrls:PivotItem  
            Name="PivotItemFx" 
            Title=" " 
            Header="Effects " >
        <phoneCtrls:PivotItem.Resources>
            <vm:EffectItems x:Key="effects"/>
        </phoneCtrls:PivotItem.Resources>
        <ListBox Name="ListBoxEffects" 
                        SelectionMode="Single" 
                        ItemsSource="{StaticResource effects}" 
                        SelectionChanged="ListBox_SelectionChanged" >
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <controlsToolkit:WrapPanel Width="480" />
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical" Margin="14" >
                        <Image Width="128"
                                    Height="128" 
                                    Source="{Binding Thumbnail}" />
                        <TextBlock Text="{Binding Name}" 
                                   FontSize="{StaticResource PhoneFontSizeNormal}" 
                                   VerticalAlignment="Center" 
                                   HorizontalAlignment="Center" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </phoneCtrls:PivotItem>
</phoneCtrls:PivotControl>

The first Pivot item contains only the Image showing the selected picture. The second view contains a ListBox control with the thumbnails of the effects (see Figure 1). The list is data-bound to the StaticResource “effects,” an instance of the EffectItems class that is derived from ObservableCollection<EffectItem> and consists of EffectItem elements:

 

C#

public class EffectItems : ObservableCollection<EffectItem>
{
   public EffectItems()
   {
      Add(new EffectItem(new BlackWhiteEffect(), "data/icons/BlackWhite.png"));
      Add(new EffectItem(new SepiaEffect(), "data/icons/Sepia.png"));
   }
}

public class EffectItem
{
   public IEffect Effect { get; private set; }
   public string Name { get; private set; }
   public ImageSource Thumbnail { get; set; }

   public EffectItem(IEffect effect)
   {
      Effect = effect;
      Name = effect.Name;
   }

   public EffectItem(IEffect effect, string thumbnailRelativeResourcePath)
      : this(effect)
   {
      // Load the thumbnail from the resource stream 
      // using the WriteableBitmapEx lib
      Thumbnail = new WriteableBitmap(0, 0).
            FromResource(thumbnailRelativeResourcePath);
   }
 }

The ListBox.ItemTemplate defines the visual representation of the data (EffectItem). The Image is data-bound to the Thumbnail property and the TextBlock is data-bound to the Name property of the EffectItem. The two controls are stacked together. Each Thumbnail has a size of 128 x 128 pixels and a Margin of 14 is used between the items so they get evenly spaced. Please also note the use of a StaticResource “PhoneFontSizeNormal” rather than a fixed FontSize. You can find all the predefined styles in the file %ProgramFiles%\SDKs\Windows Phone\v7.0\Design\ThemeResources.xaml. These styles, margins, and sizes are important because the Windows Phone is a multi-touch device that supports various themes. The Windows Phone UI Design and Interaction Guide provides more information about this topic.

The small thumbnail image is compiled into the assembly's resources. The WriteableBitmapEx' FromResource extension method is then used to load the actual image data at runtime. The FromResource method takes the relative path of the image and automatically builds the Pack URI that is needed to load data from the assembly's resources. Please note that it's recommended to add larger images as “Content” and not as “Resource” to keep the assembly file small.

The ListBox.ItemsPanel specifies the layout of the items. A WrapPanel is used here so the items are stacked horizontally and wrapped when the panel limit is reached. The ItemsPanelTemplate specifies a StackPanel as default, which wastes precious space here. The WrapPanel originally comes from the Silverlight Toolkit and was ripped by Matthias Shapiro and compiled for the Windows Phone 7.

The Application Bar

The Windows Phone Application Bar is a very useful system control that can contain up to four icon buttons and a set of text menu items. It's normally used as a toolbar to provide quickly accessible functionality. The Application Bar is easy to use and very handy for Windows Phone app development.

image
Figure 2: The Application Bar from the PicFx app

The relevant part from the MainPage.xaml :  

XAML

<phone:PhoneApplicationPage.ApplicationBar>
    <shell:ApplicationBar IsVisible="True">
        <shell:ApplicationBarIconButton 
                Text="Choose" 
                IconUri="/data/appbar/appbar.folder.rest.png" 
                Click="ApplicationBarIconFolderButton_Click"/>
        <shell:ApplicationBarIconButton 
                Text="Take" 
                IconUri="/data/appbar/appbar.feature.camera.rest.png" 
                Click="ApplicationBarIconCameraButton_Click"/>
        <shell:ApplicationBarIconButton 
                Text="Save" 
                IconUri="/data/appbar/appbar.save.rest.png" 
                Click="ApplicationBarIconSaveButton_Click"/>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

The MainPage is derived from the PhoneApplicationPage class, which defines the ApplicationBar property. Three ApplicationBarIconButtons are added here. Each one needs to have set a Text and an Icon. Microsoft provides a set of common application bar icons that are used in the PicFx app. The set contains 32 dark and 32 light icons along with the corresponding vector versions.

The Code Behind

The UI is properly set up, the user is able to select an effect and click on the Application Bar buttons, but nothing will actually happen. Now it's time to breathe life into the UI elements.

image

Choosing a Picture

First of all we need to get a picture. One way to do this is to call the Windows Phone picture hub from our app and let the user select an image from the library. Fortunately, this task is pretty easy when using the Windows Phone Task API:

 

C#

private void Initialize()
{
   // ...
   // Init tasks
   photoChooserTask = new PhotoChooserTask();
   photoChooserTask.Completed += 
      PhotoProviderTaskCompleted;
   // ...
}

A member variable of the PhotoChooserTask is instantiated in the Initialize method and an event handler for the Completed event is attached. The Initialize method is called in the Loaded event handler of the page:

 

C#

private void PhotoProviderTaskCompleted
   (object sender, PhotoResult e)
{
   // Load the photo from the task result
   if (e != null && e.TaskResult == TaskResult.OK)
   {
      var bmpi = new BitmapImage();
      bmpi.SetSource(e.ChosenPhoto);
      original = new WriteableBitmap(bmpi);
   }
}

The Chooser's Completed event provides a PhotoResult that contains the TaskResult, the ChosenPhoto as a Stream, and some other properties. If the user hasn't cancelled the operation, the stream is used as the source of a BitmapImage, which is then assigned to a WriteableBitmap member variable.

To launch the picture application, the PhotoChooserTask.Show method is called when the corresponding Application Bar button is clicked:

 

C#

private void ApplicationBarIconFolderButton_Click
   (object sender, EventArgs e)
{
   photoChooserTask.Show();
}

The Show method launches the Windows Phone picture hub. The current version of the Windows Phone operating system allows only one application to run at the same time, and our app is therefore terminated when the Chooser is started. Here's where the concept of tombstoning comes into play.

After the user selected an image or pressed the back button, the Chooser's Completed event is raised. You might have noticed the black screen that appears after the choose operation when you debug the application. As a result of the app termination, the Visual Studio debugging session is stopped. The Windows Phone 7 emulator detected that the app was started in a debug context and the emulator now waits at the black screen for re-attaching. So, just go back to Visual Studio and hit F5 (“Start Debugging”), then the debugger is reattached and the app continues. Of course, it's also possible to “Start Without Debugging” in Visual Studio.

Taking a Picture

Another way to get a picture into the app is to launch the camera application and let the user take an on-the-fly picture with the phone's camera:

 

C#

private void Initialize()
{
   // ...
   // Init tasks
   cameraCaptureTask = new CameraCaptureTask();
   cameraCaptureTask.Completed += 
      PhotoProviderTaskCompleted;
   // ...
}

A member variable of the CameraCaptureTask is also instantiated in the Initialize method and the same event handler used for the PhotoChooserTask is attached.

To launch the camera application, the CameraCaptureTask.Show method is called when the corresponding Application Bar button is clicked:

 

C#

C#

private void ApplicationBarIconCameraButton_Click
(object sender, EventArgs e)
{
cameraCaptureTask.Show();
}

 

Obviously, the CameraCaptureTask API works the same way as the PhotoChooserTask.

The emulator doesn't support the camera application; therefore, the corresponding Application Bar button is disabled in the Initialize method when the app runs in the emulator:

 

C#

// Disable the camera button if the 
// app runs in the emulator
// The cast is needed since the 
// Buttons property is an IList of objects
var buttons = ApplicationBar.Buttons.Cast<ApplicationBarIconButton>();
var btn = buttons.Where(b =>    
                  b.IconUri.ToString().
                     ToLower().Contains("camera")).FirstOrDefault();
if (btn != null)
{
   btn.IsEnabled = 
      (Microsoft.Devices.Environment.DeviceType != DeviceType.Emulator);
}

In the current beta of the Windows Phone developer tools, the ApplicationBar has some limitations since it's rendered by the phone shell and is not at all a Silverlight element. This means that there's no Name property on the ApplicationBarIconButton, x:Name doesn't initialize the member variable, and data binding isn't possible. This is the reason for the above code, which uses the Buttons property and disables the button that has a camera icon if the app is executed in the emulator. As an alternative, the whole Application Bar can be set in the code-behind, including the IsEnabled property, and a working member variable for the button can be initialized.

Resizing a Picture

Both Tasks return the full-sized image. These images can be large, depending on the phone's camera, and as a result the image processing can take quite a long time. Image processing is the operation where the actual effect is applied to an image. Until the picture is saved, however, only the Image control size is needed. That's why a resized version of the picture is used until saving. This speeds the interactive process up a lot:

 

C#

// From MaingPage.ResizeAndShowImage
// Fast and simple resize by using UIElement rendering
if (Viewport != null)
{
   Viewport.Source = bitmap;
   resized = new WriteableBitmap(Viewport, null);
}

The WriteableBitmap resized member variable is being newly instantiated from the Viewport user control. This means that the Viewport image control is rendered at its actual size and the rendered pixels are used for the new and smaller WriteableBitmap instance where the effect gets applied.

Applying an Effect

When the user selects an effect item in the list, the SelectionChanged event is fired and the following event handler and methods are being called:

 

C#

// From the MainPage class
private void ListBox_SelectionChanged
   (object sender, SelectionChangedEventArgs e)
{
   if (resized != null)
   {
      // Apply selected effect and show image
      var result = ApplySelectedEffect(resized);
      ShowImage(result);
   }
}

private WriteableBitmap ApplySelectedEffect
   (WriteableBitmap bitmap)
{
   // Apply selected effect
   var result = bitmap;
   if (ListBoxEffects != null)
   {
      var item = ListBoxEffects.SelectedItem as EffectItem;
      if (item != null)
      {
         result = item.Effect.Process(bitmap);
      }
   }
   return result;
}

private void ShowImage(ImageSource result)
{
   // Show image and scroll to start page if needed
   if (Viewport != null)
   {
      Viewport.Source = result;
      if (PivotCtrl.SelectedIndex != 0)
      {
         PivotCtrl.ScrollPrev();
      }
   }
}

The event handler calls the ApplySelectedEffect method with the resized bitmap as parameter. The ApplySelectedEffect method casts the SelectedItem to an EffectItem and then applies the underlying effect to the bitmap. Afterwards, the bitmap is set as source of the Viewport Image control in the ShowImage method. As the last step, the Pivot control's ScrollPrev method is called, the Pivot control is scrolled to the image view, and the user is presented with the applied effect.

Removing an Effect

If the user wants to remove the effect selection and see the original image again, he or she can hit the phone's hardware back button. This functionality is accomplished by attaching an event handler to the page's BackKeyPress event:

 

C#

private void PhoneApplicationPage_BackKeyPress
   (object sender, CancelEventArgs e)
{
   if (ListBoxEffects != null && 
      ListBoxEffects.SelectedItem != null)
   {
      ListBoxEffects.SelectedItem = null;
      e.Cancel = true;
   }
}

The selected item of the ListBox is set to null which raises the SelectionChanged event (see above). The Cancel

property of the event argument is set to true. Therefore, the app is not closed, which is the default functionality of the back key. The app will be closed if no effect was applied (ListBoxEffects.SelectedItem == null).

Saving a Picture

After the user applied an effect he probably wants to save the result back to the picture library / photo album. But there's nothing like a PhotoSaveTask available in the Silverlight SDK. Fortunately, the Windows Phone's XNA MediaLibrary comes to the rescue. Only a reference to the Microsoft.Xna.Framework assembly is needed to use it in our Windows Phone Silverlight application. To make this task a bit easier, I wrote some reusable extension methods for the WriteableBitmap:

 

C#

// From the file WriteableBitmapMediaLibraryExtensions.cs
// Saves the WriteableBitmap encoded as JPEG 
// to the Media library.
// The quality for JPEG encoding 
// has to be in the range 0-100, 
// where 100 is the best quality with the largest size.
public static void SaveToMediaLibrary
   (this WriteableBitmap bitmap, string name, int quality)
{
   using (var stream = new MemoryStream())
   {
      // Save the picture to the WP7 media library
      bitmap.SaveJpeg(
         stream, 
         bitmap.PixelWidth, 
         bitmap.PixelHeight, 
         0, 
         quality);
      stream.Seek(0, SeekOrigin.Begin);
      new MediaLibrary().SavePicture(name, stream);
   }
}

The SaveToMediaLibrary method expects a name for the picture and the quality for JPEG encoding. The quality has to be in the range from 0 to 100, where 100 is the best image quality. Though a higher quality yields a larger file size, it's not recommended to use a quality below 70.

The MediaLibrary's SavePicture method expects a stream or a byte array as parameter containing an image encoded in the JPEG format. Therefore, the WriteableBitmap is encoded as JPEG with the built-in SaveJpeg extension method. The SaveJpeg method expects the targetStream, the width and height of the target, the orientation, which is not used at the moment, and the quality. The width and height parameters can be useful when a scaled version of the WriteableBitmap should be saved as JPEG.

The image will be saved when the disk floppy button in the ApplicationBar is clicked:

 

C#

private void ApplicationBarIconSaveButton_Click
   (object sender, EventArgs e)
{
   var result = ApplySelectedEffect(original);
   var name = String.Format(
      "PicFx_{0:yyyy-MM-dd_hh-mm-ss-tt}.jpg", 
      DateTime.Now);
   result.SaveToMediaLibrary(name);
}

The effect is applied to the original sized bitmap and a filename based on the current date and time is created. This filename is then passed to the SaveToMediaLibrary extension method, which will save the edited picture to the phone's media library / picture hub.

The Effects

Now that we have examined the UI crust and the underlying mantle, it's time to drill further into the core of our application—the image effects.

image

The effects selectable by the end-user are made out of several base effects, which can be differently combined. Therefore, it's possible to make a wide range of different composite effects (outer core) using various combinations of base effects (inner core). This approach also guarantees high code reusability.

Regardless of whether the effect is a base effect or a composite effect, both implement the same IEffect interface (Figure 3). This interface is quite small. The Process method takes a WriteableBitmap as parameter and returns a WriteableBitmap as result of the image processing.

image

 Figure 3: The IEffect interface

The Black & White Effect

This composite effect converts the input image to a grayscale image and slightly increases the contrast. The classic Black & White effect is often used for portrait photos. Most pictures tend to look better when they're converted to an old school gray image. One might think that's the reason why I use this effect on my profile picture.

image[30]
Figure 4: The Black & White effect applied to the sample picture

The Black & White composite effect internally uses both the BrightnessContrast and Tint base effects (Figure 5). Therefore, it contains almost no code at all.

image

 Figure 5: The class diagram of the BlackWhiteEffect

This is the complete code of the BlackWhiteEffect class without XML comments:

 

C#

public class BlackWhiteEffect : IEffect
{
   readonly TintEffect tintFx;
   readonly BrightnessContrastEffect contrastFx;

   public string Name { get { return "Black & White"; } }

   public float ContrastFactor 
   {
      get { return contrastFx.ContrastFactor; }
      set { contrastFx.ContrastFactor = value; }
   }

   public BlackWhiteEffect()
   {
      tintFx = TintEffect.White;
      contrastFx = new BrightnessContrastEffect 
         { ContrastFactor = 0.15f };
   }

   public WriteableBitmap Process(WriteableBitmap input)
   {
      return contrastFx.Process(tintFx.Process(input));
   }
}

The Base Effects

The Black & White effect of the Outer Core doesn't have much code. So, where's all the code? Good question. Let's dig deeper into the Inner Core to see where the magic is happening.

image

The Tint Effect

The Tint effect converts each pixel to gray and tints it with a parameterized color:

 

C#

public WriteableBitmap Process(WriteableBitmap input)
{         
   // Prepare some variables
   var result = new WriteableBitmap(
      input.PixelWidth, 
      input.PixelHeight);
   var resultPixels = result.Pixels;
   var inputPixels = input.Pixels;
   var ta = Color.A;
   var tr = Color.R;
   var tg = Color.G;
   var tb = Color.B;

   for (int i = 0; i < inputPixels.Length; i++)
   {
      // Extract color components
      var c = inputPixels[i];
      var a = (byte)(c >> 24);
      var r = (byte)(c >> 16);
      var g = (byte)(c >> 8);
      var b = (byte)(c);

      // Convert to gray with constant factors 
      // 0.2126, 0.7152, 0.0722
      int gray = (r *  6966 + g * 23436 + b *  2366) >> 15;

      // Apply Tint color
      a = (byte)((a    * ta) >> 8);
      r = (byte)((gray * tr) >> 8);
      g = (byte)((gray * tg) >> 8);
      b = (byte)((gray * tb) >> 8);

      // Set result color
      resultPixels[i] = (a << 24) | 
         (r << 16) | 
         (g << 8) | 
         b;
   }

   return result;
}

First, the result WriteableBitmap is initialized with the same size as the input image and the values of the Pixels property are assigned to local variables. Using the local variables inside the processing loop is much faster than using the Pixels property, which means that a method is being called each time. The WriteableBitmap uses the RGB color space to represent the pixels and is actually just a 32 bit integer array that stores the alpha, red, green, and blue (ARGB) byte components for all the pixels in a 1D array.

Inside the processing loop the red, green, and blue color components of each input pixel are extracted. Then, each component is multiplied with a constant factor and the elements are added to a scalar value. The result actually represents the luminance of the pixel. The human eye is known to be more sensitive to green colors, which is the reason why the green component is weighted more.

The factors aren't applied as a floating-point multiplication; instead, the components are multiplied with large integer values in the range 215. That's why the result is then divided by 32768 (215) using a cheap bit shift operation. Integer operations are usually faster on most non-specialized CPUs, and on mobile CPUs. But this has to be proven in the future with a real Windows Phone device at hand. One thing is clear, the integer operations perform better than floating point operations in the emulator.

In the last processing step the actual tint Color property is applied by multiplying each component of the color with the gray value of the input pixel. Then the byte color components are combined into the result integer pixel. Again, only fast integer operations are used here.

The Black & White effect uses white as tint color, which represents 255, 255, 255 as a byte in the common additive RGB system or 1f, 1f, 1f as a normalized floating point. This means the gray color is used directly and no tinting is actually applied.

The Brightness and Contrast Modification Effect

The second and last base effect from the Black & White effect is the brightness and contrast modification. The Black & White effect only increases the contrast, but the BrightnessContrastEffect can alter contrast and brightness in the same process since both modifications are commonly used together:

C#

public WriteableBitmap Process(WriteableBitmap input)
{         
   // Prepare some variables
   var result = new WriteableBitmap(
      input.PixelWidth, 
      input.PixelHeight);
   var resultPixels = result.Pixels;
   var inputPixels = input.Pixels;
   // Convert to integer factors
   var bfi = (int)(BrightnessFactor * 255);
   var cf = (1f + ContrastFactor) / 1f;
   cf *= cf;
   var cfi = (int)(cf * 32768);

   for (int i = 0; i < inputPixels.Length; i++)
   {
      // Extract color components
      var c = inputPixels[i];
      var a = (byte)(c >> 24);
      var r = (byte)(c >> 16);
      var g = (byte)(c >> 8);
      var b = (byte)(c);
            
      // Modify brightness (addition)
      if (bfi != 0)
      {
         // Add brightness
         int ri = r + bfi;
         int gi = g + bfi;
         int bi = b + bfi;

         // Clamp to byte boundaries
         r = (byte)(ri > 255 ? 255 : (ri < 0 ? 0 : ri));
         g = (byte)(gi > 255 ? 255 : (gi < 0 ? 0 : gi));
         b = (byte)(bi > 255 ? 255 : (bi < 0 ? 0 : bi));
      }
 
      // Modify contrast (multiplication)
      if (cfi != 0)
      {
         // Transform to range [-128, 127]
         int ri = r - 128;
         int gi = g - 128;
         int bi = b - 128;

         // Multiply contrast factor
         ri = (ri * cfi) >> 15;
         gi = (gi * cfi) >> 15;
         bi = (bi * cfi) >> 15;

         // Transform back to range [0, 255]
         ri = ri + 128;
         gi = gi + 128;
         bi = bi + 128;

         // Clamp to byte boundaries
         r = (byte)(ri > 255 ? 255 : (ri < 0 ? 0 : ri));
         g = (byte)(gi > 255 ? 255 : (gi < 0 ? 0 : gi));
         b = (byte)(bi > 255 ? 255 : (bi < 0 ? 0 : bi));
      }  

      // Set result color
      resultPixels[i] = (a << 24) | 
         (r << 16) | 
         (g << 8) | 
         b;
   }

   return result;
}

A with most of the effects, the result WriteableBitmap is first initialized with the same size as the input image and the Pixels property is locally referenced. The range of the ContrastFactor and the BrightnessFactor floating point properties is normalized to [-1, 1], which makes the class easier to use. But floating operations are slower than integer operations; therefore, local, scaled integer variables are initialized for these properties, too.

Brightness

Inside the loop, the color components of each input pixel are again extracted. Then it's determined whether the Brightness should be modified. The Brightness modification is actually an addition or subtraction of the pixel's luminance. In terms of vector arithmetic, it can be described as a translation. But I don't want to bore you with mathematical details and, as always, a picture is worth a thousand words:

image
Figure 6: The brightness transfer functions from left to right: 1:1, subraction, addition

Figure 6 shows three different brightness transfer functions along with the picture resulting from when the function is applied to the sample image. The y-axis represents the input value and the x-axis represents the corresponding output value for the pixel's luminosity. The left image is the original. The function curve in the middle has a negative offset, which means that a subtraction is performed and the brightness gets decreased. The right curve has a positive offset which means that an addition is performed and the brightness gets increased. So the brightness modification is actually the y-intercept change of the linear luminance transfer function.

Contrast

On the other hand, the Contrast modification is a multiplication of the pixel's luminance. It's a uniform scaling of the color vector (Figure 7). In the above code the value of the pixel components is transformed to the range [-128, 127] and then multiplied with the contrast factor. Due to the range transformation, darker pixels get darker and lighter pixels get lighter when the contrast is increased with a positive factor. Then the pixel is transformed back to the range [0, 255] and the boundaries of the color byte components are checked. The last processing step is the combination of the byte components into the result integer pixel. Again, everything was done with only fast integer operations.

image
Figure 7: The contrast transfer functions from left to right: 1:1, scale down, scale up

The contrast transfer function in the middle of Figure 7 has a factor below 1, which means the slope is shallower and the contrast gets decreased. The right curve has a factor above 1, which means the slope is steeper and the contrast gets increased. The contrast modification is actually the slope change of the linear luminance transfer function.

The Sepia Effect

After the boring details of the Inner Core effects, it's time to use these base effects in a different combination to make an interesting Outer Core effect.

The Sepia effect is another composite effect that tints the image and changes the contrast. Like the Black & White, the Sepia effect is also a very popular image effect to give a photo the old-touch. Sepia is some kind of a brown color tint.

image

Figure 8: The Sepia effect applied to the sample picture

The Sepia effect is basically the same as the Black & White effect, only with slightly other parameter values and a different tint color of course. Actually, only the contrast factor is a bit less than the Black & White effect and the tint color is Sepia (230, 179, 77).

image

Figure 9: The class diagram of the SepiaEffect

The code of the Sepia effect in its whole glory:
C#

public class SepiaEffect : IEffect
{
   readonly TintEffect tintFx;
   readonly BrightnessContrastEffect contrastFx;

   public string Name { get { return "Sepia"; } }

   public float ContrastFactor 
   {
      get { return contrastFx.ContrastFactor; }
      set { contrastFx.ContrastFactor = value; }
   }

   public SepiaEffect()
   {
      tintFx = TintEffect.Sepia;
      contrastFx = new BrightnessContrastEffect 
         { ContrastFactor = 0.05f };
   }

   public WriteableBitmap Process(WriteableBitmap input)
   {
      return contrastFx.Process(tintFx.Process(input));
   }
}

Conclusion

We drilled down from the UI Crust with the Pivot control template and the Windows Phone Application Bar through the UI Mantle with all the IO functionality. Then we drilled further into the Effects Core with the Black & White and Sepia effects. Finally, we reached the Inner Core and learned how the BrightnessContrast and Tint base effects work.

image

I hope you didn't get lost during our long journey because this here isn't the end. The second article of this series will describe how to implement advanced effects (the cool stuff) and many more.

About The Author

imageRené Schulte is a .Net / Silverlight developer and Microsoft Silverlight MVP passionate about real-time computer graphics, physics, AI, and algorithms. He loves C#, Shaders, Augmented Reality, and computer vision. He started the SLARToolkit, the WriteableBitmapEx, and the Matrix3DEx Silverlight open source projects, and he has a Silverlight website powered by real time soft body physics. He is also a regular author for Microsoft's Coding4Fun. Contact information can be found on his Silverlight website, his blog, or via Twitter.

Tags:

Follow the Discussion

  • Rene SchulteRene Schulte

    Thanks.

    @Darks1de:

    I usually list only the relevant parts of the code. The full source code on CodePlex is meant as reference.

  • Clint RutkasClint I'm a "developer"

    @Darks1de The application itself is fully downloadable on Codeplex.  Our articles attempt to show you the math and the "why" along with how to do it.  It becomes extremely unweldy to get every bit of code in a blog post.

  • Darks1deDarks1de

    One comment, you missed out some of the initial variables (which threw me for a bit),  Need to add the declarations for the PhotoChooser, CameraChooser and Original Image variables.

  • Darks1deDarks1de

    Fantastic and utterly thorough post, great work.

  • incendyincendy

    Really appreciate this posting, thank you!

  • AEPAEP

    Great stuff, but couldn't it be written as a full tutorial with all code, so we could code along as we read? Would be very helpful Smiley

  • Rene SchulteRene Schulte

    @AEP: Would be too much code listing and not helpful at all and it targets an intermediate audience.

  • Clint RutkasClint I'm a "developer"

    @max source code is, picfx.codeplex.com/.../changesets

    While you could do a XAP deployment on the emulator or a device, that implies you already have the dev tools installed.  At that point, F5 or a few button clicks, same amount of work.

  • maxmax

    Can you upload a source code because it is not avalable from the link at the top of this post?

    That is what i got from download page

    "download file icon Available Downloads

    There are no downloads associated with this release."

  • Clint RutkasClint I'm a "developer"

    @DarkThread, neat!

  • DarkthreadDarkthread

    What a good post! It's a pity that I can't testing camera capture on emulator, so here is my little trick (http://bit.ly/dCaKbW , forgive my terrible English Tongue Out) -- connecting PC webcam to WP7 emulator, now PicFx can capture photo from webcam even on emulator.

  • Garry McGlennonGarry McGlennon

    Great article however you'll find that your assumptions on speed (though correct based on what should happen) are incorrect. It seems that floating point is actually faster than int math on the device! Well an LG at any rate. I've put a small set of numbers together on my blog which you might find interesting.

    dotnetprofessional.com/.../Performance-Optimization-on-Windows-Phone-7.aspx

  • Rene SchulteRene Schulte

    Hi Garry,

    Thanks for the info.

    And you should provide the test code. Performance test are always complicated. Simple divs and muls ops can be optimized by the compiler / runtime / processor. Real world scenarios tend to lead to better results.

    Have you tried it on a more recent build of the tools and an other device? AFAIK the performance was optimized and the LG dev device is really not representative for perf. Smiley

  • Clint RutkasClint I'm a "developer"

    @Garry McGlennon I think that could all depend on what device you're on.  The LG developer device has some quirks with it.  I think a lot of it depends what processor is actually in the device.

  • Rene SchulteRene Schulte

    Hi Garry,

    I just wrote a quick performance test that applies a Sobel edge detection on a sample image ( en.wikipedia.org/.../Sobel_operator ).

    One implementation uses the naiv approach with floating point kernel operations. The other only uses integer arithmetic.

    I tested it on the LG. The integer implementation is 340% faster than the floating point method.

    This could be different for an other use case, but the problem domain here uses integer math for pixels. Using integer math to solve it, also avoids floating point casts.

  • Darks1deDarks1de

    XNA version of the project now blogged here

    xna-uk.net/.../pictures-barcodes-and-effects-oh-my.aspx

  • Rene SchulteRene Schulte

    Hi Darks1de,

    Cool stuff. I'm looking forward to a release.

    The next part of the article should be available within the next one or two weeks.

  • Clint RutkasClint I'm a "developer"

    @Darks1de that is one of the issues working with beta software, stuff changes.  Rene is doing a part 2 of this article which I do know works on RTM tooling.

  • Darks1deDarks1de

    Be aware, the PicFX project no longer works as expected in the RTM.

    Tombstoning has now changed so the Loaded event no longer works.

    Need to change the Loaded function to use the overloaded "onNavigatedTo" event and call initilize.  then it works again.

  • CattoCatto

    Hey Now René Schulte,

    Stellar Post & app!

    Thx 4 the info,

    Catto

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.