UWP - How to make a background image?

In a universal Windows application, I am trying to use a background image (from ImageSource) and break it down into a control.

Xaml

<Grid x:Name="gridBackground"> <ContentPresenter /> </Grid> 

WITH#

 void UpdateBackground(ImageSource source) { // ... gridBackground.Background = new ImageBrush { ImageSource = source, Stretch = Stretch.None }; } 

According to MSDN , ImageBrush is inherited from TileBrush. He even says:

Uses for ImageBrush include decorative effects for text or tiled backgrounds for controls or layouts.

I would suggest that this should smooth the image if stretching is disabled, but alas, it just draws the image in the middle of the control. I do not see any real properties to make it a tile.

In WPF, the TileMode property exists , and a ViewPort can be set to specify tile sizes. But this seems absent from the Universal Platform.

A previous question relates to WinRT (Windows 8), but I hope for a brush-based solution, not for filling the canvas with images.

How to create a background image using UWP?

+5
source share
5 answers

The previous question relates to WinRT (Windows 8), but I hope for a brush-based solution, and not for filling the canvas with images.

Currently, there are only two solutions for displaying the background image in Tile mode in the UWP application, the first of which, as you know, fills the canvas.

The second one I use is to create a panel and draw an image on it, this idea is derived from this article

What this method does is that it abuses the fact that we draw repeating sets of rows in a rectangular shape. Firstly, he is trying to draw a block at the top with the same height as our tile. Then he copies this block down until it reaches the bottom.

I changed the code and fixed some problems:

 public class TiledBackground : Panel { public ImageSource BackgroundImage { get { return (ImageSource)GetValue(BackgroundImageProperty); } set { SetValue(BackgroundImageProperty, value); } } // Using a DependencyProperty as the backing store for BackgroundImage. This enables animation, styling, binding, etc... public static readonly DependencyProperty BackgroundImageProperty = DependencyProperty.Register("BackgroundImage", typeof(ImageSource), typeof(TiledBackground), new PropertyMetadata(null, BackgroundImageChanged)); private static void BackgroundImageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((TiledBackground)d).OnBackgroundImageChanged(); } private static void DesignDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ((TiledBackground)d).OnDesignDataChanged(); } private ImageBrush backgroundImageBrush = null; private bool tileImageDataRebuildNeeded = true; private byte[] tileImagePixels = null; private int tileImageWidth = 0; private int tileImageHeight = 0; private readonly BitmapPixelFormat bitmapPixelFormat = BitmapPixelFormat.Bgra8; private readonly BitmapTransform bitmapTransform = new BitmapTransform(); private readonly BitmapAlphaMode bitmapAlphaMode = BitmapAlphaMode.Straight; private readonly ExifOrientationMode exifOrientationMode = ExifOrientationMode.IgnoreExifOrientation; private readonly ColorManagementMode coloManagementMode = ColorManagementMode.ColorManageToSRgb; public TiledBackground() { this.backgroundImageBrush = new ImageBrush(); this.Background = backgroundImageBrush; this.SizeChanged += TiledBackground_SizeChanged; } private async void TiledBackground_SizeChanged(object sender, SizeChangedEventArgs e) { await this.Render((int)e.NewSize.Width, (int)e.NewSize.Height); } private async void OnBackgroundImageChanged() { tileImageDataRebuildNeeded = true; await Render((int)this.ActualWidth, (int)this.ActualHeight); } private async void OnDesignDataChanged() { tileImageDataRebuildNeeded = true; await Render((int)this.ActualWidth, (int)this.ActualHeight); } private async Task RebuildTileImageData() { BitmapImage image = BackgroundImage as BitmapImage; if ((image != null) && (!DesignMode.DesignModeEnabled)) { string imgUri = image.UriSource.OriginalString; if (!imgUri.Contains("ms-appx:///")) { imgUri += "ms-appx:///"; } var imageSource = new Uri(imgUri); StorageFile storageFile = await StorageFile.GetFileFromApplicationUriAsync(imageSource); using (var imageStream = await storageFile.OpenAsync(FileAccessMode.Read)) { BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream); var pixelDataProvider = await decoder.GetPixelDataAsync(this.bitmapPixelFormat, this.bitmapAlphaMode, this.bitmapTransform, this.exifOrientationMode, this.coloManagementMode ); this.tileImagePixels = pixelDataProvider.DetachPixelData(); this.tileImageHeight = (int)decoder.PixelHeight; this.tileImageWidth = (int)decoder.PixelWidth; } } } private byte[] CreateBackgroud(int width, int height) { int bytesPerPixel = this.tileImagePixels.Length / (this.tileImageWidth * this.tileImageHeight); byte[] data = new byte[width * height * bytesPerPixel]; int y = 0; int fullTileInRowCount = width / tileImageWidth; int tileRowLength = tileImageWidth * bytesPerPixel; //Stage 1: Go line by line and create a block of our pattern //Stop when tile image height or required height is reached while ((y < height) && (y < tileImageHeight)) { int tileIndex = y * tileImageWidth * bytesPerPixel; int dataIndex = y * width * bytesPerPixel; //Copy the whole line from tile at once for (int i = 0; i < fullTileInRowCount; i++) { Array.Copy(tileImagePixels, tileIndex, data, dataIndex, tileRowLength); dataIndex += tileRowLength; } //Copy the rest - if there is any //Length will evaluate to 0 if all lines were copied without remainder Array.Copy(tileImagePixels, tileIndex, data, dataIndex, (width - fullTileInRowCount * tileImageWidth) * bytesPerPixel); y++; //Next line } //Stage 2: Now let copy those whole blocks from top to bottom //If there is not enough space to copy the whole block, skip to stage 3 int rowLength = width * bytesPerPixel; int blockLength = this.tileImageHeight * rowLength; while (y <= (height - tileImageHeight)) { int dataBaseIndex = y * width * bytesPerPixel; Array.Copy(data, 0, data, dataBaseIndex, blockLength); y += tileImageHeight; } //Copy the rest line by line //Use previous lines as source for (int row = y; row < height; row++) Array.Copy(data, (row - tileImageHeight) * rowLength, data, row * rowLength, rowLength); return data; } private async Task Render(int width, int height) { Stopwatch fullsw = Stopwatch.StartNew(); if (tileImageDataRebuildNeeded) await RebuildTileImageData(); if ((height > 0) && (width > 0)) { using (var randomAccessStream = new InMemoryRandomAccessStream()) { Stopwatch sw = Stopwatch.StartNew(); var backgroundPixels = CreateBackgroud(width, height); sw.Stop(); Debug.WriteLine("Background generation finished: {0} ticks - {1} ms", sw.ElapsedTicks, sw.ElapsedMilliseconds); BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, randomAccessStream); encoder.SetPixelData(this.bitmapPixelFormat, this.bitmapAlphaMode, (uint)width, (uint)height, 96, 96, backgroundPixels); await encoder.FlushAsync(); if (this.backgroundImageBrush.ImageSource == null) { BitmapImage bitmapImage = new BitmapImage(); randomAccessStream.Seek(0); bitmapImage.SetSource(randomAccessStream); this.backgroundImageBrush.ImageSource = bitmapImage; } else ((BitmapImage)this.backgroundImageBrush.ImageSource).SetSource(randomAccessStream); } } else this.backgroundImageBrush.ImageSource = null; fullsw.Stop(); Debug.WriteLine("Background rendering finished: {0} ticks - {1} ms", fullsw.ElapsedTicks, fullsw.ElapsedMilliseconds); } } 

Using:

 <Grid x:Name="rootGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <tileCtrl:TiledBackground BackgroundImage="Assets/avatar1.png" Width="{Binding ActualWidth, ElementName=rootGrid}" Height="{Binding ActualHeight, ElementName=rootGrid}"/> </Grid> 

Screenshot

Check out the solution on Github

+4
source

All of these options are difficult for the GPU. You must do this through the Composition API using BorderEffect .

  var compositor = ElementCompositionPreview.GetElementVisual(this).Compositor; var canvasDevice = CanvasDevice.GetSharedDevice(); var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice); var bitmap = await CanvasBitmap.LoadAsync(canvasDevice, new Uri("ms-appx:///YourProject/Assets/texture.jpg")); var drawingSurface = graphicsDevice.CreateDrawingSurface(bitmap.Size, DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied); using (var ds = CanvasComposition.CreateDrawingSession(drawingSurface)) { ds.Clear(Colors.Transparent); ds.DrawImage(bitmap); } var surfaceBrush = compositor.CreateSurfaceBrush(drawingSurface); surfaceBrush.Stretch = CompositionStretch.None; var border = new BorderEffect { ExtendX = CanvasEdgeBehavior.Wrap, ExtendY = CanvasEdgeBehavior.Wrap, Source = new CompositionEffectSourceParameter("source") }; var fxFactory = compositor.CreateEffectFactory(border); var fxBrush = fxFactory.CreateBrush(); fxBrush.SetSourceParameter("source", surfaceBrush); var sprite = compositor.CreateSpriteVisual(); sprite.Size = new Vector2(1000000); sprite.Brush = fxBrush; ElementCompositionPreview.SetElementChildVisual(YourCanvas, sprite); 

I tried a sprite with a size of 1000000x1000000 and it worked without much effort.

Win2d will throw an exception if your size is more than 16386 pixels.

+1
source

Is there no way to do this in such a way as to rely more on the primitives of drawing, perhaps by switching from the UWP panel to the layout API and from there to Direct 2D? It would seem that this was the "correct" way, and he would avoid messing with pixels directly.

0
source

See my answer to this question :

You can use the tile using the Win2D library. They have sample code ; there is a tile pattern under the “effects” ( EffectsSample.xaml.cs ).

0
source

In fact, you can now create a custom brush (using the Layout API and Win2D) to achieve the tiling effect. Sample code here: UWP TiledBrush

In short, you simply subclass XamlCompositionBrushBase and override the OnConnected method:

 public class TiledBrush : XamlCompositionBrushBase { protected override void OnConnected() { var surface = LoadedImageSurface.StartLoadFromUri(ImageSourceUri); var surfaceBrush = Compositor.CreateSurfaceBrush(surface); surfaceBrush.Stretch = CompositionStretch.None; var borderEffect = new BorderEffect() { Source = new CompositionEffectSourceParameter("source"), ExtendX = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap, ExtendY = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap }; var borderEffectFactory = Compositor.CreateEffectFactory(borderEffect); var borderEffectBrush = borderEffectFactory.CreateBrush(); borderEffectBrush.SetSourceParameter("source", surfaceBrush); } } 

And then use it as expected:

 <Grid> <Grid.Background> <local:TiledBrush ImageSourceUri="Assets/Texture.jpg" /> </Grid.Background> </Grid> 
-1
source

Source: https://habr.com/ru/post/1243216/


All Articles