PicFx – Windows Phone Picture Effects Application – Part 2

The first part of this short series showed how to create the base Windows Phone application and how to implement a Black & White and Sepia effect. The basic Windows Phone picture manipulation workflow was explained, and I showed how to load, resize, take, and save an image. The User Interface with the Pivot control template was introduced and some important Windows Phone development key points were also discussed. We also learned how to implement the Black & White and the Sepia effect with the reusable Tint and Contrast & Brightness modification effects.

In this second, final part of the series, we will learn how to make the application more responsive by offloading the image processing computation to a background thread. Furthermore, how to implement a nice vintage Polaroid-like and a miniature faking (tilt shift) effect will be demonstrated, along with how to brand the finished image with a custom logo.

The App in Action

The video below introduces the complete PicFx application features and demonstrates how to use them. It was recorded with the application running in the emulator.

Background music is “A Silent Goodbye” by NCThompson

How it works

In the last article, we drilled down from the UI Crust with the Pivot control template and the Windows Phone Application Bar through the UI Mantle with the UI functionality until we finally reached the Effects Core with the image processing algorithms.

image

The User Interface

As in the first part, we again start our journey on the surface of the application.

image

Some things have changed on the surface of the app—it still uses the Windows Phone Application Bar, but the open source Pivot control implementation from CodePlex was replaced by the official control from Microsoft. The WrapPanel from the official Silverlight for Windows Phone Toolkit is now also used. As you can see in Figure 1, thumbnails of the two new effects are shown on the second Pivot page.

image
Figure 1: The Pivot layout of the extented PicFx application

The current MainPage.xaml: 

XAML

<phoneCtrls:Pivot Name="PivotCtrl" Title="PicFx" >   <phoneCtrls:PivotItem Name="PivotItemPic" Header="Pic" >
      <Grid Height="510" VerticalAlignment="Top" >
         <Image Name="Viewport" Stretch="Uniform" />
         <ProgressBar Name="ProgessBar" 
             IsIndeterminate="True" 
             Height="20" Width="200" 
                     Visibility="Collapsed" 
                     HorizontalAlignment="Center" 
                     VerticalAlignment="Center" 
                     />
      </Grid>
   </phoneCtrls:PivotItem>   <phoneCtrls:PivotItem  Name="PivotItemFx" 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 Source="{Binding Thumbnail}" Width="128" Height="128" />
                <TextBlock Text="{Binding Name}" 
                          FontSize="{StaticResource PhoneFontSizeNormal}" 
                          VerticalAlignment="Center" 
                           HorizontalAlignment="Center" />
               </StackPanel>
            </DataTemplate>
         </ListBox.ItemTemplate>
      </ListBox>
   </phoneCtrls:PivotItem>
         
</phoneCtrls:PivotControl>

An Indeterminate ProgressBar was added to the first Pivot item overlaying the Image, which shows the selected picture. The ProgressBar is hidden by default and only made visible when the picture with the full resolution is processed and saved.

The ListBox with the thumbnails of the effects (see Figure 1) is still data-bound to the StaticResource “effects,” an instance of the EffectItems class that 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"));
      Add(
         new EffectItem(new TiltShiftEffect(), 
            "data/icons/TiltShift.png"));
      Add(
         new EffectItem(new PolaroidEffect(), 
            "data/icons/PolaYellow.png", "Pola"));
   }
}

The two new EffectItems are added and the vintage Polaroid-like effect gets a custom display name—“Pola”—to avoid a mix-up with the original Polaroid brand.

I also added Clint Rutkas' Coding4Fun About control as an Application Bar menu item. This control is a typical about page which provides some information about the app and Coding4Fun.

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.MenuItems>
            <shell:ApplicationBarMenuItem Text="About" 
               Click="ApplicationBarMenuItemAbout_Click" />
        </shell:ApplicationBar.MenuItems>
    </shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

The Code Behind

The first part introduced the Windows Phone picture manipulation workflow and explained how to load, resize, take, and save an image. This section will show how to keep the UI responsive by performing the image processing asynchronously.

image

