Search code examples
xamlwindows-phone-8

Windows Store App: set an image as a background for an UI element


Sorry for asking a really basic question, but it's probably the first time for a number of years I feel really confused.

Windows provides two set of controls: Windows.UI.Xaml namespace (I thinks this is referred as Metro), used for Windows Store Apps, and System.Windows (WPF) for Desktop.

Since I am going to develop Windows Store Apps mainly for Windows 8.1 and Windows 10 phones, I will have to stick to Windows.UI.Xaml, and this has not only a separate set of UI elements, but also separate set of bitmaps, brushes, etc. (Windows.UI.Xaml.Media vs System.Windows.Media).

I found that Windows.UI.Xaml provides a very limited support for graphics, much less than provided by WPF, Android or (even!) iOS platform. To start with, I got stuck with a simple task: tiling a background!

Since Windows.UI.Xaml.Media.ImageBrush do not support tiling, I wanted to to do that "manually". Some sites suggest making numerous number of children, each holding a tile. Honestly, it looks as a rather awkward approach to me, so I decided to do it in what appears a more natural way: create an off-screen tiled image, assign it to a brush and assign the brush as the background for a panel.

The tiling code is rather straightforward (it probably has mistakes, possibly won't even not run on a phone, because of some unavailable classes used).

int panelWidth = (int) contentPanel.Width;
int panelHeight = (int) contentPanel.Height;

Bitmap bmpOffscreen = new Bitmap(panelWidth, panelHeight);

Graphics gOffscreen = Graphics.FromImage(bmpOffscreen);

string bmpPath = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "Assets/just_a_tile.png");
 System.Drawing.Image tile = System.Drawing.Image.FromFile(bmpPath,  true);
 int tileWidth = tile.Width;
 int tileHeight = tile.Height;

 for (int y = 0; y < panelHeight; y += tileHeight)
     for (int x = 0; x < panelWidth; x += tileWidth)
         gOffscreen.DrawImage(tile, x, y);

Now I presumably have the tiled image in bmpOffscreen. But how assign it to a brush? To do that I need to convert Bitmap to BitmapSource, while I couldn't find something similar to System.Windows.Imaging.CreateBitmapSourceFromHBitmap available for WPF structure!


Solution

  • Well, first of all System.Drawing namespace is not available in Windows Universal Platform, so you won't be able to use Bitmap class

    But, all hope is not lost - you can use Windows.UI.Xaml.Media.Imaging.WriteableBitmap

    If you look at example included on this page, you will see that at one point image data is extracted to a byte array - all you need to do is copy it according to your needs

    Please let me know if you want me to include a complete code sample.

    Edit:

    StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
    Scenario4WriteableBitmap = new WriteableBitmap(2000, 2000);
    // Ensure a file was selected
    if (file != null)
    {
        using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
        {
            int columns = 4;
            int rows = 4;
            BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
    
            // Scale image to appropriate size
            BitmapTransform transform = new BitmapTransform()
            {
                ScaledHeight = Convert.ToUInt32(Scenario4ImageContainer.Height),
                ScaledWidth = Convert.ToUInt32(Scenario4ImageContainer.Width)
            };
    
            PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
                BitmapPixelFormat.Bgra8,    // WriteableBitmap uses BGRA format
                BitmapAlphaMode.Straight,
                transform,
                ExifOrientationMode.IgnoreExifOrientation, // This sample ignores Exif orientation
                ColorManagementMode.DoNotColorManage);
    
            // An array containing the decoded image data, which could be modified before being displayed
            byte[] sourcePixels = pixelData.DetachPixelData();
    
            // Open a stream to copy the image contents to the WriteableBitmap's pixel buffer
            using (Stream stream = Scenario4WriteableBitmap.PixelBuffer.AsStream())
            {
                for (int i = 0; i < columns * rows; i++)
                {
                    await stream.WriteAsync(sourcePixels, 0, sourcePixels.Length);
                }
            }
         }
    
         // Redraw the WriteableBitmap
         Scenario4WriteableBitmap.Invalidate();
         Scenario4Image.Source = Scenario4WriteableBitmap;
         Scenario4Image.Stretch = Stretch.None;
     }