Taoffi's blog

prisonniers du temps

Xamarin forms – the complete WP circle image!

James Montemagno published a post about implementing circle images using Xamarin Forms.

His proposed implementation for Win Phone was good. It clipped the image into a circle, but stopped a little short to draw the circle image border.

 

Droid

WP

 

I played with this to solve this small issue.

Here is a more complete version for WP renderer:

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
	base.OnElementPropertyChanged(sender, e);

	CircleImage		circleImg	= Element as CircleImage;

	if(circleImg == null || Control == null || Control.Clip != null || circleImg.Source == null)
		return;
			
	double		controlRadius	= Math.Min(Element.Width, Element.Height) / 2.0f;
	double		controlDiameter	= controlRadius * 2.0;

	if(controlRadius <= 0)
		return;


	// **********************************************
	// Note: this section ignores PNG transparency. 
	// **********************************************
	try
	{ 
		BitmapImage			srcBmp		= Control.Source as BitmapImage;

		WriteableBitmap		wrBmpSrc	= new WriteableBitmap((int) controlDiameter,
												 (int) controlDiameter);
		ImageBrush		imgBrush	= new ImageBrush();

		// use an image brush with the control’s image
		imgBrush.ImageSource = srcBmp;

		// set border thickness
		int	borderThickness	= circleImg.BorderThickness;
		Color	borderColor			= XamarinColor2WinColor(circleImg.BorderColor);

		// create a path with an ellipse geometry
		EllipseGeometry				ellipseG	= new EllipseGeometry()
			{
				Center		= new System.Windows.Point(controlRadius, controlRadius),
				RadiusX		= controlRadius,
				RadiusY		= controlRadius
			};
		GeometryGroup				geomGroup	= new GeometryGroup();

		geomGroup.Children.Add(ellipseG);

		System.Windows.Shapes.Path	path		= new System.Windows.Shapes.Path()
		{
			Stroke 		= new SolidColorBrush(borderColor),
			StrokeThickness	= borderThickness,
			Data			= geomGroup,
			Width			= controlDiameter,
			Height		= controlDiameter,
			Fill			= imgBrush,
		};

		wrBmpSrc.Render(path, null);
		wrBmpSrc.Invalidate();

		using (MemoryStream memStream = new MemoryStream())
		{
			wrBmpSrc.SaveJpeg(memStream, wrBmpSrc.PixelWidth, wrBmpSrc.PixelHeight, 0, 100);

			srcBmp.CreateOptions = BitmapCreateOptions.IgnoreImageCache;

			srcBmp = new BitmapImage();
			srcBmp.SetSource(memStream);
		}

		Control.Source			= srcBmp;
	}
	catch(Exception ex)
	{
		Debug.WriteLine(ex.Message);
		return;
	}

	// clip the control into circle
	Control.Clip = new EllipseGeometry
		{
			Center	= new System.Windows.Point(controlRadius, controlRadius),
			RadiusX	= Math.Max(controlRadius, 0),
			RadiusY	= Math.Max(controlRadius, 0)
		};
}

 

 

Color conversion helper method

 

		public static Color XamarinColor2WinColor(Xamarin.Forms.Color xamColor)
		{
			return Color.FromArgb (	(byte)(xamColor.A * 255),
									(byte)(xamColor.R * 255),
									(byte)(xamColor.G * 255),
									(byte)(xamColor.B * 255));
		}

 

 

 

Droid

WP

 

Xamarin forms: an image source dilemma

 

As we saw during the first 'inside-look', Xamarin exposes the Image control (or 'View', as you like) with its Source (ImageSource) that can be either a FileImageSource, a StreamImageSource or an UriImageSource.

Most of the samples that talk about how to use Image are delivered with FileImageSource… something like this:

<Image Source="photo.png" />

Or, sometimes:

<Image Source="http://company.com/photo.png" />

Great!... it looks like the Image is quite intelligent to directly know if the source is a FileImageSource or a UriImageSource… mazing work… the guys @Xamarin are quite smart and helpful… right?

Not so fast!

After having some trouble with this (you should expect some in this juvenile environmentJ), I understood something: Many (if not all) samples about Xamarin features are often delivered with the very 'trivial' situation… as they may say: for 'pedagogic' reasons (exactly the same reason we heard in primary/secondary schoolJ)

What if we were 'mature' (if not 'professional') people?... well, apparently, you do it yourself!

In the Image case is also probably accentuated by the fact that the ImageSource is a class whose base is the Element class (which is the grand-grand-parent of the Image class!)

The case

What I had to do with Image was to display an image dynamically generated by a service call. A product image for instance which you can get with an Uri that may look like:

http://mycompany.com/myService.svc?productId=123&thumbnail=1

If you try the somehow trivial cases, you simply put:

<Image Source="http://mycompany.com/myService.svc?productId=123&thumbnail=1" />

That simply 'does not work'! (please don't ask me why J)

Fortunately, we know by other experiences that software is sometimes a craftsmanship process.

I first tried using the StreamImageSource (feeding it with a stream filled up with the bytes coming from the service)… but ended up by asking myself why this object exists (it is not probably totally useless… but it was in my case!)

Image source Binding

As you now know, real life is a little different from schools.

So, as you may imagine: I in fact will not have my Image written like this:

<Image Source="http://mycompany.com/myService.svc?productId=123&thumbnail=1" />

It will rather be written like this:

<Image Source="{Binding ProductImageSource}" />

 

ProductImageSource property in this case was returning a string containing the address for getting the image. Counting on the Image intelligence for translating this address to an UriImageSource. Which did not work.

The solution (that works… and not only on my machine!)

Let us change the ProductImageSource from a string to its meaning: an ImageSource.

 

public ImageSource ProductImageSource 
{ 
    Get {    return GetImageUriSource(ProductImageUrl, 5.0);     } 
}

 

GetImageUriSource, a helper method somewhere (in a static class for instance) can do this work for products or other objects when needed:

public static ImageSourceGetImageUriSource(string strUrl, double cacheDurationMinutes) 
{ 
    if (string.IsNullOrEmpty(strUrl)) 
        return null; 

    // escape the url
    strUrl        = Uri.EscapeUriString(strUrl); 
 

    if(!Uri.IsWellFormedUriString(strUrl, UriKind.RelativeOrAbsolute)) 
        return null; 

    Uri            imgUri        = new Uri(strUrl); 

    return new UriImageSource() 
    { 
        CachingEnabled = true, 
        Uri                 = imgUri, 
        CacheValidity = TimeSpan.FromMinutes(cacheDurationMinutes) 
    }; 
}

 

That works!