The two new effects introduced in this article are more computationally expensive. If these would be applied to the original sized picture, the UI thread would get blocked for a few seconds. This is a No Go for a professional application and in order to pass the Marketplace validation, an app has to be responsive and needs to avoid hang-ups. This and other important requirements are defined in the official Windows Phone 7 Application Certification Requirements document.

Asynchronous Processing

To achieve a good responsiveness of the application, the work has to be offloaded from the UI thread to a background thread. Here is where Silverlight's multi-threading strength comes into play.

There is only one problem—due to its base classes, the WriteableBitmap can't be used in a non-UI thread. As we know from the first article, the WriteableBitmap uses the RGB color space to represent the pixels. It's 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, and stores the width and the height as integer properties. This leads us to the natural solution: perform the whole cascade of image processing effects with an integer array (pixels) along with the width and height and only copy the final result back to WriteableBitmap on the UI thread.

No sooner said than done, the following code is executed when the user hits the Save button:

C#

if (ListBoxEffects.SelectedItem == null)
{
   return;
}// Set Save.. state and get UI parameters
Viewport.Opacity = 0.2;
ProgessBar.Visibility = Visibility.Visible;
var effect = ((EffectItem)ListBoxEffects.SelectedItem).Effect;
var dispatcher = Dispatcher;ThreadPool.QueueUserWorkItem((state) =>
{
   try
   {
      // Apply Effect on int[] since WriteableBitmap 
      // can't be used in background thread
      var width = original.PixelWidth;
      var height = original.PixelHeight;
      var resultPixels = effect.Process(original.Pixels, width, height);      // Convert int[] to WriteabelBitmap
      // WriteableBitmap ctor has to be invoked on the UI thread
      dispatcher.BeginInvoke(() =>
      {
         // Turbo copy the pixels to the WriteableBitmap
         var result = new WriteableBitmap(width, height);
         Buffer.BlockCopy(resultPixels, 
        0, 
        result.Pixels, 
        0, 
        resultPixels.Length * 4);
         
         // Save WriteableBitmap
         var name = String.Format(
        "PicFx_{0:yyyy-MM-dd_hh-mm-ss-tt}.jpg", 
        DateTime.Now);
         result.SaveToMediaLibrary(name);
      });
   }
   finally
   {
      // Set controls to initial state
      dispatcher.BeginInvoke(() =>
      {
         ProgessBar.Visibility = Visibility.Collapsed;
         Viewport.Opacity = 1;
      });
   }
});

The ProgressBar is shown during the asynchronous processing and the Opacity of the Image control is reduced (Figure 2). The actual image processing with the selected effect is performed on a background thread. Using the ThreadPool class and its QueueUserWorkItem method accomplishes this. The ThreadPool provides a pool of threads and the QueueUserWorkItem is used to queue a work operation for processing. The main benefit is that resources aren't unnecessarily hogged—the creation of a thread takes some time and each thread needs certain resources such as its own memory stack. Also note that, for common computational scenarios, it's best to keep a balance between threads and processor cores. A thread pool avoids the creation overhead through a certain amount of threads that are kept alive and all the queued work is executed one after another by these threads.

The IEffect's new Process method overload with the pixels integer array and size parameters is used inside the background thread processing. Read more about the IEffect interface change below.

After the effects processing chain is done, a new WriteableBitmap is then instantiated on the UI thread with the use of the Page's Dispatcher, which executes code on the UI thread. Then the integer pixels array is copied to the WriteableBitmap's Pixels property with the fast BlockCopy. The BlockCopy method copies a block of bytes in one single operation in memory, just like the good ol' memcpy.

The final bitmap is then saved to the picture library/photo album with the SaveToMediaLibrary extension method that was introduced in the first part. Finally, the ProgessBar is again hidden and the Image's opacity is restored.

image

Figure 2: Saving a picure shows the ProgressBar over the semi-transparent Image

The Effects

Now that we made the image processing asynchronous and the UI is responsive even when complex computations are performed, it's time to leverage this feature for some advanced effects.

image

The IEffect interface had to be changed for the asynchronous WriteableBitmap-less processing. The new Process method overload expects the pixels as ARGB32 integer array, and the width and height of the bitmap as parameters. The return value is the processed bitmap as integer pixel array of the same size.

image
Figure 3: The changed IEffect interface

As we learned in the first part, the end user effects are called composite effects (outer core), which are made out of various, reusable base effects (inner core).

The Vintage Polaroid-like Effect

This composite effect gives a picture an old-touch so it looks like it was taken with an old, Polaroid-like camera.

image

Figure 4: Vintage Polaroid-like effect applied to the sample picture

The Polaroid-like composite effect uses three internal base effects: a Gaussian blur, an effect that adds a black vignette, and the Tint base effects introduced in the first part (Figure 5).

image
Figure 5: The class diagram of the PolaroidEffect

Old photos weren't as sharp as modern photos and therefore the Blurriness property defines how much the picture will be blurred. The Vignette property controls the size of the round vignette. Additionally, the amount of tinting and the tint color can be changed with properties.

C#

public class PolaroidEffect : IEffect
{
   readonly GaussianBlurEffect blurFx;
   readonly VignetteEffect vignetteFx;
   readonly TintEffect tintFx;
   readonly BitmapMixer mixer;   public string Name { get { return "Vintage"; } }   public float Blurriness 
   {
      get { return blurFx.Sigma; }
      set { blurFx.Sigma = value; }
   }   public float Vignette
   {
      get { return vignetteFx.Size; }
      set { vignetteFx.Size = value; }
   }   public float Tinting
   {
      get { return mixer.Mixture; }
      set { mixer.Mixture = value; }
   }   public Color TintColor
   {
      get { return tintFx.Color; }
      set { tintFx.Color = value; }
   }   public PolaroidEffect()
   {
      blurFx = new GaussianBlurEffect { Sigma = 0.15f };
      vignetteFx = new VignetteEffect();
      tintFx = TintEffect.Sepia;
      mixer = new BitmapMixer { Mixture = 0.5f };
   }   public WriteableBitmap Process(WriteableBitmap input)
   {
      var width = input.PixelWidth;
      var height = input.PixelHeight;
      return Process(input.Pixels, width, height)
          .ToWriteableBitmap(width, height);
   }   public int[] Process(int[] inputPixels, int width, int height)
   {
      var resultPixels = blurFx.Process(inputPixels, width, height);
      resultPixels = vignetteFx.Process(resultPixels, width, height);
      var tintedPixels = tintFx.Process(resultPixels, width, height);
      return mixer.Mix(resultPixels, tintedPixels, width, height);
   }
}

First the input is blurred and the vignette is added. Afterward, a new Sepia-tinted version of the processed image is created. In the last processing step, the non-tinted and the tinted bitmap are mixed together with the use of the new BitmapMixer class. This results in a slight Sepia tint rather than the full Sepia tone.

The New Base Effects

The vintage Polaroid-like effect uses three base effects and a mixer to achieve the old look. Now it's time to see how its Inner Core effects work.

image

The Blur Effect

There are several different available blur algorithms, and one of the most common is the Gaussian blur. The Gaussian blur applies a Gaussian function to an image in order to smooth it and reduce details. The naïve implementation uses a convolution kernel, which is basically a 2D array of n x n elements. In the case of a Gaussian filter the kernel values represent a discrete 2D Gaussian function, which has the typical bell shape. The usual kernel has a size of 5 x 5, though other kernels range from 7 x 7 on up.

image
Figure 6: Gaussian blur applied to the sample image

During the actual image processing, a pixel of the input image is multiplied with all of the kernel elements. The products of the pixel-kernel-element multiplication are summed and divided, and then the result is assigned as the output pixel. This is done for all the pixels of the input image.

The disadvantage of this approach is that the computation time increases when the blurring is increased. In order to blur the image more, the kernel size is usually enlarged, thus meaning n x n multiplications need to be performed for each pixel. Fortunately, in 1995 Ian T. Young and Lucas J. van Vliet invented a better algorithm that is independent of the width. They describe the method in detail in their paper, “Recursive implementation of the Gaussian filter.”

It gets even better—Andrew Marshall already implemented the recursive Gaussian filter in C# for his Silverlight and CUDA interop blog post and allowed me the use it. As you can imagine, the implementation is quite complex and could make up an article on its own. In fact, Young & van Vliet already wrote this article by writing their paper. Please read it if you want to know the mathematical details behind the GaussianBlurEffect class.

The Vignette Effect

The effect of vignetting reduces the brightness of the pixels towards the edges. This is done by computing the pixel's distance to the center and multiplying this with the pixel color. This generates the vignette effect as a fadeout to black towards the edges.

 

image
Figure 7: A black vignette

The Process method of the VignetteEffect implements the vignetting.

C#

public int[] Process(int[] inputPixels, int width, int height)
{
   // Prepare some variables
   var resultPixels = new int[inputPixels.Length];
   var ratio = width > height ? 
      height * 32768 / width : 
      width * 32768 / height;   // Calculate center, min and max
   var cx = width >> 1;
   var cy = height >> 1;
   var max = cx * cx + cy * cy;
   var min = (int)(max * (1 - Size));
   var diff = max - min;   var index = 0;
   for (int y = 0; y < height; y++)
   {
      for (int x = 0; x < width; x++)
      {
         var c = inputPixels[index];         // Extract color components
         var a = (byte)(c >> 24);
         var r = (byte)(c >> 16);
         var g = (byte)(c >> 8);
         var b = (byte)(c);         // Calculate distance to center 
         // and adapt aspect ratio
         var dx = cx - x;
         var dy = cy - y;
         if (width > height)
         {
            dx = (dx * ratio) >> 15;
         }
         else
         {
            dy = (dy * ratio) >> 15;
         }
         int distSq = dx * dx + dy * dy;         if (distSq > min)
         {
            // Calculate vignette
            var v = ((max - distSq) << 8) / diff;
            v *= v;            // Apply vignette
            var ri = (r * v) >> 16;
            var gi = (g * v) >> 16;
            var bi = (b * v) >> 16;            // Check bounds
            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));            // Combine components
            c = (a << 24) | 
               (r << 16) | 
               (g << 8) | 
               b;
         }         resultPixels[index] = c;
         index++;
      }
   }   return resultPixels;
}

The x and y coordinate of the image's center and the aspect ratio are calculated. As you can see, only fast integer operations are used again.

Inside the loop, the color components of each input pixel are extracted and the distance vector to the center is calculated with respect to the picture's aspect ratio. The squared length of the distance vector is tested against the minimum vignette size. If the pixel falls within the range, the scaled distance length is multiplied with each color component. The result is an adapted brightness as described above. The last step ensures that the color components are in the byte range and then combines these to the result integer pixel color.

The BitmapMixer

As the name might imply, the purpose of the BitmapMixer class is to mix two images. The Mix method mixes two ARGB32 integer bitmaps of the same size and returns the mixed bitmap. This is actually an alpha blending operation where the Mixture property defines the opacity of the input2 image. A Mixture value of 0 means input1 is fully visible and a value of 1 means that input2 is shown—everything in between is a mix of both.

C#

public float Mixture { get; set; }public int[] Mix(int[] inputPixels1, 
   int[] inputPixels2, 
   int width, 
   int height)
{
   // Prepare some variables
   var resultPixels = new int[inputPixels1.Length];
   var m = Mixture;
   var mi = 1 - m;   for (var i = 0; i < inputPixels1.Length; i++)
   {
      // Extract color components
      var c1 = inputPixels1[i];
      var a1 = (byte)(c1 >> 24);
      var r1 = (byte)(c1 >> 16);
      var g1 = (byte)(c1 >> 8);
      var b1 = (byte)(c1);      var c2 = inputPixels2[i];
      var a2 = (byte)(c2 >> 24);
      var r2 = (byte)(c2 >> 16);
      var g2 = (byte)(c2 >> 8);
      var b2 = (byte)(c2);      // Mix it!
      var d = ((byte)(a1 * mi + a2 * m) << 24) |
              ((byte)(r1 * mi + r2 * m) << 16) |
              ((byte)(g1 * mi + g2 * m) << 8) |
              ((byte)(b1 * mi + b2 * m));      // Set result color
      resultPixels[i] = d;
   }   return resultPixels;
}

