2016-02-17 77 views
5

在通用Windows應用程序中,我試圖使用背景圖像(來自ImageSource)並將其平鋪在控件上。UWP - 如何平鋪背景圖片?

XAML

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

C#

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

根據MSDN,圖像刷從繼承的TileBrush。它甚至說:

用於ImageBrush包括用於文本的裝飾效果或用於控件或佈局容器的平鋪的背景 。

我會假設這應該平鋪圖像,如果拉伸禁用,但唉,它只是在控制中間繪製圖像。我沒有看到任何實際的屬性使其平鋪。

在WPF中,有一個TileMode屬性,可以設置ViewPort來指定圖塊的尺寸。但在通用平臺下這似乎不存在。

A previous question引用WinRT(Windows 8),但我希望基於畫筆的解決方案,而不是用圖像填充畫布。

如何使用UWP平鋪背景圖像?

回答

4

以前的問題是指WinRT(Windows 8),但我希望基於畫筆的解決方案,而不是用圖像填充畫布。

目前,只有兩種解決方案可以在UWP應用程序中以平鋪模式顯示背景圖像,其中第一個解決方案是填充畫布。

我使用的是第二個是在其上創建面板,並繪製圖像,這個想法是從this article

得出什麼這種方法確實是它濫用的事實是,我們正在繪製重複套矩形形狀的線條。首先,它試圖在與我們的瓷磚高度相同的頂部繪製一個塊。然後它複製該塊,直到它到達底部。

我修改一些代碼並修復了一些問題:

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's 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); 
     } 
} 

用法:

<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

檢查解決方案Github

+0

好一個富蘭克林!看起來有點浪費內存只是爲了填充特定的位圖,但我已經將您的代碼調整到了我的自定義控件,它確實有效。謝謝:) – cleardemon

0

是否有辦法以更多地依賴繪製原語的方式來執行此操作,可能是通過從UWP面板轉到組合API並從那裏轉到Direct 2D?這似乎是「正確」的方式,並且會避免直接與像素混淆。

0

見我的回答this question

可以平鋪使用Win2D庫。他們也有sample code;在「效果」下有一個拼貼樣本(EffectsSample.xaml.cs)。

+0

下面是一個實現Win2D解決方案的示例項目:https://github.com/r2d2rigo/Win2D-Samples/tree/master/TiledBackground – dex3703

1

所有這些變體對GPU都很重。你應該通過組成 API使用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); 

我試過1000000x1000000大小雪碧,它沒有努力。

如果您的尺寸大於16386px,Win2d會引發異常。