Writing a Xaml attached property in C++/CX to resize Images, with a Performance twist

By jay at February 04, 2013 21:41 Tags: , , , , , ,

TL;DR: Writing Xaml/C++ attached properties sometimes gives a 30% improvement over the C# version, which can be caused by the use of events. This article shows code sample for both versions.

 

 

Since it is possible to write a XAML application entirely in C++/CX, I decided to give a try to the performance of some simple code.

There is, after all, some marshaling involved when communicating from C# to native code, particularly with events.

 

The ImageDecodeSizeBehavior

WinRT’s BitmapImage class supports, as does WPF and Silverlight, the DecodePixelWidth and DecodePixelHeight properties.

These are very useful properties that forces the memory surface to store the image to fit a certain size, and avoid the waste of memory induced by large downscaled images. This is a very common performance issue for applications that display variable sized images, where the memory can grow very quickly.

So in C#, the following attached property can be written pretty easily :

public static class ImageDecodeSizeBehavior
{
    public static void SetSource(Image image, string value)
    {
        image.SetValue(SourceProperty, value);
    }

    public static string GetSource(Image image)
    {
        return (string)image.GetValue(SourceProperty);
    }

    public static readonly DependencyProperty SourceProperty =
        DependencyProperty.Register(
            "Source",
            typeof(string), 
            typeof(ImageDecodeSizeBehavior), 
            new PropertyMetadata(null, (s, e) => OnSourceChanged(s, e))
        );

    private static void OnSourceChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
    {
        var image = (Image)s;

	    image.SetValue(SourceProperty, e.NewValue);

	    image.SizeChanged += (_, __) => RefreshBitmap(image);

	    RefreshBitmap(image);
    }

    private static void RefreshBitmap(Image image)
    {
	    var source = (String)image.GetValue(SourceProperty);

	    if(source != null)
	    {
		    var uri = new Uri(source);
		    var bitmap = new BitmapImage(uri);

		    bitmap.DecodePixelWidth = (int)image.Width;
		    bitmap.DecodePixelHeight = (int)image.Height;

		    image.Source = bitmap;
	    }
    }
}

The idea here is to use the current image control size as the target surface size, do be gentle with the memory. The downside is that if images need to be zoomed in, then they're going to be pixelated, unless the image is re-created.

In this sample, the hook on SizeChanged is more of a speed test, because one would not want to re-create the image every time the size changes, particularly if it changes too much (though the Rx Throttle operator may help).

It’s used that way :

<Image local:ImageDecodeSizeBehavior.Source="{Binding}" Width="50" Height="50" />
 

The same, in C++/CX

With C++/CX, the code is looking very much like its C# counter part :

using namespace Platform;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::UI::Xaml::Media::Imaging;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Metadata;

public ref class ImageDecodeSizeBehavior sealed : DependencyObject
{
public:
	ImageDecodeSizeBehavior();

	static property DependencyProperty^ SourceProperty { 
		DependencyProperty^ get() { return _sourceProperty; }
	}

	static String^ ImageDecodeSizeBehavior::GetSource(Image^ image) {
		return (String^)image->GetValue(SourceProperty);
	}

	static void ImageDecodeSizeBehavior::SetSource(Image^ image, String^ source) {
		image->SetValue(SourceProperty, source);
	}

private:
	~ImageDecodeSizeBehavior();

	static DependencyProperty^ _sourceProperty;

	static void RefreshBitmap(Image^ image, String^ value);
	static void OnSourceChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e);
};

And the cpp file:

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Documents;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Media::Imaging;

DependencyProperty^ ImageDecodeSizeBehavior::_sourceProperty = 
	DependencyProperty::RegisterAttached(
		"Source", 
		TypeName(String::typeid), 
		TypeName(ImageDecodeSizeBehavior::typeid), 
		ref new PropertyMetadata(
			nullptr, 
			ref new PropertyChangedCallback(OnSourceChanged)
		)
	);

void ImageDecodeSizeBehavior::OnSourceChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
	auto image = (Image^)d;
	auto value = (String^)e->NewValue;

	image->SetValue(SourceProperty, value);

	auto sizeChanged = [=](Object^ sender, SizeChangedEventArgs^){
			RefreshBitmap(image, value);
	};

	image->SizeChanged += ref new SizeChangedEventHandler(sizeChanged);

	RefreshBitmap(image, value);
}


void ImageDecodeSizeBehavior::RefreshBitmap(Image^ image, String^ source)
{
	if(source == nullptr)
	{
		source = (String^)image->GetValue(SourceProperty);
	}

	if(source != nullptr)
	{
		auto uri = ref new Uri(source);
		auto bitmap = ref new BitmapImage(uri);

		bitmap->DecodePixelWidth = (int)image->Width;
		bitmap->DecodePixelHeight = (int)image->Height;

		image->Source = bitmap;
	}
}

Except that it’s a lot more verbose. Particularly the lambda syntax that uses all the available braces on the keyboard.

 

Performance

How do the two compare, in terms of performance ? Testing this in a real control is a bit complex, because of the performance issue I outlined in this post, but also because the measured time varies a lot, of about +/- 2%. This margin of error is not small enough to show the actual improvements.

Still, it is possible to measure in a more isolated code :

var w = Stopwatch.StartNew();
for (int i = 0; i < 1000; i++)
{
   var b = new Image();
   ImageDecodeSizeBehavior.SetSource(b, "http://google.com/images/srpr/logo3w.png");
}
var elapsed = w.Elapsed;

Which shows that, on a Surface RT, attaching to 1000 images takes 722ms in C#, and 505ms in C++/CX, which is about a 30% faster.

Now, the reason for this difference is the event registration.

Removing both event registrations sets the bar to 474ms for C++/CX and 493ms for C#, which almost accounts for all the complexity added by the hidden EventRegistrationToken and WindowsRuntimeMarshal.AddEventHandler magic added by the C# compiler.

The simple += operator added in C# is expanded to something like this, in IL :

WindowsRuntimeMarshal.AddEventHandler(
   new Func(image.add_SizeChanged), 
   new Action(image.remove_SizeChanged), 
   delegate(object _, SizeChangedEventArgs __) {
      ImageDecodeSizeBehavior.RefreshBitmap(image);
   }
);

Which is a bit of a change from a simple delegate registration. I'm guessing that there is a lot of work related to reference tracking and marshalling, under the hood, which may account for the loss of performance.

 

It is worth the investment ?

We’re talking about quite small amounts of time, yet we’ve got 16.6ms to perform operations on the UI thread, and keep the 60fps rate to preserve smooth animations. So, 0.7ms may count in the balance.

C++/CX requires a bit more maintenance than its C# counterpart, which may add to the development cost. It also forces the creation of packages for all the available platforms, as it requires the creation of a native WinMD component.

I, for one, tend to favor maintainability over performance, unless it is in a very critical path.

blog comments powered by Disqus

About me

My name is Jerome Laban, I am a Software Architect, C# MVP and .NET enthustiast from Montréal, QC. You will find my blog on this site, where I'm adding my thoughts on current events, or the things I'm working on, such as the Remote Control for Windows Phone.