Blur(ぼかし)エフェクトをかける

UWPにてXAMLコントロールに対するBlur(ぼかし)効果は廃止されました。

しかし、RenderTargetBitmapというXAMLコントロールをビットマップ化するAPIを用いてビットマップ化 -> ぼかし画像処理 -> コントロールのBackgroundに適用をすることでぼかしを再現できます。

というわけでこんなBlurコントロールを作ってみました。

public sealed class Blur : Panel
{
    public int Level
    {
        get { return (int)GetValue(LevelProperty); }
        set { SetValue(LevelProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Level.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty LevelProperty =
        DependencyProperty.Register("Level", typeof(int), typeof(Blur), new PropertyMetadata(6));



    public Blur()
    {
        
    }

    protected override async void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        await UpdateBlur(0);
    }

    public void DeleteBlur()
    {
        this.Background = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
    }

    public async Task UpdateBlur(int delayMillisec)
    {
        //ブラーをかけたImageSourceをBackgroundに
        this.Background = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
        var brush = new ImageBrush();
        brush.ImageSource = await BlurElementAsync(this, Level, 0);
        this.Background = brush;
    }

    /// <summary>
    /// 指定elementにブラーをかける
    /// </summary>
    /// <param name="element">ブラーをかける要素(Page配下じゃないとダメ)</param>
    /// <param name="blurPixelNum">ブラーをかけるレベル</param>
    /// <param name="delayMillisec">ブラーをかけるまでの遅延時間</param>
    /// <returns></returns>
    private async Task<ImageSource> BlurElementAsync(UIElement element, int blurPixelNum, int delayMillisec = 0)
    {

        //ブラー適用を指定時間遅らせる
        await Task.Delay(TimeSpan.FromMilliseconds(delayMillisec));

        //VisualTreeHelperでPage要素までたどる
        var parent = (UIElement)VisualTreeHelper.GetParent(element);
        while (!(parent is Page))
        {
            parent = (UIElement)VisualTreeHelper.GetParent(parent);
        }

        //Pageをピクセル化
        var rtb = new RenderTargetBitmap();
        await rtb.RenderAsync(parent);

        var pixelBuffer = await rtb.GetPixelsAsync();
        var parentPixels = pixelBuffer.ToArray();
        var parentWidth = (int)(rtb.PixelWidth);
        var parentHeight = (int)(rtb.PixelHeight);

        var targetWidth = (int)element.RenderSize.Width;
        var targetHeight = (int)element.RenderSize.Height;

        //Page要素からみて、Blurコントロールがどの位置にいるのか基点を取得
        var origin = new Point(0, 0);
        var transform = element.TransformToVisual(parent);
        origin = transform.TransformPoint(new Point(0, 0));
        if (origin.X < 0)
        {
            origin.X = 0;
        }
        if (origin.Y < 0)
        {
            origin.Y = 0;
        }

        var targetPixels = await Task.Run(() =>
        {
            //BGRA形式のバイトデータを構造体に格納
            var pixelData = new Pixel[parentWidth * parentHeight];
            var counter = 0;
            for (int i = 0; i < parentPixels.Length; i += 4)
            {
                pixelData[counter].Blue = parentPixels[i];
                pixelData[counter].Green = parentPixels[i + 1];
                pixelData[counter].Red = parentPixels[i + 2];
                pixelData[counter].Alpha = parentPixels[i + 3];
                counter++;
            }

            //Page要素から見たBlurコントロールのピクセルを取得
            var cutPixels = new Pixel[(targetWidth * targetHeight)];
            for (int w = 0; w < targetWidth; w++)
            {
                for (int h = 0; h < targetHeight; h++)
                {
                    cutPixels[h * targetWidth + w] = pixelData[(int)((h + origin.Y) * parentWidth + (w + origin.X))];
                }
            }

            //回りnピクセルの平均値をとることでピクセルをぼかす
            for (int w = 0; w < targetWidth; w++)
            {
                for (int h = 0; h < targetHeight; h++)
                {
                    if (w > blurPixelNum && h > blurPixelNum && w < (targetWidth - blurPixelNum) && h < (targetHeight - blurPixelNum))
                    {
                        var sum = new Pixel();
                        sum.Blue = 0;
                        sum.Green = 0;
                        sum.Red = 0;
                        sum.Alpha = 0;
                        counter = 0;

                        for (int ww = w - blurPixelNum; ww < w + blurPixelNum; ww++)
                        {
                            for (int hh = h - blurPixelNum; hh < h + blurPixelNum; hh++)
                            {
                                if (!(ww == w && hh == h))
                                {
                                    sum.Blue += cutPixels[hh * targetWidth + ww].Blue;
                                    sum.Green += cutPixels[hh * targetWidth + ww].Green;
                                    sum.Red += cutPixels[hh * targetWidth + ww].Red;
                                    sum.Alpha += cutPixels[hh * targetWidth + ww].Alpha;

                                    counter++;
                                }
                            }
                        }
                        cutPixels[h * targetWidth + w].Blue = (int)(sum.Blue / counter);
                        cutPixels[h * targetWidth + w].Green = (int)(sum.Green / counter);
                        cutPixels[h * targetWidth + w].Red = (int)(sum.Red / counter);
                        cutPixels[h * targetWidth + w].Alpha = (int)(sum.Alpha / counter);

                    }
                }

            }

            //ぼかしたピクセルを再度バイト配列に再変換
            byte[] newpixels = new byte[(targetWidth * targetHeight) * 4];
            counter = 0;
            for (int i = 0; i < newpixels.Length; i += 4)
            {
                newpixels[i] = (byte)cutPixels[counter].Blue;
                newpixels[i + 1] = (byte)cutPixels[counter].Green;
                newpixels[i + 2] = (byte)cutPixels[counter].Red;
                newpixels[i + 3] = (byte)cutPixels[counter].Alpha;
                counter++;
            }

            return newpixels;

        });

        //ピクセルデータをImageSourceに変換
        var bmp = new WriteableBitmap(targetWidth, targetHeight);
        using (var stream = bmp.PixelBuffer.AsStream())
        {
            stream.Write(targetPixels, 0, targetPixels.Length);
            await stream.FlushAsync();
        }
        return bmp;

    }

}
public struct Pixel
{
    public int Blue;
    public int Green;
    public int Red;
    public int Alpha;

}

 

あとはこんな感じにUpdateBlurメソッドを適用するとOKです。

await blur.UpdateBlur(0);

 


ブラーをSplitViewのPaneの背景にするとすごくかっこいいですがSplitViewのPaneは出るのに時間がかかるため、ブラーがへんなところにかかります。

そこでupdateBlurの引数にdelayの時間を指定することで指定時間後にブラーをかけることで対処できます。

超参考になったサイト

Render XAML to image, Blur app UI, or how to enable awesome user experiences in your UWP app

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください