Search code examples
c#uwpuwp-xamlwin2d

Win 2D producing different effect level when applied directly on a file and UIElement


I am applying Win2D gaussian blur on an Image File(Storage file) and directly on an UIElement - Image (Which is a XAML image) and I see that for the same value for the BlurAmount I am getting different output for the Storage file output and the XAML Image ..

Original Image

enter image description here

Output when 100% blur applied to XAML Image (Or any UIElement)

enter image description here

Output when 100% blur applied to a Storage file

enter image description here


Relevant Code :

For image File :

using (var stream = await originalFile.OpenAsync(FileAccessMode.Read))
 {
    var device = new CanvasDevice();
    var bitmap = await CanvasBitmap.LoadAsync(device, stream);
    var renderer = new CanvasRenderTarget(device, bitmap.SizeInPixels.Width, bitmap.SizeInPixels.Height, bitmap.Dpi);

    using (var ds = renderer.CreateDrawingSession())
    {
      var blur = new GaussianBlurEffect();
      blur.BlurAmount = eo.effectAmount1;
      blur.BorderMode = EffectBorderMode.Hard;                       
      blur.Source = bitmap;
      ds.DrawImage(blur);                    
    }
    var saveFile = await ApplicationData.Current.TemporaryFolder.CreateFileAsync("ImageName.jpg", CreationCollisionOption.GenerateUniqueName);

    using (var outStream = await saveFile.OpenAsync(FileAccessMode.ReadWrite))
    {
       await renderer.SaveAsync(outStream, CanvasBitmapFileFormat.Png,1.0f);
    }
 }                    

For UIElement(XAML Image) :

using (var stream = await sourceElement.RenderToRandomAccessStream())               
 {
   var device = new CanvasDevice();
   var bitmap = await CanvasBitmap.LoadAsync(device, stream);

   var renderer = new CanvasRenderTarget(device,bitmap.SizeInPixels.Width,
                                                          bitmap.SizeInPixels.Height,
                                                          bitmap.Dpi);

     using (var ds = renderer.CreateDrawingSession())
     {
       var blur = new GaussianBlurEffect();
       blur.BlurAmount = blurAmount;
       blur.Source = bitmap;
       blur.BorderMode = EffectBorderMode.Hard;
       ds.DrawImage(blur);
      }

      stream.Seek(0);
      await renderer.SaveAsync(stream, CanvasBitmapFileFormat.Png);

   }

Question : Is this the expected behaviour ? If yes, how can I make both the cases have the same output ? If no, what am I doing wrong ?


Solution

  • Can you try using bitmap.Size instead of bitmap.SizeInPixels?

    var renderer = new CanvasRenderTarget(bitmap, bitmap.Size);
    

    I'm guessing you're experiencing blur effect plus pixelation.

    Edit 1

    And if that doesn't work, put this assertion after you create your renderer. SizeInPixels needs to match:

    Debug.Assert(
        bitmap.SizeInPixels.Width == renderer.SizeInPixels.Width 
        && bitmap.SizeInPixels.Height == renderer.SizeInPixels.Height
    );
    

    Edit 2

    And if pixels are ok, try setting the StretchMode on the image element to None.

    <Image Stretch="None" />
    

    I'm just suspicious of pixelation somewhere in the visual tree.

    Explanation

    You need to accommodate for DPI scale. Your screen is probably set to 200%. So, if you have an Image control with dimensions 100x100, you actually need 200x200 pixel image to fill it. If you give it a 100x100 bitmap source, then it will stretch it to fit. This is why you get pixelation.

    So, to solve your problem, either disable any stretching on the image OR resize it by the size of the source reduced by the DPI scale. That is, set the image width and height to 50x50 for a 100x100 pixel image at 200% DPI scale.

    DPI scale can be obtained on the UI thread using:

    var dpiScale = DisplayInformation.GetForCurrentView().LogicalDpi / 96f;