2016-02-17 26 views
5

Trong một ứng dụng Universal Windows, tôi đang cố gắng sử dụng một hình nền (từ một ImageSource) và gạch nó trên một điều khiển.UWP - Làm cách nào để xếp hình nền?

XAML

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

C#

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

Theo MSDN, ImageBrush thừa hưởng từ TileBrush. Nó thậm chí còn nói:

Sử dụng cho ImageBrush bao gồm hiệu ứng trang trí cho văn bản hoặc lát nền cho điều khiển hoặc vùng chứa bố trí.

Tôi giả định rằng điều này sẽ xếp hình ảnh, nếu kéo dài bị tắt, nhưng than ôi, nó chỉ vẽ hình ảnh ở giữa điều khiển. Tôi không thấy bất kỳ thuộc tính thực tế nào để tạo ra nó.

Trong WPF, có thuộc tính TileMode và ViewPort có thể được đặt để chỉ định kích thước của ô. Nhưng điều này dường như vắng mặt trong nền tảng phổ quát.

A previous question đề cập đến WinRT (Windows 8), nhưng tôi hy vọng cho giải pháp dựa trên cọ vẽ, thay vì điền khung bằng hình ảnh.

Làm cách nào để tô hình nền với UWP?

Trả lời

4

Câu hỏi trước đây đề cập đến WinRT (Windows 8), nhưng tôi hy vọng cho giải pháp dựa trên cọ vẽ, thay vì điền khung bằng hình ảnh.

Hiện tại, chỉ có hai giải pháp để hiển thị hình nền trong chế độ Ngói trong ứng dụng UWP, thứ đầu tiên bạn biết là lấp đầy khung.

Điều thứ hai tôi đang sử dụng là tạo ra một Panel và vẽ hình ảnh trên đó, ý tưởng này có nguồn gốc từ this article

gì phương pháp này thực hiện là nó lạm dụng thực tế là chúng ta đang vẽ bộ lặp đi lặp lại các đường thẳng trong một hình chữ nhật. Đầu tiên, nó cố gắng vẽ một khối ở trên cùng với chiều cao tương tự như ngói của chúng tôi. Sau đó nó sao chép khối đó xuống cho đến khi nó chạm đáy.

Tôi đã sửa đổi một số mã và sửa chữa một số vấn đề:

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

Cách sử dụng:

<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

Kiểm tra dung dịch trongGithub

+0

Đẹp nhất Franklin! Có vẻ như một chút lãng phí bộ nhớ để có một bitmap cụ thể chỉ để lấp đầy khu vực, nhưng tôi đã điều chỉnh mã của bạn để kiểm soát tùy chỉnh của tôi và nó hoạt động. Cảm ơn :) – cleardemon

0

Không có cách nào để làm điều này theo cách dựa nhiều hơn vào bản vẽ nguyên thủy, có lẽ bằng cách đi từ UWP Panel đến API thành phần và từ đó đến Direct 2D? Điều đó có vẻ là cách "đúng" và sẽ tránh không quan trọng với các điểm ảnh trực tiếp.

0

Xem câu trả lời của tôi để this question:

Bạn có thể gạch sử dụng thư viện Win2D. Họ cũng có sample code; có một mẫu ốp lát dưới "hiệu ứng" (EffectsSample.xaml.cs).

+0

Đây là dự án mẫu triển khai giải pháp Win2D: https://github.com/r2d2rigo/Win2D-Samples/tree/master/TiledBackground – dex3703

1

Tất cả các biến thể này đều nặng đối với GPU. Bạn nên thực hiện thông qua Thành phần API sử dụng 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); 

Tôi đã thử kích thước 1000000x1000000 sprite và không hoạt động.

Win2d sẽ ném bạn ngoại lệ nếu kích thước của bạn lớn hơn 16386px.

Các vấn đề liên quan