The color components of the two input images are extracted. Each color component of input2 is then multiplied with the Mixture factor and input2 is multiplied with the inverse of the Mixture. Then, both color component products are added and the new values are combined to form the new integer output pixel.

The Tilt Shift Effect

Now that we've learned the details of some of the new Inner Core effects, it's time to use some of them in a different combination and make an interesting Outer Core effect.

image

The digital tilt shift effect lets a scene look like a miniature scale model. It's quite popular nowadays and you might have seen it applied to video in some ads. It's commonly called miniature faking and produces a nice result if it's applied to a photo that was taken from a high angle.

 

imageimage


Figure 8: The tilt shift Effect applied to a photo of Dresden that I have taken from a Ferris wheel

In the first processing stage the TiltShiftEffect increases the contrast of the image with the BrightnessContrastModification effect, which was introduced in the first part. Afterward, the picture gets blurred with the GaussianBlurEffect. The blurred version is then combined with the non-blurred to produce the shallow depth of field of a close-up shot.

image
Figure 9: The class diagram of the TiltShiftEffect

The UpperFallOff property defines the relative y coordinate where the depth of field (camera focus) is completely faded out. The LowerFadeOff defines the lower focus counterpart:

C#

public float UpperFallOff { get; set; }
public float LowerFallOff { get; set; }private int[] contrastedPixels;
private int[] blurredPixels;public int[] Process(
   int[] inputPixels, 
   int width, 
   int height)
{
   // Increase contrast
   CreateBlurredBitmap(inputPixels, width, height);   // Mix the fade off
   return ProcessOnlyFocusFadeOff(
      inputPixels, 
      width, 
      height);
}private void CreateBlurredBitmap(
   int[] inputPixels, 
   int width, 
   int height)
{
   // Increase contrast
   contrastedPixels = contrastFx.Process(
      inputPixels, 
      width, 
      height);   // Blur 
   blurredPixels = blurFx.Process(
                              contrastedPixels, 
                              width, 
                              height);
}public int[] ProcessOnlyFocusFadeOff(
   int[] inputPixels, 
   int width, 
   int height)
{
   // Check if the cache is empty
   if (contrastedPixels == null || blurredPixels == null)
   {
      CreateBlurredBitmap(inputPixels, width, height);
   }   var resultPixels = blurredPixels;   // If not fully blurred?
   if (UpperFallOff < LowerFallOff)
   {
      // Prepare some variables
      resultPixels = new int[inputPixels.Length];      // Calculate fade area
      var uf = (int)(UpperFallOff * height);
      var lf = (int)(LowerFallOff * height);
      var fo = ((lf - uf) >> 1);
      var mf = uf + fo;
      var mfu = mf;
      var mfl = mf;      // Limit fall off and calc inverse
      if (fo > height * MaxFallOffFactor)
      {
         fo = (int)(height * MaxFallOffFactor);
         mfu = uf + fo;
         mfl = lf - fo;
      }
      var ifo = 1f / fo;
      // Blend
      var index = 0;
      for (var y = 0; y < height; y++)
      {
         for (var x = 0; x < width; x++)
         {
            var c2 = contrastedPixels[index];            // Above or below the fading area
            if (y < mfu || y > mfl)
            {
               var c = blurredPixels[index];               // Inside the fading area, 
               // but not in the focused area
               if (y > uf || y < lf)
               {
                  // Extract color components
                  var a1 = (byte)(c >> 24);
                  var r1 = (byte)(c >> 16);
                  var g1 = (byte)(c >> 8);
                  var b1 = (byte)(c);                  var a2 = (byte)(c2 >> 24);
                  var r2 = (byte)(c2 >> 16);
                  var g2 = (byte)(c2 >> 8);
                  var b2 = (byte)(c2);                  // Calculate blending
                  float m = y < mf ? (mfu - y) : (y - mfl);
                  m *= ifo;
                  if (m > 1)
                  {
                     m = 1f;
                  }
                  var mi = 1 - m;                  // Mix it!
                  c = ((byte)(a1 * m + a2 * mi) << 24) |
                        ((byte)(r1 * m + r2 * mi) << 16) |
                        ((byte)(g1 * m + g2 * mi) << 8) |
                        ((byte)(b1 * m + b2 * mi));
               }               // Set result color
               resultPixels[index] = c;
            }
            else
            {
               resultPixels[index] = c2;
            }
            index++;
         }
      }
   }   return resultPixels;
}

