Ваша основная проблема связана с тем, что из-за 1-пиксельной границы вокруг Canvas
его вектор VisualOffset
равен (1,1)
. Таким образом, любой визуальный эффект, например кисть фона, будет применен с этим смещением. Когда вы визуализируете визуальное изображение в растровое изображение, оно захватывает текущий вид, а затем, когда вы устанавливаете растровое изображение в качестве кисти, оно смещается.
По иронии судьбы, один из самых простых способов исправить это - вставить еще один элемент <Border/>
в ваш XAML:
<Border BorderBrush="Black" BorderThickness="1" Grid.Row="1" Grid.Column="1">
<Border>
<Canvas x:Name="Pad">
<Rectangle Height="100" Width="100" Fill="Red" Canvas.Left="10" Canvas.Top="10"/>
</Canvas>
</Border>
</Border>
Тогда смещение, вызванное внешним элементом <Border/>
, обрабатывается преобразованием нового элемента <Border/>
, а не применяется к элементу <Canvas/>
.
Это изменение само по себе почти полностью исправит ваш код. Тем не менее, есть еще один маленький артефакт, который вы все равно можете заметить: каждый раз, когда вы визуализируете визуальное изображение, оно становится чуть более размытым. Это связано с тем, что значение по умолчанию для свойства Stretch
объекта Brush
равно Stretch.Fill
, а элемент <Canvas/>
не является целой шириной или высотой, поэтому растровое изображение (которое обязательно имеет интегральная ширина и высота) при рендеринге растягивается чуть-чуть. С каждой итерацией это становится все более очевидным.
Это можно исправить, установив для свойства Stretch
значение Stretch.None
. В то же время вы также захотите установить выравнивание кисти на Left
и Top
:
private void Window_KeyDown(object sender, KeyEventArgs e)
{
RenderTargetBitmap renderer = new RenderTargetBitmap(
Convert.ToInt32(Pad.ActualWidth), Convert.ToInt32(Pad.ActualHeight), 96, 96, PixelFormats.Pbgra32);
renderer.Render(Pad);
ImageBrush brush = new ImageBrush(renderer);
brush.AlignmentX = AlignmentX.Left;
brush.AlignmentY = AlignmentY.Top;
brush.Stretch = Stretch.None;
Pad.Background = brush;
Pad.Children.Clear();
}
Значения по умолчанию: Center
, что снова вызывает ошибку округления и приведет к перемещению и размытию изображения после многократных итераций процесса.
С учетом вышеуказанных изменений я нашел идеально стабильное изображение независимо от количества итераций.
Отсюда возникла идея "завернуть в рамку": https://blogs.msdn.microsoft.com/jaimer/2009/07/03/rendertargetbitmap-tips/
На этой странице вы найдете более универсальное решение, которое не требует модификации фактического XAML. В приведенном выше примере подход «обтекание границ» выглядит как разумный обходной путь, но он, правда, не такой чистый, как принудительное добавление неукрашенного контекста, в который можно визуализировать визуальный элемент, как показано на этой странице блога.