As you can see, the processing is split into three methods and two member variables are used to cache both the contrast-increased result and the blurred result. This is useful when only the FallOff properties are changed interactively in real-time, which is described below.

image 

Figure 10: The simulated depth of field fade out

The actual processing mixes the contrast-increased image and the blurred image by using a linear fading function. Figure 10 illustrates this. The red color represents the blurred version and the gray stands for the contrast-increased image.

This fading uses the FallOff properties, converts these properties into absolute values, and calculates some y coordinates, which are needed for the fade in/out. Inside the loop, the color components of both bitmaps are extracted and the mixture factor is computed. Then the color components are multiplied with the factors like in the BitmapMixer's Mix method. The last step combines the component results and sets the integer pixel of the result image.

Multitouch Focus Fade Out Manipulation

The Windows Phone is a nice multitouch device with very good usability. Its multitouch power is used in the PicFx app to let the user interactively change the FallOff properties of the TiltShiftEffect; therefore, the focused area can be altered in an intuitive way.

image

Silverlight and the Windows Phone Silverlight version provide the static Touch class, which has only one member, the FrameReported event. This event is fired each time a set of touch points is registered.

An event handler is attached in the Initialize method of the MainPage. Please note that one would actually encapsulate the following code in a separate class like ViewModel for the effect, but I decided to leave this out to keep the code simpler and focused on the nitty gritty.

C#

private void Initialize()
{
   // Attach touch event handler
   Touch.FrameReported += Touch_FrameReported;
   // ...
}private void Touch_FrameReported(
   object sender, 
   TouchFrameEventArgs e)
{
   SetTiltShiftFocus(e.GetTouchPoints(Viewport));
}private void SetTiltShiftFocus(IList<TouchPoint> points)
{
   IEffect effect = null;
   if (ListBoxEffects != null)
   {
      var item = ListBoxEffects.SelectedItem as EffectItem;
      if (item != null)
      {
         effect = item.Effect;
      }
   }   var tiltFx = effect as TiltShiftEffect;
   if (tiltFx == null)
   {
      return;
   }
   var result = Viewport.Source;
   var isManipulating = points.Any(
      p => p.Action == TouchAction.Down
                  || p.Action == TouchAction.Move);
   if (isManipulating)
   {
      if (points.Count > 1)
      {
         var y1 = (int)points[0].Position.Y;
         var y2 = (int)points[1].Position.Y;         // FallOff is expected as relative coordinate
         var ih = 1f / resized.PixelHeight;         // Topmost point is upper FallOff
         if (y1 < y2)
         {
            tiltFx.UpperFallOff = y1 * ih;
            tiltFx.LowerFallOff = y2 * ih;
         }
         else
         {
            tiltFx.UpperFallOff = y2 * ih;
            tiltFx.LowerFallOff = y1 * ih;
         }         // Apply selected effect
         var processed = 
            tiltFx.ProcessOnlyFocusFadeOff(resized);         // Add FallOff marker lines
         const int markerHeight = 4;
         processed.FillRectangle(
            0, 
            y1 - markerHeight,
            resized.PixelWidth,
            y1 + markerHeight,
            Colors.LightGray);         processed.FillRectangle(
            0, 
            y2 - markerHeight, 
            resized.PixelWidth, 
            y2 + markerHeight, 
            Colors.LightGray);         result = processed;
      }
   }
   else
   {
      // Apply selected effect
      result = tiltFx.Process(resized);
   }   // Show the result
   ShowImage(result);
}

Every time the multitouch event is fired, the SetTiltShiftFocus method gets called. This method converts the absolute coordinates into relative and assigns the values to the appropriate properties. The topmost point is always interpreted as UpperFallOff.

Two small, gray rectangles are drawn at the position of the FallOff values to give the user some feedback. This is done with the WriteableBitmapEx' FillRectangle extension method. To keep the UI responsive, the ProcessOnlyFocusFadeOff method of the TiltShiftEffect is called, and this method uses the cached contrast-increased and blurred images by mixing them. This speeds the process up a lot up.

The Watermark

Now it's time to brand our final image with a custom logo before it gets saved. This watermark is useful to customize or add information to a photo.

image

Figure 11: Watermark logo applied to the sample image

The Watermarker class has the Watermark property, which represents a WriteableBitmap that is used as watermark logo. The RelativeSize defines the size of the logo relative to the size of the input bitmap it should get applied to:

C#

public class Watermarker
{
   public WriteableBitmap Watermark { get; private set; }
   public float RelativeSize { get; set; }   public Watermarker(string relativeResourcePath)
   {
      Watermark = new WriteableBitmap(0, 0)
         .FromResource(relativeResourcePath);      RelativeSize = 0.4f;
   }   public WriteableBitmap Apply(WriteableBitmap input)
   {
      // Resize watermark
      var w = Watermark.PixelWidth;
      var h = Watermark.PixelHeight;
      var ratio = (float) w / h;
      h = (int) (input.PixelHeight * RelativeSize);
      w = (int) (h * ratio);
      var watermark = Watermark.Resize(
         w, 
         h, 
         WriteableBitmapExtensions.Interpolation.Bilinear);      // Blit watermark into copy of the input 
      // Bottom right corner
      var result = input.Clone();
      var position = new Rect(
         input.PixelWidth - w, 
         input.PixelHeight - h, 
         w, 
         h);      result.Blit(position, watermark, new Rect(0, 0, w, h));      return result;
   }
}

The constructor provides an easy way to pass a bitmap from the resource stream. In the Apply method, the watermark bitmap is scaled with the use of the WriteableBitmapEx' Resize method. After this the position is calculated, the watermark logo is blitted into the bottom right corner of the input image and the result is returned. Note that the WriteableBitmapEx' Blit method is used here.

An instance of the Watermarker class is created in the MainPage.xaml.cs.

C#

private void Initialize()
{
   watermarker = new Watermarker("data/watermark.png");
   // ...
}private void ApplySelectedEffectAndSaveAsync()
{
   // ...
      // Turbo copy the pixels to the WriteableBitmap
      var result = new WriteableBitmap(width, height);
      Buffer.BlockCopy(
         resultPixels, 
         0, 
         result.Pixels, 
         0, 
         resultPixels.Length * 4);
      
      // Apply logo
      result = watermarker.Apply(result);      // Save WriteableBitmap
      var name = String.Format(
         "PicFx_{0:yyyy-MM-dd_hh-mm-ss-tt}.jpg", 
         DateTime.Now);
      result.SaveToMediaLibrary(name);
   // ...
}

The watermark is applied after the image processing was performed and before the picture gets saved to the media library.

Conclusion

In the first part we drilled down from the UI Crust with the Pivot control template and the Windows Phone Application Bar through the UI Mantle. Finally we reached the Effects Core with the Black & White, Sepia, BrightnessContrast, and Tint effects.

In this second part, we again journeyed to the core, starting on the surface in order to learn how to keep the UI responsive with asynchronous processing. We then entered the core and l explained the Polaroid-like vintage, its Gaussian blur, the Vignette effects, and the BitmapMixer. I also demonstrated the miniature faking Tilt Shift effect, including the multitouch manipulation of its parameters. The last step showed how to add a custom logo watermark to the final picture.

image

This short series, or two articles, if you will, has come to end. Yep, it's over now—BUT Coding4Fun has released this development stage of the PicFx app for free on the Marketplace! Furthermore, I continued my work on this project and shipped it with enhanced effects, without watermark, but with extra features and a bunch of new effects, including essential ones like auto adjust, soften and many more. Check out the app called Pictures Lab aimed to be nothing less than THE image effects addition to the Windows Phone Pictures Hub.

About The Author

imageRené Schulte is a .Net, Silverlight and Windows Phone 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

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.